JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

生成對抗網路:偽造者與偵探的對決

與其逐一比對像素,不如讓兩個網路對決——一個偽造假影像、一個努力揪出它們——直到假影像與真品難分軒輊?

偽造者與偵探

想像一個偽造者在密室裡費盡心力印製假鈔,銀行裡則有一位偵探,他唯一的工作就是揪出偽鈔。最初的假鈔很可笑——顏色不對、印章模糊、沒有浮水印——偵探一眼就識破。但偽造者會從每次失敗中學習:每被抓到一次,他就推敲出是哪裡露了餡並加以修正。偵探也隨之變得更銳利,去搜尋愈來愈細微的破綻。一回合接一回合,假鈔愈做愈好、偵探愈來愈挑剔,直到有一天偽鈔完美到偵探只能靠擲硬幣來猜。這場不斷升級的對決,正是生成對抗網路的全部精髓。

兩位玩家:生成器從雜訊偽造影像,判別器則評斷真偽。

一張示意圖,顯示生成器把雜訊向量轉成假影像,真實影像與假影像一同送入判別器,判別器輸出「真或假」的機率。

在上一篇導引中,VAE 透過「重建」影像來生成,並逐像素地拿結果與原圖比對來衡量好壞。正是這種逐像素比對的損失,讓 VAE 的樣本看起來柔和而模糊:當模型不確定哪張清晰圖才對時,把多個可能的像素值取平均最能降低誤差——而許多清晰圖的平均就是一團模糊。GAN 則完全捨棄這種損失。它沒有「要去比對某張特定目標圖」的指令。取而代之的,是由第二個網路——一位習得而來的評審——來評斷品質,這位評審只問一個問題:這看起來像真的嗎?模糊的影像最容易露餡,於是偽造者被推往清晰、果斷的影像,因為那才是能騙過評審的影像。

偽造者是一個叫做生成器的神經網路,偵探則是一個叫做判別器的神經網路。關鍵在於,它們是「一起、且對立地」被訓練的。生成器從不直接看到真實影像;它只從判別器對自己假貨的反應中學習。判別器則從真實照片與生成器最新偽造品的混合中學習。兩者的命運緊緊相連:任一方的進步,都讓另一方的工作更難——正是這股相互施壓的力量,把雙方一起拖向爐火純青。

生成是一條單行道:一顆隨機種子向前流動,化為一張全新的影像。

一張插圖,顯示一顆隨機潛在種子向前通過網路,被轉換成一張全新生成的影像。

生成器:把雜訊變成圖像

讓我們打開偽造者來看看。生成器有一份既奇特又美妙的工作:它必須從純粹的隨機中變出一整張影像。它的輸入不是照片、不是草圖、也不是標籤——而是一串從雜訊抽取出來的隨機數字。我們把這串數字稱為潛在向量,記作 z。生成器從這顆隨機的種子出發,逐層運算,把這寥寥幾個數字膨脹成由成千上萬乃至數百萬像素組成的完整圖像。

x_{\text{fake}} = G(z), \qquad z \sim \mathcal{N}(0,\, I)

雜訊進、影像出——這就是生成器的全部契約。

可以這樣讀它:要做出一張假影像,先抽一個隨機向量 z,再把它送進生成器 G。符號 z 就是潛在變數——那個隨機的雜訊向量,也就是我們的種子。記號 z ~ N(0, I) 表示 z 取自標準常態分布:它的每一個分量都是以 0 為中心、變異數為 1 的獨立隨機數,正是我們在 VAE 裡見過的那個先驗。粗體的 I 是單位共變異數矩陣,是「各分量彼此獨立、且每個變異數都為 1」的簡潔寫法。G 是生成器網路,而 x_fake = G(z) 就是它輸出的影像。最重要的一點是:z 是唯一的輸入。隨機進去、影像出來——生成器再無其他輸入,沒有要複製的目標,也沒有真實照片可倚靠。

這正是 GAN 的生成器與 VAE 的鮮明分野。VAE 有兩半:一個把真實影像壓縮成編碼的編碼器,以及一個從該編碼重建影像的解碼器。GAN 的生成器就像「只有解碼器」——從頭到尾都沒有編碼器。生成器只往一個方向走:雜訊 → 影像。它從不走影像 → 編碼,因為它根本不看真實影像。可以把它想成一位只憑想像作畫、從未對著實景臨摹過的藝術家:餵給他不同的白日夢(不同的 z 向量),他就畫出不同的作品;但他連原則上都無法拿一張真實照片去複製它——因為他沒有看真實世界的眼睛,只有一支畫筆。

