Key Findings
  • 擴散模型(Diffusion Models)在 2021 年首次於 FID 指標上全面超越 GAN[7],成為圖像生成的新王者——而 Stable Diffusion 的開源更引爆了生成式 AI 的產業革命[8]
  • 核心原理極其優雅:前向過程逐步加噪將圖片變為純噪聲,逆向過程學習去噪——訓練目標僅是「預測噪聲」,卻能生成高品質圖像[1]
  • 擴散框架不限於圖像——離散擴散模型(D3PM)[14]與 Diffusion-LM[15] 已將此範式擴展至文字生成,實現可控的語言生成
  • 本文附兩個 Google Colab 實作:離散擴散 × BERT 文字生成(CPU)、Stable Diffusion × 文字到圖像生成與去噪過程視覺化(GPU)

一、為什麼擴散模型改變了生成式 AI 的遊戲規則

在 2020 年之前,圖像生成的王者是 GAN(Generative Adversarial Networks)。GAN 能產生驚人的逼真圖像,但它有三個致命缺陷:訓練極度不穩定(模式崩潰)、多樣性不足(容易只學會幾種「套路」)、無法計算精確的對數似然。

2020 年,Ho 等人在 NeurIPS 發表了 DDPM(Denoising Diffusion Probabilistic Models)[1],將 Sohl-Dickstein 等人 2015 年提出的非平衡熱力學啟發框架[2]重新包裝為一個簡潔有效的訓練方案。結果令整個社群震動:擴散模型在圖像品質上逼近 GAN,同時擁有穩定的訓練過程和完整的對數似然框架。

一年後,Dhariwal 與 Nichol[7]用實驗證明了擴散模型在 FID 指標上全面超越 GAN——在 ImageNet 256×256 上達到 FID 3.94,而當時最好的 BigGAN-deep 是 6.95。這篇論文的標題直接宣告了時代交替:「Diffusion Models Beat GANs on Image Synthesis」。

此後擴散模型的發展呈爆炸式增長:2022 年 Stability AI 開源了 Stable Diffusion[8],讓任何人都能在消費級 GPU 上生成高品質圖像;OpenAI 推出 DALL·E 2[10],Google 發表 Imagen[11];2023 年 SDXL[12] 將解析度和美學品質推向新高;而 DiT[13](Diffusion Transformer)則預示了用 Transformer 取代 U-Net 的架構演進方向,催生了 Sora、FLUX 等新一代模型。

但擴散模型的影響力不止於圖像。離散擴散模型[14]將這個框架帶入了文字生成、蛋白質設計、分子生成等離散資料領域。理解擴散模型的原理,等於拿到了打開整個生成式 AI 大門的鑰匙。

二、數學直覺:前向加噪與逆向去噪

擴散模型的核心思想可以用一句話概括:學會如何從噪聲中恢復資料,就等於學會了如何生成資料。

2.1 前向過程(Forward Process)

給定一張真實圖片 x₀,前向過程在 T 個時間步中逐步添加高斯噪聲:

q(xₜ | xₜ₋₁) = N(xₜ; √(1-βₜ) · xₜ₋₁, βₜI)

其中 βₜ 是噪聲排程(noise schedule),通常從很小的值(如 β₁ = 0.0001)線性或餘弦增長到較大的值(如 β_T = 0.02)。當 T 足夠大(通常 T = 1000),最終的 x_T 幾乎是純高斯噪聲。

DDPM 的一個關鍵技巧是:不需要逐步加噪,可以直接從 x₀ 跳到任意時間步 t 的噪聲版本:

q(xₜ | x₀) = N(xₜ; √ᾱₜ · x₀, (1-ᾱₜ)I)

其中 ᾱₜ = ∏ᵢ₌₁ᵗ (1-βᵢ)。這讓訓練時可以隨機取樣任意時間步,大幅提升效率。

2.2 逆向過程(Reverse Process)

逆向過程嘗試「撤銷」加噪——從 x_T(純噪聲)逐步去噪回 x₀(清晰圖像)。這個過程由神經網路 ε_θ 參數化:

p_θ(xₜ₋₁ | xₜ) = N(xₜ₋₁; μ_θ(xₜ, t), σₜ²I)

