M5.3 16 分鐘閱讀 2026年9月

M5.3 只看得到樣本,怎麼調機器:對 Generator 微分

本篇重用M0.0霧中下山:Gradient 與方向·M5.1用錯的機率表下注一年會多輸多少:KL Divergence 與 Cross-Entropy·M0.3漂流的溫度計:全導數、微分穿過積分與 JVP

一台把亂數變成產品的機器

工廠裡有一台機器:每次按下按鈕,它內部擲一把骰子(亂數 zz),然後照它的設定 θ\theta 把骰子的結果加工成一個產品 x=Gθ(z)x=G_\theta(z)。你看不到骰子、看不到內部,只看得到吐出來的產品。

客戶給了你一個目標:產品的分佈要像某個規格 pp^*——不是每個產品都一樣,而是整體的散佈要對。你可以轉機器上的旋鈕 θ\theta。你要往哪個方向轉?

先用直覺回答:你會怎麼做?拿一批產品、跟規格比、然後怎麼把「比出來的差距」變成「旋鈕該轉的方向」?寫下你有多確定。

課堂上的回答通常是「多抽一些,看哪邊多了就往反方向調」。這是對的方向,但它說不出每個旋鈕該轉多少——尤其當旋鈕很多(θ\theta 是幾百萬維)時,「哪邊多了」根本無法逐一看。我們需要的是一個梯度:分佈的差距對每個旋鈕的偏導數。

課堂提問Q1

翻成數學。「產品的分佈」是 θ\theta 的什麼函數?「跟規格的差距」用哪個量寫?直接對它微分會卡在哪裡?

先想一想,再展開看整理後的答案

產品 x=Gθ(z)x=G_\theta(z)zpzz\sim p_z(骰子的分佈,固定、與 θ\theta 無關)。產品的分佈 pθp_\thetapzp_zGθG_\theta 的 pushforward——同一把骰子、不同的 θ\theta,吐出不同的分佈。差距用 M5.1 的 KL:

L(θ)=KL(pθp)=Expθ[logpθ(x)logp(x)].L(\theta)=\mathrm{KL}(p_\theta\,\|\,p^*)=\mathbb E_{x\sim p_\theta}\big[\log p_\theta(x)-\log p^*(x)\big].

(方向是 KL(pθp)\mathrm{KL}(p_\theta\|p^*):對「機器把產品放在規格幾乎不允許的地方」罰得重——這正是「不要生出不合規格的產品」要的方向。)

直接微分會卡在兩處。第一,期望是pθp_\theta 取的,而 pθp_\theta 本身隨 θ\theta 變——「抽樣」這個動作不可微,你沒辦法對「從一個分佈抽一個數」這件事求導。第二,被積函數裡有 logpθ(x)\log p_\theta(x),它也隨 θ\theta 變。兩個 θ\theta 糾在一起。

解開第一個結的方法,就是這一篇的物件:把隨機性搬到 θ\theta 外面xpθx\sim p_\theta 與「zpzz\sim p_z 然後 x=Gθ(z)x=G_\theta(z)」是同一件事,但第二種寫法裡隨機的只有 zz,它不依賴 θ\thetaθ\theta 只出現在一個普通的、可微的函數 GθG_\theta 裡。這叫 reparameterization(重參數化)。

先抓住這個畫面:固定骰子,只轉旋鈕

想像你把機器內部的骰子換成一疊預先擲好的紙條:z1,z2,,zNz_1,z_2,\dots,z_N。現在機器不再隨機——同一張紙條、同一個 θ\theta,永遠吐出同一個產品 Gθ(zi)G_\theta(z_i)。轉一點旋鈕,每個產品都往某個方向動一點,動多少由 θGθ(zi)\partial_\theta G_\theta(z_i) 決定:旋鈕對第 ii 個產品的靈敏度