因為 z 是隨機的,每抽一次都會得到不同的潛在向量,因而得到不同的影像。一個 z 也許解碼成一張戴眼鏡的臉,另一個則解碼成側臉。生成器的任務嚴苛到近乎殘酷:不只是它的「部分」輸出,而是「每一個」它可能拿到的合理 z,都必須解碼成看起來像真的東西。實際上,它是在定義一整套影像的生成模型——一道把雜訊那團簡單的鐘形雲,變成豐富而有結構之可信圖像世界的食譜。在內部,它的運作很像把先前章節的 CNN 倒著跑:不是把大圖池化成小摘要,而是用上採樣與(轉置)卷積,把一個小向量「長大」成一張大圖,並在每一層學會如何加上愈來愈精細的細節。

生成器這一半:低維的 z 逐層擴張,成為一張全解析度的影像。

生成器的示意圖:一個小的潛在向量通過一連串逐漸放大的上採樣與轉置卷積層,最後輸出一張完整影像。

判別器:受過訓練的懷疑者

現在來認識偵探。好消息是:你早就知道判別器到底是什麼了。它就是一個普通的二元影像分類器——和你在先前分類章節裡建過的那種 CNN 一模一樣,堆疊卷積與非線性,把一張影像濃縮成單一決定。唯一不尋常的,是它要把影像分成的那兩類:不是「貓對狗」或「數字 0 到 9」,而是單純的「真對假」。

D(x) \in [0,\,1]

一張影像進,一個機率出。

這式子是說:判別器 D 吃進任何一張影像 x,回傳一個介於 0 與 1 之間的數字。我們把這個數字解讀為機率:D(x) 表示判別器有多相信「x 是一張真實影像」。接近 1 表示「我很有把握這是真的」;接近 0 表示「我很有把握這是偽造的」。所以如果偵探稱職,它會對每一張真實照片 x 把 D(x) 推向 1,對生成器產生的每一張假貨把 D(G(z)) 推向 0。請注意 G(z) 就只是一張和其他影像沒兩樣的影像——判別器並沒有被告知哪些是假的,它得自己判斷。

判別器從什麼東西學習?一個一半是從資料集抽出的真實照片、一半是生成器「此時此刻」剛產生的假貨的訓練集。正是這後半,使它和你以前訓練過的任何分類器都不同:「假」這一類是一個移動的標靶。隨著生成器進步,上週那些一眼假的偽造品,會被本週那些細膩難辨的取代,於是判別器永遠無法安定下來——它必須不斷適應一個不斷變換偽裝的對手。

極小極大賽局:把這場較量寫成式子

該把這場較量精確寫下來了。GAN 是一個雙人賽局,而就像任何賽局,我們都能用一個共用的計分板——一個價值函數 V(D, G)——來描述它:一位玩家想把它推高,另一位想把它壓低。這種彼此對立的目標叫做極小極大賽局(minimax game):一方對「同一個量」做最大化,另一方做最小化。在把整個式子組裝起來之前,先一塊一塊地把計分板建出來。

判別器靠「答對」賺分。對一張真實影像 x,答對意味著輸出一個高的 D(x),所以我們用 log D(x) 來獎勵它:當 D(x) 接近 1 時,這一項很大(接近它的最大值 0),而當 D(x) 掉向 0 時,它會直墜向負無窮。對一張假影像 G(z),答對意味著輸出一個低的 D(G(z))——等價於一個高的 1 − D(G(z))——所以我們用 log(1 − D(G(z))) 來獎勵它。把第一份獎勵對所有真實影像取平均、把第二份對所有雜訊抽樣取平均、再相加,就得到判別器的總分,而這正是價值函數。

\min_{G}\ \max_{D}\ V(D,G) = \mathbb{E}_{x \sim p_{\text{data}}}\big[\log D(x)\big] + \mathbb{E}_{z \sim p_{z}}\big[\log\big(1 - D(G(z))\big)\big]

