U7.3 約 85 分鐘 2026年10月

評審只肯打分,這些草稿接著怎麼畫?

選順序完整探索完成後挑選、途中 SMC 與起點 MCMC/SPT;真的生成樣本、追蹤祖先與鏈,再做最後統整。

想連著讀? 技能書 連續閱讀版 · U7.3 →

評審只肯給分數
Nori 戴 orca 帽的北極熊

使用者還是想看不同的狗照片,也比較喜歡某些帽子、光線與構圖。他交給我們一個評分程式:放進完成的圖片,程式會回傳分數,卻說不出哪個 pixel 應該往哪裡改。生成模型的參數 θ\theta 保持固定,只有分數,還能讓喜歡的結果更常出現嗎?

把完成圖片記成 x0x_0、分數記成 reward R(x0)R(x_0)。這種只能詢問結果、拿不到可用導數的評分,稱為 black-box reward;外部模擬器、碰撞檢查或人類評審都可能提供這種訊號。它不必是機率,也不必能微分。

一張戴紅帽的柯基完成照片交給評分程式,程式只回傳 7 分與沒有修改線索;圖中沒有其他候選、排名、挑選或修改流程。

這張圖得到的 7 分是假設的評分,不是機率或模型實測。箭頭只表示交出圖片、拿回分數;程式沒有提供修改方向。接著能怎麼使用這個分數,先換我們來想。

點圖可放大
Mugi:多畫幾張,再決定留下誰
Mugi 有柴犬臉的柯基

「不知道怎麼改,那我們先照原本方式畫幾張。評審給完分,再選一張回傳。至少不需要請他說出修改方向。」

Mugi 保留每張圖片原本的生成過程,把這次偏好放在完成後的選擇。可以挑最高分,也可以讓高分比較容易被抽到;這兩種規則會得到一樣的出現比例嗎?

Lumi:比較有希望的草稿,多留幾份
Lumi 有翅膀的鹿

「可是每張都畫完才評分,會不會花太多時間?如果畫到一半能先猜它最後會變成什麼,那比較有希望的草稿多留幾份,接著畫;其他草稿就少分一點時間。」

Lumi 想改的是哪些草稿得到接下來的生成計算。評審仍只看完成品,所以途中得先估計未來,不能直接把 noisy image 當成完成照片。估計也可能看錯,不能一開始就把其他草稿全丟掉。

Nori:如果相同草稿總會畫成同一張呢?
Nori 戴 orca 帽的北極熊

「假如同一個起點總會生成同一張圖,那複製草稿也只會畫出一樣的答案。能不能換個起點試畫,把容易畫出喜歡結果的起點找出來?」

Nori 想保留整套生成規則,改的是從哪些起點開始。每個新起點仍要生成與評分;從幾次試畫找到高分起點,也還不等於已經抽到指定的分布。

這次想要怎樣的出現比例?
Lumi 有翅膀的鹿

如果只想找一張高分照片,挑最高分就很合理。但使用者還想保留不同答案;我們這次要檢查的是:反覆回傳圖片後,各種結果的比例,是否接近下面的 reward tilt?

pR(x0)=pdata(x0)eλR(x0)ZR,ZR=Ex0∼pdata[eλR(x0)].\begin{gathered} p_R(x_0)=\frac{p_{\mathrm{data}}(x_0)e^{\lambda R(x_0)}}{Z_R},\\ Z_R=\mathbb E_{x_0\sim p_{\mathrm{data}}} \big[e^{\lambda R(x_0)}\big]. \end{gathered}

pdatap_{\mathrm{data}} 是原本的 base distribution,λ>0\lambda>0 決定 reward 的影響,ZRZ_R 是對 base 抽出的完成品平均 reward 倍率。假設 0<ZR<∞0<Z_R<\infty,除掉它才讓總量等於 11。離散作品用機率,連續圖片用 density;後者的某個精確點不代表有非零的抽中機率。

這個目標讓高分結果增加相對權重,仍保留 base 的比例。它的動機與最佳性在 U7.2 推過;今天追的是怎麼真的抽,而不是重新更新模型。先假設 base sampler 精確生成 pdatap_{\mathrm{data}},實際模型的差別等一下再檢查。

三條路共用的資料與目標
Nori 戴 orca 帽的北極熊

沿用同一個可實作的 base:12N(−2,0.552)+12N(2,0.552)\frac12\mathcal N(-2,0.55^2)+\frac12\mathcal N(2,0.55^2)。位置 x0x_0 是完成資料,xtx_t 是 noisy state;橫軸的數字是 toy 資料,不是即時生成的狗照片。每條路都用同一分布真的抽樣,並和數值積分得到的 reward tilt 對照。

先完整探索一種做法,再回來選下一條

順序由你選。每條路都包含預測、真正抽樣畫圖、必要推導與限制;按最後的返回才標記「已完整探索」。中途可以先回列表,稍後接著讀。

畫完後選:Best-of-N 與 IW

先把一批作品的最高分留下來
Mugi 有柴犬臉的柯基

「先畫 NN 張,再挑分數最高的。這個做法我們現在就能做,對吧?」

六張獨立完成的狗照片,帽子的顏色與品種不同;右邊是只回傳一張的空框,還沒有選定哪張。六張不是同一張圖的去噪步驟。

Mugi 提出多畫幾張之後,再來看這個情境。左邊是分開生成的候選,右邊只回傳一張;每張都要完整畫完,再交給評審。

點圖可放大

從 base 獨立生成 x0(1),…,x0(N)x_0^{(1)},\ldots,x_0^{(N)},評分後找出最高分編號 i⋆i^\star,輸出那張圖片:

i⋆=arg⁡max⁡1≤i≤NR(x0(i)),x^0=x0(i⋆).\begin{gathered} i^\star=\arg\max_{1\le i\le N}R(x_0^{(i)}),\\ \widehat x_0=x_0^{(i^\star)}. \end{gathered}

這就是 Best-of-NN。arg⁡max⁡\arg\max 找的是候選編號,不是在更新 pixels 或參數;同分時可以訂一個固定或隨機的選擇規則。它不需要 reward gradient,也沒改每張候選原本的 sampling path。

每次重新生成 NN 張、選出一張,再把整趟獨立重跑 nn 次,就收集到 nn 張輸出。NN 是每趟比較多少張,nn 是最後收集多少次輸出。 增加 nn 能看清這個規則的出現比例,還得算它是否等於 pRp_R。

真正生成候選,觀察 Best-of-N 分布
Nori 戴 orca 帽的北極熊

先讓每趟的候選真的從雙峰資料抽出,依評分挑一個。觀察排序與倍率的差別,再回來算離散例子的完整比例。

先預測,再觀察分布

N 增加會讓分數變高,但回傳分布就會接近黑色點線的 reward tilt 嗎?固定排序,只放大 λ 又會怎樣?

A 每趟 N = 1;B 用你選的 N。每一趟重新生成候選,只回傳最高分的一個,獨立重跑 1,000 趟。

灰線先畫出原本雙峰資料。執行後才會加上實際樣本;參考曲線不代表已經抽到了它。

Base、參考密度與實際樣本直方圖 灰色虛線為固定 base;黑色點線為標示的參考密度;金色為本次實際樣本,A/B 對照另用藍色輪廓表示 A。 0.000.420.85 -6-4-20246 x₀density
固定 base執行後標示參考密度實際樣本

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()

兩張裡只要有 C,它就會贏嗎?
Mugi 有柴犬臉的柯基

把作品簡化成 A,B,CA,B,C 三種,base 各有 1/31/3 的機率。評審的分數經過 eλRe^{\lambda R},得到倍率 1,2,71,2,7;這些是倍率,不是原始 reward,所以 CC 的分數最高。新目標希望 A,B,CA,B,C 的比例是 1/10,2/10,7/101/10,2/10,7/10。

現在每次只畫兩張,也就是 Best-of-22。最後回傳 CC 的機率是多少? 可以先算兩張都不是 CC 的機率,再取補事件。它會剛好等於目標的 7/107/10 嗎?

