我是靠谱客的博主 热心网友,这篇文章主要介绍学习PyTorch 写一个极简 DDPM(Diffusion Model),理解图片生成模型到底怎么训练和生成,现在分享给大家,希望可以做个参考。

這條路和現在主流圖片生成模型的核心思想是相通的:訓練時把圖片逐步加噪,讓神經網路學會預測噪聲;生成時從純噪聲開始,一步一步把噪聲去掉。 Hugging Face 現在的官方訓練範例也是用 UNet + DDPM scheduler 做這件事。

一、我們先做一個「真的能跑」的最小模型

我建議第一版不要做:

❌ Stable Diffusion

❌ Transformer

❌ CLIP

❌ Text Encoder

❌ VAE

❌ LoRA

❌ GPU 多卡

❌ 512×512

而是做:

32×32 RGB 圖片 → DDPM → 生成 32×32 圖片

例如我們準備:

dataset/
    001.jpg
    002.jpg
    003.jpg
    ...

全部是「貓」:

🐱 🐱 🐱 🐱 🐱

訓練完成後:

隨機噪聲
   ↓
   ↓ 去噪
   ↓
   ↓
   ↓
一隻新的貓

注意,它不是把某一張圖片「記住後複製」。

它學的是:

「我的訓練資料中的圖片,大概符合什麼樣的分布?」

這正是 unconditional image generation 的基本概念。



二、整個模型其實只有這幾個東西

可以把它理解成:

                訓練
                  │
                  ▼
             真實圖片 x₀
                  │
                  │ 加噪
                  ▼
             x₁ → x₂ → ... → xₜ
                              │
                              ▼
                         神經網路
                         UNet(xₜ,t)
                              │
                              ▼
                       預測噪聲 ε
                              │
                              ▼
                       MSE Loss
                              │
                              ▼
                        更新模型

生成的時候反過來:

純噪聲 xₜ
   │
   ▼
UNet 預測噪聲
   │
   ▼
去掉一點噪聲
   │
   ▼
xₜ₋₁
   │
   ▼
UNet
   │
   ▼
xₜ₋₂
   │
   ▼
...
   │
   ▼
x₀
   │
   ▼
圖片

這就是 Diffusion 最重要的原理。



三、第一個版本甚至可以不用 Hugging Face

我建議你第一遍純 PyTorch 寫

這樣你才能真正看到:

noise = torch.randn_like(images)

以及:

noisy_images = ...

和:

predicted_noise = model(noisy_images, t)

究竟在幹什麼。



四、安裝環境

Windows + NVIDIA GPU 的話:

pip install torch torchvision pillow tqdm

確認:

import torch

print(torch.cuda.is_available())
print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU")

如果:

True
NVIDIA GeForce RTX ...

就可以直接用 GPU。



五、第一個模型:極簡 UNet

先建立:

mini_diffusion/
│
├── dataset/
│   ├── 001.jpg
│   ├── 002.jpg
│   └── ...
│
├── model.py
├── train.py
└── generate.py

model.py

import torch
import torch.nn as nn


class SimpleUNet(nn.Module):

    def __init__(self):
        super().__init__()

        self.down1 = nn.Sequential(
            nn.Conv2d(3, 64, 3, padding=1),
            nn.ReLU(),
            nn.Conv2d(64, 64, 3, padding=1),
            nn.ReLU(),
        )

        self.down2 = nn.Sequential(
            nn.Conv2d(64, 128, 3, padding=1),
            nn.ReLU(),
            nn.Conv2d(128, 128, 3, padding=1),
            nn.ReLU(),
        )

        self.up1 = nn.Sequential(
            nn.Conv2d(128, 64, 3, padding=1),
            nn.ReLU(),
            nn.Conv2d(64, 64, 3, padding=1),
            nn.ReLU(),
        )

        self.out = nn.Conv2d(64, 3, 1)

        self.pool = nn.MaxPool2d(2)

    def forward(self, x):

        x1 = self.down1(x)

        x2 = self.pool(x1)
        x2 = self.down2(x2)

        x2 = nn.functional.interpolate(
            x2,
            size=x1.shape[-2:],
            mode="nearest"
        )

        x = self.up1(x2)

        x = x + x1

        return self.out(x)

這還不是嚴格意義上的完整 UNet,故意簡化

我們的目標不是先做最強模型,而是理解原理。



六、最重要的部分:加噪聲

這是整個 Diffusion 最核心的地方。

假設:

原圖:

🐱

我們逐步加噪:

x0

🐱
 ↓
🐱 + 一點噪聲
 ↓