GAN 的價值函數:一項管真實影像,一項管假影像。

讓我們替每一塊命名。min_G max_D 是雙人拔河:判別器 D 挑選自己的權重去「最大化」V(盡可能分得準),而生成器 G 挑選自己的權重去「最小化」V(毀掉判別器的分數)。E_{x~p_data}[…] 這一項意指「對真實影像取平均」,其中 p_data 是資料集中照片的真實分布;在它內部,D 想要 log D(x) 大——它想把真的叫做真的。E_{z~p_z}[…] 這一項意指「對雜訊向量取平均」,其中 p_z 是我們抽 z 的先驗 N(0, I);在它內部,當 D(G(z)) 小、也就是當 D 正確地把假的叫做假的時,log(1 − D(G(z))) 就大。在這裡,兩位玩家對「同一項」往相反方向拉:D 想要 1 − D(G(z)) 大(揪出假貨),而 G 想要它小——G 想要 D(G(z)) 接近 1,讓假的被誤認為真的。這個被雙方爭奪的第二項,正是生成器判別器這場對決核心的對抗損失

想像兩個極端。訓練初期,生成器的假貨是垃圾;判別器輕易就能對真圖給 D(x) ≈ 1、對假圖給 D(G(z)) ≈ 0,於是兩項都坐在接近最大值的位置、V 很高——偵探正在贏。隨著生成器進步,D(G(z)) 緩緩爬向 1,把 log(1 − D(G(z))) 拖向負無窮、把 V 壓低——偽造者正在把分數搶回來。理想的終點是均衡:假貨在統計上變得與真貨無法區分,判別器除了猜以外別無辦法,於是對一切都輸出 D ≈ 0.5。到了那一刻,任一玩家單方面改變都無法再進步——偽造者贏了,而擲硬幣已是偵探所能做到的最好。

訓練這場對決:輪流出招

你到底要怎麼訓練兩個想要相反東西的網路?讓它們對練,而且嚴格輪流。你絕不同時更新兩者:先凍結一個,讓另一個對著它進步,然後交換。一次完整的迭代有兩步出招。

  1. 更新偵探。凍結生成器的權重。從資料集抽一批真實影像,並從隨機 z 生成一批假影像。把兩者都拿給判別器看,只更新它的權重,讓它對真圖提高 D、對假圖降低 D——把兩者分得更清楚。
  2. 更新偽造者。現在凍結判別器的權重。生成新的一批假影像,讓它們通過被凍結的判別器。只更新生成器的權重,讓判別器對這些假貨的判決朝「真的」上升(用非飽和的 −log D(G(z)))。生成器靠的是「借用」那些回流穿過被凍結偵探的梯度來學習。

難就難在「平衡」,而且非常微妙。兩位玩家必須齊步進步,像實力相當的對練夥伴。如果判別器太快變得太強,它會以十足的信心否決每一張假貨——D(G(z)) ≈ 0——回傳給生成器的梯度就消失了;偽造者得到的只是一個平板的「不行」,毫無如何改進的線索,學習就此停滯。反過來,如果生成器一路狂奔超前,判別器的回饋就變成雜訊,品質也隨之漂移。健康的訓練會讓這場較量維持勢均力敵。

# One training iteration of a GAN (minibatch SGD).
# G = generator, D = discriminator.
for each training step:

    # ---- Step 1: train the discriminator (G frozen) ----
    x_real = sample_batch(dataset)        # a batch of real images
    z      = sample_normal(batch, dim)    # noise z ~ N(0, I)
    x_fake = G(z).detach()                # fakes; detach so G is NOT updated here
    # D should output 1 on real images and 0 on fakes:
    loss_D = -(log(D(x_real)) + log(1 - D(x_fake))).mean()
    update(D, loss_D)                     # adjust D's weights only

    # ---- Step 2: train the generator (D frozen) ----
    z      = sample_normal(batch, dim)    # fresh noise
    x_fake = G(z)                         # do NOT detach: we want G's gradient
    # Non-saturating loss: push D toward calling the fake "real":
    loss_G = -log(D(x_fake)).mean()
    update(G, loss_G)                     # adjust G's weights only
一次迭代:先用當前假貨訓練 D,再用(此時凍結的)D 訓練 G。
每次更新都是一步梯度下降——只是這裡有兩個彼此對立的目標同時在下降。