也可以先在心裡想,再看整理。文字只留在這個頁面,重新載入就會消失。

把真的輸出比例算出來
Mugi 有柴犬臉的柯基

兩張獨立,而且只要有一張 CC,它就會贏。因此

Pr⁡(x^0=C)=1−(2/3)2=5/9,Pr⁡(x^0=A)=(1/3)2=1/9,Pr⁡(x^0=B)=(2/3)2−(1/3)2=3/9.\begin{gathered} \Pr(\widehat x_0=C)\\ =1-(2/3)^2=5/9,\\ \Pr(\widehat x_0=A)\\ =(1/3)^2=1/9,\\ \Pr(\widehat x_0=B)\\ =(2/3)^2-(1/3)^2=3/9. \end{gathered}

輸出 AA 必須兩張都是 AA;輸出 BB 則是沒有 CC,但至少有一張 BB。三個機率加起來是 11,卻不是目標的 1:2:71:2:7。Best-of-NN 看的是誰勝過其他候選,reward tilt 看的是 base 乘上倍率後的比例。

甚至把倍率 77 換成 7070,只要分數排序相同,Best-of-22 的這三個比例仍不變;目標比例卻會變成 1:2:701:2:70。所以只看排序的選擇,無法一般地保留倍率的資訊。

每天重跑同一個規則,比例會去哪裡?
Nori 戴 orca 帽的北極熊

我們維持 Best-of-22,每天重新生成,最後收集十萬張輸出。CC 的比例會比較接近哪個量?

每趟輸出 C 的機率仍是九分之五;增加收集次數不會改掉每趟規則。

十分之七是指定 target 的比例。反覆使用另一個輸出分布,不會因為次數多就換成 target。

每趟仍只比兩張,C 有時不在候選裡。增加的是重跑次數,不是每趟的候選數。

Mugi:不要每次都挑第一名呢?
Mugi 有柴犬臉的柯基

「假如桌上剛好是一張 AA、一張 BB、一張 CC,那讓它們各以 1/10,2/10,7/101/10,2/10,7/10 被選到呢?CC 還是常出現,但其他作品也有機會。」

這是在固定這一批候選之後,按權重抽一張。真正每趟從 base 生成,桌上不一定三種各有一張;所以還要問:base 原本的比例已經在哪裡反映?我們該替每張候選補哪個倍率?

候選已經從 base 抽過,還缺哪一項?
Lumi 有翅膀的鹿
再聽一次夥伴的提案

原本常見的作品,已經比較容易進入候選集合。想讓它們對應 pRp_R,要補的是「target 相對於抽樣來源增加多少」。在 base 為正的位置,試著把下面比值化簡:

pR(x0)pdata(x0)=pdata(x0)eλR(x0)ZR pdata(x0).\begin{gathered} \frac{p_R(x_0)}{p_{\mathrm{data}}(x_0)}\\ =\frac{p_{\mathrm{data}}(x_0)e^{\lambda R(x_0)}} {Z_R\,p_{\mathrm{data}}(x_0)}. \end{gathered}

哪些量會消掉?哪些量對所有候選相同? 若最後還要除以這批權重的總和,那個共同常數需要先算出來嗎?

也可以先在心裡想,再看整理。文字只留在這個頁面,重新載入就會消失。

把候選的貢獻補成新的比例
Lumi 有翅膀的鹿

Base 在分子與分母消去,剩下 eλR(x0)/ZRe^{\lambda R(x_0)}/Z_R。ZRZ_R 對所有候選相同,正規化時也會消去,因此第 ii 張只需要正權重

ω(i)=eλR(x0(i)),ωˉ(i)=ω(i)∑ℓ=1Nω(ℓ).\begin{aligned} \omega^{(i)}&=e^{\lambda R(x_0^{(i)})},\\ \bar\omega^{(i)} &=\frac{\omega^{(i)}}{\sum_{\ell=1}^N\omega^{(\ell)}}. \end{aligned}

ω(i)\omega^{(i)} 是相對倍率,ωˉ(i)\bar\omega^{(i)} 才是固定這批候選後,抽中第 ii 張的機率。Base 的比例由候選出現的頻率反映,選擇時補上 reward 倍率;不是再乘一次 base probability。

用 target/抽樣來源的比值修正貢獻,是 importance sampling 的核心。這裡先做 importance weighting(IW),再按 normalized weights 抽出一個編號,回傳那張完成品。

如果只想算 target 下的平均,需要真的抽一張嗎?

令 f(x0)f(x_0) 是想平均的量,例如某類作品的指示函數,並假設 target 下的絕對期望有限。把期望的積分乘上並除以 base density,得到

EpR[f]=Epdata[pRpdataf]=Epdata[eλRf]Epdata[eλR].\begin{aligned} \mathbb E_{p_R}[f] &=\mathbb E_{p_{\mathrm{data}}} \left[\frac{p_R}{p_{\mathrm{data}}}f\right]\\ &=\frac{\mathbb E_{p_{\mathrm{data}}}[e^{\lambda R}f]} {\mathbb E_{p_{\mathrm{data}}}[e^{\lambda R}]}. \end{aligned}

兩個 expectation 都平均 base 抽出的完成品;分子多乘想平均的 ff,分母是 ZRZ_R。用同一批 NN 個獨立候選估計兩個平均,1/N1/N 相除消去後,得到

EpR[f]≈∑i=1Nωˉ(i)f(x0(i)).\mathbb E_{p_R}[f] \approx\sum_{i=1}^N\bar\omega^{(i)}f(x_0^{(i)}).

這是 self-normalized importance sampling:直接計算加權平均,不必再抽一張。有限樣本的比值通常有偏差;若候選獨立、相關量可積分,增加 NN 讓分子與分母的樣本平均趨近真正期望,才讓比值趨近 target 平均。

同一個 base,改成按倍率抽候選
Nori 戴 orca 帽的北極熊

這次不用 argmax。每批候選重新生成,從當批正規化權重抽編號;對照 N = 1 與較大的 N,並檢查 target 是否真的被近似。

先預測,再觀察分布

候選已經從 base 抽過,只按 exponential 倍率選一個,N = 1 和較大的 N 會有什麼不同?

A 每趟 N = 1;B 用你選的 N。每批都生成新候選,再正規化該批倍率,抽一個編號。

灰線先畫出原本雙峰資料。執行後才會加上實際樣本;參考曲線不代表已經抽到了它。

Base、參考密度與實際樣本直方圖 灰色虛線為固定 base;黑色點線為標示的參考密度;金色為本次實際樣本,A/B 對照另用藍色輪廓表示 A。 0.000.420.85 -6-4-20246 x₀density
固定 base執行後標示參考密度實際樣本

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()

桌上這批,C 有多少被抽中的機會?
Mugi 有柴犬臉的柯基

這趟畫出的三張是 A,A,CA,A,C,倍率是 1,1,71,1,7。固定這批候選後,最後回傳 CC 的機率是多少?

正規化用的是這批候選的總權重。這趟沒有 B,也不能把它憑空加入分母。

按權重抽樣仍可能抽中 A。每次只取最大值才會在這批必定選 C。

目標比例,和這批候選的比例
Nori 戴 orca 帽的北極熊

7/107/10 是三種作品在整個 target 的比例。眼前桌上卻只有 A,A,CA,A,C,權重總和是 99,沒有 BB 的那份權重;固定這批的選擇只能在它們之間發生。

最極端的例子是 N=1N=1:無論權重多高,只有一張就只能選它,輸出仍是 base。按權重抽樣是在有限候選上近似 target,並非每批都精確重現 pRp_R。

選法改了,哪些成本還在?
Nori 戴 orca 帽的北極熊

每趟的流程現在是:從 base 獨立畫 NN 張 → 評分並算倍率 → 正規化 → 按機率選一張。增加重跑次數 nn,能看清固定 NN 的輸出分布;增加每趟候選數 NN,才有機會更充分涵蓋 base,改善這個有限候選近似。

