「不知道怎麼改,那我們先照原本方式畫幾張。評審給完分,再選一張回傳。至少不需要請他說出修改方向。」
Mugi 保留每張圖片原本的生成過程,把這次偏好放在完成後的選擇。可以挑最高分,也可以讓高分比較容易被抽到;這兩種規則會得到一樣的出現比例嗎?
「不知道怎麼改,那我們先照原本方式畫幾張。評審給完分,再選一張回傳。至少不需要請他說出修改方向。」
Mugi 保留每張圖片原本的生成過程,把這次偏好放在完成後的選擇。可以挑最高分,也可以讓高分比較容易被抽到;這兩種規則會得到一樣的出現比例嗎?
「可是每張都畫完才評分,會不會花太多時間?如果畫到一半能先猜它最後會變成什麼,那比較有希望的草稿多留幾份,接著畫;其他草稿就少分一點時間。」
Lumi 想改的是哪些草稿得到接下來的生成計算。評審仍只看完成品,所以途中得先估計未來,不能直接把 noisy image 當成完成照片。估計也可能看錯,不能一開始就把其他草稿全丟掉。
「假如同一個起點總會生成同一張圖,那複製草稿也只會畫出一樣的答案。能不能換個起點試畫,把容易畫出喜歡結果的起點找出來?」
Nori 想保留整套生成規則,改的是從哪些起點開始。每個新起點仍要生成與評分;從幾次試畫找到高分起點,也還不等於已經抽到指定的分布。
如果只想找一張高分照片,挑最高分就很合理。但使用者還想保留不同答案;我們這次要檢查的是:反覆回傳圖片後,各種結果的比例,是否接近下面的 reward tilt?
是原本的 base distribution, 決定 reward 的影響, 是對 base 抽出的完成品平均 reward 倍率。假設 ,除掉它才讓總量等於 。離散作品用機率,連續圖片用 density;後者的某個精確點不代表有非零的抽中機率。
這個目標讓高分結果增加相對權重,仍保留 base 的比例。它的動機與最佳性在 U7.2 推過;今天追的是怎麼真的抽,而不是重新更新模型。先假設 base sampler 精確生成 ,實際模型的差別等一下再檢查。
順序由你選。每條路都包含預測、真正抽樣畫圖、必要推導與限制;按最後的返回才標記「已完整探索」。中途可以先回列表,稍後接著讀。
畫完後選:Best-of-N 與 IW
「先畫 張,再挑分數最高的。這個做法我們現在就能做,對吧?」
Mugi 提出多畫幾張之後,再來看這個情境。左邊是分開生成的候選,右邊只回傳一張;每張都要完整畫完,再交給評審。
點圖可放大從 base 獨立生成 ,評分後找出最高分編號 ,輸出那張圖片:
這就是 Best-of-。 找的是候選編號,不是在更新 pixels 或參數;同分時可以訂一個固定或隨機的選擇規則。它不需要 reward gradient,也沒改每張候選原本的 sampling path。
每次重新生成 張、選出一張,再把整趟獨立重跑 次,就收集到 張輸出。 是每趟比較多少張, 是最後收集多少次輸出。 增加 能看清這個規則的出現比例,還得算它是否等於 。
先讓每趟的候選真的從雙峰資料抽出,依評分挑一個。觀察排序與倍率的差別,再回來算離散例子的完整比例。
先預測,再觀察分布
N 增加會讓分數變高,但回傳分布就會接近黑色點線的 reward tilt 嗎?固定排序,只放大 λ 又會怎樣?
A 每趟 N = 1;B 用你選的 N。每一趟重新生成候選,只回傳最高分的一個,獨立重跑 1,000 趟。
灰線先畫出原本雙峰資料。執行後才會加上實際樣本;參考曲線不代表已經抽到了它。
用剛才的圖與數字說明
Best-of-N 只依 reward 排名選贏家;λ 不改排序,就不改同一批候選的贏家。Reward tilt 的倍率卻會隨 λ 改變。因此多跑輸出能看清這個規則,但不能把規則本身改成 target。候選表記錄的是真正生成與選中的數字。
這是標準對照的機制說明;自由改條件或 Python 後,以你實際執行的設定與圖為準。
每個候選都直接從同一個 Gaussian mixture 抽出。主圖的直方圖來自 1,000 次獨立回傳;黑色點線是數值積分得到的 tilt 參考,不是 Best-of-N 的理論密度。
可以改參數,也可以改抽樣規則。按執行時才載入 Python;運算在你的瀏覽器進行。每次執行從這段程式重新開始,文字不會存檔。
首次展開會帶入上方的控制項設定;之後重開會保留你改過的程式。帶入的是條件,不包含上方累積訓練的參數或生成樣本,因此兩邊的有限抽樣圖不必完全相同。
程式包含相同方法的完整計算與 Matplotlib 分布圖。首次展開套用目前控制項;每次執行重新抽樣。先改最上方一個參數,其他條件保持相同。原生圖不會假裝成你改過的 Python 結果。
import math
import numpy as np
import matplotlib.pyplot as plt
# 1. SETTINGS -- change one, run again, and explain the plotted evidence.
strength = 2
N = 8 # candidates per returned sample
reward_kind = "right" # "right" or "two"
rng = np.random.default_rng(42)
sigma = 0.55
grid = np.linspace(-6, 6, 481)
dx = grid[1] - grid[0]
# 2. FIXED MODEL -- analytic functions, no network training in this cell.
def pdf(x, mu=0.0, var=1.0):
return np.exp(-0.5 * (x-mu)**2 / var) / np.sqrt(2*np.pi*var)
def R(x):
return x if reward_kind == "right" else -(np.abs(x)-2.5)**2
def moments(t):
a = np.exp(-8*t)
return np.sqrt(a), a*sigma**2 + 1-a
def sample_base(size, t=0.0):
signal, var = moments(t)
mu = np.where(rng.random(size) < 0.5, -2.0, 2.0)
return signal*mu + np.sqrt(var)*rng.normal(size=size)
base = 0.5*pdf(grid, -2, sigma**2)+0.5*pdf(grid, 2, sigma**2)
target = base*np.exp(strength*R(grid))
target /= target.sum()*dx # finite-grid quadrature reference
reference = target
reference_label = "Tilt reference (quadrature on [-6,6])"
extra_figure = None
# 3. GENERATION -- 1000 independent batches; return ONE sample per batch.
batch = sample_base((1000, N))
rewards = R(batch)
selected = np.argmax(rewards, axis=1)
samples = batch[np.arange(1000), selected]
print("Generated candidates and reward calls:", 1000*N)
print("First batch:", batch[0], "selected index:", selected[0])
print("Finite N does not guarantee the reference tilt.")
# 4. PLOT AND CHECK -- the histogram is from the samples above.
fig, ax = plt.subplots(figsize=(8, 3.6))
ax.plot(grid, base, "--", color="gray", label="Original base")
ax.plot(grid, reference, ":", color="black", label=reference_label)
ax.hist(samples, bins=np.linspace(-6,6,49), weights=np.ones(len(samples))/(len(samples)*0.25), alpha=0.5, label="Actual generated samples")
ax.set(xlabel="clean x0", ylabel="density", xlim=(-6,6))
ax.legend(fontsize=8)
fig.tight_layout()
print("Outputs:", len(samples), "mean:", np.mean(samples), "right share:", np.mean(samples > 0))
print("Outside the plotted window:", np.sum(np.abs(samples)>6))
plt.show()
把作品簡化成 三種,base 各有 的機率。評審的分數經過 ,得到倍率 ;這些是倍率,不是原始 reward,所以 的分數最高。新目標希望 的比例是 。
現在每次只畫兩張,也就是 Best-of-。最後回傳 的機率是多少? 可以先算兩張都不是 的機率,再取補事件。它會剛好等於目標的 嗎?
也可以先在心裡想,再看整理。文字只留在這個頁面,重新載入就會消失。
兩張獨立,而且只要有一張 ,它就會贏。因此
輸出 必須兩張都是 ;輸出 則是沒有 ,但至少有一張 。三個機率加起來是 ,卻不是目標的 。Best-of- 看的是誰勝過其他候選,reward tilt 看的是 base 乘上倍率後的比例。
甚至把倍率 換成 ,只要分數排序相同,Best-of- 的這三個比例仍不變;目標比例卻會變成 。所以只看排序的選擇,無法一般地保留倍率的資訊。
我們維持 Best-of-,每天重新生成,最後收集十萬張輸出。 的比例會比較接近哪個量?
每趟輸出 C 的機率仍是九分之五;增加收集次數不會改掉每趟規則。
十分之七是指定 target 的比例。反覆使用另一個輸出分布,不會因為次數多就換成 target。
每趟仍只比兩張,C 有時不在候選裡。增加的是重跑次數,不是每趟的候選數。
「假如桌上剛好是一張 、一張 、一張 ,那讓它們各以 被選到呢? 還是常出現,但其他作品也有機會。」
這是在固定這一批候選之後,按權重抽一張。真正每趟從 base 生成,桌上不一定三種各有一張;所以還要問:base 原本的比例已經在哪裡反映?我們該替每張候選補哪個倍率?
原本常見的作品,已經比較容易進入候選集合。想讓它們對應 ,要補的是「target 相對於抽樣來源增加多少」。在 base 為正的位置,試著把下面比值化簡:
哪些量會消掉?哪些量對所有候選相同? 若最後還要除以這批權重的總和,那個共同常數需要先算出來嗎?
也可以先在心裡想,再看整理。文字只留在這個頁面,重新載入就會消失。
Base 在分子與分母消去,剩下 。 對所有候選相同,正規化時也會消去,因此第 張只需要正權重
是相對倍率, 才是固定這批候選後,抽中第 張的機率。Base 的比例由候選出現的頻率反映,選擇時補上 reward 倍率;不是再乘一次 base probability。
用 target/抽樣來源的比值修正貢獻,是 importance sampling 的核心。這裡先做 importance weighting(IW),再按 normalized weights 抽出一個編號,回傳那張完成品。
令 是想平均的量,例如某類作品的指示函數,並假設 target 下的絕對期望有限。把期望的積分乘上並除以 base density,得到
兩個 expectation 都平均 base 抽出的完成品;分子多乘想平均的 ,分母是 。用同一批 個獨立候選估計兩個平均, 相除消去後,得到
這是 self-normalized importance sampling:直接計算加權平均,不必再抽一張。有限樣本的比值通常有偏差;若候選獨立、相關量可積分,增加 讓分子與分母的樣本平均趨近真正期望,才讓比值趨近 target 平均。
這次不用 argmax。每批候選重新生成,從當批正規化權重抽編號;對照 N = 1 與較大的 N,並檢查 target 是否真的被近似。
先預測,再觀察分布
候選已經從 base 抽過,只按 exponential 倍率選一個,N = 1 和較大的 N 會有什麼不同?
A 每趟 N = 1;B 用你選的 N。每批都生成新候選,再正規化該批倍率,抽一個編號。
灰線先畫出原本雙峰資料。執行後才會加上實際樣本;參考曲線不代表已經抽到了它。
用剛才的圖與數字說明
N = 1 時唯一候選的正規化權重一定是 1,所以輸出仍是 base。N 增加改善候選涵蓋,但有限候選的 self-normalized resampling 仍是近似;選中某張的條件機率,也不等於跨所有批次的結果比例。
這是標準對照的機制說明;自由改條件或 Python 後,以你實際執行的設定與圖為準。
每批候選的 base 頻率已反映原分布,權重只補 exp(λR),沒有再乘一次 base density。1,000 批各回傳一個,圖與表都來自實際抽樣。
可以改參數,也可以改抽樣規則。按執行時才載入 Python;運算在你的瀏覽器進行。每次執行從這段程式重新開始,文字不會存檔。
首次展開會帶入上方的控制項設定;之後重開會保留你改過的程式。帶入的是條件,不包含上方累積訓練的參數或生成樣本,因此兩邊的有限抽樣圖不必完全相同。
程式包含相同方法的完整計算與 Matplotlib 分布圖。首次展開套用目前控制項;每次執行重新抽樣。先改最上方一個參數,其他條件保持相同。原生圖不會假裝成你改過的 Python 結果。
import math
import numpy as np
import matplotlib.pyplot as plt
# 1. SETTINGS -- change one, run again, and explain the plotted evidence.
strength = 2
N = 8 # candidates per returned sample
reward_kind = "right" # "right" or "two"
rng = np.random.default_rng(42)
sigma = 0.55
grid = np.linspace(-6, 6, 481)
dx = grid[1] - grid[0]
# 2. FIXED MODEL -- analytic functions, no network training in this cell.
def pdf(x, mu=0.0, var=1.0):
return np.exp(-0.5 * (x-mu)**2 / var) / np.sqrt(2*np.pi*var)
def R(x):
return x if reward_kind == "right" else -(np.abs(x)-2.5)**2
def moments(t):
a = np.exp(-8*t)
return np.sqrt(a), a*sigma**2 + 1-a
def sample_base(size, t=0.0):
signal, var = moments(t)
mu = np.where(rng.random(size) < 0.5, -2.0, 2.0)
return signal*mu + np.sqrt(var)*rng.normal(size=size)
base = 0.5*pdf(grid, -2, sigma**2)+0.5*pdf(grid, 2, sigma**2)
target = base*np.exp(strength*R(grid))
target /= target.sum()*dx # finite-grid quadrature reference
reference = target
reference_label = "Tilt reference (quadrature on [-6,6])"
extra_figure = None
# 3. GENERATION -- 1000 independent batches; return ONE sample per batch.
batch = sample_base((1000, N))
rewards = R(batch)
weights = np.exp(strength*(rewards-rewards.max(axis=1, keepdims=True)))
weights /= weights.sum(axis=1, keepdims=True)
selected = np.sum(rng.random((1000, 1)) > np.cumsum(weights, axis=1), axis=1).clip(0, N-1)
samples = batch[np.arange(1000), selected]
print("Generated candidates and reward calls:", 1000*N)
print("First batch:", batch[0], "selected index:", selected[0])
print("Finite N does not guarantee the reference tilt.")
# 4. PLOT AND CHECK -- the histogram is from the samples above.
fig, ax = plt.subplots(figsize=(8, 3.6))
ax.plot(grid, base, "--", color="gray", label="Original base")
ax.plot(grid, reference, ":", color="black", label=reference_label)
ax.hist(samples, bins=np.linspace(-6,6,49), weights=np.ones(len(samples))/(len(samples)*0.25), alpha=0.5, label="Actual generated samples")
ax.set(xlabel="clean x0", ylabel="density", xlim=(-6,6))
ax.legend(fontsize=8)
fig.tight_layout()
print("Outputs:", len(samples), "mean:", np.mean(samples), "right share:", np.mean(samples > 0))
print("Outside the plotted window:", np.sum(np.abs(samples)>6))
plt.show()
這趟畫出的三張是 ,倍率是 。固定這批候選後,最後回傳 的機率是多少?
正規化用的是這批候選的總權重。這趟沒有 B,也不能把它憑空加入分母。
按權重抽樣仍可能抽中 A。每次只取最大值才會在這批必定選 C。
是三種作品在整個 target 的比例。眼前桌上卻只有 ,權重總和是 ,沒有 的那份權重;固定這批的選擇只能在它們之間發生。
最極端的例子是 :無論權重多高,只有一張就只能選它,輸出仍是 base。按權重抽樣是在有限候選上近似 target,並非每批都精確重現 。
每趟的流程現在是:從 base 獨立畫 張 → 評分並算倍率 → 正規化 → 按機率選一張。增加重跑次數 ,能看清固定 的輸出分布;增加每趟候選數 ,才有機會更充分涵蓋 base,改善這個有限候選近似。
可是每張還是得完整畫完、評一次分。若每次符合要求的機率是 ,獨立畫 張至少命中一次的機率是 ;當 、,也只有約 。這是候選中有沒有命中,不是按權重選出的那張一定成功的機率。
Mugi 已經讓分數進入選擇,Lumi 問的成本仍在:有些草稿畫到一半就很沒希望,能不能把後面的計算分給別張?
你已實作 Best-of-N 與 IW,讀過候選相對權重、有限 N 的輸出 law、重跑 n 趟的意義與生成成本。回到列表,探索能否在途中分配計算,或改抽起點。
途中重抽:SMC 與完整 FK 推導
「先請 denoiser 猜一張完成圖,再讓評審看看;或者把同一張草稿試著完成幾次,看看最後的倍率平均是多少。這樣能不能在途中比較?」
可以嘗試,但兩種都要分清估計與完成品。眼前還帶噪聲的狀態是 ,去噪估計是 ,真正走到終點才得到 。在 VP 表示下,若 ,去噪估計是
是模型預測的 noise, 是 noise amplitude。扣掉預測的 noise,再除回訊號縮放,得到一張乾淨估計;它不是已經確定的未來圖片。
用 估計未來倍率,只需詢問 reward,不需微分。若改用多次獨立試畫,就先替每個完成品算 ,再取平均;試畫本身也有生成成本。這些都只是中途估計,不保證等於精確的未來平均。
Lumi 把去噪估計交給評審,取得很高的分數。這個數字目前能怎麼使用?
評分的輸入是預測完成品。這條路可以不用 gradient,但仍保留預測的不確定性。
被評的是去噪估計,不是真正走到終點的圖片。高 noise 時,兩者可能差很多。
程式能回傳分數,不表示可以對 noisy image 微分;目前做的是比較候選。
假設這一段三張草稿應增加的正權重是 ,正規化後是 。我們希望下一段仍保留三份候選,每次按這些機率抽一個草稿,允許同一張被抽到多次。
抽三次,第三張平均會拿到多少份?每一趟都必須剛好有這個份數嗎?先想想「平均份數」和「真的抽到的份數」差在哪裡。
也可以先在心裡想,再看整理。文字只留在這個頁面,重新載入就會消失。
每次選到第三張的機率是 ,抽三次的平均份數是 。前兩張平均是 份,但真正每趟是整數:可能抽到 ,也可能三次都選 ;這些是可能結果,不是指定要複製的名單。
按機率重抽候選,稱為 resampling。重抽後每份候選重新具有相同權重 ;高權重草稿平均有更多後代,得到更多後續計算,總份數仍是三份。原本生成模型與評分程式都沒有重新訓練。
這裡的 先假設是「這一段應增加的倍率」。有了估計,還得算它怎麼變成增量權重;等一下就檢查這一步。
把生成中的候選稱為 particle,同時保留 個。沿用 diffusion 從高 noise 往 生成的時間,選 checkpoints ; 是段數, 是時刻 的第 份草稿。
從 到 ,先用原 reverse sampler 讓每份草稿走一段,稱為 propagate。接著替第 份計算這段新增的權重 ,稱為 weight;除以這批的權重總和得到 ,再用這些機率重抽 個編號,稱為 resample。
重複「走一段 → 算增量權重 → 按機率重抽」,就是 Sequential Monte Carlo(SMC) 的基本操作。這裡每個 checkpoint 都重抽,重抽後權重回到 ;到 ,從最後 張等機率選一張回傳。改變的是哪些候選有後代,並非 。
每個粒子有自己的位置與祖先。先逐步執行一趟,再跑 300 趟各回傳一個的直方圖;故意把中途 H 估錯,觀察最後的真實 reward 校正能否救回已消失的左峰。後面會完整推導這個新舊比值為何必要。
先預測,再觀察分布
中途估計故意只偏右,最後即使用真正 reward 校正,有限粒子還能保留左峰嗎?哪些草稿會共享祖先?
A 使用 posterior 平均倍率;B 使用故意偏右的中途估計。M、checkpoints、terminal reward 和各趟種子相同。
也可以先用一趟 population 逐步檢查。每次只做一個動作:走一段 → 算增量權重 → 重抽。
灰線先畫出原本雙峰資料。執行後才會加上實際樣本;參考曲線不代表已經抽到了它。
用剛才的圖與數字說明
每個新狀態只除以自己親代的舊 H,終點用真正 exp(λR),所以理想路徑的中間 H 相消。有限 M 仍可能失去某一群;增加 M 也不保證每個種子都更好。彩色圖是一趟的祖先,主直方圖則是 300 趟獨立 population 各回傳一個,不把同祖先後代當成獨立樣本。
這是標準對照的機制說明;自由改條件或 Python 後,以你實際執行的設定與圖為準。
此 toy 的 base transition 使用已知 mixture VP 過程的精確 reverse conditional,讓誤差集中在有限粒子與中途估計。起點 H_K = 1,終點 H₀ = exp(λR),每段正規化後 multinomial 重抽 M 份;posterior 的 H 由解析積分取得,沒有隱藏的試畫。
可以改參數,也可以改抽樣規則。按執行時才載入 Python;運算在你的瀏覽器進行。每次執行從這段程式重新開始,文字不會存檔。
首次展開會帶入上方的控制項設定;之後重開會保留你改過的程式。帶入的是條件,不包含上方累積訓練的參數或生成樣本,因此兩邊的有限抽樣圖不必完全相同。
程式包含相同方法的完整計算與 Matplotlib 分布圖。首次展開套用目前控制項;每次執行重新抽樣。先改最上方一個參數,其他條件保持相同。原生圖不會假裝成你改過的 Python 結果。
import math
import numpy as np
import matplotlib.pyplot as plt
# 1. SETTINGS -- change one, run again, and explain the plotted evidence.
strength = 2
M = 24 # particles per population
estimator = "posterior" # "posterior", "plugin", "misleading"
checkpoints = 6
reward_kind = "two" # "right" or "two"
rng = np.random.default_rng(42)
sigma = 0.55
grid = np.linspace(-6, 6, 481)
dx = grid[1] - grid[0]
# 2. FIXED MODEL -- analytic functions, no network training in this cell.
def pdf(x, mu=0.0, var=1.0):
return np.exp(-0.5 * (x-mu)**2 / var) / np.sqrt(2*np.pi*var)
def Phi(x):
return 0.5 * (1 + np.vectorize(math.erf)(np.asarray(x) / np.sqrt(2)))
def R(x):
return x if reward_kind == "right" else -(np.abs(x)-2.5)**2
def moments(t):
a = np.exp(-8*t)
return np.sqrt(a), a*sigma**2 + 1-a
def sample_base(size, t=0.0):
signal, var = moments(t)
mu = np.where(rng.random(size) < 0.5, -2.0, 2.0)
return signal*mu + np.sqrt(var)*rng.normal(size=size)
def sigmoid(v):
return np.exp(-np.logaddexp(0, -v))
def post_params(x, t):
signal, var = moments(t)
right = sigmoid(4*signal*x/var)
gain = signal*sigma**2/var
left_mu = -2 + gain*(x + 2*signal)
right_mu = 2 + gain*(x - 2*signal)
variance = sigma**2*(1-np.exp(-8*t))/var
return right, left_mu, right_mu, variance
def posterior_mean(x, t):
w, m0, m1, v = post_params(x, t)
return (1-w)*m0+w*m1
def h(x, t):
if t == 0:
return np.exp(strength*R(x))
w, m0, m1, v = post_params(x, t)
if reward_kind == "right":
return (1-w)*np.exp(strength*m0+0.5*strength**2*v)+w*np.exp(strength*m1+0.5*strength**2*v)
d = 1+2*strength*v
def component(mu):
result = 0.0
for side in [-1, 1]:
center = (mu+2*strength*v*side*2.5)/d
factor = np.exp(-strength*(mu-side*2.5)**2/d)/np.sqrt(d)
result += factor*Phi(side*center/np.sqrt(v/d))
return result
return (1-w)*component(m0)+w*component(m1)
base = 0.5*pdf(grid, -2, sigma**2)+0.5*pdf(grid, 2, sigma**2)
target = base*np.exp(strength*R(grid))
target /= target.sum()*dx # finite-grid quadrature reference
reference = target
reference_label = "Tilt reference (quadrature on [-6,6])"
extra_figure = None
# 3. SMC -- exact reverse transitions of the known mixture; finite M resampling.
def reverse_base(x, current_t, next_t):
sig0, var0 = moments(next_t)
sig1, var1 = moments(current_t)
r = np.exp(-4*(current_t-next_t))
right = sigmoid(4*sig1*x/var1)
mu = np.where(rng.random(x.shape) < right, 2.0, -2.0)
gain = r*var0/var1
return sig0*mu+gain*(x-sig1*mu)+np.sqrt(max(0, var0-gain*gain*var1))*rng.normal(size=x.shape)
def H(x, t):
if t == 1:
return np.ones_like(x) # H_K = 1
if t == 0:
return np.exp(strength*R(x)) # actual terminal reward
if estimator == "posterior":
return np.maximum(1e-100, h(x, t))
if estimator == "plugin":
return np.maximum(1e-100, np.exp(strength*R(posterior_mean(x, t))))
return np.exp(np.clip(4*strength*x, -100, 100)) # deliberately wrong intermediate estimate
runs = 300
x = sample_base((runs, M), 1.0)
ancestor = np.tile(np.arange(M), (runs, 1))
trace = [x[0].copy()]
lineage = [ancestor[0].copy()]
for k in range(checkpoints):
t = 1-k/checkpoints
next_t = 0 if k+1 == checkpoints else 1-(k+1)/checkpoints
proposed = reverse_base(x, t, next_t)
log_weights = np.log(H(proposed, next_t))-np.log(H(x, t))
weights = np.exp(log_weights-log_weights.max(axis=1, keepdims=True))
weights /= weights.sum(axis=1, keepdims=True)
# Each proposed child is divided by its OWN parent's H before resampling.
cdf = np.cumsum(weights, axis=1)
parents = np.stack([np.searchsorted(cdf[i], rng.random(M)).clip(0, M-1) for i in range(runs)])
x = np.take_along_axis(proposed, parents, axis=1)
ancestor = np.take_along_axis(ancestor, parents, axis=1)
trace.append(x[0].copy())
lineage.append(ancestor[0].copy())
# ONE output from each independent population, not 300*M independent descendants.
samples = x[np.arange(runs), rng.integers(M, size=runs)]
print("Independent populations:", runs, "particles each:", M)
print("Reverse transitions:", runs*M*checkpoints, "H calls:", 2*runs*M*checkpoints)
print("Surviving ancestors in the first population:", len(np.unique(ancestor[0])))
extra_figure, ax = plt.subplots(figsize=(8, 3.2))
for i, (positions, origins) in enumerate(zip(trace, lineage)):
ax.scatter(positions, np.full(M, i), c=origins, cmap="tab20", s=15, vmin=0, vmax=max(1,M-1))
ax.set(xlabel="noisy state x_t (last row is clean)", ylabel="checkpoint in generation order")
ax.invert_yaxis()
extra_figure.tight_layout()
# 4. PLOT AND CHECK -- the histogram is from the samples above.
fig, ax = plt.subplots(figsize=(8, 3.6))
ax.plot(grid, base, "--", color="gray", label="Original base")
ax.plot(grid, reference, ":", color="black", label=reference_label)
ax.hist(samples, bins=np.linspace(-6,6,49), weights=np.ones(len(samples))/(len(samples)*0.25), alpha=0.5, label="Actual generated samples")
ax.set(xlabel="clean x0", ylabel="density", xlim=(-6,6))
ax.legend(fontsize=8)
fig.tight_layout()
print("Outputs:", len(samples), "mean:", np.mean(samples), "right share:", np.mean(samples > 0))
print("Outside the plotted window:", np.sum(np.abs(samples)>6))
plt.show()
Mugi 說:「既然想把時間分給好的草稿,那每次只留最高分的一張,把它複製成 份,不就好了?」這個想法漏看了什麼?
有限候選需要保留探索。評分的是對未來的估計,不是已知道哪條路最後最好。
模型參數仍然固定。只留一個祖先會改變候選與計算分配,但不等於更新生成模型參數。
如果後續 sampler 有新的隨機性,相同草稿的複本可能走到不同結果。但沿完全相同的 deterministic ODE 走,相同狀態就會繼續走同一條路;複製十份,也還是同一種答案。
只剩少數祖先、探索越來越窄的現象稱為 particle degeneracy。按 normalized weights 隨機重抽也可能發生它,尤其權重很集中時。若另外加探索或擾動,還得檢查那個動作是否保留 target;不能只因複本數很多就宣稱多樣性增加。
只剩少數祖先、後續探索越來越窄,稱為 particle degeneracy。即使用隨機重抽,權重很集中時仍可能發生;相同草稿的複本,還得靠後續的隨機生成才可能分開。若沿同一個 deterministic 規則走,複製份數也不會增加不同答案。
「我們已經知道怎麼重抽。可是某張草稿的未來倍率先估成 ,走到完成品是 。兩段各乘一次 、,最後是 ;我們想增加的明明只有 倍,怎麼多算了?」
把 checkpoint 的正估計記成 。它可以是剛才的便宜估計,不要求等於 U7.2 的精確 posterior 平均。開始取 ,完成時取真正的 ;中間都要求正值。
我們想讓每條完整的 base path,最後只增加一次 terminal reward 倍率。這裡追的是累積乘積應該是多少,不是每段評分各有多高。先一起示範,再換你選下一個動作。
這條路只切兩段:、、。每次只展開一個動作,看看想算的量,怎麼決定我們使用的工具。
從累積倍率,找出每段應增加多少
先解「 乘上多少等於 」,第一段的倍率就是
這裡 是同一條路在這兩個 checkpoints 的估計值。第一段完成後,這條路已經累積 倍;下一段不能假裝還沒乘過。
這次要解「 乘上多少等於 」,因此新增的是
直接再乘 會得到 ;相減得到的 也不是要乘的倍率。選相除,是因為現在要解的是乘法裡還缺哪個因子。
把兩段接起來,得到
中間估計先幫忙分配計算,再被下一段的比值抵消。不是把 reward 忽略掉,而是讓同一份終點偏好只累積一次。
換一條路:上一個 checkpoint 已累積 倍,新的估計希望累積到 倍。這段應新增哪個倍率?先想要解的是「乘上多少」,再選工具。
新舊比值補的是累積乘積還缺多少,已經乘過的部分不會再算一次。
相減得到 9,是加法上的差;把它當倍率乘上原本的 3,會得到 27,而不是希望的 12。
原本已經有 3 倍,再直接乘 12,就累積到 36 倍;相當於把之前的那份又算進去。
現在把同一條路切成三段。起點、中間兩個 checkpoints、終點的正估計依序是 ;這裡先記同一路徑上的取值,等一下再把狀態寫回函數的輸入。
每段都用新估計除以舊估計,累積是
哪些項能消去?最後還剩誰? 再加上起點取 、終點使用真正的 reward 倍率,試著用一句話說出:為什麼中間估計不必精確,整條路的理想倍率仍能保留?
也可以先在心裡想,再看整理。文字只留在這個頁面,重新載入就會消失。
三段乘積裡, 都在分子、分母各出現一次,只剩 。接著把這個動作寫回每一段的狀態,檢查真正需要的條件。
從同一路徑的比值,推到終點的倍率
將這段的倍率函數記成 potential:
分子是走完這段後的估計,分母是走之前的估計。新狀態不能拿別張草稿的舊估計來相除;否則前後相消的那個數字就不再相同。
正文使用原 sampler 走這一段,所以第 個候選的增量權重是
用這批 的總和正規化,才得到選中各候選的機率。改用別的 proposal 時還要校正抽樣來源,後面的頁內補充會展開那個比值。
沿同一條祖先路徑,把各段的比值相乘;中間取值就像剛才三段的例子一樣消去,只剩
這是沿一條路徑檢查累積倍率,不是在平均整批 particles;不同路徑有自己的中間狀態與估計。
起點取 、終點取 ,所以
中途估計分配的是計算,兩端的選擇確定了累積倍率。 中間估計須為正,才能使用比值;維持這個構造時,它不改掉理想的 terminal tilt。但有限 的草稿可能早就被淘汰,實際近似仍受估計影響。
我們已知道每條路最後只乘 。可是同一張完成品可能由不同路徑抵達。想算完成品在新分布下占多少,接著該做哪個數學動作?
要算的是完成品的總量,每條通往它的路都可能有貢獻,且保留原本的路徑機率。
只留最可能的路,會拿掉其他也會抵達這個終點的貢獻;那是在挑路,不是在算終點的總機率。
各路在 base 下不一定一樣常發生。平均 reward 倍率卻丟掉路徑原本的機率,就沒有保留 base 的出現比例。
先換一個只有四條 base paths 的小例子:兩條抵達 ,機率是 ;兩條抵達 ,機率是 。終點倍率是 得 、 得 。加權後的總量是
同一個終點的倍率能提出求和,所以也能先把 base 路徑機率加成 、,再各乘 。正規化總量是 ,得到 占 、 占 。
這就是剛才選「累積所有路徑」的理由;只保留一條路,就會漏掉另一條的質量。
接著用原 sampler 的路徑分布 ,把「每條路增加多少」接到「完成品應占多少」。整組 checkpoints 記成 ;我們先定義路徑 target ,再檢查它的終點。
先寫路徑權重,再把途中狀態累積起來
表示 base 分配給這些路徑的機率質量。這一行的乘積沿用前面定義的狀態輸入; 是所有 base paths 加權後的總量,假設有限且非零。
新權重只看終點 。所以兩條路只要抵達同一種完成品,就會乘上同一個 terminal weight;原本機率不同的路,仍保留它們的相對貢獻。
剛才四條路已算出 A 的加權質量為 0.3、B 為 2.1。現在固定同一個終點,所有通往它的路都乘相同的 terminal reward;因此倍率能提出對中途狀態的求和。連續空間用積分,保留的是各條 base path 原本的貢獻。
若 base sampler 的終點分布是 ,和剛才四條路一樣,把各終點的所有路徑累積後得到
離散時是終點機率,連續時是 density。我們先定義路徑怎麼加權,再推出它的 terminal target;這個定義裡沒有 particle 數 ,也還沒有真的抽出圖片。
這套用 potentials 加權路徑的架構,稱為 Feynman–Kac(FK)model。SMC 才是用有限候選近似它的抽樣演算法;完整推導給出理想 target,有限草稿的近似與退化仍要另外檢查。
Singhal 等人的 FK steering 將這套構造用在 diffusion 引導,選擇中途估計、potentials、proposal 與重抽時機,再用 particle 方法抽樣。Kim、Kim 與 Park 的 DAS(Diffusion Alignment as Sampling) 也使用 SMC,透過 reward tempering 逐步增加中途 target 的偏好影響。
這些名稱要對回「target 與 potentials 怎麼構造」和「實際怎麼抽」兩層,不表示 FK 和 SMC 是互斥的方法。
正文用原 sampler 走一段,所以實際 proposal 和 base transition 相同。如果改用另一個 transition density 提出新狀態,而 base 的 transition density 是 ,每段重抽、上一段權重相同時,就還要補抽樣來源的比值:
第一項是 target 相對於 base path 增加的倍率;第二項補償「真正從 proposal 抽,而不是從 base transition 抽」的差別,和完成品 IW 用的是同一個校正理由。Proposal 要涵蓋 target 會有質量的區域,才能使用這個比值。
當 ,後面比值是 ,才回到正文的 。這裡的 是增量權重函數,後面的 則是 deterministic generator;下標與輸入區分它們的角色。
某個中間 估得很差,但維持正值、沿同一路徑用新舊比值,, 仍用真正的 terminal reward。下面哪個判斷符合剛才的構造?
路徑 target 由累積倍率確定,有限 particles 的誤差與退化由實際抽樣過程產生。
差的估計可能過早淘汰後來很好的草稿。乘積正確,不代表有限 M 已充分探索。
相消後剩下的是 。如果最後仍用預測圖的 reward,沒有換回真正 的 ,那剩下的就可能是另一個 terminal weight。
同樣地,若起點 隨 改變、卻沒有補相應的起始加權,最後會多出 。增加 可以改善抽樣近似,不能把已經改掉的 FK target 自動修回來。
現在可以把 Lumi 的操作完整接起來:抽 個原 sampler 起點;每段保留舊狀態,讓它們各走一段,算新 除以自己來路的舊 ;正規化後獨立重抽 個編號,允許重複。重抽後等權重,再進下一段;終點的 使用完成品真正的 reward。
最後的 張可以表示一個 empirical distribution:
是把質量放在第 張完成品上的 point mass。想算某種作品的比例,就數這 張有幾張屬於它,再除以 ;每份等權重,不表示它們是彼此獨立的樣本,因為可能共享祖先。從它們等機率抽一張,便是這趟回傳的結果。
令 是有界的圖片統計量,例如 的指示函數。固定 checkpoints、使用正確權重,且符合相應的可積分性條件時,SMC 的一致性寫成
左邊是一趟裡的 個 particles,右邊是 target 下的平均。「依機率」表示兩者的差超過任意固定容許值的機率趨近 ;不要求這趟的 particles 彼此獨立。
增加 才是在改善每趟對 FK target 的近似。固定 、獨立重跑 趟,只會更清楚這個有限 sampler 的輸出比例,仍可能留下偏差。這個極限也不適用於已經設錯 terminal weighting 的 target。
不必。可以用 effective sample size(ESS) 描述 normalized weights 有多集中:
等權重時,每項是 ,總和是 ,所以 ESS 是 。若只有一份權重接近 ,總和接近 ,ESS 就接近 ;它描述權重集中程度,不是在數獨立祖先。
可以等 ESS 低於門檻才重抽。某段沒有重抽時,要保留舊權重,下段再乘新增的 potential;不能一面略過重抽,一面仍假設下一段從等權重開始。
每趟只用 個 particles,但獨立重跑很多趟。只增加重跑次數 ,就足以消除每趟有限 的抽樣偏差嗎?
你已看見粒子的重抽與祖先,讀過增量比值、potentials 相消、FK 的終點分布,以及有限 M 的近似與退化。理想 target 的構造與有限抽樣的表現,要分別檢查。
保留生成規則、改抽起點:MCMC 與 SPT
「回到我的提案。如果複製草稿總會畫出同一張,那我們保留生成規則,改試起點呢?」
把 deterministic generator 記成 :起點 送進它,完成品是 ; 包含這次固定的生成計算,給定同一個 就得到同一個結果。原本起點的 density 是 ,所以原本抽的是 。
每個起點都有能詢問的分數 。想讓高分結果更常出現,可以把 reward 倍率放回起點:
這裡用 保留原本起點的相對 density,用 exponential 提高高分結果所對應的權重。生成規則沒有改,換的是想抽的 source distribution。 它的 target 寫得出來,還不表示我們已會從它抽樣。
暫時用三個離散起點做例子: 原本各有 機率,、。完成品的 reward 倍率是 得 、 得 。
把倍率放回起點,三個未正規化權重是 。正規化後,生成 的機率是多少? 試著把會畫成同一種作品的起點機率加起來,再和圖片端的 base 乘 reward 比較。
也可以先在心裡想,再看整理。文字只留在這個頁面,重新載入就會消失。
起點權重總和是 ,所以三個起點的機率是 。 只有一個來源,機率是 ; 有兩個來源,相加得到 。
圖片端原本是 占 、 占 ,乘倍率 後權重同樣是 ,正規化也是 。不需要 一對一,所有會畫出同一類結果的來源都要算進來。
更一般地,把起點端的正規化常數也記成 ,它是 base 起點生成的完成品之 reward 倍率平均:。從 抽起點後用同一個 ,任意可積分的圖片統計量 的平均是
原本 經 產生 base output law;上式正是這個 base 的 reward 加權平均。因此 source tilt 接回的是 generator 真正的 output law,不需要計算圖片端的 density,也不表示原模型誤差消失。
「現在有了起點 target。那先從目前的 提出一個新 ,畫出 並評分,再決定要不要換過去,好嗎?」
可以,但如果永遠只收較高分者,就又變成找高分點,而不是抽指定比例。把目前起點、下一個起點串成序列,稱為一條 chain;接受時移到 ,拒絕時仍留在 ,留下不動也是這個序列的一步。
用有接受機率的 Markov transition 保留指定 target,再收集樣本,稱為 Markov chain Monte Carlo(MCMC)。先考慮對稱 proposal:從 提出 ,和反向提出的機率相同。那接受機率是
是這條 chain 的 reward 強度, 是 chain 編號。Normalizing constant 相除消去;比的是 prior density 乘 reward 倍率。每次抽 ,若 就換成 ,否則留下 。這些計算都不需要 reward gradient。
把此 chain 的 target 記成 ,proposal density 暫記為 。對稱時 ,正向的機率流量是
反向把 對調,因為 proposal 對稱,得到相同的量;加上拒絕時留在原位的機率,就不會讓 target 的質量淨流向某一邊。這個正反流量相等的條件稱為 detailed balance,它給出 target invariance。
若 proposal 不對稱,target 比值還要乘 ,不能仍沿用只有 prior 與 reward 的版本。保留 target 是 transition 的性質;有限次更新是否探索充分,還是另一個問題。
固定的 quantile generator 將 standard Normal 起點變成同一個雙峰 base。只改起點的接受規則;查看較低分接受、重複位置與實際輸出直方圖。
先預測,再觀察分布
若每次只收較高 reward,會得到目標分布嗎?真正的接受規則,會接受較低分的起點嗎?
A 用 λ = 0;B 用你選的 λ。兩條單鏈都從 z = −0.8 開始,保留相同步數、步幅與種子。
灰線先畫出原本雙峰資料。執行後才會加上實際樣本;參考曲線不代表已經抽到了它。
用剛才的圖與數字說明
接受規則同時看 standard Normal prior 與 reward 比值,允許較低分的 proposal。拒絕時仍留在原位,因此重複位置也要收集;只收接受的步會改變分布。有限鏈可能困在一側,400 步 warm-up 並不保證已收斂。
這是標準對照的機制說明;自由改條件或 Python 後,以你實際執行的設定與圖為準。
固定 G(z) = F_base⁻¹(Φ(z)):Φ 是 standard Normal 的累積機率,F_base 是雙峰資料的累積機率。先找 z 的累積比例,再找 base 中同一比例的位置,便將 z ~ N(0,1) 變成同一個雙峰 base;同一 z 每次得到同一 x₀。數值反查沒有訓練或微分 reward。對稱 Gaussian proposal 使用 prior × reward 的接受比;直方圖包含拒絕與相關樣本。
可以改參數,也可以改抽樣規則。按執行時才載入 Python;運算在你的瀏覽器進行。每次執行從這段程式重新開始,文字不會存檔。
首次展開會帶入上方的控制項設定;之後重開會保留你改過的程式。帶入的是條件,不包含上方累積訓練的參數或生成樣本,因此兩邊的有限抽樣圖不必完全相同。
程式包含相同方法的完整計算與 Matplotlib 分布圖。首次展開套用目前控制項;每次執行重新抽樣。先改最上方一個參數,其他條件保持相同。原生圖不會假裝成你改過的 Python 結果。
import math
import numpy as np
import matplotlib.pyplot as plt
# 1. SETTINGS -- change one, run again, and explain the plotted evidence.
strength = 2
steps = 1600
reward_kind = "two" # "right" or "two"
proposal_step = 0.35
rng = np.random.default_rng(42)
sigma = 0.55
grid = np.linspace(-6, 6, 481)
dx = grid[1] - grid[0]
# 2. FIXED MODEL -- analytic functions, no network training in this cell.
def pdf(x, mu=0.0, var=1.0):
return np.exp(-0.5 * (x-mu)**2 / var) / np.sqrt(2*np.pi*var)
def R(x):
return x if reward_kind == "right" else -(np.abs(x)-2.5)**2
base = 0.5*pdf(grid, -2, sigma**2)+0.5*pdf(grid, 2, sigma**2)
target = base*np.exp(strength*R(grid))
target /= target.sum()*dx # finite-grid quadrature reference
reference = target
reference_label = "Tilt reference (quadrature on [-6,6])"
extra_figure = None
# 3. SOURCE SEARCH -- a fixed quantile generator G; standard-Normal source prior.
def G(z):
p = min(1-1e-12, max(1e-12, 0.5*(1+math.erf(z/math.sqrt(2)))))
lo, hi = -8.0, 8.0
for _ in range(40):
mid = (lo+hi)/2
cdf = 0.25*(2+math.erf((mid+2)/(sigma*math.sqrt(2)))+math.erf((mid-2)/(sigma*math.sqrt(2))))
if cdf < p:
lo = mid
else:
hi = mid
return (lo+hi)/2
lambdas = [strength]
proposal_steps = [proposal_step] # step size is a separate choice
states = []
for _ in lambdas:
x = G(-0.8)
states.append([-0.8, x, float(R(x))])
samples, trace = [], []
accepted = swaps = lower_accepted = 0
burn = 400
for k in range(steps+burn):
for j, lam in enumerate(lambdas):
z, x, old_reward = states[j]
proposed_z = z+proposal_steps[j]*rng.normal()
proposed_x = G(proposed_z)
proposed_reward = float(R(proposed_x))
log_ratio = -0.5*(proposed_z**2-z**2)+lam*(proposed_reward-old_reward)
if math.log(max(1e-300, rng.random())) < min(0, log_ratio):
accepted += 1
lower_accepted += proposed_reward < old_reward
states[j] = [proposed_z, proposed_x, proposed_reward]
if len(states) > 1:
j = k % 2
log_ratio = (lambdas[j]-lambdas[j+1])*(states[j+1][2]-states[j][2])
if math.log(max(1e-300, rng.random())) < min(0, log_ratio):
states[j], states[j+1] = states[j+1], states[j] # swap z, G(z), reward together
swaps += 1
if k >= burn:
samples.append(states[-1][1]) # keep rejected/repeated states too
trace.append([state[0] for state in states])
samples = np.asarray(samples)
print("G and reward evaluations:", len(lambdas)*(steps+burn+1))
print("Accepted:", accepted, "accepted lower rewards:", lower_accepted, "swaps:", swaps)
print("Histogram includes rejections; states are correlated. Burn-in does not certify convergence.")
extra_figure, ax = plt.subplots(figsize=(8, 3.2))
for j, lam in enumerate(lambdas):
ax.plot(np.asarray(trace)[:,j], label=f"source chain, lambda={lam:g}", alpha=0.8)
ax.set(xlabel="retained iteration (rejections included)", ylabel="source z")
ax.legend(fontsize=8)
extra_figure.tight_layout()
# 4. PLOT AND CHECK -- the histogram is from the samples above.
fig, ax = plt.subplots(figsize=(8, 3.6))
ax.plot(grid, base, "--", color="gray", label="Original base")
ax.plot(grid, reference, ":", color="black", label=reference_label)
ax.hist(samples, bins=np.linspace(-6,6,49), weights=np.ones(len(samples))/(len(samples)*0.25), alpha=0.5, label="Actual generated samples")
ax.set(xlabel="clean x0", ylabel="density", xlim=(-6,6))
ax.legend(fontsize=8)
fig.tight_layout()
print("Outputs:", len(samples), "mean:", np.mean(samples), "right share:", np.mean(samples > 0))
print("Outside the plotted window:", np.sum(np.abs(samples)>6))
plt.show()
對稱 proposal 下,新舊起點的 prior density 相同,新候選的 reward weight 是舊候選的一半。這次應該怎麼決定?
接受規則允許較低分的候選,保留正反向的機率流量。
永遠拒絕分數較低的候選,會把接受規則改成只爬分數,失去剛才的 target 保留理由。
對稱的是 proposal,不是 target。Reward weight 的差別仍要進入接受機率。
「如果高分的起點分成幾個相隔很遠的區域,一條 chain 可能一直只找到同一種構圖。能不能同時用幾種 reward 強度找,再讓它們交換線索?」
讓第 條 chain 使用 ,target 是
小的 比較不受 reward 影響,可以搭配較大的提案步伐探索 base 有機會生成的其他區域;大的 更偏好高分起點,可以搭配較小步伐修正。強度不會自動決定提案步長,兩者仍是分開選的設計。
各 chain 先依剛才的接受規則試新起點,再提出跨 chain 的交換,把探索找到的結果帶到目標強度。這是 Wang 等人的 Source Parallel Tempering(SPT):在 source space 同時抽不同強度的 targets,且交換也有接受機率。最後從指定強度 的 chain 收集 ,再輸出 。
兩條 chain 的強度是 ,起點是 。Joint target 是兩個 targets 的乘積;若交換起點,新舊比值中與 reward 有關的部分是
兩邊都含相同的 ,prior 因子已經消去。試著把 exponential 的指數合併,看看哪些強度與 reward 的差會乘在一起。
也可以先在心裡想,再看整理。文字只留在這個頁面,重新載入就會消失。
分子的指數減去分母的指數,再把共同項提出來,就得到
在對稱選擇交換 pair 的設定下,這個接受規則保留各 chain targets 的乘積。不能看到另一條分數高就直接搬過來,否則改掉的不只是探索速度,也可能是 target。
實際一輪 SPT 是:各 chain 提出新起點 → 生成與評分 → 依 接受或留在原位 → 對一對 chains 依 決定是否交換。交換時起點與已算好的 reward 一起換,避免拿某個起點配上另一張圖片的分數。
每個新 proposal 都有生成與評分成本。重複這個流程後,目標 chain 的樣本可能相關;還要看它是否探索到不同高分區域,保留 target 的規則,不等於有限搜尋已到達 target。
比較單鏈與三鏈的完整執行紀錄。每鏈迭代數相同,因此三鏈花更多 G 與 reward 呼叫;用軌跡與兩峰的涵蓋來判讀,不把較高平均 reward 當成已收斂。
先預測,再觀察分布
多條不同強度的鏈,交換後能找到另一個高分區域嗎?額外的生成與評分成本是多少?
A 是同一個 target 強度的單鏈;B 是 0、λ/2、λ 三條鏈並嘗試相鄰交換。每鏈步數相同,B 的評分成本為三倍,並非等預算比較。
灰線先畫出原本雙峰資料。執行後才會加上實際樣本;參考曲線不代表已經抽到了它。
用剛才的圖與數字說明
每條鏈先做自己的 MCMC proposal,再依交換比值一起交換 z、完成品和 reward。只從 λ 那條鏈收樣本;低強度鏈的樣本不能直接混入。較大的 proposal 是另外設定,不由 λ 自動產生;有限次結果仍可能探索不足。
這是標準對照的機制說明;自由改條件或 Python 後,以你實際執行的設定與圖為準。
同一個固定 quantile generator。三條鏈的 Gaussian proposal 標準差分別為 1.4、0.8、你設定的目標鏈步幅。交換接受比為 exp[(λᵢ−λⱼ)(Rⱼ−Rᵢ)] 截至 1。來源圖畫的是 z 隨迭代改變,並非 diffusion time。
可以改參數,也可以改抽樣規則。按執行時才載入 Python;運算在你的瀏覽器進行。每次執行從這段程式重新開始,文字不會存檔。
首次展開會帶入上方的控制項設定;之後重開會保留你改過的程式。帶入的是條件,不包含上方累積訓練的參數或生成樣本,因此兩邊的有限抽樣圖不必完全相同。
程式包含相同方法的完整計算與 Matplotlib 分布圖。首次展開套用目前控制項;每次執行重新抽樣。先改最上方一個參數,其他條件保持相同。原生圖不會假裝成你改過的 Python 結果。
import math
import numpy as np
import matplotlib.pyplot as plt
# 1. SETTINGS -- change one, run again, and explain the plotted evidence.
strength = 2
steps = 1600
reward_kind = "two" # "right" or "two"
proposal_step = 0.35
rng = np.random.default_rng(42)
sigma = 0.55
grid = np.linspace(-6, 6, 481)
dx = grid[1] - grid[0]
# 2. FIXED MODEL -- analytic functions, no network training in this cell.
def pdf(x, mu=0.0, var=1.0):
return np.exp(-0.5 * (x-mu)**2 / var) / np.sqrt(2*np.pi*var)
def R(x):
return x if reward_kind == "right" else -(np.abs(x)-2.5)**2
base = 0.5*pdf(grid, -2, sigma**2)+0.5*pdf(grid, 2, sigma**2)
target = base*np.exp(strength*R(grid))
target /= target.sum()*dx # finite-grid quadrature reference
reference = target
reference_label = "Tilt reference (quadrature on [-6,6])"
extra_figure = None
# 3. SOURCE SEARCH -- a fixed quantile generator G; standard-Normal source prior.
def G(z):
p = min(1-1e-12, max(1e-12, 0.5*(1+math.erf(z/math.sqrt(2)))))
lo, hi = -8.0, 8.0
for _ in range(40):
mid = (lo+hi)/2
cdf = 0.25*(2+math.erf((mid+2)/(sigma*math.sqrt(2)))+math.erf((mid-2)/(sigma*math.sqrt(2))))
if cdf < p:
lo = mid
else:
hi = mid
return (lo+hi)/2
lambdas = [0.0, strength/2, strength]
proposal_steps = [1.4, 0.8, proposal_step] # step size is a separate choice
states = []
for _ in lambdas:
x = G(-0.8)
states.append([-0.8, x, float(R(x))])
samples, trace = [], []
accepted = swaps = lower_accepted = 0
burn = 400
for k in range(steps+burn):
for j, lam in enumerate(lambdas):
z, x, old_reward = states[j]
proposed_z = z+proposal_steps[j]*rng.normal()
proposed_x = G(proposed_z)
proposed_reward = float(R(proposed_x))
log_ratio = -0.5*(proposed_z**2-z**2)+lam*(proposed_reward-old_reward)
if math.log(max(1e-300, rng.random())) < min(0, log_ratio):
accepted += 1
lower_accepted += proposed_reward < old_reward
states[j] = [proposed_z, proposed_x, proposed_reward]
if len(states) > 1:
j = k % 2
log_ratio = (lambdas[j]-lambdas[j+1])*(states[j+1][2]-states[j][2])
if math.log(max(1e-300, rng.random())) < min(0, log_ratio):
states[j], states[j+1] = states[j+1], states[j] # swap z, G(z), reward together
swaps += 1
if k >= burn:
samples.append(states[-1][1]) # keep rejected/repeated states too
trace.append([state[0] for state in states])
samples = np.asarray(samples)
print("G and reward evaluations:", len(lambdas)*(steps+burn+1))
print("Accepted:", accepted, "accepted lower rewards:", lower_accepted, "swaps:", swaps)
print("Histogram includes rejections; states are correlated. Burn-in does not certify convergence.")
extra_figure, ax = plt.subplots(figsize=(8, 3.2))
for j, lam in enumerate(lambdas):
ax.plot(np.asarray(trace)[:,j], label=f"source chain, lambda={lam:g}", alpha=0.8)
ax.set(xlabel="retained iteration (rejections included)", ylabel="source z")
ax.legend(fontsize=8)
extra_figure.tight_layout()
# 4. PLOT AND CHECK -- the histogram is from the samples above.
fig, ax = plt.subplots(figsize=(8, 3.6))
ax.plot(grid, base, "--", color="gray", label="Original base")
ax.plot(grid, reference, ":", color="black", label=reference_label)
ax.hist(samples, bins=np.linspace(-6,6,49), weights=np.ones(len(samples))/(len(samples)*0.25), alpha=0.5, label="Actual generated samples")
ax.set(xlabel="clean x0", ylabel="density", xlim=(-6,6))
ax.legend(fontsize=8)
fig.tight_layout()
print("Outputs:", len(samples), "mean:", np.mean(samples), "right share:", np.mean(samples > 0))
print("Outside the plotted window:", np.sum(np.abs(samples)>6))
plt.show()
SPT 每次試新起點,都重新生成並評分。這些操作可以一直保留生成模型的參數 嗎?
你已實作 MCMC 與 SPT,完整讀過 source tilt 如何回到 output law、接受與交換的推導,以及有限搜尋與生成成本。回列表後,才能用最後的比較表統整不同限制。
方法名稱已經介紹過了。現在把實際動作與限制放回它們發生的位置;每完成一列,先看整理,再比較下一列。
你能保存多個 noisy states,讓它們各走下一段,也有便宜的未來倍率估計。希望在完成前重新分配計算,哪個操作直接符合需求?
這是途中 SMC 的介面前提,仍須檢查估計與祖先退化。
這仍花完每張候選的生成成本,沒有在途中重分配。
會重複計入同一份偏好,須使用與目標一致的增量比值。
這次生成器是 deterministic,從相同草稿往後總會得到相同結果。你想保留生成規則,也想探索不同答案。哪個做法比較符合這次需求?
完全相同的狀態沿同一條 deterministic 規則走,複製很多份仍會得到同一張,沒有增加不同答案。
固定 G 不代表固定 z;起點可以改用保留指定 source target 的抽樣規則。
單靠找到一個高分點,沒有檢查其他結果的比例,也沒有說明是否探索到其他高分區域。
我們剛才的主線,讓分數決定哪些候選留下來。還有另一個延伸:保留每張草稿,參考當下最好候選的預測完成品,改動接下來的 prediction。 它也不必對 reward 微分,但操作與 SMC 的重抽不同。
時間 上保留 個 ,各自算去噪估計 ,評分後選當下最高分編號
這裡評的是預測完成品,不是已經生成到終點的 。對第 張而言, 是從自己預測移向參考預測的向量。Kim 等人將這種用最好候選引導其他 particles 的方法稱為 Best-of- Guidance(BoNG);這裡沿用 表示途中候選數。
怎麼將這個位移換成 noise prediction?先固定目前的 ,沿用 VP 的去噪公式:
第二行是兩個去噪估計相減,固定的 消去。若想讓左邊等於 ,在 、 時移項,就得到
是本次位移的強度。 保留原 prediction; 時,固定目前 state 算出的新去噪估計剛好等於參考候選,但不表示下一步 state 或最後圖片已經相同。將合成 prediction 交給原 sampler,才讓各張圖走向下一個較低 noise level。
每個 reverse step 重新「去噪估計 → 評分選參考 → 修改每張 prediction → 繼續生成」,最好編號也可以改變。參考候選本身的位移是零。這是一種用候選差異提出方向的方式,沒有計算 ,也沒有更新 。
以上沒有像 FK 那樣證出 terminal weighting 等於 ,所以不能直接宣稱 BoNG 抽到 exact tilt。來源筆記也將它列為延伸;所用 VP 換算保留了 ,不能和 signal amplitude 為 的另一套 noise schedule 混用。
本頁主線已經給出完成品 IW、SMC 的每段循環、SPT 的接受與交換規則;來源技能書另將每個流程寫成數學 pipeline 與 pseudocode,方便逐步查閱。
評審只肯打分,我們仍可以引導:完成後按倍率抽選,在途中用增量權重重抽草稿,或保留 deterministic generator、改抽起點。挑到高分作品和近似指定的出現比例,要用各自的規則檢查;多算幾次不會自動把它們變成同一件事。
這張來源圖整理終點挑選、途中重抽與 source 搜尋。SPT 的各列是 reward 強度,不是 diffusion time;圖中的生成器 對應本頁固定的 ,而 FK 的 是另一個增量權重函數。機制示意不代表有限計算已精確抽到 。
點圖可放大正文為了檢查 sampling 規則,先假設 base 是 。若原模型實際輸出的是 ,同樣的 terminal weighting 近似的是以下權重正規化後的分布:
若還使用有限步 sampler,base 更精確地說是它真正產生的 output law,也包含離散化誤差。增加 candidates、particles 或 chain 搜尋,可以改善相對於這個 base 的抽樣近似,不會自動把 base 修成 。
某種作品若在 base 完全沒有機會,有限 reward 的重新加權也不能讓它出現;低機率區域即使提高倍率,也可能尚未被有限候選看見。因此還要分開看模型誤差、sampling 近似、reward、合理性與多樣性。
接著換一個更具體的要求:使用者交來一張模糊照片,希望生成的清楚圖片真的和它相容。評審該怎麼打分,才是在還原這張照片,而不只是選一張好看的狗? U7.4 從量測過程接續這個問題。
本篇的同一組比例、FK/SMC 推導、SPT 接受規則與 BoNG 延伸,也可以在 U7.3 Reward 只會打分、不會給方向,還能怎麼引導? 連著讀。