其中均值 μ_θ 由模型預測的噪聲 ε_θ(xₜ, t) 計算得出。

2.3 訓練目標:預測噪聲

Ho 等人[1]的關鍵貢獻是證明了一個驚人簡潔的訓練目標就足夠:

L_simple = E[‖ε - ε_θ(xₜ, t)‖²]

翻譯成白話:隨機取一張圖、隨機加某個程度的噪聲、讓模型預測加了多少噪聲——就這樣。這個簡化版本丟棄了 ELBO 推導中的權重項,但在實務中生成品質反而更好。

Song 等人[4]後來在 ICLR 2021 將 DDPM 和 score-based 模型[3]統一在 SDE(隨機微分方程)的框架下,證明兩者只是同一個連續過程的不同離散化方式。這個統一觀點為後續的取樣加速方法奠定了理論基礎。

三、里程碑演進:從 DDPM 到 Stable Diffusion 到 DiT

年份里程碑核心貢獻影響
2015Sohl-Dickstein et al.[2]非平衡熱力學啟發的擴散框架理論奠基
2019Score Matching[3]Score-based 生成模型替代理論視角
2020DDPM[1]簡潔的噪聲預測訓練目標擴散模型的「GPT 時刻」
2021DDIM[5]非馬可夫取樣,1000 步→50 步首次實用化加速
2021Improved DDPM[6]餘弦噪聲排程、可學習變異數品質與對數似然改進
2021Diffusion Beats GANs[7]Classifier guidance + 架構優化FID 首次超越 GAN
2021Score SDE[4]統一 DDPM 與 score-based 模型理論框架統一
2022LDM / Stable Diffusion[8]在潛空間做擴散,效率提升數十倍開源引爆產業革命
2022DALL·E 2[10]CLIP 引導 + 級聯式擴散文字到圖像的商業化
2022Imagen[11]T5-XXL 文字編碼器 + 級聯式超解析度證明語言模型理解力的重要性
2022CFG[17]無分類器引導,統一條件/無條件生成成為條件生成標準方法
2023SDXL[12]更大 U-Net + 雙文字編碼器美學品質飛躍
2023DiT[13]Transformer 取代 U-Net開啟 Sora / FLUX 時代
2023LCM[16]一致性蒸餾,2-4 步生成推論效率突破

四、圖像擴散模型的核心架構

4.1 U-Net:從像素空間到潛空間

DDPM 的原始架構在像素空間直接操作——對於 256×256 的圖片,每一步去噪都要處理 256×256×3 = 196,608 維的資料。這導致訓練和推論都非常昂貴。

Rombach 等人[8]提出的 Latent Diffusion Model(LDM)解決了這個問題:先用一個預訓練的 VAE 將圖片壓縮到低維潛空間(latent space),再在潛空間中做擴散。512×512 的圖片被壓縮為 64×64×4 的潛表示——維度降低了 48 倍。擴散模型只需要學習在這個壓縮空間中去噪,計算量和記憶體需求大幅下降。

Stable Diffusion 就是 LDM 的開源實現,其架構由三個核心元件組成:

4.2 Classifier-Free Guidance:控制生成品質

Ho 與 Salimans[17]提出的 Classifier-Free Guidance(CFG)是擴散模型條件生成的標準方法。核心思想:同時訓練條件生成和無條件生成(隨機丟棄文字條件),推論時用兩者的差異來「放大」文字條件的影響:

ε̃ = ε_unconditional + w · (ε_conditional - ε_unconditional)

引導尺度 w(guidance scale)控制文字對圖像的影響力度:w = 1 等於無引導,w = 7-12 是常用範圍,w 越大圖像越符合文字描述但多樣性降低。

4.3 DiT:Transformer 接管擴散模型

Peebles 與 Xie[13]DiT(Diffusion Transformer)用標準 Vision Transformer 取代了 U-Net 作為去噪網路。他們將潛空間的 patch 序列化,用 Transformer 處理,並通過 adaLN-Zero(adaptive layer normalization)注入時間步和條件資訊。

DiT 的重要性在於:Transformer 的 scaling law 比 U-Net 更清晰——模型越大、訓練越久、品質越好。這個發現直接催生了 OpenAI 的 Sora(影片生成)和 Black Forest Labs 的 FLUX 等新一代大規模擴散模型。