可是每張還是得完整畫完、評一次分。若每次符合要求的機率是 pp,獨立畫 NN 張至少命中一次的機率是 1−(1−p)N1-(1-p)^N;當 p=0.001p=0.001、N=1000N=1000,也只有約 0.6320.632。這是候選中有沒有命中,不是按權重選出的那張一定成功的機率。

Mugi 已經讓分數進入選擇,Lumi 問的成本仍在:有些草稿畫到一半就很沒希望,能不能把後面的計算分給別張?

完成品這條路已探索完整
Nori 戴 orca 帽的北極熊

你已實作 Best-of-N 與 IW,讀過候選相對權重、有限 N 的輸出 law、重跑 n 趟的意義與生成成本。回到列表,探索能否在途中分配計算,或改抽起點。

途中重抽:SMC 與完整 FK 推導

Lumi:先猜它完成後會得到什麼分數
Lumi 有翅膀的鹿

「先請 denoiser 猜一張完成圖,再讓評審看看;或者把同一張草稿試著完成幾次,看看最後的倍率平均是多少。這樣能不能在途中比較?」

可以嘗試,但兩種都要分清估計與完成品。眼前還帶噪聲的狀態是 xtx_t,去噪估計是 x^0(xt,t)\hat x_0(x_t,t),真正走到終點才得到 x0x_0。在 VP 表示下,若 αˉt>0\bar\alpha_t>0,去噪估計是

x^0(xt,t)=xt−σtϵθ(xt,t)αˉt.\hat x_0(x_t,t) =\frac{x_t-\sigma_t\epsilon_\theta(x_t,t)} {\sqrt{\bar\alpha_t}}.

ϵθ\epsilon_\theta 是模型預測的 noise,σt=1−αˉt\sigma_t=\sqrt{1-\bar\alpha_t} 是 noise amplitude。扣掉預測的 noise,再除回訊號縮放,得到一張乾淨估計;它不是已經確定的未來圖片。

用 eλR(x^0)e^{\lambda R(\hat x_0)} 估計未來倍率,只需詢問 reward,不需微分。若改用多次獨立試畫,就先替每個完成品算 eλRe^{\lambda R},再取平均;試畫本身也有生成成本。這些都只是中途估計,不保證等於精確的未來平均。

途中的數字,代表哪種資訊?
Nori 戴 orca 帽的北極熊
再聽一次夥伴的提案

Lumi 把去噪估計交給評審,取得很高的分數。這個數字目前能怎麼使用?

評分的輸入是預測完成品。這條路可以不用 gradient,但仍保留預測的不確定性。

被評的是去噪估計,不是真正走到終點的圖片。高 noise 時,兩者可能差很多。

程式能回傳分數,不表示可以對 noisy image 微分;目前做的是比較候選。

三份位置,要怎麼分給草稿?
Lumi 有翅膀的鹿

假設這一段三張草稿應增加的正權重是 1,2,71,2,7,正規化後是 0.1,0.2,0.70.1,0.2,0.7。我們希望下一段仍保留三份候選,每次按這些機率抽一個草稿,允許同一張被抽到多次。

抽三次,第三張平均會拿到多少份?每一趟都必須剛好有這個份數嗎?先想想「平均份數」和「真的抽到的份數」差在哪裡。

也可以先在心裡想,再看整理。文字只留在這個頁面,重新載入就會消失。

抽編號,接著讓那些草稿繼續畫
Lumi 有翅膀的鹿

每次選到第三張的機率是 0.70.7,抽三次的平均份數是 3×0.7=2.13\times0.7=2.1。前兩張平均是 0.3,0.60.3,0.6 份,但真正每趟是整數:可能抽到 (A,C,C)(A,C,C),也可能三次都選 CC;這些是可能結果,不是指定要複製的名單。

按機率重抽候選,稱為 resampling。重抽後每份候選重新具有相同權重 1/31/3;高權重草稿平均有更多後代,得到更多後續計算,總份數仍是三份。原本生成模型與評分程式都沒有重新訓練。

這裡的 1,2,71,2,7 先假設是「這一段應增加的倍率」。有了估計,還得算它怎麼變成增量權重;等一下就檢查這一步。

每走一段,再比較一次
Lumi 有翅膀的鹿

把生成中的候選稱為 particle,同時保留 MM 個。沿用 diffusion 從高 noise 往 t=0t=0 生成的時間,選 checkpoints T=tK>⋯>t0=0T=t_K>\cdots>t_0=0;KK 是段數,xtk(i)x_{t_k}^{(i)} 是時刻 tkt_k 的第 ii 份草稿。

從 tkt_k 到 tk−1t_{k-1},先用原 reverse sampler 讓每份草稿走一段,稱為 propagate。接著替第 ii 份計算這段新增的權重 ωk−1(i)\omega_{k-1}^{(i)},稱為 weight;除以這批的權重總和得到 ωˉk−1(i)\bar\omega_{k-1}^{(i)},再用這些機率重抽 MM 個編號,稱為 resample。

重複「走一段 → 算增量權重 → 按機率重抽」,就是 Sequential Monte Carlo(SMC) 的基本操作。這裡每個 checkpoint 都重抽,重抽後權重回到 1/M1/M;到 t=0t=0,從最後 MM 張等機率選一張回傳。改變的是哪些候選有後代,並非 θ\theta。

逐段操作:哪些草稿真的留下後代?
Nori 戴 orca 帽的北極熊

每個粒子有自己的位置與祖先。先逐步執行一趟,再跑 300 趟各回傳一個的直方圖;故意把中途 H 估錯,觀察最後的真實 reward 校正能否救回已消失的左峰。後面會完整推導這個新舊比值為何必要。

先預測,再觀察分布

中途估計故意只偏右,最後即使用真正 reward 校正,有限粒子還能保留左峰嗎?哪些草稿會共享祖先?

A 使用 posterior 平均倍率;B 使用故意偏右的中途估計。M、checkpoints、terminal reward 和各趟種子相同。

灰線先畫出原本雙峰資料。執行後才會加上實際樣本;參考曲線不代表已經抽到了它。

Base、參考密度與實際樣本直方圖 灰色虛線為固定 base;黑色點線為標示的參考密度;金色為本次實際樣本,A/B 對照另用藍色輪廓表示 A。 0.000.420.85 -6-4-20246 x₀density
固定 base執行後標示參考密度實際樣本

每個新狀態只除以自己親代的舊 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 有柴犬臉的柯基

Mugi 說:「既然想把時間分給好的草稿,那每次只留最高分的一張,把它複製成 MM 份,不就好了?」這個想法漏看了什麼?

有限候選需要保留探索。評分的是對未來的估計,不是已知道哪條路最後最好。

模型參數仍然固定。只留一個祖先會改變候選與計算分配,但不等於更新生成模型參數。

相同草稿,接下來用什麼規則?
Nori 戴 orca 帽的北極熊

如果後續 sampler 有新的隨機性,相同草稿的複本可能走到不同結果。但沿完全相同的 deterministic ODE 走,相同狀態就會繼續走同一條路;複製十份,也還是同一種答案。

只剩少數祖先、探索越來越窄的現象稱為 particle degeneracy。按 normalized weights 隨機重抽也可能發生它,尤其權重很集中時。若另外加探索或擾動,還得檢查那個動作是否保留 target;不能只因複本數很多就宣稱多樣性增加。

每次都乘未來的分數,會算太多次嗎?
Nori 戴 orca 帽的北極熊

只剩少數祖先、後續探索越來越窄,稱為 particle degeneracy。即使用隨機重抽,權重很集中時仍可能發生;相同草稿的複本,還得靠後續的隨機生成才可能分開。若沿同一個 deterministic 規則走,複製份數也不會增加不同答案。

「我們已經知道怎麼重抽。可是某張草稿的未來倍率先估成 22,走到完成品是 66。兩段各乘一次 22、66,最後是 1212;我們想增加的明明只有 66 倍,怎麼多算了?」

把 checkpoint tkt_k 的正估計記成 Hk(xtk)H_k(x_{t_k})。它可以是剛才的便宜估計,不要求等於 U7.2 的精確 posterior 平均。開始取 HK≡1H_K\equiv1,完成時取真正的 H0(x0)=eλR(x0)H_0(x_0)=e^{\lambda R(x_0)};中間都要求正值。