現在「分佈的差距」怎麼變?每個產品 xix_i 站在一個地形上:地形的高度是 logpθ(x)logp(x)\log p_\theta(x)-\log p^*(x)——「機器認為這裡多常出現」減「規格認為這裡該多常出現」。產品站在高處(機器放太多、規格要太少)時,把它往地形低處推會讓差距變小。往低處推的方向是 x[logpθlogp]-\nabla_x\big[\log p_\theta-\log p^*\big]M0.0),而旋鈕能把它推的方向是 θG\partial_\theta G。兩者的內積,對所有紙條平均,就是梯度。

所以整條公式的畫面是:每個樣本站在「機器分佈 vs 規格」的地形上,地形的坡告訴它該往哪走,旋鈕的靈敏度告訴我們轉哪個旋鈕能讓它那樣走。

在一維上把它算出來

一維、θ\theta 是一個實數。reparameterize 之後:

L(θ)=Ez[logpθ(Gθ(z))logp(Gθ(z))].L(\theta)=\mathbb E_{z}\Big[\log p_\theta\big(G_\theta(z)\big)-\log p^*\big(G_\theta(z)\big)\Big].

期望現在是對固定的 pzp_z 取的,可以直接把 θ\partial_\theta 搬進去(M0.3 的微分穿過積分)。被積函數裡 θ\theta 出現在兩處:GθG_\theta 裡、以及 pθp_\theta 的下標裡。用 chain rule 全部展開:

θL=Ez[(xlogpθxlogp)(Gθ(z))θGθ(z)(A) 沿 x 的坡×靈敏度  +  (θlogpθ)(Gθ(z))(B) 下標裡的 θ].\begin{aligned} \partial_\theta L &=\mathbb E_z\Big[\underbrace{\big(\partial_x\log p_\theta-\partial_x\log p^*\big)\big(G_\theta(z)\big)\cdot\partial_\theta G_\theta(z)}_{\text{(A) 沿 }x\text{ 的坡}\times\text{靈敏度}} \;+\;\underbrace{\big(\partial_\theta\log p_\theta\big)\big(G_\theta(z)\big)}_{\text{(B) 下標裡的 }\theta}\Big]. \end{aligned}

(B) 看起來很麻煩——它是「分佈本身對 θ\theta 的變化率」在樣本處的值。但它的期望是零:

Ez[θlogpθ(Gθ(z))]=Expθ[θlogpθ(x)]=pθθpθpθdx=θ ⁣pθdx=θ1=0.\mathbb E_z\big[\partial_\theta\log p_\theta(G_\theta(z))\big]=\mathbb E_{x\sim p_\theta}\big[\partial_\theta\log p_\theta(x)\big]=\int p_\theta\,\frac{\partial_\theta p_\theta}{p_\theta}\,dx=\partial_\theta\!\int p_\theta\,dx=\partial_\theta1=0.

(第一個等號只是把 Gθ(z)G_\theta(z) 換回名字 xx;最後一步又是微分穿過積分。)所以

  θKL(pθp)=Ez[(xlogpθ(x)xlogp(x))x=Gθ(z)θGθ(z)].  \boxed{\;\partial_\theta\,\mathrm{KL}(p_\theta\|p^*)=\mathbb E_{z}\Big[\big(\partial_x\log p_\theta(x)-\partial_x\log p^*(x)\big)\Big|_{x=G_\theta(z)}\cdot\partial_\theta G_\theta(z)\Big].\;}

多維時 xlog\partial_x\log 換成 xlog\nabla_x\logθG\partial_\theta G 換成 Jacobian,形狀是「向量 ×\times 矩陣」,其餘一字不改。

讀一遍每個符號。xlogp(x)\nabla_x\log p(x) 是密度的對數梯度——有時叫 score function,它指向「密度上升最快」的方向,並且不需要知道 normalizing constantlog(p~/Z)\log(\tilde p/Z)xx 微分時 logZ\log Z 消失(M0.0log\nabla\log 的意義)。θGθ(z)\partial_\theta G_\theta(z) 是旋鈕的靈敏度,由機器本身給。整條公式說:梯度 == 在每個樣本處,機器的 score 減規格的 score,乘上靈敏度,取平均。

