Diffusion 初探:加雜訊、學猜答案、從雜訊一步步走回來
還沒進入任何數學框架之前,先用「操作」的角度把 diffusion model 跑一遍。訓練只做一件事:把乾淨資料弄髒,再叫網路把乾淨的版本猜回來。生成則是反過來,從一團全新的雜訊出發,反覆「猜一次、往乾淨走一小步、再猜一次」。後面四章的變分、score、SDE、flow 觀點,都是在用不同的數學解釋這同一個流程。
讀完這頁你應該能夠
- 寫出加雜訊的公式 \(\mathbf x_t=\alpha_t\mathbf x_0+\sigma_t\boldsymbol\epsilon\),說明 \(\alpha_t,\sigma_t\) 這兩個旋鈕的作用,以及 \(p_t\) 這一族「快照分佈」從資料走到雜訊。
- 說明訓練為什麼「幾乎是機械式的」:因為雜訊是我們自己加的,正確答案永遠已知。
- 說明用平方誤差訓練時,網路最好的猜法是條件期望 \(\mathbb E[\mathbf x_0\mid\mathbf x_t,t]\),以及雜訊越大猜得越模糊的原因。
- 寫出一步取樣的更新式:保留預測的訊號與預測的雜訊,用低一點雜訊等級的係數重新混合。
- 解釋為什麼不能只猜一次就停,必須多步慢慢走。
前置知識
- B01:生成模型要學 \(p_{\text{data}}\),生成就是抽樣。
- 高斯分佈 \(\mathcal N(\mathbf 0,\mathbf I)\)、平方誤差、條件期望。條件期望不熟可以先看 CS236 補充課 A。
「書 §」標出原文位置。這一段在書裡是 Part B 開頭一個不編號的小節(位於第 1 章和第 2 章之間)。灰框「轉述者補充」是我另加的內容,藍框「推導」是展開的步驟。
01一句話講完書 Part B 開場
基本構造只有兩部分:
- 定義一個已知的弄髒規則,能把乾淨資料變成任何指定雜訊等級的帶雜訊觀測。
- 訓練一個神經網路,從帶雜訊的觀測推回乾淨的訊號。
這個「推回」的能力在各種雜訊等級都學會之後,就能拿來生成:從全新的雜訊出發,一邊把雜訊等級從高往低降,一邊反覆使用這個網路。得到的就是一個逐漸長出資料結構的新樣本。
02製造帶雜訊的資料書 Part B 開場
拿一個乾淨的資料樣本 \(\mathbf x_0\sim p_{\text{data}}\)(一張圖片,或任何我們在乎的資料),在雜訊等級 \(t\) 混進獨立的高斯雜訊:
$$\mathbf x_t=\alpha_t\mathbf x_0+\sigma_t\boldsymbol\epsilon,\qquad\boldsymbol\epsilon\sim\mathcal N(\mathbf 0,\mathbf I).$$把 \(\alpha_t\) 和 \(\sigma_t\) 想成兩個事先固定好的旋鈕:在雜訊等級 \(t\),一個決定留下多少乾淨訊號,一個決定混進多少雜訊。
最簡單的選擇是 \(\alpha_t=1\)、\(\sigma_t=t\):
$$\mathbf x_t=\mathbf x_0+t\boldsymbol\epsilon.$$這時 \(t\) 就真的是一個雜訊旋鈕:轉到接近 0,\(\mathbf x_t\) 幾乎就是乾淨的樣本;轉大,雜訊項 \(t\boldsymbol\epsilon\) 就越來越佔上風。
書的註腳:\(\mathbf x_t=\alpha_t\mathbf x_0+\sigma_t\boldsymbol\epsilon\) 直接寫出「累積到雜訊等級 \(t\) 的總效果」。DDPM(第 2 章,B04)和 Score SDE(第 4 章,B08)用很多個小步驟依序加雜訊,在同一個雜訊等級也能得到同樣的邊際分佈。之後的 DDPM 離散記號把這個累積的破壞寫成 \(\mathbf x_i=\bar\alpha_i\mathbf x_0+\sqrt{1-\bar\alpha_i^2}\,\boldsymbol\epsilon\)。
為什麼「一次加到位」和「很多小步慢慢加」一樣
獨立高斯相加還是高斯:每一步加一點獨立雜訊,累積起來的雜訊仍然是高斯,只要平均和變異數對得上,就和一次加完的分佈相同。B04 第 2 節有逐步的推導。
快照分佈 \(p_t\)
更一般地說,每個雜訊等級 \(t\) 都定義了一個分佈 \(p_t\)。把它想成一張快照:如果用同一個 \(t\) 產生很多帶雜訊的樣本,它們的分佈就是 \(p_t\)。
旋鈕 \((\alpha_t,\sigma_t)\) 的設計讓這些快照從乾淨資料一路走到簡單的雜訊分佈:
$$p_0=p_{\text{data}}\quad\longrightarrow\quad p_t\quad\longrightarrow\quad p_T\approx p_{\text{prior}},$$其中 \(p_{\text{prior}}\) 通常是好抽樣的高斯分佈。這一整族 \(\{p_t\}_{t\in[0,T]}\) 描述了「雜訊等級升高時,分佈怎麼逐漸變化」。
Demo A雜訊旋鈕:看快照分佈怎麼從資料變成雜訊轉述者補充
資料分佈是二維平面上 6 個小團(權重不同:上面兩團各 25%、中央 20%、其他三團各 10%)。拉動 \(t\),看 1,500 個帶雜訊樣本 \(\mathbf x_t\) 的分佈。可以切換兩種旋鈕:書裡的 \(\alpha_t=1,\sigma_t=t\)(雜訊越加越大,\(T=6\) 時 \(p_T\approx\mathcal N(\mathbf 0,36\mathbf I)\)),以及常見的「保持變異數」版本 \(\alpha_t=\cos\tfrac{\pi t}{2},\sigma_t=\sin\tfrac{\pi t}{2}\)(\(T=1\) 時恰好是 \(\mathcal N(\mathbf 0,\mathbf I)\))。
注意兩件事:第一種旋鈕的雲越來越大,所以畫面會自動縮放(看讀數裡的範圍);第二種的雲大小差不多,資料的結構先糊掉,最後變成一團標準高斯。兩者的共同點是:不同的資料點,最後都被送進同一團簡單的雜訊,生成就可以從那裡出發。
03訓練:把乾淨的訊號猜回來書 Part B 開場
有了弄髒資料的方法,訓練就幾乎是機械式的,關鍵只有一個技巧:雜訊是我們自己加的,所以正確答案永遠是已知的。
做一筆訓練資料的步驟:抽一個乾淨樣本 \(\mathbf x_0\),挑一個雜訊等級 \(t\),抽雜訊 \(\boldsymbol\epsilon\),組出
$$\mathbf x_t=\alpha_t\mathbf x_0+\sigma_t\boldsymbol\epsilon.$$\(\mathbf x_t\) 是我們親手造的,當然知道是哪一個 \(\mathbf x_0\) 造出來的。
但網路看不到 \(\mathbf x_0\) 和 \(\boldsymbol\epsilon\)。它只看到 \((\mathbf x_t,t)\):髒掉的觀測,以及它髒到什麼程度。它的任務是猜出背後的乾淨資料:
$$\mathbf x_0^{\text{pred}}:=\mathbf x_{\bm\phi}(\mathbf x_t,t).$$因為知道正確答案,就能用普通的平方誤差替這個猜測打分數、再改進它:
$$\big\Vert\mathbf x_0^{\text{pred}}-\mathbf x_0\big\Vert_2^2.$$在不同的資料、不同的雜訊、不同的雜訊等級上重複很多次,同一個網路就學會在整個範圍內預測乾淨資料:從幾乎沒被碰過,到幾乎全是雜訊。
網路到底學會了什麼
值得停下來想一想這個損失函數教網路預測的是什麼。在平方誤差下,最好的預測是條件期望:
$$\mathbf x_0^*(\mathbf x_t,t)=\mathbb E[\mathbf x_0\mid\mathbf x_t,t].$$一個帶雜訊的觀測,通常不對應唯一一個乾淨樣本:好幾個不同的乾淨樣本都可能產生差不多的 \(\mathbf x_t\)。只要雜訊等級不是 0,這種模稜兩可就存在;雜訊越大,就越嚴重。所以網路學到的是把所有「在目前的觀測下說得通」的乾淨樣本平均起來。(雜訊等級從上下文看得出來時,書常把它簡寫成 \(\mathbb E[\mathbf x_0\mid\mathbf x_t]\)。)
為什麼平方誤差的最佳解是條件期望
對任何只看 \(\mathbf x_t\) 的預測函數 \(\mathbf f(\mathbf x_t)\),令 \(\mathbf m(\mathbf x_t)=\mathbb E[\mathbf x_0\mid\mathbf x_t]\),把誤差拆開:
$$\mathbb E\Vert\mathbf f(\mathbf x_t)-\mathbf x_0\Vert^2=\mathbb E\Vert\mathbf f(\mathbf x_t)-\mathbf m(\mathbf x_t)\Vert^2+\mathbb E\Vert\mathbf m(\mathbf x_t)-\mathbf x_0\Vert^2+2\,\mathbb E\big[(\mathbf f-\mathbf m)^\top(\mathbf m-\mathbf x_0)\big].$$交叉項先對給定 \(\mathbf x_t\) 的 \(\mathbf x_0\) 取期望:\(\mathbf f-\mathbf m\) 只依賴 \(\mathbf x_t\),而 \(\mathbb E[\mathbf m-\mathbf x_0\mid\mathbf x_t]=\mathbf 0\),所以交叉項為 0。第二項和 \(\mathbf f\) 無關,於是誤差在 \(\mathbf f=\mathbf m\) 時最小。這也說明了「無法消除的誤差」\(\mathbb E\Vert\mathbf m-\mathbf x_0\Vert^2\) 正是模稜兩可的程度。
Demo B訓練一個小網路:它學到的是平均轉述者補充
一維資料:三個窄峰,位置 \(-1.5\)、\(1\)、\(2.5\),權重 0.5、0.3、0.2。用書的旋鈕 \(\mathbf x_t=\mathbf x_0+t\boldsymbol\epsilon\),\(t\in[0,3]\)。按「訓練」,一個兩層、每層 32 個神經元的 MLP 會照第 3 節的流程(造資料、藏答案、平方誤差)訓練 4,000 步,大約 1–2 秒。之後拉動 \(t\),比較網路的預測(藍)和真正的 \(\mathbb E[\mathbf x_0\mid\mathbf x_t]\)(綠虛線,這個資料分佈下可以精確算出)。
看 \(t\) 小時:曲線幾乎是三段平台,每一段都把 \(\mathbf x_t\) 拉回最近那個峰。\(t\) 變大:三段平台被抹平成一條斜率很小的線,在 \(\mathbf x_t=0\) 附近的值接近資料的總平均 \(0.05\)(\(t=3\) 時是 \(-0.03\));\(t\) 再大,它會越來越平,最後就是一條停在總平均的水平線。這就是「雜訊越大,網路只能猜一個越模糊的平均」。
04取樣:從全新的雜訊出發書 Part B 開場
訓練留給我們一個很會「從髒資料猜乾淨資料」的網路。下一個問題是:怎麼用這個本事生出新東西?
訓練結束後,把參數凍結,記作 \(\bm\phi^\times\)。生成的方向和前面相反:不是從資料出發加雜訊,而是從純雜訊出發,
$$\tilde{\mathbf x}_T\sim p_{\text{prior}}.$$生成過程中的狀態加上波浪號 \(\tilde{\mathbf x}_t\),和訓練時由前向弄髒產生的 \(\mathbf x_t\) 區分開來。
這個區分比想像中重要。\(\tilde{\mathbf x}_T\) 不是偷偷把某個真實樣本弄髒得來的,背後沒有一個真的 \(\mathbf x_0\) 等著被找回。所以生成不是「把一段錄影倒帶」,因為根本沒有這段錄影。我們做的是:在每個雜訊等級問網路「你現在覺得乾淨的訊號長什麼樣」,然後根據這個看法往資料的方向退一步。
一步取樣怎麼走
假設目前的狀態是 \(\tilde{\mathbf x}_t\)。餵給網路,得到乾淨資料的猜測:
$$\mathbf x_0^{\text{pred}}:=\mathbf x_{\bm\phi^\times}(\tilde{\mathbf x}_t,t).$$當 \(t>0\),還可以算出一個「隱含的雜訊殘差」,也就是把預測的訊號拿掉之後剩下的部分:
$$\boldsymbol\epsilon_t^{\text{pred}}:=\frac{\tilde{\mathbf x}_t-\alpha_t\mathbf x_0^{\text{pred}}}{\sigma_t}.$$這兩個量不是各自獨立的猜測。照定義,它們恰好能拼回目前的狀態:
$$\tilde{\mathbf x}_t=\alpha_t\mathbf x_0^{\text{pred}}+\sigma_t\boldsymbol\epsilon_t^{\text{pred}}.$$一步取樣的想法是:預測的訊號和預測的雜訊都保留不變,只把混合的係數換成稍低一點的雜訊等級 \(t-\Delta t\):
$$\tilde{\mathbf x}_{t-\Delta t}=\alpha_{t-\Delta t}\,\mathbf x_0^{\text{pred}}+\sigma_{t-\Delta t}\,\boldsymbol\epsilon_t^{\text{pred}}.$$等價地,寫成在目前狀態上加一個小更新:
$$\tilde{\mathbf x}_{t-\Delta t}=\tilde{\mathbf x}_t+(\alpha_{t-\Delta t}-\alpha_t)\,\mathbf x_0^{\text{pred}}+(\sigma_{t-\Delta t}-\sigma_t)\,\boldsymbol\epsilon_t^{\text{pred}}.$$兩種寫法為什麼相同
把 \(\tilde{\mathbf x}_t=\alpha_t\mathbf x_0^{\text{pred}}+\sigma_t\boldsymbol\epsilon_t^{\text{pred}}\) 代進第二式的右邊:
$$\alpha_t\mathbf x_0^{\text{pred}}+\sigma_t\boldsymbol\epsilon_t^{\text{pred}}+(\alpha_{t-\Delta t}-\alpha_t)\mathbf x_0^{\text{pred}}+(\sigma_{t-\Delta t}-\sigma_t)\boldsymbol\epsilon_t^{\text{pred}}=\alpha_{t-\Delta t}\mathbf x_0^{\text{pred}}+\sigma_{t-\Delta t}\boldsymbol\epsilon_t^{\text{pred}}.$$這就是一步。接著把新狀態 \(\tilde{\mathbf x}_{t-\Delta t}\) 再餵給網路,得到一個更新過、通常不一樣的乾淨預測,再往低雜訊走一步。取樣就是把這個迴圈從 \(t=T\) 一路重複到 \(t=0\)。
為什麼不猜一次就好
一個自然的問題:為什麼不直接輸出第一次的預測 \(\mathbf x_0^{\text{pred}}\) 就結束?因為在高雜訊等級,目前的狀態幾乎不帶任何乾淨結構的資訊,那個預測不過是許多可能樣本的粗略平均(Demo B 裡 \(t\) 很大時那條幾乎平坦的線)。往低一點的雜訊等級走一步,網路面對的狀態就沒那麼模稜兩可,下一個預測就能更銳利。重複很多次,才讓一個真正有細節的樣本慢慢浮現。
書的註腳:這個確定性的更新之後會在第 9 章連到 DDIM 取樣和它的 Euler 型解釋;隨機性的 DDPM 取樣器在第 2 章介紹。
05最簡單的例子:往預測的方向混一點書 Part B 開場
用前面的 \(\alpha_t=1\)、\(\sigma_t=t\) 代進更新式,得到
$$\tilde{\mathbf x}_{t-\Delta t}=\mathbf x_0^{\text{pred}}+(t-\Delta t)\,\frac{\tilde{\mathbf x}_t-\mathbf x_0^{\text{pred}}}{t},$$整理成一個簡單的加權平均:
$$\tilde{\mathbf x}_{t-\Delta t}=\frac{t-\Delta t}{t}\,\tilde{\mathbf x}_t+\frac{\Delta t}{t}\,\mathbf x_0^{\text{pred}}.$$代入與整理
\(\alpha=1\) 時 \(\boldsymbol\epsilon_t^{\text{pred}}=(\tilde{\mathbf x}_t-\mathbf x_0^{\text{pred}})/t\),而 \(\sigma_{t-\Delta t}=t-\Delta t\),所以 \(\tilde{\mathbf x}_{t-\Delta t}=\mathbf x_0^{\text{pred}}+(t-\Delta t)(\tilde{\mathbf x}_t-\mathbf x_0^{\text{pred}})/t\)。展開:
$$\mathbf x_0^{\text{pred}}+\frac{t-\Delta t}{t}\tilde{\mathbf x}_t-\frac{t-\Delta t}{t}\mathbf x_0^{\text{pred}}=\frac{t-\Delta t}{t}\tilde{\mathbf x}_t+\Big(1-\frac{t-\Delta t}{t}\Big)\mathbf x_0^{\text{pred}}=\frac{t-\Delta t}{t}\tilde{\mathbf x}_t+\frac{\Delta t}{t}\mathbf x_0^{\text{pred}}.$$換句話說,\(\Delta t\) 很小時,下一個狀態就是「目前所在位置」和「網路認為應該到達的位置」的混合,每一步只往預測的方向挪一點點。
「目前」兩個字很關鍵:一旦挪動了,網路看到的是一個新的、比較乾淨的狀態,通常會改變它的預測。所以最後的樣本不是某一次猜測的輸出,而是由一整串猜測堆出來的,每一次都根據上一次的結果修正。
Demo C一步 vs. 很多步:用這個迴圈生成轉述者補充
回到 Demo A 的 6 團資料。為了專心看取樣迴圈,這裡不訓練網路,直接用精確的 \(\mathbb E[\mathbf x_0\mid\mathbf x_t]\) 當作「完美訓練好的網路」(這個資料分佈可以算出封閉解)。1,000 個粒子從 \(p_{\text{prior}}\) 出發,照第 4 節的更新式走 \(N\) 步。灰色小點是真正的資料分佈,彩色點是生成的樣本,細線是 30 個粒子的軌跡。
試試 \(N=1\):幾乎所有粒子(約 99%)都擠到資料的總平均 \((-0.29,0.39)\) 一帶,被算進中央那團。因為從純雜訊出發的第一次預測,就是 Demo B 裡 \(t\) 很大時那條幾乎平坦、停在總平均的線。\(N\) 增加,粒子才分散到 6 團,每團的比例也逐漸接近真正的權重:保持變異數的旋鈕大約 5–10 步就差不多;第一種旋鈕在 10 步時比例誤差還有 0.07 左右,50 步才降到 0.04–0.05,而且在 \(N=2\)–\(5\) 時,樣本離團中心的平均距離反而比 \(N=1\) 還大。原因是時間點均勻切分時,第一種旋鈕把太多步浪費在高雜訊端。怎麼挑步數和時間點,是第 9 章 solver 的主題。
改步數或旋鈕時,沿用同一組起點雜訊(種子 3),上面的數字可以重現;按「從新的雜訊生成」才換一組新的雜訊,比例會有幾個百分點的抽樣起伏。
06這和接下來的章節有什麼關係書 Part B 開場
這裡的「預測乾淨資料」範本只是第一個操作性的觀點,但基本的模式在整本書都不會變:訓練時,從乾淨資料造出帶雜訊的觀測,學會從中預測有用的資訊;生成時,從全新的雜訊出發,在逐漸降低的雜訊等級上用這些學到的預測組出一個新樣本。
接下來幾章用不同的數學觀點解釋同一個過程:
- 第 2 章(變分觀點):把 DDPM 連到潛在變數模型。固定的加雜訊過程扮演 encoder,學到的反向轉移扮演 decoder。
- 第 3 章(score 觀點):用 score 描述去雜訊,它給出更新帶雜訊樣本的方向。
- 第 4 章(Score SDE):從離散的雜訊等級走到連續時間,把生成描述成反向時間的隨機動力學。
- 第 5 章(flow 觀點):把生成看成沿一條連續路徑,把樣本從簡單的先驗運送到資料分佈。
- 第 6 章:說明這些觀點彼此怎麼連在一起。
最主要的收穫很簡單:diffusion model 從資料在很多雜訊等級的帶雜訊版本學習,生成時反覆使用這些學到的預測,把全新的雜訊變成一個新樣本。