我們想讓每條完整的 base path,最後只增加一次 terminal reward 倍率。這裡追的是累積乘積應該是多少,不是每段評分各有多高。先一起示範,再換你選下一個動作。

先看我們怎麼選下一個數學動作
Nori 戴 orca 帽的北極熊

這條路只切兩段:H2=1H_2=1、H1=2H_1=2、H0=6H_0=6。每次只展開一個動作,看看想算的量,怎麼決定我們使用的工具。

從累積倍率,找出每段應增加多少

先讓累積倍率到達中途估計

想算什麼
走到第一個檢查點,累積倍率要變成 2。
用哪個工具
新估計除以原本累積的倍率。
為什麼用它
開始時是 1 倍;我們要補的是乘上多少,才會到 2 倍。

先解「11 乘上多少等於 22」,第一段的倍率就是

H1H2=21=2.\frac{H_1}{H_2}=\frac21=2.

這裡 H1,H2H_1,H_2 是同一條路在這兩個 checkpoints 的估計值。第一段完成後,這條路已經累積 22 倍;下一段不能假裝還沒乘過。

只補上第二段還欠的倍率

想算什麼
目前已經有 2 倍,完成時希望是 6 倍。
用哪個工具
解乘法關係,用 6 除以 2。
為什麼用它
我們在找乘上多少,不是在問分數增加了幾分。

這次要解「22 乘上多少等於 66」,因此新增的是

H0H1=62=3.\frac{H_0}{H_1}=\frac62=3.

直接再乘 66 會得到 1212;相減得到的 44 也不是要乘的倍率。選相除,是因為現在要解的是乘法裡還缺哪個因子。

把兩段相乘,檢查有沒有重複計算

想算什麼
整條路最後只增加完成品的 6 倍。
用哪個工具
相乘,再約掉同一個中間估計。
為什麼用它
中途的 2 在一段分子、另一段分母各出現一次。

把兩段接起來,得到

H1H2H0H1=2162=6.\frac{H_1}{H_2}\frac{H_0}{H_1} =\frac21\frac62=6.

中間估計先幫忙分配計算,再被下一段的比值抵消。不是把 reward 忽略掉,而是讓同一份終點偏好只累積一次。

換你選下一個數學工具
Nori 戴 orca 帽的北極熊

換一條路:上一個 checkpoint 已累積 33 倍,新的估計希望累積到 1212 倍。這段應新增哪個倍率?先想要解的是「乘上多少」,再選工具。

新舊比值補的是累積乘積還缺多少,已經乘過的部分不會再算一次。

相減得到 9,是加法上的差;把它當倍率乘上原本的 3,會得到 27,而不是希望的 12。

原本已經有 3 倍,再直接乘 12,就累積到 36 倍;相當於把之前的那份又算進去。

只給框架,你能補出相消那一步嗎?
Nori 戴 orca 帽的北極熊

現在把同一條路切成三段。起點、中間兩個 checkpoints、終點的正估計依序是 H3,H2,H1,H0H_3,H_2,H_1,H_0;這裡先記同一路徑上的取值,等一下再把狀態寫回函數的輸入。

每段都用新估計除以舊估計,累積是

H2H3H1H2H0H1.\frac{H_2}{H_3}\frac{H_1}{H_2}\frac{H_0}{H_1}.

哪些項能消去?最後還剩誰? 再加上起點取 11、終點使用真正的 reward 倍率,試著用一句話說出:為什麼中間估計不必精確,整條路的理想倍率仍能保留?

也可以先在心裡想,再看整理。文字只留在這個頁面,重新載入就會消失。

每一段只增加新的比例
Nori 戴 orca 帽的北極熊

三段乘積裡,H2,H1H_2,H_1 都在分子、分母各出現一次,只剩 H0/H3H_0/H_3。接著把這個動作寫回每一段的狀態,檢查真正需要的條件。

從同一路徑的比值,推到終點的倍率

先確認比的是同一條路的前後狀態

想算什麼
替從 t_k 走到 t_{k−1} 的這一段定義倍率。
用哪個工具
新舊估計的比值。
為什麼用它
走完這段的候選,必須配回它真正的來路,才是在補同一條路的累積倍率。

將這段的倍率函數記成 potential:

Gk−1(xtk−1,xtk)=Hk−1(xtk−1)Hk(xtk).\begin{gathered} G_{k-1}(x_{t_{k-1}},x_{t_k})\\ =\frac{H_{k-1}(x_{t_{k-1}})}{H_k(x_{t_k})}. \end{gathered}

分子是走完這段後的估計,分母是走之前的估計。新狀態不能拿別張草稿的舊估計來相除;否則前後相消的那個數字就不再相同。

將第 i 份候選的狀態代進去

想算什麼
算出這張草稿在這一段要用的抽樣權重。
用哪個工具
把候選的前後狀態代入 potential。
為什麼用它
倍率函數描述一般規則;實際重抽需要每張草稿自己的數值。

正文使用原 sampler 走這一段,所以第 ii 個候選的增量權重是

ωk−1(i)=Gk−1(xtk−1(i),xtk(i)).\begin{gathered} \omega_{k-1}^{(i)}\\ =G_{k-1}(x_{t_{k-1}}^{(i)},x_{t_k}^{(i)}). \end{gathered}

用這批 ω\omega 的總和正規化,才得到選中各候選的機率。改用別的 proposal 時還要校正抽樣來源,後面的頁內補充會展開那個比值。

沿同一條祖先路徑相乘

想算什麼
檢查所有段數累積後,整條路增加多少倍。
用哪個工具
乘積中的相同因子前後消去。
為什麼用它
每個中間 H 都在相鄰兩段的分子與分母各出現一次。

沿同一條祖先路徑,把各段的比值相乘;中間取值就像剛才三段的例子一樣消去,只剩

∏k=1KGk−1(xtk−1,xtk)=H0(x0)HK(xT).\begin{gathered} \prod_{k=1}^K G_{k-1}(x_{t_{k-1}},x_{t_k})\\ =\frac{H_0(x_0)}{H_K(x_T)}. \end{gathered}

這是沿一條路徑檢查累積倍率,不是在平均整批 particles;不同路徑有自己的中間狀態與估計。

代入起點與終點的條件

想算什麼
最後只留下完成品真正的 reward 倍率。
用哪個工具
代入 H_K=1 與終點的 exponential weight。
為什麼用它
相消只保證剩下 H_0/H_K;它是否等於想要的倍率,要看這兩端怎麼選。

起點取 HK≡1H_K\equiv1、終點取 H0(x0)=eλR(x0)H_0(x_0)=e^{\lambda R(x_0)},所以

H0(x0)HK(xT)=eλR(x0).\frac{H_0(x_0)}{H_K(x_T)} =e^{\lambda R(x_0)}.

中途估計分配的是計算,兩端的選擇確定了累積倍率。 中間估計須為正,才能使用比值;維持這個構造時,它不改掉理想的 terminal tilt。但有限 MM 的草稿可能早就被淘汰,實際近似仍受估計影響。

路徑的倍率有了,怎麼回到完成品的比例?
Lumi 有翅膀的鹿

我們已知道每條路最後只乘 eλR(x0)e^{\lambda R(x_0)}。可是同一張完成品可能由不同路徑抵達。想算完成品在新分布下占多少,接著該做哪個數學動作?

要算的是完成品的總量,每條通往它的路都可能有貢獻,且保留原本的路徑機率。

只留最可能的路,會拿掉其他也會抵達這個終點的貢獻;那是在挑路,不是在算終點的總機率。

各路在 base 下不一定一樣常發生。平均 reward 倍率卻丟掉路徑原本的機率,就沒有保留 base 的出現比例。

先把四條路徑的貢獻真的加起來
Nori 戴 orca 帽的北極熊

先換一個只有四條 base paths 的小例子:兩條抵達 AA,機率是 0.1,0.20.1,0.2;兩條抵達 BB,機率是 0.3,0.40.3,0.4。終點倍率是 AA 得 11、BB 得 33。加權後的總量是

