WeHelp
PyTorch 可以應用在迴歸預測模型、分類預測模型、影像辨識、物件偵測、生成式模型等等領域。
  1. 房屋價格預測
  2. 乳癌良性 / 惡性分類
  3. 紅酒產地分類
  4. CNN 影像辨識
  5. R-CNN 物件偵測
  6. GAN 生成對抗網路
GAN 生成對抗網路
以下以「**生成手寫數字影像,作為 OCR 或影像辨識系統的合成訓練資料**」為實務案例,使用 PyTorch 建立一個簡單的 GAN。資料集採用 MNIST,影像大小為 \(28 \times 28\)。 --- ## 1. GAN 架構 GAN 包含兩個神經網路: - **Generator(生成器,G)** 輸入隨機噪聲 \(z\),輸出一張假影像。 - **Discriminator(判別器,D)** 輸入一張影像,判斷它是真實資料或生成資料。 簡單來說: - 判別器希望正確分辨真、假影像。 - 生成器希望產生足以欺騙判別器的影像。 --- ## 2. 安裝套件 ```bash pip install torch torchvision matplotlib ``` --- ## 3. 完整 PyTorch 實作 ```python import os import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torchvision.utils import save_image from torch.utils.data import DataLoader # ========================= # 1. 基本設定 # ========================= device = torch.device("cuda" if torch.cuda.is_available() else "cpu") seed = 42 torch.manual_seed(seed) batch_size = 128 latent_dim = 100 image_dim = 28 * 28 epochs = 20 learning_rate = 0.0002 os.makedirs("outputs", exist_ok=True) # ========================= # 2. 載入 MNIST # ========================= # 將像素值從 [0, 1] 映射到 [-1, 1] # 因為 Generator 最後使用 Tanh transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,)) ]) dataset = datasets.MNIST( root="./data", train=True, download=True, transform=transform ) dataloader = DataLoader( dataset, batch_size=batch_size, shuffle=True, num_workers=2, pin_memory=True if torch.cuda.is_available() else False ) # ========================= # 3. 建立 Generator # ========================= class Generator(nn.Module): def __init__(self, latent_dim=100): super().__init__() self.model = nn.Sequential( nn.Linear(latent_dim, 256), nn.LeakyReLU(0.2, inplace=True), nn.Linear(256, 512), nn.BatchNorm1d(512), nn.LeakyReLU(0.2, inplace=True), nn.Linear(512, 1024), nn.BatchNorm1d(1024), nn.LeakyReLU(0.2, inplace=True), nn.Linear(1024, image_dim), nn.Tanh() ) def forward(self, z): image = self.model(z) return image.view(-1, 1, 28, 28) # ========================= # 4. 建立 Discriminator # ========================= class Discriminator(nn.Module): def __init__(self): super().__init__() self.model = nn.Sequential( nn.Linear(image_dim, 512), nn.LeakyReLU(0.2, inplace=True), nn.Dropout(0.3), nn.Linear(512, 256), nn.LeakyReLU(0.2, inplace=True), nn.Dropout(0.3), # 不使用 Sigmoid,搭配 BCEWithLogitsLoss nn.Linear(256, 1) ) def forward(self, image): image = image.view(image.size(0), -1) return self.model(image) G = Generator(latent_dim).to(device) D = Discriminator().to(device) # ========================= # 5. 權重初始化 # ========================= def initialize_weights(model): for module in model.modules(): if isinstance(module, nn.Linear): nn.init.normal_(module.weight, mean=0.0, std=0.02) if module.bias is not None: nn.init.constant_(module.bias, 0) initialize_weights(G) initialize_weights(D) # ========================= # 6. Loss 與 Optimizer # ========================= criterion = nn.BCEWithLogitsLoss() optimizer_G = optim.Adam( G.parameters(), lr=learning_rate, betas=(0.5, 0.999) ) optimizer_D = optim.Adam( D.parameters(), lr=learning_rate, betas=(0.5, 0.999) ) # 固定噪聲,用來觀察每個 epoch 的生成結果 fixed_noise = torch.randn(64, latent_dim, device=device) # ========================= # 7. GAN 訓練 # ========================= for epoch in range(epochs): G.train() D.train() total_g_loss = 0.0 total_d_loss = 0.0 for real_images, _ in dataloader: real_images = real_images.to(device) batch_size_now = real_images.size(0) real_labels = torch.ones(batch_size_now, 1, device=device) fake_labels = torch.zeros(batch_size_now, 1, device=device) # --------------------------------- # Step A: 訓練 Discriminator # --------------------------------- optimizer_D.zero_grad() # 判斷真實影像 real_logits = D(real_images) d_real_loss = criterion(real_logits, real_labels) # 生成假影像 z = torch.randn(batch_size_now, latent_dim, device=device) fake_images = G(z) # detach 避免更新 Generator fake_logits = D(fake_images.detach()) d_fake_loss = criterion(fake_logits, fake_labels) d_loss = d_real_loss + d_fake_loss d_loss.backward() optimizer_D.step() # --------------------------------- # Step B: 訓練 Generator # --------------------------------- optimizer_G.zero_grad() z = torch.randn(batch_size_now, latent_dim, device=device) fake_images = G(z) fake_logits = D(fake_images) # Generator 希望 D 將假影像判斷為真 g_loss = criterion(fake_logits, real_labels) g_loss.backward() optimizer_G.step() total_d_loss += d_loss.item() total_g_loss += g_loss.item() avg_d_loss = total_d_loss / len(dataloader) avg_g_loss = total_g_loss / len(dataloader) # 儲存固定噪聲產生的影像 G.eval() with torch.no_grad(): generated_images = G(fixed_noise) save_image( generated_images, f"outputs/epoch_{epoch + 1:03d}.png", nrow=8, normalize=True ) print( f"Epoch [{epoch + 1:02d}/{epochs}] " f"D Loss: {avg_d_loss:.4f}, " f"G Loss: {avg_g_loss:.4f}" ) # 儲存模型 torch.save(G.state_dict(), "outputs/generator.pth") torch.save(D.state_dict(), "outputs/discriminator.pth") print("訓練完成,模型已儲存至 outputs/。") ``` --- ## 4. 關鍵設定說明 ### 4.1 資料正規化 MNIST 原始像素值為 \([0,1]\),程式將其轉成: \[ [-1, 1] \] 因為生成器最後一層使用: ```python nn.Tanh() ``` 因此真實影像與生成影像必須使用相同的數值範圍。 --- ### 4.2 Generator 設計 Generator 的輸入是 100 維隨機向量: ```python z = torch.randn(batch_size, latent_dim) ``` 經過多層全連接網路後,輸出: ```text 1 × 28 × 28 ``` 實務上,對於較大的影像,可以改用反卷積或上採樣卷積,例如: ```text Linear → Reshape → ConvTranspose2d → BatchNorm → ReLU ``` 本案例使用全連接層,是為了讓模型容易理解與快速執行。 --- ### 4.3 Discriminator 設計 Discriminator 將影像攤平成 784 維向量,輸出一個 logit: ```python nn.Linear(256, 1) ``` 這裡沒有加 `Sigmoid`,因為使用: ```python nn.BCEWithLogitsLoss() ``` 此損失函數已經內含 Sigmoid,通常比自行使用 `Sigmoid + BCELoss` 更穩定。 --- ### 4.4 訓練順序 每一個 batch 會進行兩個步驟: #### 訓練判別器 ```python d_real_loss = criterion(D(real_images), real_labels) d_fake_loss = criterion(D(fake_images.detach()), fake_labels) ``` 使用 `detach()` 是為了避免判別器更新時,同時反向更新 Generator。 #### 訓練生成器 ```python g_loss = criterion(D(fake_images), real_labels) ``` Generator 將假影像標記成「真」,藉此學習欺騙 Discriminator。 --- ### 4.5 Optimizer 設定 GAN 常使用 Adam: ```python optim.Adam( model.parameters(), lr=0.0002, betas=(0.5, 0.999) ) ``` 其中: - learning rate:`0.0002` - `beta1=0.5` 常見於 GAN,可降低訓練震盪 - batch size:`128` - epoch:`20` 實際訓練時,可能需要 30~100 個 epochs 才能得到較清晰的數字。 --- ## 5. 產生結果 程式會在 `outputs/` 資料夾產生: ```text epoch_001.png epoch_002.png ... epoch_020.png generator.pth discriminator.pth ``` 其中每張 `epoch_xxx.png` 為 64 張生成影像的排列圖,可以觀察模型逐步學習的過程: - 初期:大多是雜訊 - 中期:開始出現數字輪廓 - 後期:數字形狀較清晰,但可能出現重複或變形 --- ## 6. 模型評估 GAN 不適合只使用準確率或均方誤差評估,因為生成影像不一定要與某一張真實影像完全相同。常見的評估方式如下。 ### 6.1 視覺化評估 固定相同的 `fixed_noise`,比較不同 epoch 的影像: ```python fixed_noise = torch.randn(64, latent_dim, device=device) ``` 這樣可以觀察模型是否逐漸生成清楚的手寫數字。 --- ### 6.2 使用判別器分數 可以計算判別器對生成影像的平均信心: ```python import torch G.load_state_dict(torch.load("outputs/generator.pth", map_location=device)) D.load_state_dict(torch.load("outputs/discriminator.pth", map_location=device)) G.eval() D.eval() with torch.no_grad(): z = torch.randn(1000, latent_dim, device=device) fake_images = G(z) fake_logits = D(fake_images) fake_scores = torch.sigmoid(fake_logits) print("平均生成影像真實分數:", fake_scores.mean().item()) ``` 不過要注意: > 判別器分數高不一定代表生成影像品質真的好,因為判別器本身也可能過度適應訓練資料。 --- ### 6.3 多樣性評估 除了影像清晰度,也要確認生成器沒有發生 **Mode Collapse**,也就是只會生成少數幾種數字。 可以先計算生成影像的平均像素標準差: ```python with torch.no_grad(): z = torch.randn(1000, latent_dim, device=device) fake_images = G(z) pixel_std = fake_images.std().item() print("生成影像像素標準差:", pixel_std) ``` 這只能作為簡單參考。更完整的方式是: - 將生成影像交給 MNIST 分類器,觀察數字類別是否涵蓋 0~9 - 比較每個數字類別的生成數量 - 使用 FID 或 KID - 計算生成影像與真實影像在特徵空間的距離 --- ## 7. 實務上的評估建議 若將此 GAN 用於產生 OCR 訓練資料,可以採用以下流程: 1. 生成 10,000 張手寫數字影像。 2. 使用已訓練好的 MNIST 分類器進行分類。 3. 檢查 0~9 每一類的比例。 4. 人工檢視部分生成影像。 5. 將生成資料加入原始 MNIST,重新訓練 OCR 模型。 6. 比較加入 GAN 資料前後的驗證集準確率。 如果加入生成資料後,OCR 模型在驗證集上的準確率提升,且各類生成影像分布合理,才表示 GAN 具備實務效益。 --- ## 8. 常見問題與改善方式 ### 生成影像過於模糊 可以: - 增加訓練 epochs - 使用 DCGAN 架構 - 增加 Generator 和 Discriminator 的層數 - 使用卷積層取代全連接層 ### 發生 Mode Collapse 可以: - 使用 Wasserstein GAN(WGAN) - 使用 Gradient Penalty - 降低 Discriminator 的學習速度 - 使用 label smoothing - 使用 minibatch discrimination ### Loss 不穩定 GAN 的 G Loss 和 D Loss 不一定會像一般分類模型一樣單調下降,應搭配: - 生成影像視覺化 - 類別分布 - FID/KID - 下游任務表現 共同評估,而不能只看 Loss。 --- ## 9. 本案例重點整理 | 項目 | 設定 | |---|---| | 資料集 | MNIST | | 影像尺寸 | \(1 \times 28 \times 28\) | | 潛在向量維度 | 100 | | Generator 輸出函數 | Tanh | | Discriminator Loss | BCEWithLogitsLoss | | Optimizer | Adam | | Learning rate | 0.0002 | | Batch size | 128 | | 訓練 epochs | 20 | | 評估方法 | 影像視覺化、判別器分數、多樣性、下游分類效能 | 這個模型適合作為 GAN 的入門實務案例;若要處理人臉、商品圖片或醫療影像,則應進一步改用卷積式 DCGAN、WGAN-GP 或 StyleGAN 等架構。
相關學習地圖、教學課程
Python 人工智慧
建議完成「Python 資料工程」教程後,繼續學習以下課程。