░🐱░
 ↓
░░▓░
 ↓
▒░▒░
 ↓
▓▒▓▒
 ↓
純噪聲

數學上:

xₜ = √ᾱₜ x₀ + √(1-ᾱₜ) ε

其中:

x₀       原始圖片
ε        隨機噪聲
t        第幾步
ᾱₜ       控制噪聲比例
xₜ       加噪後的圖片

這一步甚至不需要神經網路。



七、寫成 Python

例如:

import torch


def add_noise(x, t, alpha_bar):

    noise = torch.randn_like(x)

    a = alpha_bar[t]

    a = a.view(-1, 1, 1, 1)

    noisy_x = (
        torch.sqrt(a) * x
        +
        torch.sqrt(1 - a) * noise
    )

    return noisy_x, noise

這裡非常重要:

noise = torch.randn_like(x)

假設:

x = 一張貓

我們隨機生成:

noise = 隨機雜訊

然後:

noisy_x = ...

得到:

貓 + 雜訊


八、真正的訓練邏輯

這是你最值得研究的地方。

for images in dataloader:

    images = images.to(device)

    # 隨機選一個時間
    t = torch.randint(
        0,
        T,
        (images.shape[0],),
        device=device
    )

    # 加噪
    noisy_images, noise = add_noise(
        images,
        t,
        alpha_bar
    )

    # 模型預測噪聲
    predicted_noise = model(
        noisy_images
    )

    # 比較真正的噪聲和模型預測的噪聲
    loss = torch.mean(
        (predicted_noise - noise) ** 2
    )

    optimizer.zero_grad()

    loss.backward()

    optimizer.step()

你可以把這段理解成:

我給你一張被污染的圖片,你猜我剛才加進去的是什麼噪聲。


九、模型根本不知道「貓」是什麼

例如:

原圖:

        🐱

我們隨機加噪:

        🐱
         ↓
      ░🐱░
         ↓
      ░▒▓░
         ↓
      ▓▓▒▓

模型收到:

▓▓▒▓

它不知道:

這是貓

它只需要學:

這張圖片裡面
哪些東西比較像 noise?

因此:

model(noisy_image)

輸出的不是:

cat

而是一張:

predicted_noise

這一點非常重要。



十、Loss 就簡單到令人意外

最基本版本:

loss = ((predicted_noise - noise) ** 2).mean()

也就是:

真實噪聲
    ↓
    ε

模型預測
    ↓
    ε'