展開細節為什麼 (B) 消失這件事重要:兩種估計量與變異數

若不用 reparameterization,另一條路是「score-function estimator」(REINFORCE):∂θ E{p_θ}[f(x)] = E_{p_θ}[f(x)·∂_θ log p_θ(x)]。它不需要 G 可微、連 x 是離散的都能用,但變異數通常很大——f(x) 的絕對大小整個進入估計量。reparameterization 把「分佈怎麼變」換成「每個樣本怎麼動」,估計量只含 f 的梯度,變異數小得多(Mohamed et al. [3] 的系統比較)。

而 (B) 這一項:它的期望是零,但單一樣本上不是零。實作時若把 log p_θ(G_θ(z)) 對 θ 直接自動微分,autodiff 會忠實地算出 (A)+(B)——(B) 平均下來是零,卻帶進額外的雜訊。Roeder et al. [4] 指出把 (B) 手動去掉(對 p_θ 的參數做 stop-gradient、只讓梯度從 G_θ 流過)會得到變異數更低的估計量;在 p_θ = p* 的最佳點,去掉 (B) 的估計量每個樣本都恰好為零,保留 (B) 的則仍在抖。「一個期望為零的項,留著或去掉」是實作品質的分水嶺。

回到工廠:公式能用,但缺一樣東西

回答起點問題。要轉旋鈕,你需要在每個產品 xi=Gθ(zi)x_i=G_\theta(z_i) 處算三樣東西:

  1. θGθ(zi)\partial_\theta G_\theta(z_i)——機器的靈敏度。機器是你自己的,可微就有。
  2. xlogp(xi)\nabla_x\log p^*(x_i)——規格的 score。若客戶給的規格是一條公式(例如「常態、均值 5、標準差 1」),直接算。若客戶只給了一批合規格的樣本、沒有公式——這是現實中最常見的情形——這一項你沒有。從樣本估一個分佈的 score 是一個獨立的問題(score matching [5]),要另外訓一個模型。
  3. xlogpθ(xi)\nabla_x\log p_\theta(x_i)——機器自己的 score。這也不是免費的:你知道 GθG_\theta,但你不知道 pθp_\theta 的密度——pushforward 的密度要算 Gθ1G_\theta^{-1} 的 Jacobian,一般的 GG 做不到。所以這一項也要另外估:拿機器吐出來的樣本、訓一個 score 模型。

於是「只看得到樣本、要調分佈」這件事的完整流程是:兩個 score 模型(一個學規格、一個學機器)加一個可微的機器,梯度是兩個 score 的差乘靈敏度。直覺「哪邊多了就往反方向調」的精確版本正是這樣——「哪邊多」由兩個 score 的差說出,「怎麼調」由靈敏度說出。

這個判斷依賴的假設:GθG_\thetaθ\theta 可微、對 zz 是連續的(產品若是離散的類別,reparameterization 就沒有直接版本,要用展開框裡的 score-function estimator 或其他 relaxation);兩個 score 估得夠準(尤其在樣本稀疏的區域,估錯的 score 會把產品推向錯的方向);以及 KL 的方向——KL(pθp)\mathrm{KL}(p_\theta\|p^*) 允許機器「漏掉」規格的某一塊(mode dropping),若客戶要求每一種合規格的產品都要出現,該用另一個方向或另一個距離。

回到工廠:一維跑一次,然後把 (B) 留著看它抖

一維、Gθ(z)=θ+zG_\theta(z)=\theta+zzN(0,1)z\sim\mathcal N(0,1),所以 pθ=N(θ,1)p_\theta=\mathcal N(\theta,1);規格 p=N(5,1)p^*=\mathcal N(5,1)。此時一切都有 closed form:KL=(θ5)2/2\mathrm{KL}=(\theta-5)^2/2,真梯度是 θ5\theta-5。兩個 score:xlogpθ(x)=(xθ)\partial_x\log p_\theta(x)=-(x-\theta)xlogp(x)=(x5)\partial_x\log p^*(x)=-(x-5),差是 θ5\theta-5——與 xx 無關,每個樣本都給出精確的梯度;靈敏度 θG=1\partial_\theta G=1