五、文字與擴散的交匯:從 CLIP 引導到離散文字擴散

擴散模型與文字 AI 的關係有兩個面向:一是文字作為條件引導圖像生成;二是擴散直接應用於文字本身。

5.1 文字條件:CLIP 如何讓擴散模型「理解」語言

Stable Diffusion 使用 CLIP[9](Contrastive Language-Image Pretraining)作為文字編碼器。CLIP 在 4 億個圖文配對上以對比學習訓練,學會了將文字和圖像映射到共享的表徵空間——語義相近的文字和圖像在這個空間中距離很近。

在 Stable Diffusion 中,CLIP 的文字編碼器將提示詞轉換為一系列 token 向量,這些向量通過 U-Net 的交叉注意力層(cross-attention)注入去噪過程。U-Net 的每個注意力層都在「查詢」文字條件:「現在這個位置應該畫什麼?」——文字向量提供答案。

Google 的 Imagen[11]則選擇了不同路線:使用凍結的 T5-XXL(一個純文字的大型語言模型)作為文字編碼器。Imagen 的實驗結果揭示了一個重要洞見:文字編碼器的語言理解能力比擴散模型本身的大小更重要。更大更強的文字編碼器帶來的品質提升,遠超增大 U-Net 的效果。

5.2 離散擴散:將擴散範式帶入文字生成

標準擴散模型在連續空間中操作(像素值是連續的),但文字是離散的(由 token ID 組成)。Austin 等人[14]提出的 D3PM(Discrete Denoising Diffusion Probabilistic Models)將擴散框架推廣到離散狀態空間。

D3PM 的前向過程不是加高斯噪聲,而是逐步「腐蝕」token——將 token 隨機替換為 [MASK] 或其他 token。其中最直覺的變體是吸收態擴散(Absorbing State Diffusion):每個時間步有一定機率將 token 變為 [MASK],最終所有 token 都被 mask。逆向過程則從全 [MASK] 的序列出發,逐步「填回」正確的 token。

Li 等人[15]Diffusion-LM 走了另一條路:在連續的 token embedding 空間中做擴散,然後用 rounding 步驟將連續向量映射回離散 token。Diffusion-LM 的獨特優勢是可控性——可以通過梯度引導讓生成的文字滿足特定約束(如句法結構、語義屬性),這是自回歸語言模型難以做到的。

值得注意的是,BERT 的遮罩語言模型(Masked Language Modeling)本質上就是離散擴散的單步去噪器——給定一個部分被 mask 的序列,預測 mask 位置的 token。將 BERT 嵌入迭代式的擴散取樣框架,就得到了一個實用的離散文字擴散系統。

六、Hands-on Lab 1:離散擴散 × BERT 文字生成(Google Colab)

這個 Lab 展示如何用 BERT 作為去噪器,實現離散擴散文字生成——從全 [MASK] 的序列出發,逐步「去噪」為連貫的文字。這是 D3PM[14] 吸收態擴散的簡化實作。

打開 Google Colab(CPU 即可運行),新建 Notebook,依序貼入以下程式碼:

6.1 環境安裝

# ★ 安裝所需套件 ★
!pip install transformers torch -q

6.2 載入 BERT 並理解離散擴散

import torch
import torch.nn.functional as F
from transformers import BertTokenizer, BertForMaskedLM
import numpy as np

# ★ 載入預訓練 BERT(遮罩語言模型 = 離散擴散的單步去噪器)★
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertForMaskedLM.from_pretrained('bert-base-uncased').eval()

MASK_ID = tokenizer.mask_token_id
CLS_ID = tokenizer.cls_token_id
SEP_ID = tokenizer.sep_token_id

print(f"BERT vocab size: {tokenizer.vocab_size}")
print(f"[MASK] token ID: {MASK_ID}")
print(f"Model parameters: {sum(p.numel() for p in model.parameters())/1e6:.0f}M")

6.3 前向過程:逐步腐蝕文字