Loss = (ε - ε')²

模型不斷調整參數:

ε' 越來越接近 ε

最後:

loss
1.2
0.8
0.5
0.3
0.18
0.12
0.08
...


十一、然後最神奇的事情發生了

訓練的時候我們是:

圖片
 ↓
加噪
 ↓
模型學習預測噪聲

但是生成的時候:

純噪聲
 ↓
模型
 ↓
去掉噪聲
 ↓
模型
 ↓
去掉噪聲
 ↓
模型
 ↓
...
 ↓
圖片

所以:

訓練

圖片 → 噪聲

生成

噪聲 → 圖片

這就是 Diffusion 最核心的「正向過程 + 反向過程」。



十二、生成程式

最簡化可以理解成:

@torch.no_grad()
def generate(model, shape, T):

    x = torch.randn(shape).to(device)

    for t in reversed(range(T)):

        predicted_noise = model(x)

        x = x - predicted_noise * 0.1

    return x

當然,這只是教學版概念,不是正確 DDPM sampling 方程

真正的 DDPM 要按照 β、α、ᾱ 和 posterior variance 來計算:

xₜ → xₜ₋₁

這個部分我們第二步再完整實現。



十三、為什麼我建議你從 32×32 開始?

因為圖片大小對計算量影響非常大。

例如:

32 × 32 = 1,024 pixels

64 × 64 = 4,096 pixels

256 × 256 = 65,536 pixels

512 × 512 = 262,144 pixels

512×512 是 32×32 的:

256 倍 pixels

而且真正的 Stable Diffusion 也不是直接在 512×512 RGB pixel 上做全部運算,而是先經過 VAE 壓縮到 latent space

所以我們第一階段:

32×32 RGB

非常合理。



十四、然後逐步升級

我建議你按照這個順序學。

Level 1:最小 Diffusion

PyTorch
 ↓
32×32
 ↓
簡單 UNet
 ↓
DDPM
 ↓
生成圖片

目的:

理解 Diffusion。


Level 2:真正的 UNet

加入:

Downsample
Residual Block
Upsample
Skip Connection
Time Embedding

變成:

                 ┌───────────────┐
                 │               │
圖片 → Down → Down → Bottleneck → Up → Up → 圖片
          │                         ↑
          └──── Skip Connection ────┘

這時候你會真正理解 Stable Diffusion 的 UNet。



Level 3:加入時間 t

這個非常重要。

因為:

t = 10

和:

t = 900

代表圖片處於完全不同的噪聲程度。

所以模型必須知道:

現在是第幾步?

因此:

model(x, t)

而不是:

model(x)

會加入:

Sinusoidal Time Embedding

這就是 Transformer 裡也非常常見的一類 positional encoding 思想。



十五、Level 4:加入文字

到這一步,你就開始接近 Stable Diffusion 了:

               Text
                │
                ▼
          Text Encoder
                │
                ▼
       "一隻在海邊的貓"
                │
                ▼
              UNet
                ▲
                │
             noisy image

也就是:

Image + Text
     ↓
   UNet
     ↓
Noise prediction

這就是:

Text-to-Image


十六、Level 5:VAE

接下來再理解:

圖片
 ↓
VAE Encoder
 ↓
Latent
 ↓
Diffusion
 ↓
Latent
 ↓
VAE Decoder
 ↓
圖片

例如:

512×512×3

不直接進 Diffusion。

而是:

512×512×3
       ↓
     VAE
       ↓
64×64×4 latent
       ↓
   Diffusion

這就是 Stable Diffusion 為什麼可以把計算量大幅降低。



十七、Level 6:你才開始碰 LoRA

到了這裡你就會明白 LoRA 到底在幹什麼。

不是:

重新訓練一個 Stable Diffusion

而是:

原始模型
     +
很少量可訓練參數
     ↓
LoRA

所以:

Stable Diffusion
       │
       ├── 原本參數:凍結
       │
       └── LoRA:訓練

這也是為什麼 LoRA 可以用相對低的硬體成本做風格、人物、產品等微調。



十八、最終你會看到完整架構

等你把前面全部串起來:

                 Prompt
                   │
                   ▼
              Text Encoder
                   │
                   ▼
              Text Embedding
                   │
                   ▼
Random Noise → UNet ← Time Embedding
                   │
                   ▼
              Denoising
                   │
                   ▼
                Latent
                   │
                   ▼
              VAE Decoder
                   │
                   ▼
             512×512 Image

這時候你再去看:

Stable Diffusion

SDXL

FLUX

Imagen

各種 DiT

LoRA

ControlNet

就不會再覺得它們是「一個神秘的 AI 黑盒子」。



十九、你甚至可以用自己的攝影素材來做實驗

這對你其實非常合適。

例如你有大量旅遊攝影:

photos/
├── hongkong/
├── shenzhen/
├── guangzhou/
├── macau/
├── taiwan/
└── ...

第一個實驗甚至可以:

只拿 1,000 張香港照片

訓練:

Hong Kong Diffusion

生成:

random noise
     ↓
    ...
     ↓
一張「像你的香港攝影資料分布」的新圖片

但這時候還沒有:

「香港夜景」
「香港維港」
「香港街頭」

這些文字控制。

那是下一階段:

Image + Caption


二十、我建議我們真的動手做

如果你願意,我可以直接帶你做一個完整可執行的「迷你 Stable Diffusion 教學專案」,不是只講原理。

我會分成大約 6 個版本

V1
最小 DDPM
32×32
        ↓
V2
真正 UNet
        ↓
V3
Time Embedding
        ↓
V4
Text Conditioning
        ↓
V5
VAE + Latent Diffusion
        ↓
V6
LoRA 微調

每一版都給你:

完整 Python 代碼
↓
訓練資料準備
↓
訓練命令
↓
生成命令
↓
每一段代碼解釋
↓
觀察 loss / 中間圖片
↓
下一版到底增加了什麼

而且第一版我可以直接寫成一個 Windows + NVIDIA GPU 可以執行的完整專案,大概只有 train.py + model.py + generate.py 三個文件,讓你今天就能跑起第一個「自己訓練出來的圖片生成模型」。

這比直接拿 Hugging Face 的 train_unconditional.py 跑更適合你理解原理;官方腳本本身已經把 UNet、scheduler、dataset、checkpoint 等大量細節封裝起來了。


最后

以上就是热心网友最近收集整理的关于学习PyTorch 写一个极简 DDPM(Diffusion Model),理解图片生成模型到底怎么训练和生成的全部内容,更多相关学习PyTorch内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(16)

评论列表共有 0 条评论

立即
投稿
返回
顶部