A: 0.1⋅1+0.2⋅1=0.3,B: 0.3⋅3+0.4⋅3=2.1.\begin{aligned} A:&\,0.1\cdot1+0.2\cdot1=0.3,\\ B:&\,0.3\cdot3+0.4\cdot3=2.1. \end{aligned}

同一個終點的倍率能提出求和,所以也能先把 base 路徑機率加成 pdata(A)=0.3p_{\mathrm{data}}(A)=0.3、pdata(B)=0.7p_{\mathrm{data}}(B)=0.7,再各乘 1,31,3。正規化總量是 2.42.4,得到 AA 占 1/81/8、BB 占 7/87/8。

這就是剛才選「累積所有路徑」的理由;只保留一條路,就會漏掉另一條的質量。

先寫出路徑要占多少,再真的抽
Lumi 有翅膀的鹿

接著用原 sampler 的路徑分布 PP,把「每條路增加多少」接到「完成品應占多少」。整組 checkpoints 記成 xtK:0x_{t_K:0};我們先定義路徑 target PRP_R,再檢查它的終點。

先寫路徑權重,再把途中狀態累積起來

先對每條 base path 加權

想算什麼
定義希望每條生成路徑增加的相對權重。
用哪個工具
原路徑質量乘上各段 potentials,再正規化。
為什麼用它
只有倍率還沒有機率;要保留各條路原本的貢獻,也要讓總質量回到 1。
PR(dxtK:0)=1ZRP(dxtK:0)∏k=1KGk−1.\begin{gathered} P_R(\mathrm d x_{t_K:0})\\ =\frac{1}{Z_R}P(\mathrm d x_{t_K:0}) \prod_{k=1}^K G_{k-1}. \end{gathered}

P(dxtK:0)P(\mathrm d x_{t_K:0}) 表示 base 分配給這些路徑的機率質量。這一行的乘積沿用前面定義的狀態輸入;ZRZ_R 是所有 base paths 加權後的總量,假設有限且非零。

代入剛才驗證過的累積倍率

想算什麼
看清一條完整路徑最後依賴哪個 reward。
用哪個工具
把 potentials 的乘積換成相消後的結果。
為什麼用它
前面已檢查同一路徑的中間 H 前後消去,不需要重新乘一次中途估計。
PR(dxtK:0)=eλR(x0)ZRP(dxtK:0).\begin{gathered} P_R(\mathrm d x_{t_K:0})\\ =\frac{e^{\lambda R(x_0)}}{Z_R} P(\mathrm d x_{t_K:0}). \end{gathered}

新權重只看終點 x0x_0。所以兩條路只要抵達同一種完成品,就會乘上同一個 terminal weight;原本機率不同的路,仍保留它們的相對貢獻。

把抵達同一完成品的路都算進來

想算什麼
從路徑的分布算出終點的出現比例。
用哪個工具
對中途狀態求和;連續空間使用積分。
為什麼用它
終點不告訴我們走了哪條路,要將所有相容路徑的貢獻累積起來。

剛才四條路已算出 A 的加權質量為 0.3、B 為 2.1。現在固定同一個終點,所有通往它的路都乘相同的 terminal reward;因此倍率能提出對中途狀態的求和。連續空間用積分,保留的是各條 base path 原本的貢獻。

讀回一般的 terminal target

想算什麼
確認新的終點比例就是指定的 reward tilt。
用哪個工具
提出只依賴終點的倍率,保留 base 的終點分布。
為什麼用它
固定同一終點時,倍率不隨中途狀態改變;把中途可能性累積後,剩下原 sampler 的終點分布。

若 base sampler 的終點分布是 pdatap_{\mathrm{data}},和剛才四條路一樣,把各終點的所有路徑累積後得到

pR(x0)=eλR(x0)ZRpdata(x0).p_R(x_0) =\frac{e^{\lambda R(x_0)}}{Z_R} p_{\mathrm{data}}(x_0).

離散時是終點機率,連續時是 density。我們先定義路徑怎麼加權,再推出它的 terminal target;這個定義裡沒有 particle 數 MM,也還沒有真的抽出圖片。

這套用 potentials 加權路徑的架構,稱為 Feynman–Kac(FK)model。SMC 才是用有限候選近似它的抽樣演算法;完整推導給出理想 target,有限草稿的近似與退化仍要另外檢查。

FK steering 與 DAS,和這兩層有什麼關係?

Singhal 等人的 FK steering 將這套構造用在 diffusion 引導,選擇中途估計、potentials、proposal 與重抽時機,再用 particle 方法抽樣。Kim、Kim 與 Park 的 DAS(Diffusion Alignment as Sampling) 也使用 SMC,透過 reward tempering 逐步增加中途 target 的偏好影響。

這些名稱要對回「target 與 potentials 怎麼構造」和「實際怎麼抽」兩層,不表示 FK 和 SMC 是互斥的方法。

換了走下一段的 proposal,weight 還等於 potential 嗎?

正文用原 sampler 走一段,所以實際 proposal 和 base transition 相同。如果改用另一個 transition density τk−1\tau_{k-1} 提出新狀態,而 base 的 transition density 是 pbasep_{\mathrm{base}},每段重抽、上一段權重相同時,就還要補抽樣來源的比值:

ωk−1(i)=Gk−1(xtk−1(i),xtk(i))⋅pbase(xtk−1(i)∣xtk(i))τk−1(xtk−1(i)∣xtk(i)).\begin{aligned} \omega_{k-1}^{(i)} &=G_{k-1}(x_{t_{k-1}}^{(i)},x_{t_k}^{(i)})\\ &\quad\cdot\frac{p_{\mathrm{base}}(x_{t_{k-1}}^{(i)}\mid x_{t_k}^{(i)})} {\tau_{k-1}(x_{t_{k-1}}^{(i)}\mid x_{t_k}^{(i)})}. \end{aligned}

第一項是 target 相對於 base path 增加的倍率;第二項補償「真正從 proposal 抽,而不是從 base transition 抽」的差別,和完成品 IW 用的是同一個校正理由。Proposal 要涵蓋 target 會有質量的區域,才能使用這個比值。

當 τk−1=pbase\tau_{k-1}=p_{\mathrm{base}},後面比值是 11,才回到正文的 ω=G\omega=G。這裡的 Gk−1G_{k-1} 是增量權重函數,後面的 G(z)G(z) 則是 deterministic generator;下標與輸入區分它們的角色。

中途看錯了,哪一層會受影響?
Lumi 有翅膀的鹿

某個中間 HkH_k 估得很差,但維持正值、沿同一路徑用新舊比值,HK=1H_K=1,H0H_0 仍用真正的 terminal reward。下面哪個判斷符合剛才的構造?

路徑 target 由累積倍率確定,有限 particles 的誤差與退化由實際抽樣過程產生。

差的估計可能過早淘汰後來很好的草稿。乘積正確,不代表有限 M 已充分探索。

最後剩下的,真的是原本那個倍率嗎?
Nori 戴 orca 帽的北極熊

相消後剩下的是 H0/HKH_0/H_K。如果最後仍用預測圖的 reward,沒有換回真正 x0x_0 的 eλR(x0)e^{\lambda R(x_0)},那剩下的就可能是另一個 terminal weight。

同樣地,若起點 HKH_K 隨 xTx_T 改變、卻沒有補相應的起始加權,最後會多出 1/HK(xT)1/H_K(x_T)。增加 MM 可以改善抽樣近似,不能把已經改掉的 FK target 自動修回來。

把有限草稿真的接到終點
Lumi 有翅膀的鹿

現在可以把 Lumi 的操作完整接起來:抽 MM 個原 sampler 起點;每段保留舊狀態,讓它們各走一段,算新 HH 除以自己來路的舊 HH;正規化後獨立重抽 MM 個編號,允許重複。重抽後等權重,再進下一段;終點的 H0H_0 使用完成品真正的 reward。

最後的 MM 張可以表示一個 empirical distribution:

p^R(M)(dx0)=1M∑i=1Mδx0(i)(dx0).\begin{gathered} \widehat p_R^{(M)}(\mathrm d x_0)\\ =\frac1M\sum_{i=1}^M\delta_{x_0^{(i)}}(\mathrm d x_0). \end{gathered}

δx0(i)\delta_{x_0^{(i)}} 是把質量放在第 ii 張完成品上的 point mass。想算某種作品的比例,就數這 MM 張有幾張屬於它,再除以 MM;每份等權重,不表示它們是彼此獨立的樣本,因為可能共享祖先。從它們等機率抽一張,便是這趟回傳的結果。

一趟增加 M,和把固定 M 重跑 n 次,哪裡不同?

令 f(x0)f(x_0) 是有界的圖片統計量,例如 CC 的指示函數。固定 checkpoints、使用正確權重,且符合相應的可積分性條件時,SMC 的一致性寫成

1M∑i=1Mf(x0(i))→M→∞依機率Ex0∼pR[f(x0)].\begin{gathered} \frac1M\sum_{i=1}^M f(x_0^{(i)})\\ \xrightarrow[M\to\infty]{\text{依機率}}\\ \mathbb E_{x_0\sim p_R}[f(x_0)]. \end{gathered}

左邊是一趟裡的 MM 個 particles,右邊是 target 下的平均。「依機率」表示兩者的差超過任意固定容許值的機率趨近 00;不要求這趟的 particles 彼此獨立。

增加 MM 才是在改善每趟對 FK target 的近似。固定 MM、獨立重跑 nn 趟,只會更清楚這個有限 sampler 的輸出比例,仍可能留下偏差。這個極限也不適用於已經設錯 terminal weighting 的 target。

一定要每段都重抽嗎?

不必。可以用 effective sample size(ESS) 描述 normalized weights 有多集中:

ESS=1∑i=1M(ωˉ(i))2.\mathrm{ESS}=\frac{1}{\sum_{i=1}^M(\bar\omega^{(i)})^2}.

等權重時,每項是 1/M21/M^2,總和是 1/M1/M,所以 ESS 是 MM。若只有一份權重接近 11,總和接近 11,ESS 就接近 11;它描述權重集中程度,不是在數獨立祖先。

可以等 ESS 低於門檻才重抽。某段沒有重抽時,要保留舊權重,下段再乘新增的 potential;不能一面略過重抽,一面仍假設下一段從等權重開始。

一趟只有三份,多跑一萬趟呢?
Nori 戴 orca 帽的北極熊

每趟只用 M=3M=3 個 particles,但獨立重跑很多趟。只增加重跑次數 nn,就足以消除每趟有限 MM 的抽樣偏差嗎?

操作與完整推導都讀完了
Nori 戴 orca 帽的北極熊

你已看見粒子的重抽與祖先,讀過增量比值、potentials 相消、FK 的終點分布,以及有限 M 的近似與退化。理想 target 的構造與有限抽樣的表現,要分別檢查。

保留生成規則、改抽起點:MCMC 與 SPT

同一個起點,固定會畫出同一張圖
Nori 戴 orca 帽的北極熊

「回到我的提案。如果複製草稿總會畫出同一張,那我們保留生成規則,改試起點呢?」

把 deterministic generator 記成 GG:起點 zz 送進它,完成品是 x0=G(z)x_0=G(z);GG 包含這次固定的生成計算,給定同一個 zz 就得到同一個結果。原本起點的 density 是 pZp_Z,所以原本抽的是 z∼pZz\sim p_Z。

每個起點都有能詢問的分數 R(G(z))R(G(z))。想讓高分結果更常出現,可以把 reward 倍率放回起點:

pZR(z)∝pZ(z)eλR(G(z)).p_Z^R(z)\propto p_Z(z)e^{\lambda R(G(z))}.

這裡用 pZp_Z 保留原本起點的相對 density,用 exponential 提高高分結果所對應的權重。生成規則沒有改,換的是想抽的 source distribution。 它的 target 寫得出來,還不表示我們已會從它抽樣。

不同起點如果畫出同一種作品呢?
Nori 戴 orca 帽的北極熊

暫時用三個離散起點做例子:za,zb,zcz_a,z_b,z_c 原本各有 1/31/3 機率,G(za)=AG(z_a)=A、G(zb)=G(zc)=BG(z_b)=G(z_c)=B。完成品的 reward 倍率是 AA 得 11、BB 得 33。

把倍率放回起點,三個未正規化權重是 1/3,1,11/3,1,1。正規化後,生成 A,BA,B 的機率是多少? 試著把會畫成同一種作品的起點機率加起來,再和圖片端的 base 乘 reward 比較。

也可以先在心裡想,再看整理。文字只留在這個頁面,重新載入就會消失。

把會畫成同一種作品的起點加起來
Nori 戴 orca 帽的北極熊

起點權重總和是 7/37/3,所以三個起點的機率是 1/7,3/7,3/71/7,3/7,3/7。AA 只有一個來源,機率是 1/71/7;BB 有兩個來源,相加得到 6/76/7。

圖片端原本是 AA 占 1/31/3、BB 占 2/32/3,乘倍率 1,31,3 後權重同樣是 1/3,21/3,2,正規化也是 1/7,6/71/7,6/7。不需要 GG 一對一,所有會畫出同一類結果的來源都要算進來。

更一般地,把起點端的正規化常數也記成 ZRZ_R,它是 base 起點生成的完成品之 reward 倍率平均:ZR=Ez∼pZ[eλR(G(z))]Z_R=\mathbb E_{z\sim p_Z}[e^{\lambda R(G(z))}]。從 pZRp_Z^R 抽起點後用同一個 GG,任意可積分的圖片統計量 ff 的平均是

Ez∼pZR[f(G(z))]=1ZREz∼pZ[eλR(G(z))f(G(z))].\begin{gathered} \mathbb E_{z\sim p_Z^R}[f(G(z))]\\ =\frac1{Z_R}\mathbb E_{z\sim p_Z}\big[\\ e^{\lambda R(G(z))}f(G(z))\big]. \end{gathered}

原本 z∼pZz\sim p_Z 經 GG 產生 base output law;上式正是這個 base 的 reward 加權平均。因此 source tilt 接回的是 generator 真正的 output law,不需要計算圖片端的 density,也不表示原模型誤差消失。

試一個新起點,再決定留不留下
Nori 戴 orca 帽的北極熊

「現在有了起點 target。那先從目前的 zz 提出一個新 z′z',畫出 G(z′)G(z') 並評分,再決定要不要換過去,好嗎?」

可以,但如果永遠只收較高分者,就又變成找高分點,而不是抽指定比例。把目前起點、下一個起點串成序列,稱為一條 chain;接受時移到 z′z',拒絕時仍留在 zz,留下不動也是這個序列的一步。

用有接受機率的 Markov transition 保留指定 target,再收集樣本,稱為 Markov chain Monte Carlo(MCMC)。先考慮對稱 proposal:從 zz 提出 z′z',和反向提出的機率相同。那接受機率是