# ★ 前向擴散過程:逐步將 token 替換為 [MASK] ★
def forward_diffusion(text, num_steps=10):
    """
    模擬吸收態離散擴散的前向過程。
    每一步以遞增的機率將 token 替換為 [MASK]。
    """
    tokens = tokenizer.encode(text, return_tensors='pt')[0]  # [seq_len]
    seq_len = len(tokens) - 2  # 扣除 [CLS] 和 [SEP]
    content_tokens = tokens[1:-1].clone()

    print(f"原始文字: {text}")
    print(f"Token 數量: {seq_len}")
    print("-" * 60)

    for step in range(num_steps + 1):
        mask_prob = step / num_steps
        mask = torch.rand(seq_len) < mask_prob
        noisy = content_tokens.clone()
        noisy[mask] = MASK_ID

        decoded = tokenizer.decode(noisy)
        n_masked = mask.sum().item()
        print(f"t={step:2d} (mask {mask_prob:.0%}, {n_masked:2d}/{seq_len} masked): {decoded}")

    return tokens

# ★ 展示前向過程 ★
print("【前向擴散過程:文字 → 噪聲】")
print("=" * 60)
forward_diffusion("The quick brown fox jumps over the lazy dog", num_steps=8)

6.4 逆向過程:從噪聲生成文字

# ★ 逆向擴散取樣:從全 [MASK] 逐步去噪為文字 ★
@torch.no_grad()
def reverse_diffusion_step(token_ids, temperature=1.0):
    """
    一步逆向擴散:用 BERT 預測所有 [MASK] 位置,
    選擇最有信心的位置進行 unmask。
    """
    outputs = model(token_ids.unsqueeze(0))
    logits = outputs.logits[0]  # [seq_len, vocab_size]

    # 找到所有 [MASK] 位置
    mask_positions = (token_ids == MASK_ID).nonzero(as_tuple=True)[0]
    if len(mask_positions) == 0:
        return token_ids, 0

    # 計算每個 [MASK] 位置的預測信心度
    probs = F.softmax(logits[mask_positions] / temperature, dim=-1)
    max_probs, predictions = probs.max(dim=-1)

    return token_ids, mask_positions, max_probs, predictions

def generate_text(seq_length=12, num_steps=15, temperature=1.0, top_k=0):
    """
    完整的離散擴散取樣:從全 [MASK] 序列逐步去噪。
    每步 unmask 最有信心的若干位置。
    """
    # 初始狀態:[CLS] + 全 [MASK] + [SEP]
    ids = torch.full((seq_length + 2,), MASK_ID, dtype=torch.long)
    ids[0] = CLS_ID
    ids[-1] = SEP_ID

    print(f"\n【逆向擴散過程:噪聲 → 文字】(length={seq_length}, steps={num_steps})")
    print("=" * 60)

    for step in range(num_steps):
        # 計算每個 [MASK] 的預測
        with torch.no_grad():
            logits = model(ids.unsqueeze(0)).logits[0]

        mask_positions = (ids == MASK_ID).nonzero(as_tuple=True)[0]
        # 排除 [CLS] 和 [SEP]
        mask_positions = mask_positions[(mask_positions > 0) & (mask_positions < len(ids)-1)]

        if len(mask_positions) == 0:
            break

        # 預測每個 mask 位置
        probs = F.softmax(logits[mask_positions] / temperature, dim=-1)

        if top_k > 0:
            # Top-k 取樣
            topk_probs, topk_indices = probs.topk(top_k, dim=-1)
            topk_probs = topk_probs / topk_probs.sum(dim=-1, keepdim=True)
            sampled = torch.multinomial(topk_probs, 1).squeeze(-1)
            predictions = topk_indices.gather(1, sampled.unsqueeze(-1)).squeeze(-1)
            confidence = topk_probs.gather(1, sampled.unsqueeze(-1)).squeeze(-1)
        else:
            # 貪婪選擇(最大機率)
            confidence, predictions = probs.max(dim=-1)

        # 每步 unmask 的數量:線性遞減
        total_masks = len(mask_positions)
        n_unmask = max(1, int(total_masks * (1.0 / (num_steps - step))))

        # 選信心最高的位置 unmask
        _, top_indices = confidence.topk(min(n_unmask, len(mask_positions)))
        chosen_positions = mask_positions[top_indices]
        chosen_tokens = predictions[top_indices]

        ids[chosen_positions] = chosen_tokens

        # 顯示當前狀態
        text = tokenizer.decode(ids[1:-1])
        remaining = (ids == MASK_ID).sum().item()
        print(f"Step {step+1:2d} ({remaining:2d} masks left): {text}")

    final_text = tokenizer.decode(ids[1:-1])
    print(f"\n✓ Final: {final_text}")
    return final_text