一張梯度下降在損失曲面上沿坡下行的插圖,這裡用來描繪生成器與判別器交替更新的過程。

維持那份平衡,正是 GAN 之所以背負「最難訓練的模型之一」這個名聲的原因。當對決失衡得太厲害,訓練可能以各種壯觀又令人抓狂的方式崩潰——這正是下一篇導引的主題。眼下,先牢牢抓住這個核心畫面:兩個網路,輪流出招,把彼此一步步拖上坡。

DCGAN:用卷積讓 GAN 真正運作

極小極大賽局在理論上很美,但最早把它用在影像上的嘗試極不穩定。讓影像 GAN 變得可靠、能穩定訓練的突破,是一種叫做 DCGAN——深度卷積生成對抗網路(Deep Convolutional GAN)——的架構。與其說它是新點子,不如說是一份得來不易的食譜:一組具體的架構規則,合在一起就能讓這場對決穩定到足以真正收斂。請把它當成實務工程智慧,每條規則都是反覆試錯換來的。

  1. 用帶步幅的卷積取代池化。在判別器裡捨棄固定的最大池化(降採樣)、在生成器裡捨棄固定的上採樣;改用帶步幅卷積與轉置卷積。為什麼:這讓每個網路自己學會如何上採樣與降採樣,而不是把它寫死,於是它能以對任務有利的方式,自行決定怎麼縮小或放大空間尺寸。
  2. 在兩個網路裡都用批次正規化(batch normalization)。在訓練時把每一層的激活值正規化到一個穩定的尺度。為什麼:GAN 的損失會隨對手變動而劇烈擺盪,而批次正規化能避免激活值爆炸或塌縮,平滑梯度的流動,讓整場對決發散的機率大為降低。
  3. 拿掉全連接的隱藏層。從頭到尾改成全卷積。為什麼:卷積會保留空間結構,且參數遠少於密集層,這既能穩定訓練,又能讓模型專注於局部影像樣式,而不是去硬背一串攤平的向量。
  4. 刻意挑選激活函數。生成器內部用 ReLU、判別器內部用 LeakyReLU、生成器的最終輸出用 tanh。為什麼:LeakyReLU 即使對負輸入也讓一點點梯度通過,於是判別器永遠不會把訊號完全餓死給生成器;tanh 把輸出映射到 [−1, 1],以對應被縮放到該範圍的影像,給出清晰且界限分明的像素。

退一步看,形狀就清楚了:DCGAN 的生成器本質上就是一個倒著跑的分類 CNN。一個正常的分類器從一張大圖出發,透過帶步幅卷積,一步步把它縮成一疊小而深的特徵,最後收束成一個類別。生成器則是它的鏡像:它從一個由 z 重塑而來的小而深的方塊出發,透過轉置卷積,一步步把它「長大」成一張大而淺的影像。判別器就只是普通分類器的方向。而你在訓練深層分類器時見過的批次正規化,正是那層黏著劑,讓這兩疊高高的層在彼此纏鬥時保持數值上的健康。

判別器是你熟悉的 CNN 分類器;生成器則是同一個概念的鏡像。

一張卷積神經網路示意圖,堆疊的卷積層把影像縮成一個決定,用以說明判別器,而其鏡像即為生成器。

批次正規化把激活值維持在穩定的尺度——這正是 DCGAN 所倚賴的穩定器。

一張批次正規化的示意圖,在把一層的激活值傳遞下去之前,先把它們重新縮放成零均值、單位變異數。

DCGAN 做的不只是穩定訓練——它還揭示了潛在空間已經學到了真實的結構。研究者發現,你可以直接在 z 向量上做算術。拿「戴眼鏡男性」的平均潛在向量,減去「沒戴眼鏡男性」的平均,再加上「沒戴眼鏡女性」的平均,然後把結果解碼——出來的就是一位戴著眼鏡的女性。那組著名的「微笑女性 − 中性女性 + 中性男性 = 微笑男性」示範,說明了 z 中的「方向」對應的是有意義、且平滑變化的概念,而非隨機雜訊。這種潛在結構正是整個章節一再繞回的主題,也正是最後一篇導引將要善加利用的東西——好讓我們能對生成器畫出什麼,進行刻意而細緻的控制。