Paccept(z,z′)=min⁡{1,pZ(z′)pZ(z)⋅eλj[R(G(z′))−R(G(z))]}.\begin{gathered} P_{\mathrm{accept}}(z,z')\\ =\min\big\{1,\\ \frac{p_Z(z')}{p_Z(z)}\\ \cdot e^{\lambda_j[R(G(z'))-R(G(z))]}\big\}. \end{gathered}

λj\lambda_j 是這條 chain 的 reward 強度,jj 是 chain 編號。Normalizing constant 相除消去;比的是 prior density 乘 reward 倍率。每次抽 U∼Uniform(0,1)U\sim\mathrm{Uniform}(0,1),若 U<PacceptU<P_{\mathrm{accept}} 就換成 z′z',否則留下 zz。這些計算都不需要 reward gradient。

這個接受機率,為什麼能保留 target?

把此 chain 的 target 記成 π(z)=pZλj(z)\pi(z)=p_Z^{\lambda_j}(z),proposal density 暫記為 a(z′∣z)a(z'\mid z)。對稱時 a(z′∣z)=a(z∣z′)a(z'\mid z)=a(z\mid z'),正向的機率流量是

π(z)a(z′∣z)⋅min⁡{1,π(z′)/π(z)}=a(z′∣z)⋅min⁡{π(z),π(z′)}.\begin{gathered} \pi(z)a(z'\mid z)\\ \cdot\min\{1,\pi(z')/\pi(z)\}\\ =a(z'\mid z)\\ \cdot\min\{\pi(z),\pi(z')\}. \end{gathered}

反向把 z,z′z,z' 對調,因為 proposal 對稱,得到相同的量;加上拒絕時留在原位的機率,就不會讓 target 的質量淨流向某一邊。這個正反流量相等的條件稱為 detailed balance,它給出 target invariance。

若 proposal 不對稱,target 比值還要乘 a(z∣z′)/a(z′∣z)a(z\mid z')/a(z'\mid z),不能仍沿用只有 prior 與 reward 的版本。保留 target 是 transition 的性質;有限次更新是否探索充分,還是另一個問題。

真正跑一條 source chain,保留拒絕的步
Nori 戴 orca 帽的北極熊

固定的 quantile generator 將 standard Normal 起點變成同一個雙峰 base。只改起點的接受規則;查看較低分接受、重複位置與實際輸出直方圖。

先預測,再觀察分布

若每次只收較高 reward,會得到目標分布嗎?真正的接受規則,會接受較低分的起點嗎?

A 用 λ = 0;B 用你選的 λ。兩條單鏈都從 z = −0.8 開始,保留相同步數、步幅與種子。

灰線先畫出原本雙峰資料。執行後才會加上實際樣本;參考曲線不代表已經抽到了它。

Base、參考密度與實際樣本直方圖 灰色虛線為固定 base;黑色點線為標示的參考密度;金色為本次實際樣本,A/B 對照另用藍色輪廓表示 A。 0.000.420.85 -6-4-20246 x₀density
固定 base執行後標示參考密度實際樣本

接受規則同時看 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()

這次分數低一點,還可能保留嗎?
Mugi 有柴犬臉的柯基

對稱 proposal 下,新舊起點的 prior density 相同,新候選的 reward weight 是舊候選的一半。這次應該怎麼決定?

接受規則允許較低分的候選,保留正反向的機率流量。

永遠拒絕分數較低的候選,會把接受規則改成只爬分數,失去剛才的 target 保留理由。

對稱的是 proposal,不是 target。Reward weight 的差別仍要進入接受機率。

一直在同一種構圖附近,別條路能幫忙嗎?
Lumi 有翅膀的鹿

「如果高分的起點分成幾個相隔很遠的區域,一條 chain 可能一直只找到同一種構圖。能不能同時用幾種 reward 強度找,再讓它們交換線索?」

讓第 jj 條 chain 使用 λj\lambda_j,target 是

pZλj(z)∝pZ(z)eλjR(G(z)).p_Z^{\lambda_j}(z) \propto p_Z(z)e^{\lambda_jR(G(z))}.

小的 λj\lambda_j 比較不受 reward 影響,可以搭配較大的提案步伐探索 base 有機會生成的其他區域;大的 λj\lambda_j 更偏好高分起點,可以搭配較小步伐修正。強度不會自動決定提案步長,兩者仍是分開選的設計。

各 chain 先依剛才的接受規則試新起點,再提出跨 chain 的交換,把探索找到的結果帶到目標強度。這是 Wang 等人的 Source Parallel Tempering(SPT):在 source space 同時抽不同強度的 targets,且交換也有接受機率。最後從指定強度 λ\lambda 的 chain 收集 zz,再輸出 G(z)G(z)。

交換前後,哪些因子會消掉?
Lumi 有翅膀的鹿

兩條 chain 的強度是 λi,λj\lambda_i,\lambda_j,起點是 zi,zjz_i,z_j。Joint target 是兩個 targets 的乘積;若交換起點,新舊比值中與 reward 有關的部分是

eλiR(G(zj))eλjR(G(zi))eλiR(G(zi))eλjR(G(zj)).\frac{e^{\lambda_iR(G(z_j))}e^{\lambda_jR(G(z_i))}} {e^{\lambda_iR(G(z_i))}e^{\lambda_jR(G(z_j))}}.

兩邊都含相同的 pZ(zi)pZ(zj)p_Z(z_i)p_Z(z_j),prior 因子已經消去。試著把 exponential 的指數合併,看看哪些強度與 reward 的差會乘在一起。

也可以先在心裡想,再看整理。文字只留在這個頁面,重新載入就會消失。

交換也要保留各自的比例
Lumi 有翅膀的鹿

分子的指數減去分母的指數,再把共同項提出來,就得到

Pswap=min⁡{1,exp⁡ ⁣[(λi−λj)⋅(R(G(zj))−R(G(zi)))]}.\begin{gathered} P_{\mathrm{swap}}=\min\big\{1,\\ \exp\!\left[\begin{gathered} (\lambda_i-\lambda_j)\\ \cdot\big(R(G(z_j))\\ -R(G(z_i))\big) \end{gathered}\right]\big\}. \end{gathered}

在對稱選擇交換 pair 的設定下,這個接受規則保留各 chain targets 的乘積。不能看到另一條分數高就直接搬過來,否則改掉的不只是探索速度,也可能是 target。

實際一輪 SPT 是:各 chain 提出新起點 → 生成與評分 → 依 PacceptP_{\mathrm{accept}} 接受或留在原位 → 對一對 chains 依 PswapP_{\mathrm{swap}} 決定是否交換。交換時起點與已算好的 reward 一起換,避免拿某個起點配上另一張圖片的分數。

每個新 proposal 都有生成與評分成本。重複這個流程後,目標 chain 的樣本可能相關;還要看它是否探索到不同高分區域,保留 target 的規則,不等於有限搜尋已到達 target。

跑三條不同強度的鏈,按機率交換
Nori 戴 orca 帽的北極熊

比較單鏈與三鏈的完整執行紀錄。每鏈迭代數相同,因此三鏈花更多 G 與 reward 呼叫;用軌跡與兩峰的涵蓋來判讀,不把較高平均 reward 當成已收斂。

先預測,再觀察分布

多條不同強度的鏈,交換後能找到另一個高分區域嗎?額外的生成與評分成本是多少?

A 是同一個 target 強度的單鏈;B 是 0、λ/2、λ 三條鏈並嘗試相鄰交換。每鏈步數相同,B 的評分成本為三倍,並非等預算比較。

灰線先畫出原本雙峰資料。執行後才會加上實際樣本;參考曲線不代表已經抽到了它。

Base、參考密度與實際樣本直方圖 灰色虛線為固定 base;黑色點線為標示的參考密度;金色為本次實際樣本,A/B 對照另用藍色輪廓表示 A。 0.000.420.85 -6-4-20246 x₀density
固定 base執行後標示參考密度實際樣本

每條鏈先做自己的 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()

新的起點,每次都要重新學嗎?
Nori 戴 orca 帽的北極熊

SPT 每次試新起點,都重新生成並評分。這些操作可以一直保留生成模型的參數 θ\theta 嗎?

起點分布、接受與交換都探索完整
Nori 戴 orca 帽的北極熊

你已實作 MCMC 與 SPT,完整讀過 source tilt 如何回到 output law、接受與交換的推導,以及有限搜尋與生成成本。回列表後,才能用最後的比較表統整不同限制。

回到工作桌,各種做法改了什麼?
Nori 戴 orca 帽的北極熊

方法名稱已經介紹過了。現在把實際動作與限制放回它們發生的位置;每完成一列,先看整理,再比較下一列。

完成後 · 使用分數 把一批完成圖片評分後,再決定回傳哪張。
途中 · 使用分數 從草稿估未來倍率,決定哪些後代繼續生成。
起點 · 使用分數 從新起點畫出完成品,評分後決定是否換起點。
完成後 · 選擇規則 最高分挑選,或用 exponential 倍率正規化後抽一張。
途中 · 選擇規則 用校正後的增量權重重抽 M 份,允許相同祖先。
起點 · 選擇規則 依 target 比值接受新起點,也依接受機率交換。
完成後 · 計算限制 Rare target 要很多候選;有限 N 的 IW 仍有近似誤差。
途中 · 計算限制 看估計與祖先是否集中;增加 M 不會修正設錯的 target。
起點 · 計算限制 每個 proposal 都要畫完並評分,有限搜尋可能困在局部。

生成工具允許停下來,該怎麼用?
Nori 戴 orca 帽的北極熊

你能保存多個 noisy states,讓它們各走下一段,也有便宜的未來倍率估計。希望在完成前重新分配計算,哪個操作直接符合需求?

這是途中 SMC 的介面前提,仍須檢查估計與祖先退化。

這仍花完每張候選的生成成本,沒有在途中重分配。

會重複計入同一份偏好,須使用與目標一致的增量比值。

最後換一個生成工具試試看
Mugi 有柴犬臉的柯基

這次生成器是 deterministic,從相同草稿往後總會得到相同結果。你想保留生成規則,也想探索不同答案。哪個做法比較符合這次需求?

完全相同的狀態沿同一條 deterministic 規則走,複製很多份仍會得到同一張,沒有增加不同答案。

固定 G 不代表固定 z;起點可以改用保留指定 source target 的抽樣規則。

單靠找到一個高分點,沒有檢查其他結果的比例,也沒有說明是否探索到其他高分區域。

其他草稿,也能參考當下最好的那張嗎?
Mugi 有柴犬臉的柯基

我們剛才的主線,讓分數決定哪些候選留下來。還有另一個延伸:保留每張草稿,參考當下最好候選的預測完成品,改動接下來的 prediction。 它也不必對 reward 微分,但操作與 SMC 的重抽不同。

BoNG:怎麼用候選的差異提供修改方向?

時間 tt 上保留 MM 個 xt(i)x_t^{(i)},各自算去噪估計 x^0(i)\hat x_0^{(i)},評分後選當下最高分編號

i⋆=arg⁡max⁡1≤i≤MR(x^0(i)).i^\star=\arg\max_{1\le i\le M}R(\hat x_0^{(i)}).

這裡評的是預測完成品,不是已經生成到終點的 x0x_0。對第 ii 張而言,x^0(i⋆)−x^0(i)\hat x_0^{(i^\star)}-\hat x_0^{(i)} 是從自己預測移向參考預測的向量。Kim 等人將這種用最好候選引導其他 particles 的方法稱為 Best-of-NN Guidance(BoNG);這裡沿用 MM 表示途中候選數。

怎麼將這個位移換成 noise prediction?先固定目前的 xt(i)x_t^{(i)},沿用 VP 的去噪公式:

x^0(i)=xt(i)−σtϵθ(xt(i),t)αˉt,x^~0(i)−x^0(i)=−σtαˉt⋅(ϵ~θ(i)−ϵθ(xt(i),t)).\begin{gathered} \hat x_0^{(i)}\\ =\frac{x_t^{(i)}-\sigma_t\epsilon_\theta(x_t^{(i)},t)} {\sqrt{\bar\alpha_t}},\\ \widetilde{\hat x}_0^{(i)}-\hat x_0^{(i)}\\ =-\frac{\sigma_t}{\sqrt{\bar\alpha_t}}\\ \cdot\big(\widetilde\epsilon_\theta^{(i)} -\epsilon_\theta(x_t^{(i)},t)\big). \end{gathered}

第二行是兩個去噪估計相減,固定的 xt(i)x_t^{(i)} 消去。若想讓左邊等於 wB(x^0(i⋆)−x^0(i))w_{\mathrm B}(\hat x_0^{(i^\star)}-\hat x_0^{(i)}),在 σt>0\sigma_t>0、αˉt>0\bar\alpha_t>0 時移項,就得到

ϵ~θ(i)=ϵθ(xt(i),t)−wBαˉtσt(x^0(i⋆)−x^0(i)).\begin{gathered} \widetilde\epsilon_\theta^{(i)} =\epsilon_\theta(x_t^{(i)},t)\\ -w_{\mathrm B}\frac{\sqrt{\bar\alpha_t}}{\sigma_t} \big(\hat x_0^{(i^\star)}-\hat x_0^{(i)}\big). \end{gathered}

wB≥0w_{\mathrm B}\ge0 是本次位移的強度。wB=0w_{\mathrm B}=0 保留原 prediction;wB=1w_{\mathrm B}=1 時,固定目前 state 算出的新去噪估計剛好等於參考候選,但不表示下一步 state 或最後圖片已經相同。將合成 prediction 交給原 sampler,才讓各張圖走向下一個較低 noise level。

每個 reverse step 重新「去噪估計 → 評分選參考 → 修改每張 prediction → 繼續生成」,最好編號也可以改變。參考候選本身的位移是零。這是一種用候選差異提出方向的方式,沒有計算 ∇R\nabla R,也沒有更新 θ\theta。

以上沒有像 FK 那樣證出 terminal weighting 等於 eλR(x0)e^{\lambda R(x_0)},所以不能直接宣稱 BoNG 抽到 exact tilt。來源筆記也將它列為延伸;所用 VP 換算保留了 αˉt\sqrt{\bar\alpha_t},不能和 signal amplitude 為 11 的另一套 noise schedule 混用。

想對照來源研究與演算法的完整寫法?

本頁主線已經給出完成品 IW、SMC 的每段循環、SPT 的接受與交換規則;來源技能書另將每個流程寫成數學 pipeline 與 pseudocode,方便逐步查閱。

  • Singhal 等人的 FK steering:中途估計、potentials 與 particle 抽樣。
  • Kim、Kim 與 Park 的 DAS:reward tempering 與 SMC。
  • Wang 等人的 SPT:在 deterministic generator 的 source space 提案與交換。
  • Kim 等人的 BoNG:用最好候選的預測完成品引導其他 particles,為來源列出的近期 preprint。
只有分數,我們仍能做哪些選擇?
Nori 戴 orca 帽的北極熊

評審只肯打分,我們仍可以引導:完成後按倍率抽選,在途中用增量權重重抽草稿,或保留 deterministic generator、改抽起點。挑到高分作品和近似指定的出現比例,要用各自的規則檢查;多算幾次不會自動把它們變成同一件事。

三種機制並列:Best-of-N 完整生成後只留最高分;SMC 按權重重抽且 particle 總數保持固定;SPT 在不同 reward 強度的 source chains 間提案與交換。圖中曲線是機制示意,不是模型實測軌跡。

這張來源圖整理終點挑選、途中重抽與 source 搜尋。SPT 的各列是 reward 強度,不是 diffusion time;圖中的生成器 GθG_\theta 對應本頁固定的 GG,而 FK 的 Gk−1G_{k-1} 是另一個增量權重函數。機制示意不代表有限計算已精確抽到 pRp_R。

點圖可放大
如果原模型沒有精確學到資料分布呢?

正文為了檢查 sampling 規則,先假設 base 是 pdatap_{\mathrm{data}}。若原模型實際輸出的是 pθp_\theta,同樣的 terminal weighting 近似的是以下權重正規化後的分布:

pθ(x0)eλR(x0).p_\theta(x_0)e^{\lambda R(x_0)}.

若還使用有限步 sampler,base 更精確地說是它真正產生的 output law,也包含離散化誤差。增加 candidates、particles 或 chain 搜尋,可以改善相對於這個 base 的抽樣近似,不會自動把 base 修成 pdatap_{\mathrm{data}}。

某種作品若在 base 完全沒有機會,有限 reward 的重新加權也不能讓它出現;低機率區域即使提高倍率,也可能尚未被有限候選看見。因此還要分開看模型誤差、sampling 近似、reward、合理性與多樣性。

接著換一個更具體的要求:使用者交來一張模糊照片,希望生成的清楚圖片真的和它相容。評審該怎麼打分,才是在還原這張照片,而不只是選一張好看的狗? U7.4 從量測過程接續這個問題。

本篇的同一組比例、FK/SMC 推導、SPT 接受規則與 BoNG 延伸,也可以在 U7.3 Reward 只會打分、不會給方向,還能怎麼引導? 連著讀。