# ★ 貪婪生成(確定性)★
generate_text(seq_length=10, num_steps=12, temperature=1.0)

6.5 Top-k 取樣與溫度控制

# ★ Top-k 取樣(增加多樣性)★
print("\n" + "=" * 60)
print("Top-k 取樣(k=5, temperature=1.2)— 多次生成不同結果")
print("=" * 60)

for i in range(4):
    result = generate_text(seq_length=10, num_steps=12, temperature=1.2, top_k=5)
    print()

# ★ 觀察重點 ★
# 1. 貪婪模式每次結果相同,top-k 取樣每次不同
# 2. 溫度越高,生成越多樣但可能越不連貫
# 3. 注意 BERT 傾向生成常見的短語模式

6.6 條件生成:文字填空與續寫

# ★ 條件生成:給定部分文字,讓擴散填充其餘 ★
def conditional_generate(template, num_steps=12, temperature=1.0, top_k=0):
    """
    條件離散擴散:保留已知 token,只對 [MASK] 位置做去噪。
    類似 Diffusion-LM 的 infilling 能力。
    """
    ids = tokenizer.encode(template, return_tensors='pt')[0]

    print(f"\n【條件生成】模板: {template}")
    print("=" * 60)

    for step in range(num_steps):
        with torch.no_grad():
            logits = model(ids.unsqueeze(0)).logits[0]

        mask_positions = (ids == MASK_ID).nonzero(as_tuple=True)[0]
        if len(mask_positions) == 0:
            break

        probs = F.softmax(logits[mask_positions] / temperature, dim=-1)

        if top_k > 0:
            topk_probs, topk_indices = probs.topk(top_k, dim=-1)
            topk_probs = topk_probs / topk_probs.sum(dim=-1, keepdim=True)
            sampled = torch.multinomial(topk_probs, 1).squeeze(-1)
            predictions = topk_indices.gather(1, sampled.unsqueeze(-1)).squeeze(-1)
            confidence = topk_probs.gather(1, sampled.unsqueeze(-1)).squeeze(-1)
        else:
            confidence, predictions = probs.max(dim=-1)

        total_masks = len(mask_positions)
        n_unmask = max(1, int(total_masks / max(1, num_steps - step)))

        _, top_idx = confidence.topk(min(n_unmask, len(mask_positions)))
        ids[mask_positions[top_idx]] = predictions[top_idx]

        text = tokenizer.decode(ids[1:-1])
        remaining = (ids == MASK_ID).sum().item()
        print(f"Step {step+1:2d} ({remaining:2d} masks): {text}")

    final = tokenizer.decode(ids[1:-1])
    print(f"✓ Result: {final}")
    return final

# ★ 範例 1:句子填空 ★
conditional_generate(
    "The [MASK] [MASK] [MASK] is the most [MASK] [MASK] in the world.",
    num_steps=8
)

# ★ 範例 2:主題引導 ★
conditional_generate(
    "In machine learning, [MASK] [MASK] [MASK] [MASK] are used to [MASK] [MASK] [MASK].",
    num_steps=8
)

# ★ 範例 3:情感引導 ★
conditional_generate(
    "I absolutely love [MASK] [MASK] [MASK] because [MASK] [MASK] [MASK] [MASK].",
    num_steps=8, temperature=1.1, top_k=5
)

# ★ 核心觀察 ★
# 離散擴散 vs 自回歸(GPT):
# - 自回歸:從左到右逐 token 生成,無法回頭修正
# - 離散擴散:全局同時考慮所有位置,先填高信心位置再填低信心位置
# - 擴散的優勢:天然支持 infilling、可控生成、全局一致性

七、Hands-on Lab 2:Stable Diffusion × 文字到圖像生成(Google Colab)

這個 Lab 使用 HuggingFace Diffusers 載入 Stable Diffusion,展示文字到圖像生成、引導尺度控制,以及擴散去噪過程的逐步視覺化。

打開 Google Colab,確保已切換至 GPU 執行環境(Runtime → Change runtime type → T4 GPU),新建 Notebook,依序貼入以下程式碼:

7.1 環境安裝

# ★ 安裝 Diffusers 生態系統 ★
!pip install diffusers transformers accelerate safetensors -q

7.2 載入 Stable Diffusion Pipeline

import torch
from diffusers import StableDiffusionPipeline
import matplotlib.pyplot as plt
import numpy as np

# ★ 載入 Stable Diffusion 1.5(float16,約 3.5GB VRAM)★
pipe = StableDiffusionPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    torch_dtype=torch.float16,
    safety_checker=None  # 加速用;正式部署請啟用
).to("cuda")

# 啟用記憶體優化
pipe.enable_attention_slicing()

print(f"Pipeline loaded on: {pipe.device}")
print(f"U-Net parameters: {sum(p.numel() for p in pipe.unet.parameters())/1e6:.0f}M")
print(f"Text encoder: {sum(p.numel() for p in pipe.text_encoder.parameters())/1e6:.0f}M")
print(f"VAE: {sum(p.numel() for p in pipe.vae.parameters())/1e6:.0f}M")

7.3 基本文字到圖像生成

# ★ 文字到圖像生成 ★
prompt = "A serene Japanese garden with cherry blossoms, koi pond, wooden bridge, soft morning light, photorealistic"
negative_prompt = "blurry, low quality, distorted, deformed"

image = pipe(
    prompt=prompt,
    negative_prompt=negative_prompt,
    num_inference_steps=30,
    guidance_scale=7.5,
    width=512,
    height=512,
    generator=torch.Generator("cuda").manual_seed(42)
).images[0]

plt.figure(figsize=(8, 8))
plt.imshow(image)
plt.title("Stable Diffusion 1.5 — Text-to-Image", fontsize=14)
plt.axis("off")
plt.show()

7.4 Guidance Scale 效果比較

# ★ Classifier-Free Guidance Scale 的影響 ★
# w=1(無引導)→ w=7.5(標準)→ w=15(強引導)→ w=25(過度引導)
guidance_scales = [1.0, 5.0, 7.5, 15.0]
prompt = "A futuristic cyberpunk city at night, neon lights, rain reflections"
seed = 123

fig, axes = plt.subplots(1, 4, figsize=(24, 6))

for i, scale in enumerate(guidance_scales):
    img = pipe(
        prompt=prompt,
        num_inference_steps=25,
        guidance_scale=scale,
        width=512, height=512,
        generator=torch.Generator("cuda").manual_seed(seed)
    ).images[0]

    axes[i].imshow(img)
    axes[i].set_title(f"guidance_scale = {scale}", fontsize=13)
    axes[i].axis("off")

plt.suptitle("Classifier-Free Guidance Scale 效果比較", fontsize=16)
plt.tight_layout()
plt.show()

# ★ 觀察重點 ★
# w=1.0:幾乎忽略文字,圖像隨機且模糊
# w=5-7.5:文字引導與多樣性的最佳平衡
# w=15+:強烈遵循文字,但色彩過飽和、細節失真

7.5 去噪過程逐步視覺化

# ★ 視覺化擴散的逆向去噪過程 ★
# 用 callback 捕捉每幾步的中間狀態
from diffusers import DDIMScheduler

# 切換到 DDIM scheduler(支援較少步數且確定性)
pipe.scheduler = DDIMScheduler.from_config(pipe.scheduler.config)

intermediates = []

def callback_fn(pipe, step, timestep, callback_kwargs):
    """每步記錄潛變數,解碼為可視化圖片"""
    latents = callback_kwargs["latents"]
    # 解碼潛變數為圖片
    with torch.no_grad():
        decoded = pipe.vae.decode(latents / pipe.vae.config.scaling_factor)
        img = decoded.sample[0].cpu().float()
        img = (img / 2 + 0.5).clamp(0, 1).permute(1, 2, 0).numpy()
        intermediates.append((step, timestep.item(), img))
    return callback_kwargs

# 生成並捕捉中間步驟
intermediates.clear()
prompt = "A majestic mountain landscape with aurora borealis, oil painting style"

final_image = pipe(
    prompt=prompt,
    num_inference_steps=20,
    guidance_scale=7.5,
    width=512, height=512,
    generator=torch.Generator("cuda").manual_seed(77),
    callback_on_step_end=callback_fn
).images[0]

