Key Findings
  • 本文以直覺比喻 + 完整程式碼的方式,從零實作一個 MNIST 手寫數字擴散模型——從前向加噪到反向去噪,9 個步驟全部走過一遍
  • 核心架構僅 2.17M 參數的輕量 U-Net,在 RTX 4090 上訓練 3.4 分鐘即可從純噪聲生成清晰的手寫數字
  • 完整實作條件生成Classifier-Free Guidance(CFG)[2]——輸入數字標籤即可指定生成 0~9,並比較不同 CFG 強度的效果
  • 附可下載 Jupyter Notebook,支援 Google Colab 一鍵運行,無需本地 GPU 即可完成全部實驗

📥 下載 Notebook 開始實作

完整程式碼 + 視覺化輸出,可在 Jupyter 或 Google Colab 直接運行

下載 .ipynb 檔案 在 Google Colab 開啟

擴散模型的大比喻:修復照片的專家

在深入程式碼之前,先用一個簡單的比喻理解擴散模型的完整邏輯:

想像你要訓練一個照片修復師

  1. 拿一張清晰照片 → 灑沙子蓋住它(加噪聲)
  2. 讓修復師看被沙子蓋住的照片,學會「掃沙子」(去噪)
  3. 學會後,隨便給一堆沙子(純噪聲),修復師也能「掃」出一張真實的照片

這就是擴散模型的核心[1]!接下來我們用 9 個步驟完整實現它。

Step 1:環境設定 & 載入 MNIST

1.1 匯入套件 & 偵測 GPU

第一步是確認工作環境——把所有工具擺到桌上(import),然後確認工作台是「一般桌子(CPU)」還是「超級快的電動工作台(GPU)」。

import math, torch, torchvision
import matplotlib.pyplot as plt
from torchvision import transforms
from torch.utils.data import DataLoader
from torch.optim import Adam
import torch.nn.functional as F
from torch import nn
from tqdm import tqdm

device = 'cuda' if torch.cuda.is_available() else 'cpu'

1.2 超參數設定

決定照片大小、每次看幾張、灑沙子分幾層、練習幾輪。這些是訓練的「計畫表」:

參數意義
img_size28MNIST 圖片大小 28×28 像素
batch_size128每次練習看 128 張圖
num_timesteps1000灑沙子分 1000 層(T=1000)
epochs10練習 10 輪
lr0.001學習速度

1.3 載入 MNIST 資料集

去圖書館搬 60,000 張手寫數字照片回來,裝進 469 個箱子(batch),每箱 128 張。

MNIST 手寫數字原始圖片

圖 1 — MNIST 手寫數字原始圖片,黑底白字,每張 28×28 像素

Step 2:理解前向擴散過程(加噪聲)

前向擴散的直覺:拿一張清晰照片,第 1 次灑一點沙(幾乎看不出來),第 100 次灑更多,第 1000 次灑到完全看不到原圖。數學上:

xt = √ᾱt · x0 + √(1−ᾱt) · ε
t = 原圖保留比例 | ε = 隨機噪聲

2.1 ᾱt 衰減曲線 —「灑沙子計畫表」

想像你拿一杯清水(原圖),每一步加一滴墨水(噪聲):

  • t=1(最左邊): 剛加一滴,水還是透明的(ᾱ=0.9999,幾乎原圖)
  • t=500(中間): 加了 500 滴,水變灰了(ᾱ=0.08,只剩 8% 原圖)
  • t=1000(最右邊): 全黑了(ᾱ≈0,完全是噪聲)
alpha_bar 衰減曲線

圖 2 — ᾱt 隨時間遞減曲線,從幾乎保留原圖到完全變成噪聲

2.2 加噪示範

擴散模型要學的事就是:反向走這條路!從最右邊的雜訊,一步步還原出最左邊的清晰數字。

前向擴散過程

圖 3 — 前向擴散過程:原圖逐漸變成純噪聲

Step 3:位置編碼(Positional Encoding)

修復師在掃沙子時,需要知道「現在是第幾層」。剛開始(t=1000)沙子最多,需要大力掃;快結束(t=1)沙子很少,只需輕輕拂。位置編碼用 sin/cos 波形[4],讓每個時間步 t 都有自己獨特的「指紋」:

  • 低維度(左邊)變化快 → 區分相鄰的 t(如 100 和 101)
  • 高維度(右邊)變化慢 → 區分差距大的 t(如 1 和 1000)
位置編碼

圖 4 — 不同時間步的正弦位置編碼,每個 t 都有獨特的「指紋」

Step 4:U-Net 模型架構

U-Net[3] 是修復師的「大腦」——結構像一個 U 形漏斗:左半邊壓縮圖片(從遠到近觀察),底部是核心理解,右半邊再放大回原尺寸。Skip connections 像是「備忘錄」,確保放大時不會丟失細節。

輸入(1,28,28) → [Down1: 64ch] → MaxPool → [Down2: 128ch] → MaxPool
                     ↓ skip                      ↓ skip
                                          [Bot: 256ch]
                     ↑ skip                      ↑ skip
輸出(1,28,28) ← [Up1: 64ch]  ← Upsample ← [Up2: 128ch] ← Upsample

