U1.0 生成到底在學什麼?
本篇重用5 篇
一個看起來很簡單的要求
我們手上有五萬張 32×32 的圖片。
從過去對機器學習與深度學習的理解,我們已經很熟悉一套基本做法:
定義 loss,然後用梯度下降把它降下來。
現在,突然有人丟給你一個不太一樣的問題:
只靠這些我們已經會的工具,能不能讓模型「生」出一張它從沒看過,卻看起來像是來自同一批資料的新圖片?

圖:問題長什麼樣。 這其實就是我們現在面對的問題:手上有一堆樣本,我們會做的事情是透過定義 loss 去做 gradient descent,但最後想得到的,卻是一個看起來像是新的、卻又合理的樣本。
這篇我們想要把這句看起來很直覺的要求,慢慢翻成一個我們真的可以處理的數學問題,並在工程上實作看看。首先我們要先理解:
什麼叫做「生」出一張圖片?
以及,什麼又叫做
「看起來像是來自同一批資料」?
「生」一張圖片,與「來自同一批資料」
一張 32×32 的彩色 RGB 圖片,總共由 個數字構成(RGB 共 3 種顏色)。把每個數字當成一個座標,一張圖就可以被想成 3072 維歐氏空間 裡的一個點。
補充3072 維空間裡的一個點是什麼意思?
就是一個由 3072 個數字排成的向量。比較熟悉的例子是 :一個點就是 這樣三個數字,例如 ,可以想成三維空間裡的一個位置。 只是把「三個數字」換成「3072 個數字」。
所以,每一張可能出現的 32×32 RGB 圖片,都對應到這個空間中的某一個點。但這些圖片並不是毫無規律地出現在整個空間裡:真實世界中,某些圖片比較常出現、某些圖片比較少見,還有很大量的點可能對應到的只是一團噪聲,或根本不像合理的圖片。
也就是說,所有可能的資料背後,其實存在著某種 distribution:它描述了資料比較可能出現在哪裡、又比較不可能出現在哪裡。我們把這個真實但未知的資料分布記成 。
如果把它想成連續分布, 就是它的 probability density。不要直接把它理解成「資料剛好出現在 這個點的機率」,而比較適合理解成:資料有多密集地出現在 附近。 越高,代表從這個資料來源中抽樣時,我們越容易看到落在這附近的圖片;越低,則代表這附近的圖片比較少見。
補充為什麼不說「機率」?
在連續空間裡,任何單一個點的機率都是 0;而 這個值本身甚至可以大於 1,所以它不可能是機率。有意義的是把密度乘上一小塊體積: 才近似「抽一個樣本、它落在 附近那一小塊」的機率,而如果把整個空間積分起來,則會等於 1——因為樣本一定會落在空間中的某個位置。
因此, 是一個定義在整個資料空間上的函數:空間中的每個位置都有一個 density value。如果我們能把這個值當成高度畫出來,它就像是一片地形——資料常出現的地方是高地,少出現的地方比較低,而局部最密集的位置就是 mode。
現在回頭看我們手上的五萬張圖片。這五萬張圖片不是 本身,而只是從 中抽出來的五萬個樣本;換成幾何的語言,它們就是 裡我們實際觀察到的五萬個點。因為它們來自同一個 ,所以通常也會反映出背後分布的一部分結構:某些區域的樣本很多,某些區域很少,還有大量區域我們可能一個樣本都沒有看過。
左欄是我們拿得到的照片,中欄把每張照片看成一個點,右欄才是背後的 。每個位置都有 density value,不只樣本所在處;山峰是 mode。真實圖片位於 ,這裡用二維位置加上 density 高度示意。
有了這個畫面,「生成」和「看起來像是來自同一批資料」其實就可以一起說清楚了。
所謂生成,就是希望再從 中抽出一個新的點。不是從原本五萬張圖片裡挑一張出來——那比較像是檢索——而是產生一個我們之前沒有看過的新樣本。
為什麼這個新樣本仍然會「像」原本的資料?因為它和那五萬張圖片來自同一個 distribution。 密度高的地方比較容易被抽到,密度低的地方比較不容易,所以抽出來的點大多會落在合理的區域。
但要注意,我們並沒有一條規則可以直接「禁止」它落在低密度的地方;只是那些地方本來就比較不容易被抽到而已。
所以,「看起來像是來自同一批資料」更精確地說,就是:
這個新的點,也像是從同一個 裡抽出來的。
麻煩也正在這裡:決定資料怎麼生成的是背後的 ,但我們看不到它——拿得到的只有從它抽出來的有限樣本。我們看得到的是五萬個點,真正想知道的卻是產生這些點的那片「地形」。
如果直接讓神經網路表示 density 呢?
既然「生成」就是從 再抽一個樣本,最直接的想法自然是:用一個神經網路表示 ,讓它逼近 ,訓練好後再從 抽樣。
但在寫 loss 之前,這個網路得先輸出一個合法的 density。這代表它必須同時滿足兩個要求:
- 每個位置都不能是負的: ;
- 把整個空間積分起來必須等於 1: 。
一般神經網路可以輸出任意實數,所以第一個要求不會自動成立;而且就算我們已經讓每個位置的輸出都是正的,第二個要求仍然不一定成立。因此,我們要分別用數學把這兩個要求補上。
先讓網路輸出一個數值 ,再把 放進 exponential:
這一步滿足了第一個要求。 越低, 附近的 就越高;但 的總量還不一定是 1。於是我們先算出整個空間的總量 ,再把每個位置都除以它:
正的權重,怎麼變成合法的 density?
- 每個位置已經有正的權重 ,但它還不是總量為 1 的 density。
- 把整個空間的權重積分起來,得到同一個總量 。
- 只要總量有限,每個位置都除以它,就把總量調成 1。
只要 有限,這一步就滿足了第二個要求,因為 。這種先替每個位置指定 energy,再把 energy 轉成 distribution 的模型,叫做 energy-based model [1]。
補充為什麼叫做 energy?
這個名字來自統計物理。在那裡,一個狀態出現的機率常寫成
能量 越低, 就越大,這個狀態也越容易出現。Energy-based model 沿用同一個數學形式:讓神經網路替每個 指定一個數值 ,低 energy 代表模型認為這個 比較可能,高 energy 則代表比較不可能。
這裡的 energy 不一定是真實世界中的物理能量;它只是模型用來表示「這個 和資料有多相符」的一個數值。真正的 probability density,仍要經過 exponential 與 normalization 才會得到。
現在 density 有了,我們可以用 maximum likelihood:讓資料真正出現的位置,在 下有較高的 density。把 negative log-likelihood 寫開,
第一項會把訓練資料附近的 energy 往下拉。麻煩在第二項: 是對整個 3072 維圖片空間積分,不能只靠手上的五萬張圖片算出來。
不過,真正訓練時需要的不是 的數值,而是 loss 的 gradient。對上式微分後,
第一個期望可以用 training data 估計;第二個期望卻是對模型自己的 distribution 取平均。要算它,我們得先從目前的 抽出很多 。於是出現了一個循環:我們原本想訓練一個能抽樣的 distribution,訓練它的 gradient 卻先要求我們已經會從它抽樣。
這種「式子寫得出來,但其中的計算無法直接完成」的情況,通常稱為難以直接計算(intractable)。Energy-based model 並不是不能訓練;常見方法會用 approximate sampling 來估第二項。只是對我們現在的目標來說,它已經指出第一個困難:直接學 density,normalization 與 sampling 會綁在一起。
換一個角度:不學密度,直接學一個能生成樣本的函數?
既然直接學一個可抽樣的 density 會遇到上面的循環,那換個角度:有沒有哪個 distribution 是我們本來就會抽的?有——標準 Gaussian 。
符號為什麼 Gaussian 這麼好抽?
是 上的標準常態分布。它之所以「很好抽」,關鍵不是 density 長得簡單,而是電腦已經有成熟的方法,可以直接產生標準 Gaussian random numbers;不需要先訓練另一個模型,也不需要在空間裡反覆移動許多步。
到了 維就更簡單,因為每個座標彼此獨立:
所以抽一個 維樣本,只是直接抽 個一維 Gaussian——程式裡的一行 torch.randn(d),背後做的就是這件事。
既然 Gaussian 這麼容易抽,就可以換一個角度想:把 當成一種隨手可得的原料——想要多少就抽多少。但我們真正想要的不是 Gaussian noise,而是來自 的圖片。那問題就變成:
能不能找到一台「加工機器」,把這些隨手可得的 Gaussian 原料,變成我們真正想要的資料?
把這台機器寫成一個函數(function):先抽一個 ,再把它丟進 ,得到 。如果 學得夠好,讓大量不同的 經過它之後,產生的 整體上正好遵循 ,那生成問題就解決了:
生成一個新樣本於是只剩兩件事:先抽一個很好抽的 ,再用 把它變成我們真正想要的 。 用機率的語言說, 把 這個 distribution 搬成了 ——這件事叫做 pushforward;而負責搬運的這個 ,也常被稱作把 送到 的 transport map(傳輸映射)。
定義Pushforward
給一個分布 和一個(可測)函數 ,把每個樣本都送過 之後得到的新分布記成 ,定義是:對任意集合 ,
也就是「 落在 裡」的機率,等於「 落在會被 送進 的那些位置」的機率。等價的寫法是對任意函數 ,
於是我們的目標可以一行寫完:找一個 使 。
左邊先抽 ,中間用帶參數的 決定每個 被送到哪裡,右邊收集 。目標是這整批輸出的 distribution 接近 ;沙堆變成沙堡,對應的就是重新安排每粒沙的位置。
那接下來的問題就很直接了:我們要怎麼學這個 ? 我們手上有 的樣本、也有 的樣本,得從這裡寫出一個對 可微的 loss。
課堂提問Q1
手上既然有真實資料 ,也有生成出來的 ,那用傳統 prediction 的做法不就好了:抽一批 、算出 ,再跟資料算 :
隨機配對之後,這個 loss 在要求什麼?
- 先抽一個容易取得的 Gaussian 原料 。
- 把 送進 ,得到一張生成圖片。
- 再獨立抽一張真實圖片 ,拿它來和生成圖片比較。
- 用兩張圖片的平方距離,衡量這一次配對的誤差。
- 對獨立抽到的 與 取平均,也就是把各種隨機配對都當作學習目標。
這樣訓練下去會發生什麼事?
先想一想,再展開看整理後的答案
問題在於: 需要配對,但這裡沒有人告訴我們哪個 該對到哪張圖。 是我們自己隨手抽的, 是資料集裡隨手拿的一張,兩者之間本來就沒有任何對應關係。
而如果配對是隨機的,這個 loss 的最佳解可以直接算出來。固定一個 ,因為 與 獨立,
也就是說,模型的最佳策略是不管拿到什麼 ,都輸出同一張「所有訓練圖片的平均」——一張灰灰的、模糊的四不像。它的 很低,但完全沒有多樣性,更不是從 抽出來的樣本。這正是 regression 遇到多解問題時會出現的回歸到平均。
左邊的箭頭只是這次隨機配出的 training pairs;下一次,同一區域的 又可能被配到完全不同的 。因此固定 時,target 仍來自完整的 ,右邊的最佳解才會是 ,對所有 都一樣。
再往上看一層,會發現是目標本身錯位了:我們真正要的是「 的分布等於 」——一個分布層級的條件;而 問的是「這一個 有沒有對上這一張 」——一個逐樣本的條件。兩堆樣本可以分布完全相同,隨機抽樣卻沒有任何一對彼此靠近。因此,設計一個逐樣本的 loss 去學一個分布層級的目標,最後自然只會學到平均。
於是有兩個自然的方向:
- 換一個分布層級的目標,直接比較生成出來的整體 distribution 與 ;
- 或者,自己把配對定下來——如果每個 都被指定好要變成哪個 (甚至指定整條中間路徑),逐樣本的回歸就重新變得合理。
我們先看第一個方向能走到哪裡,再回來看「自己製造配對」會帶出什麼方法。
如果 可逆,能不能把 density 算回來?
課堂提問的結論是:隨機配對的 在比較單一樣本,但我們真正需要的是一個分布層級的目標。既然 已經能產生樣本,一個自然的想法是先把它產生的 density 算出來,再用 likelihood 訓練。
麻煩是,知道怎麼算 ,不代表知道某個 的 density。要把 算回來,我們至少得知道這個 是由哪一個 變來的,以及 在附近把空間拉開或壓縮了多少。
如果 可逆而且夠光滑,第一件事可以用 找回 ;第二件事則由 Jacobian determinant 記錄。Change of variables 於是給出
第一項問的是「倒推回去的 在 Gaussian 裡有多常見」;第二項修正 對局部體積造成的改變。這類用一連串可逆 transformation,把簡單 distribution 變成複雜 distribution 的模型,叫做 normalizing flow [2, 3]。
只要 inverse 與 log-det 都算得出來, 就能直接計算,我們也就能使用 maximum likelihood:
補充什麼是 maximum likelihood?為什麼這樣做有用?
可以把模型分布 想成一個下注的人。他手上的籌碼總額固定——這對應到
他要做的,就是把這些籌碼分配到整個資料空間:某些地方押多一點,某些地方押少一點。
接著,我們把手上的五萬張圖片 一張一張攤出來,每次都問:「這張圖片出現的位置,你押了多少?」模型在 上押的量就是 ;取 log 之後的 ,就是這筆資料對 log-likelihood 的貢獻。
因為籌碼總額固定,模型不能靠「所有地方都押很多」來作弊:押到幾乎不會出現資料的地方,就代表能留給真正資料區域的籌碼變少了。所以想讓 likelihood 變高,模型就必須慢慢把更多的 probability mass 放到資料真正容易出現的地方——直覺上,就是讓 的地形越來越像 。
對 個獨立樣本來說,
因此 maximum likelihood 就是在最大化「資料實際出現位置上的平均 log probability」。
而且它確實是一個 distribution-level 的 objective,因為
第一項和 無關,所以最大化 likelihood,就等價於最小化 ——而它等於 0 的唯一情況,就是兩個分布完全相同(M5.1)。
這個方法確實解決了「怎麼為整體 distribution 寫 loss」的問題,但條件也很明確。每看到一張 training image ,我們都必須算出 ,還要計算 Jacobian determinant。一般神經網路通常沒有便宜的 inverse;而一般 Jacobian 的 determinant 需要 的計算,在 時也很難每一步都做。
因此,normalizing flow 不能直接使用任意神經網路。它必須刻意限制每一層的結構,讓 inverse 與 log-det 都算得動。這是它為精確 likelihood 做的交換:
要把 likelihood 精確寫成 loss,就得讓 保持可逆,並讓 Jacobian determinant 容易計算。
同一個終點,可以有很多種搬法
即使最後生成的 distribution 完全相同,把 Gaussian 搬成 的方法也通常不只一種。很多不同的 ,都可能滿足
想像要把一倉庫的紙箱,重新排成另一個倉庫的樣子。只要最後每個位置上的紙箱數量相同,就算搬運成功;但哪一箱要搬去哪裡、中途要走哪條路,可能有很多種安排。Distribution 只規定最後整批貨物要長成什麼樣子,沒有替每一個起點指定終點。
這就是 transport map 的 underdetermined:終點固定了,搬法卻沒有唯一答案。Maximum likelihood 可以判斷最後的 是否接近 ,但不會替我們指定唯一的配對或路徑;normalizing flow 則為了算這個 likelihood,必須限制 的結構。
互動 demo:終點相同,搬法不只一種。 按「換一組配對」, 的 Gaussian 與 的目標 distribution 都不會變;改變的是哪些點被配在一起,以及它們中途怎麼走。
如果配對不存在,我們能不能自己製造?
到這裡,我們遇到的困難其實都和「整體」有關:直接學 density,要處理整個空間的 normalization;直接學 ,隨機配對的 只會學到平均;改用 exact likelihood,又要限制 必須可逆且 log-det 好算。
但課堂提問也留下了一個線索: 本身沒有錯,錯的是我們隨便把 和 配在一起。如果 training pairs 是我們自己造出來的,普通的 regression 就可能重新變得有用。
怎麼造?先不要從 Gaussian 猜它應該變成哪張圖片,而是把方向反過來。Training 時我們手上本來就有乾淨資料 ;如果我們自己訂一套規則,逐步把它加入 noise,就能親手製造出 。
這很像我們先錄下一段「把紙箱逐步打散」的影片。影片中的每一格怎麼來,我們都知道;因此要學著倒放時,不必一口氣猜完整的搬運方式,而可以先練習:看到這一格,下一步該怎麼往前一格修正?
這套由乾淨資料逐步加入 noise 的規則,就是接下來要建立的 forward process。它不會讓 reverse direction 自動變簡單,但會提供我們可以直接抽樣的 noisy states,並把原本「一步生成整張圖片」的問題,拆成許多不同 noise levels 上的學習問題。
Diffusion 的第一步,不是先猜一個完整的 generator,而是先設計一個能由資料自行製造 training pairs 的 forward process。
接下來真正要決定的是:noise 該怎麼一步一步加入,才能讓任意時刻的 都容易產生、最後的 又變成我們熟悉且容易抽樣的 distribution?
消化一下
參考文獻
- LeCun, Y., Chopra, S., Hadsell, R., Ranzato, M., Huang, F. A Tutorial on Energy-Based Learning. 2006.
- Papamakarios, G., Nalisnick, E., Rezende, D. J., Mohamed, S., Lakshminarayanan, B. Normalizing Flows for Probabilistic Modeling and Inference. JMLR 2021.
- Rezende, D. J., Mohamed, S. Variational Inference with Normalizing Flows. ICML 2015.