# ★ 視覺化去噪過程 ★
n_show = min(8, len(intermediates))
indices = np.linspace(0, len(intermediates)-1, n_show, dtype=int)

fig, axes = plt.subplots(1, n_show, figsize=(n_show * 3.5, 3.5))
for i, idx in enumerate(indices):
    step, timestep, img = intermediates[idx]
    axes[i].imshow(img)
    axes[i].set_title(f"Step {step}\nt={timestep}", fontsize=10)
    axes[i].axis("off")

plt.suptitle("Diffusion Denoising Process — 從噪聲到圖像", fontsize=14)
plt.tight_layout()
plt.show()

print(f"\n共 {len(intermediates)} 步去噪")
print("觀察:初期形成大致構圖 → 中期建立色彩和結構 → 後期精修細節和紋理")

7.6 Image-to-Image:風格遷移

from diffusers import StableDiffusionImg2ImgPipeline
from PIL import Image

# ★ 載入 img2img pipeline(共享模型權重)★
pipe_img2img = StableDiffusionImg2ImgPipeline(**pipe.components).to("cuda")

# 使用上面生成的圖片作為輸入
init_image = final_image.resize((512, 512))

# ★ 不同 strength 的風格遷移 ★
strengths = [0.3, 0.5, 0.7, 0.9]
style_prompt = "A majestic mountain landscape, watercolor painting, artistic brush strokes, vibrant colors"

fig, axes = plt.subplots(1, 5, figsize=(30, 6))

# 原圖
axes[0].imshow(init_image)
axes[0].set_title("Original", fontsize=13)
axes[0].axis("off")

for i, strength in enumerate(strengths):
    styled = pipe_img2img(
        prompt=style_prompt,
        image=init_image,
        strength=strength,
        num_inference_steps=25,
        guidance_scale=7.5,
        generator=torch.Generator("cuda").manual_seed(42)
    ).images[0]

    axes[i+1].imshow(styled)
    axes[i+1].set_title(f"strength = {strength}", fontsize=13)
    axes[i+1].axis("off")

plt.suptitle("Image-to-Image Style Transfer — strength 控制「改變程度」", fontsize=15)
plt.tight_layout()
plt.show()

# ★ 觀察重點 ★
# strength=0.3:僅微調色彩風格,保留原始構圖
# strength=0.5:風格化明顯,但主體結構不變
# strength=0.7:大幅重繪,只保留大致佈局
# strength=0.9:幾乎全新生成,僅受原圖「啟發」

7.7 文字 Embedding 探索

# ★ 探索 CLIP 文字 embedding 如何影響生成 ★
from transformers import CLIPTextModel, CLIPTokenizer

clip_tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14")
clip_model = CLIPTextModel.from_pretrained("openai/clip-vit-large-patch14").to("cuda")

def get_text_embedding(text):
    tokens = clip_tokenizer(text, return_tensors="pt", padding=True, truncation=True).to("cuda")
    with torch.no_grad():
        emb = clip_model(**tokens).last_hidden_state
    return emb

# ★ 計算不同 prompt 之間的相似度 ★
prompts = [
    "A beautiful sunset over the ocean",
    "A gorgeous sunrise by the sea",
    "A cat sitting on a windowsill",
    "Neural network architecture diagram",
]

embeddings = [get_text_embedding(p) for p in prompts]
# 用 [CLS] token 的 embedding 做相似度
cls_embs = torch.stack([e[0, 0] for e in embeddings])
cls_embs = F.normalize(cls_embs, dim=-1)
sim_matrix = (cls_embs @ cls_embs.T).cpu().numpy()

fig, ax = plt.subplots(figsize=(7, 6))
im = ax.imshow(sim_matrix, cmap='RdYlBu_r', vmin=0.5, vmax=1.0)
ax.set_xticks(range(len(prompts)))
ax.set_yticks(range(len(prompts)))
short_labels = [p[:25] + "..." if len(p) > 25 else p for p in prompts]
ax.set_xticklabels(short_labels, rotation=45, ha='right', fontsize=9)
ax.set_yticklabels(short_labels, fontsize=9)