import numpy as np
rng = np.random.default_rng(0)
theta, target, lr = 0.0, 5.0, 0.1
for step in range(60):
    z = rng.standard_normal(256)
    x = theta + z                                              # reparameterization:固定骰子 z,x 是 θ 的可微函數
    score_model  = -(x - theta)                                # ∇ₓ log p_θ(x)
    score_target = -(x - target)                               # ∇ₓ log p*(x)
    grad_A = np.mean((score_model - score_target) * 1.0)       # (A):兩個 score 的差 × ∂G/∂θ
    grad_B = np.mean((x - theta))                              # (B):∂_θ log p_θ 在樣本處 —— 期望為零、單樣本不為零
    theta -= lr * grad_A
    if step % 15 == 0: print(step, round(theta, 3), round(grad_A, 3), round(grad_B, 3))
print(theta)                                                   # → 5.0

θ\theta 從 0 走到 5,grad_A 每步恰等於 θ5\theta-5(因為兩個 score 的差不依賴 xx,這個例子裡 (A) 沒有抽樣雜訊)。grad_B 每步在 ±0.06\pm0.06 附近抖——它的期望是零,但每一批都不是零;若把它加進更新,θ\theta 收斂後仍會在 5 附近抖動,而只用 (A) 的版本在最佳點每一步都精確為零。

刻意違反一個假設。score_target 換成一個從 pp^* 的 200 個樣本估出來的粗糙版本(例如用核密度估計取對數再差分):在樣本稀疏的尾端 score 估錯,θ\theta 會收斂到 5 附近但帶偏差;樣本越少偏得越多。再把 Gθ(z)=θ+zG_\theta(z)=\theta+z 換成 Gθ(z)=θ+zG_\theta(z)=\theta+|z|——機器只會吐正的產品,pushforward 的密度 pθp_\theta 你不再有 closed form,score_model 那一行必須改成從機器樣本估——這就是「第三樣東西也不免費」的意思。

先消化一下

想一想

一個推薦系統的「機器」輸出的是離散的商品編號 x{1,,K}x\in\{1,\dots,K\},由 θ\theta 決定各編號的機率。你想調 θ\theta 讓輸出分佈接近某個目標。本篇的 reparameterization 公式:

想一想

把 (B) 那一項 θlogpθ\partial_\theta\log p_\theta 在樣本處的值留在梯度裡,不去掉。長期而言:

想一想

客戶只給了 200 個合規格的樣本,沒有公式。你用本篇的梯度公式調機器,發現產品分佈在規格的主體吻合得很好、但尾端明顯偏掉。最合理的診斷是:

參考文獻

  1. Kingma, D. P., Welling, M. Auto-Encoding Variational Bayes. ICLR 2014.(reparameterization trick 的出處之一。)
  2. Rezende, D. J., Mohamed, S., Wierstra, D. Stochastic Backpropagation and Approximate Inference in Deep Generative Models. ICML 2014.(同時期獨立提出的「stochastic backpropagation」。)
  3. Mohamed, S., Rosca, M., Figurnov, M., Mnih, A. Monte Carlo Gradient Estimation in Machine Learning. JMLR 21, 2020.(score-function 與 pathwise(reparameterization)兩類估計量的系統比較與變異數分析。)
  4. Roeder, G., Wu, Y., Duvenaud, D. Sticking the Landing: Simple, Lower-Variance Gradient Estimators for Variational Inference. NeurIPS 2017.(把期望為零的 (B) 項去掉可以降低變異數。)
  5. Hyvärinen, A. Estimation of Non-Normalized Statistical Models by Score Matching. JMLR 6, 2005.(只有樣本時如何估 xlogp\nabla_x\log p。)