關鍵設計:

  • Skip connections:encoder 直接連到 decoder,保留細節(像備忘錄)
  • 時間步嵌入:每個 ConvBlock 都接收時間步資訊,知道「現在該掃多少沙子」
  • 輸入:一張有噪聲的圖 + 「現在是第幾步」
  • 輸出:模型猜測的噪聲 —「我猜你加了這些沙子」

修復師的大腦有 2,167,784 個參數(2.17M),但現在還是空白的。接下來要「上課」教他。

Step 5:Diffuser 類別

Diffuser 是修復師的「工作手冊」,裡面寫了三個標準流程:

  • add_noise — 如何灑沙子(前向過程)
  • denoise — 如何掃一層沙子(反向一步)
  • sample — 完整的從頭到尾掃 1000 層沙子(生成圖片)

Step 6:訓練擴散模型

訓練過程就像上課考試[1],反覆做同一件事:

  1. 老師拿一張清晰照片(x₀)
  2. 隨機灑某個量的沙子上去(得到 xt
  3. 學生看被沙子蓋住的照片,猜:「我覺得你灑了這些沙子」
  4. 老師對答案:「你猜的沙子 vs 實際灑的沙子,差了多少?」(MSE Loss)
  5. 學生根據差距調整自己的猜測能力

重複 10 輪(epoch),每輪看完 60,000 張照片。練習越多,猜得越準!

6.1 訓練結果(修復師的成績單)

輪次Loss(越低越好)比喻
Epoch 10.046950剛上課,猜得很差
Epoch 20.027240開始有感覺了
Epoch 50.021274越來越熟練
Epoch 80.019269快畢業了
Epoch 100.018689畢業!猜噪聲很準

在 RTX 4090 上,整個訓練只花了 203.2 秒(3.4 分鐘)。在 Google Colab 的免費 T4 GPU 上大約需要 10-15 分鐘。

訓練損失曲線

圖 5 — 訓練損失曲線,Loss 穩定下降代表模型逐漸學會預測噪聲

Step 7:生成圖片!

畢業考試!給修復師一堆純沙子(純噪聲),看他能不能從零「掃」出一張真實的手寫數字:

  1. 從純噪聲 xT ~ N(0, I) 開始 — 一堆隨機的沙子
  2. 用模型預測噪聲,去除一點點
  3. 重複 1000 步
  4. 最終得到一張清晰的手寫數字圖片!

20 張手寫數字成功從純噪聲中生成,全部沒有參考任何原圖,耗時僅 6 秒:

生成的手寫數字

圖 6 — 擴散模型生成的手寫數字(全部從純噪聲生成!)

Step 8:觀察去噪過程

用慢鏡頭拍下修復師掃沙子的過程,從純沙子到最終清晰的數字:

  • t=1000(最左邊):純沙子,什麼都看不到
  • t=900~700:開始隱約出現形狀,像在霧裡看東西
  • t=600~400:形狀越來越清楚,可以看出是某個數字了
  • t=300~100:數字已經很明顯
  • t=50~0(最右邊):完全清晰的手寫數字
去噪過程

圖 7 — 反向去噪過程:從純噪聲一步步還原出清晰的手寫數字

Step 9:條件生成 — 指定要生成哪個數字!

之前的修復師只會隨機掃出數字,無法指定。現在訓練升級版——你跟他說「我要 7」,他就掃出 7。做法是在灑沙子時附一張小紙條寫「這是數字 7」:

  • 把數字標籤(0~9)用 nn.Embedding 轉成向量,和時間步向量相加後餵給 U-Net
  • 訓練時有 10% 的機率隨機丟棄標籤(CFG 技巧[2])——讓修復師同時學會「有提示」和「沒提示」兩種情況
  • 比無條件模型只多了 1,100 個參數(class_embed),非常輕量
條件生成 0~9

圖 8 — 條件生成:指定生成 0~9(CFG scale=3.0)

9.1 Classifier-Free Guidance(CFG)強度比較

CFG scale 就像點菜的「語氣」——越大聲,修復師就越聽話[2]

CFG Scale比喻效果
scale = 1.0輕聲說「請給我 3」大概是 3,但偶爾跑偏——多樣性高,不太穩定
scale = 3.0正常說「我要 3」穩穩的 3,風格有變化——品質和多樣性的最佳平衡
scale = 7.0大聲喊「一定要 3!」非常清晰的 3,但每張都很像——品質最高但多樣性最低
CFG 強度比較

圖 9 — CFG 強度比較:scale 越大越「聽話」,但多樣性越低

總結:9 步走完擴散模型全流程

步驟做了什麼比喻
Step 1環境設定 & 載入 MNIST開店準備、搬教材
Step 2前向擴散(加噪聲)灑沙子蓋住照片
Step 3位置編碼每一步的「身份證」
Step 4建 U-Net 模型修復師的大腦
Step 5建 Diffuser 類別修復師的工作手冊
Step 6訓練模型上課:猜沙子,對答案
Step 7生成圖片畢業考:從純沙子掃出數字
Step 8觀察去噪過程慢動作回放掃沙子
Step 9條件生成 + CFG升級版:可以點菜了!

🚀 立即開始實作

下載 Notebook,在 Jupyter 或 Google Colab 中跑一遍,親手體驗擴散模型從噪聲中生成圖像的魔法。

下載 .ipynb 檔案 在 Google Colab 開啟

想深入了解擴散模型的數學原理、架構演進與 Stable Diffusion 實戰?請閱讀我們的擴散模型深度解析