for i in range(len(prompts)):
    for j in range(len(prompts)):
        ax.text(j, i, f"{sim_matrix[i,j]:.2f}", ha='center', va='center', fontsize=11)

plt.colorbar(im)
plt.title("CLIP Text Embedding Cosine Similarity", fontsize=13)
plt.tight_layout()
plt.show()

# ★ 觀察 ★
# sunset/sunrise 相似度高 → CLIP 理解語義相近性
# cat vs neural network 相似度低 → 語義距離大
# 這解釋了為什麼相近的 prompt 會產生相似風格的圖像

八、擴散模型的加速與優化技術

擴散模型最大的實用瓶頸是推論速度——原始 DDPM 需要 1000 步迭代取樣,即使在 GPU 上生成一張圖也要數分鐘。以下是從數學到工程的多層加速方案:

加速方法原理效果代表作
更快的取樣器非馬可夫取樣、ODE 求解器1000步 → 20-50步DDIM[5], DPM-Solver
一致性蒸餾將多步去噪蒸餾為少步50步 → 1-4步LCM[16], SDXL-Turbo
潛空間擴散在壓縮的潛空間操作計算量降低 ~48xLDM / Stable Diffusion[8]
模型壓縮剪枝 + 量化 + 蒸餾模型大小降低 30-70%BK-SDM, SVDQuant
Token 合併合併冗餘視覺 token額外 1.5-2x 加速ToMe for SD
特徵快取快取高層特徵跳過重複計算額外 2-2.5x 加速DeepCache
架構演進Transformer 取代 U-Net更好的 scalingDiT[13], FLUX

這些加速方法可以疊加使用。如我們在五大技術整合一文中詳細展示的:BK-SDM(剪枝+蒸餾)+ LCM-LoRA(一致性蒸餾)+ ToMe(Token 合併)的三技術疊加,在免費 Colab T4 上可實現 10 倍以上的端到端加速。

九、企業應用場景與策略價值

擴散模型的產業化應用正從「炫技」走向「創造價值」:

從策略角度,企業應關注三個關鍵趨勢:

  1. 微調成本急劇下降:LoRA + 量化讓企業用幾百張領域圖片就能訓練出專屬風格模型,成本從數萬美元降至數百美元
  2. 可控性持續提升:ControlNet、IP-Adapter、T2I-Adapter 等技術讓生成結果更精確可控,從「碰運氣」進化到「精確指導」
  3. 推論效率突破臨界點:LCM + 量化 + 快取優化讓高品質圖像生成從秒級進入毫秒級,打開了即時互動應用的大門

十、結語與展望

擴散模型在短短五年內完成了從理論框架到產業基礎設施的跨越。它的成功不是偶然——它建立在物理學(非平衡熱力學)、統計學(score matching)和深度學習的交叉點上,具備 GAN 不具有的理論完備性和訓練穩定性。

幾個值得關注的前沿方向:

  1. 影片生成的規模化:Sora 展示了 DiT 架構在時空一致的影片生成上的潛力。將擴散模型從 2D 擴展到 3D 時空是當前最活躍的研究方向
  2. 3D 內容生成:從文字直接生成 3D 物件和場景(DreamFusion、Zero-1-to-3),將擴散模型的成功從 2D 延伸到 3D
  3. 離散擴散的統一:將連續擴散和離散擴散統一在同一框架下,讓同一個模型能同時處理文字、圖像、程式碼、音樂等多模態資料
  4. Flow Matching:作為擴散模型的替代方案,直接學習從噪聲到資料的最優傳輸路徑,在理論和實務上都展現出優勢
  5. 推論效率的極限:從 1000 步到 1 步的旅程仍在繼續——一致性模型(Consistency Models)和矯正流(Rectified Flow)正在探索「一步生成」的品質上限

擴散模型的核心洞見——將複雜的生成問題分解為一系列簡單的去噪步驟——不僅改變了圖像生成,也正在重塑我們對生成式 AI 的認知框架。掌握這個框架,就是掌握了下一個十年 AI 創新的核心語言。

如果您的團隊正在評估擴散模型的企業應用場景,或需要建立從模型選型、微調到部署優化的完整管線,歡迎與我們進行深度技術對話。超智諮詢的研究團隊能夠協助您將擴散模型的技術潛力轉化為可衡量的商業價值。