在本文中,我們將深入研究生成建模的世界,并使用流行的 PyTorch 框架探索 DCGAN(生成對抗網絡 (GAN) 的一種變體)的實現。具體來說,我們將使用 CelebA 數據集(名人面部圖像的集合)來生成逼真的合成面部。在深入了解實現細節之前,我們首先了解 GAN 是什么以及 DCGAN 與它們有何不同,并詳細探討其架構。
那么,什么是 GAN?
簡而言之,生成對抗網絡(GAN)是一種令人著迷的機器學習模型,其中兩個玩家(生成器和鑒別器)參與競爭性游戲。生成器的目的是創建真實的合成樣本,例如圖像或文本,而鑒別器的工作是區分真實樣本和假樣本。通過這場貓鼠游戲,GAN 學會生成高度令人信服且真實的輸出,最終突破人工智能在創建新的多樣化數據方面的界限。
理解DCGAN:
DCGAN(深度卷積生成對抗網絡)是一種令人興奮的機器學習模型,可以創建極其逼真和詳細的圖像。想象一下,一個系統可以通過分析數千個示例來學習生成全新的圖片,例如面孔或風景。DCGAN 通過巧妙地結合兩個網絡來實現這一目標——一個網絡生成圖像,另一個網絡試圖區分真假圖像。通過競爭過程,DCGAN 成為生成令人信服且難以與真實圖像區分開的圖像的大師,展示了人工智能的巨大創造潛力。
DCGAN的架構:
DCGAN(深度卷積生成對抗網絡)的架構??由兩個基本組件組成:生成器和判別器。
生成器將隨機噪聲作為輸入,并逐漸將其轉換為類似于訓練數據的合成樣本。它通過一系列層來實現這一點,包括轉置卷積、批量歸一化和激活函數。這些層使生成器能夠學習復雜的模式和結構,從而生成捕獲真實數據的復雜細節的高維樣本。
另一方面,鑒別器充當二元分類器,區分真實樣本和生成樣本。它接收輸入樣本并將其傳遞給卷積層、批量歸一化和激活函數。鑒別器的作用是評估樣本的真實性并向生成器提供反饋。通過對抗性訓練過程,生成器和鑒別器不斷競爭并提高性能,最終生成越來越真實的樣本。
讓我們開始實現我們的第一個 DCGAN:
假設您的環境中安裝了 PyTorch 和 CUDA,我們首先導入必要的庫。
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
from tqdm import tqdm
import torchvision.datasets as datasets
from torchvision.datasets import ImageFolder
from torchvision.utils import make_grid
import torchvision.utils as vutils
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
from torch.utils.data import Subset
import numpy as np
導入這些庫后,您就可以使用 CelebA 數據集在 PyTorch 中繼續實施 DCGAN。
為了設置訓練 DCGAN 模型所需的配置,我們可以定義設備、學習率、批量大小、圖像大小、輸入圖像中的通道數、潛在空間維度、歷元數以及特征數判別器和生成器。以下是配置:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
LEARNING_RATE = 2e-4 # could also use two lrs, one for gen and one for disc
BATCH_SIZE = 128
IMAGE_SIZE = 64
CHANNELS_IMG = 3
Z_DIM = 100
NUM_EPOCHS = 5
FEATURES_DISC = 64
FEATURES_GEN = 64
“device”變量確保代碼在可用的 GPU 上運行,否則返回到 CPU。“LEARNING_RATE”決定了模型在優化過程中學習的速率。“BATCH_SIZE”表示每次迭代中處理的樣本數量。“IMAGE_SIZE”表示輸入圖像的所需尺寸。“CHANNELS_IMG”指定輸入圖像中顏色通道的數量(例如,RGB 為 3)。“Z_DIM”表示潛在空間的維度,它是生成器的輸入。“NUM_EPOCHS”決定了訓練期間遍歷整個數據集的次數。“FEATURES_DISC”和“FEATURES_GEN”分別表示鑒別器和生成器網絡中的特征數量。
這些配置對于訓練 DCGAN 模型至關重要,可以根據具體要求和可用資源進行調整。
要加載 CelebA 數據集并準備進行訓練,我們可以定義數據轉換、創建數據集對象并設置數據加載器:
transform = transforms.Compose([transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])dataset = datasets.ImageFolder('<path_to_celeba_dataset_in_your_directoty>', transform=transform)
dataloader = DataLoader(dataset=dataset, batch_size=BATCH_SIZE, shuffle=True)
要可視化 CelebA 數據集中的一批訓練圖像:
real_batch = next(iter(dataloader))
plt.figure(figsize=(7,7))
plt.axis("off")
plt.title("Training Images")
plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:49], padding=2, normalize=True).cpu(),(1,2,0)))
創建生成器網絡:
在 DCGAN 架構中,噪聲數據最初表示為形狀為 100x1x1 的張量,經過一系列轉置卷積運算,將其轉換為大小為 3x64x64 的輸出圖像。
以下是重塑過程的逐步分解:
- 輸入噪聲:100x1x1
- 第一個轉置卷積層:
— 輸出大小:1024x4x4
— 內核大小:4x4,步長:1,填充:0 - 第二個轉置卷積層:
— 輸出大小:512x8x8
— 內核大小:4x4,步長:2,填充:1 - 第三轉置卷積層:
— 輸出大小:256x16x16
— 內核大小:4x4,步長:2,填充:1 5. - 第四轉置卷積層:
— 輸出大小:128x32x32
— 內核大小:4x4,步長:2,填充:1 - 最終轉置卷積層:
— 輸出大小:3x64x64
— 內核大小:4x4,步幅:2,填充:1
通過將噪聲數據傳遞到這些轉置卷積層,生成器逐漸將低維噪聲放大為與所需大小 3x64x64 匹配的高維圖像。重塑過程涉及增加空間維度,同時減少通道數量,從而產生具有代表 RGB 顏色通道的三個通道和 64x64 像素尺寸的最終輸出圖像。
class Generator(nn.Module):def __init__(self, z_dim, channels_img, features_g):super(Generator, self).__init__()self.net = nn.Sequential(self._block(z_dim, features_g * 16, 4, 1, 0),self._block(features_g * 16, features_g * 8, 4, 2, 1),self._block(features_g * 8, features_g * 4, 4, 2, 1),self._block(features_g * 4, features_g * 2, 4, 2, 1),nn.ConvTranspose2d(features_g * 2, channels_img, kernel_size=4, stride=2, padding=1),nn.Tanh())def _block(self, in_channels, out_channels, kernel_size, stride, padding):return nn.Sequential(nn.ConvTranspose2d(in_channels,out_channels,kernel_size,stride,padding,bias=False),nn.BatchNorm2d(out_channels),nn.ReLU())def forward(self, x):return self.net(x)
Generator 類代表 DCGAN 架構中的生成器網絡。它將潛在空間的維度 (z_dim)、輸出圖像中的通道數 (channels_img) 和特征數 (features_g) 作為輸入。生成器被定義為順序模塊。
該_block方法在生成器中定義了一個塊,其中包含轉置卷積層、批量歸一化和 ReLU 激活函數。該塊重復多次,逐漸增加輸出圖像的空間維度。
在該forward方法中,輸入噪聲 (x) 通過生成器的連續層,從而生成生成的圖像。
生成器的架構旨在將低維潛在空間轉換為高維圖像,并逐漸將其放大到所需的輸出大小。然后輸出圖像通過雙曲正切激活函數 ( nn.Tanh()) 以確保其像素值在 [-1, 1] 范圍內。
通過以這種方式定義生成器,當提供隨機噪聲作為輸入時,它學會生成類似于訓練數據的合成圖像。
創建鑒別器網絡:
在 DCGAN 架構中,判別器采用大小為 3x64x64 的輸入圖像,并通過一系列卷積層對其進行處理,從而產生 1x1x1 的輸出。以下是重塑過程的逐步分解:
- 輸入圖像:3x64x64
- 第一個卷積層:
— 輸出大小:64x32x32
— 內核大小:4x4,步長:2,填充:1 - 第二個卷積層:
— 輸出大小:128x16x16
— 內核大小:4x4,步長:2 ,填充:1 - 第三個卷積層:
— 輸出大小:256x8x8
— 內核大小:4x4,步長:2,填充:1 5. - 第四個卷積層:
— 輸出大小:512x4x4
— 內核大小:4x4,步長:2,填充:1 - 最終卷積層:
— 輸出大小:1x1x1
— 內核大小:4x4,步長:2,填充:0
通過將輸入圖像傳遞給這些卷積層,鑒別器逐漸減小空間維度,同時增加通道數量。這種轉換允許鑒別器評估圖像并對其真實性做出決定。1x1x1 的輸出大小表示單個值,該值表示輸入圖像為真或假的概率。
class Discriminator(nn.Module):def __init__(self, channels_img, features_d):super(Discriminator, self).__init__()self.disc = nn.Sequential(nn.Conv2d(channels_img, features_d, kernel_size=4, stride=2, padding=1),nn.LeakyReLU(0.2),self._block(features_d, features_d * 2, 4, 2, 1),self._block(features_d * 2, features_d * 4, 4, 2, 1),self._block(features_d * 4, features_d * 8, 4, 2, 1),nn.Conv2d(features_d * 8, 1, kernel_size=4, stride=2, padding=0),nn.Sigmoid())def _block(self, in_channels, out_channels, kernel_size, stride, padding):return nn.Sequential(nn.Conv2d(in_channels,out_channels,kernel_size,stride,padding,bias=False),nn.BatchNorm2d(out_channels),nn.LeakyReLU(0.2))def forward(self, x):return self.disc(x)
Discriminator 類代表 DCGAN 架構中的鑒別器網絡。它將輸入圖像中的通道數 (channels_img) 和特征數 (features_d) 作為輸入。鑒別器被定義為一個順序模塊。
該_block方法在判別器內定義了一個塊,由卷積層、批量歸一化和 LeakyReLU 激活組成。該塊重復多次,逐漸增加特征數量并減少輸入圖像的空間維度。
在該forward方法中,輸入圖像 (x) 通過鑒別器的連續層,由于最后的 sigmoid 激活,輸出表示輸入為真 (1) 或假 (0) 的概率。
鑒別器的架構使其能夠區分真假圖像,使其成為 DCGAN 對抗訓練過程的重要組成部分。
創建一個函數來初始化權重:
def initialize_weights(model):classname = model.__class__.__name__if classname.find('Conv') != -1:nn.init.normal_(model.weight.data, 0.0, 0.02)elif classname.find('BatchNorm') != -1:nn.init.normal_(model.weight.data, 1.0, 0.02)nn.init.constant_(model.bias.data, 0)
在此函數中,我們接收 amodel作為輸入,它可以引用生成器或鑒別器網絡。我們迭代模型的各層并根據層類型初始化權重。這些權重是由 DCGAN 論文建議的。
對于卷積層 ( Conv),我們使用nn.init.normal_平均值 0 和標準差 0.02 來初始化權重。
對于批量歸一化層 ( BatchNorm),我們使用 來初始化權重,平均值為 1,標準差為 0.02 nn.init.normal_,并使用 來將偏差設置為 0 nn.init.constant_。
通過調用此函數并將生成器和鑒別器網絡作為參數傳遞,您可以確保網絡的權重得到適當初始化以訓練 DCGAN 模型。
gen = Generator(Z_DIM, CHANNELS_IMG, FEATURES_GEN).to(device)
disc = Discriminator(CHANNELS_IMG, FEATURES_DISC).to(device)initialize_weights(gen)
initialize_weights(disc)
gen現在,我們通過傳遞潛在空間的維度 ( Z_DIM)、輸出圖像中的通道數 ( CHANNELS_IMG) 以及生成器中的特征數量 ( FEATURES_GEN)來創建生成器網絡 ( ) 的實例。disc類似地,我們通過指定輸入通道的數量 ( CHANNELS_IMG) 和鑒別器中的特征數量 ( )創建鑒別器網絡 ( ) 的實例FEATURES_DISC。
創建網絡實例后,我們調用initialize_weights生成器和鑒別器網絡上的函數,根據 DCGAN 論文中建議的權重初始化技術來初始化它們的權重。
通過執行此代碼,您將準備好生成器和鑒別器網絡,并正確初始化它們的權重,用于訓練 DCGAN 模型。
opt_gen = optim.Adam(gen.parameters(), lr=LEARNING_RATE, betas=(0.5, 0.999))
opt_disc = optim.Adam(disc.parameters(), lr=LEARNING_RATE, betas=(0.5, 0.999))
criterion = nn.BCELoss()fixed_noise = torch.randn(32, Z_DIM, 1, 1).to(device)
我們為生成器和鑒別器網絡定義了優化器。我們使用模塊中的 Adam 優化器torch.optim。生成器優化器 ( opt_gen) 使用生成器參數、學習率LEARNING_RATE以及 Adam 優化器的 beta 參數(0.5 和 0.999)進行初始化。
類似地,判別器優化器 ( opt_disc) 使用判別器的參數、相同的學習率和 beta 參數進行初始化。
接下來,我們定義對抗訓練過程的損失標準。在這里,我們使用二元交叉熵損失 (Binary Cross Entropy Loss nn.BCELoss()),它在 GAN 中常用來將判別器的預測與真實標簽進行比較。
最后,我們創建一個固定噪聲張量 ( fixed_noise),用于在訓練過程中生成樣本圖像。該torch.randn函數根據正態分布生成隨機數。
通過設置優化器、損失準則和固定噪聲張量,您就可以開始訓練 DCGAN 模型了。
def show_tensor_images(image_tensor, num_images=32, size=(1, 64, 64)):image_tensor = (image_tensor + 1) / 2image_unflat = image_tensor.detach().cpu()image_grid = make_grid(image_unflat[:num_images], nrow=4)plt.imshow(image_grid.permute(1, 2, 0).squeeze())plt.show()
“show_tensor_images”函數是一個實用函數,它獲取圖像張量,對它們進行標準化,創建圖像網格,并使用 matplotlib 顯示它們,以便在訓練過程中輕松可視化。
訓練模型:
gen.train()
disc.train()for epoch in range(NUM_EPOCHS):for batch_idx, (real, _ ) in enumerate(dataloader):real = real.to(device)### create noise tensornoise = torch.randn((BATCH_SIZE, Z_DIM, 1, 1)).to(device)fake = gen(noise)### Train Discriminator: max log(D(x)) + log(1 - D(G(z)))disc_real = disc(real).reshape(-1)loss_disc_real = criterion(disc_real, torch.ones_like(disc_real))disc_fake = disc(fake.detach()).reshape(-1)loss_disc_fake = criterion(disc_fake, torch.zeros_like(disc_fake))loss_disc = (loss_disc_real + loss_disc_fake) / 2disc.zero_grad()loss_disc.backward()opt_disc.step()### Train Generator: min log(1 - D(G(z))) <-> max log(D(G(z))output = disc(fake).reshape(-1)loss_gen = criterion(output, torch.ones_like(output))gen.zero_grad()loss_gen.backward()opt_gen.step()### Print losses occasionally and fake images occasionallyif batch_idx % 50 == 0:print(f"Epoch [{epoch}/{NUM_EPOCHS}] Batch {batch_idx}/{len(dataloader)} \Loss D: {loss_disc:.4f}, loss G: {loss_gen:.4f}")with torch.no_grad():fake = gen(fixed_noise)img_grid_real = torchvision.utils.make_grid(real[:32], normalize=True)img_grid_fake = torchvision.utils.make_grid(fake[:32], normalize=True)show_tensor_images(img_grid_fake)
我們可以將本次培訓分為四個部分以便更好地理解。
- 噪聲生成:隨機噪聲 ( noise) 使用形狀為 的正態分布生成(BATCH_SIZE, Z_DIM, 1, 1)。
- 判別器訓練:判別器網絡是通過評估真實圖像 ( ) 并根據判別器與地面真實標簽(全部)的預測real計算損失 ( ) 來訓練的。loss_disc_real假圖像 ( fake) 是通過將噪聲傳遞到生成器網絡來生成的。評估鑒別器對假圖像 ( ) 的預測,并根據與地面真實標簽(全零)相比的預測來計算disc_fake損失 ( ) 。loss_disc_fake平均損失 ( ) 計算為和loss_disc的平均值。判別器參數的梯度設置為零,反向傳播損失,并更新判別器的優化器。loss_disc_realloss_disc_fake
- 生成器訓練:生成器網絡是通過fake使用噪聲生成假圖像( )來訓練的。獲得判別器對假圖像 ( ) 的預測,并根據與地面真實標簽(全部)相比的預測來計算output損失 ( )。loss_gen生成器參數的梯度設置為零,反向傳播損失,并更新生成器的優化器。
- 進度跟蹤和圖像可視化:每 50 個批次后,打印當前紀元、批次索引、鑒別器損失 ( loss_disc) 和生成器損失 ( )。loss_gen樣本圖像是通過將固定噪聲傳遞到生成器網絡來生成的。真實圖像和生成圖像都被轉換為圖像網格,然后顯示圖像網格以可視化生成器網絡的學習進度。
每個時期生成的假圖像:
Starting of the first epoch:
After first epoch:
After second epoch:
After third epoch:
After fourth epoch:
End results ( After 5 epochs):
結論: 如果您能夠獲得更好的結果,
- 增加模型容量:在生成器和鑒別器網絡中添加更多層或增加濾波器數量,以增強模型學習復雜模式并生成更高質量圖像的能力。
- 利用更深的卷積層:實施ResNet 或 DenseNet 等更深層次的架構來捕獲更復雜的特征和紋理,從而提高圖像質量。
- 使用更大的數據集:在更大、更多樣化的數據集上訓練 DCGAN 可以幫助模型學習更廣泛的圖像變化,并提高其生成高分辨率圖像的能力。
- 調整訓練參數:試驗學習率、批量大小和訓練迭代等超參數,以優化訓練過程并提高模型生成更高分辨率圖像的能力。
博文譯自Manohar Kedamsetti的博客。