目錄
目錄
目錄
前言
一、定義超參數
二、下載數據
三、配置數據
四、定義鑒別器
五、訓練模型并保存
總結
前言
- 🍨?本文為🔗365天深度學習訓練營中的學習記錄博客
- 🍖?原作者:K同學啊
一、定義超參數
import argparse
import os
import numpy as np
import torchvision.transforms as transforms
from torchvision.utils import save_image
from torch.utils.data import DataLoader
from torchvision import datasets
from torch.autograd import Variable
import torch.nn as nn
import torch## 創建文件夾
os.makedirs("./images/", exist_ok=True) # 記錄訓練過程的圖片效果
os.makedirs("./save/", exist_ok=True) # 訓練完成時模型保存的位置
os.makedirs("./datasets/mnist", exist_ok=True) # 下載數據集存放的位置## 超參數配置
n_epochs = 50
batch_size= 64
lr = 0.0002
b1 = 0.5
b2 = 0.999
n_cpu = 2
latent_dim= 100
img_size = 28
channels = 1
sample_interval=500# 圖像的尺寸:(1, 28, 28), 和圖像的像素面積:(784)
img_shape = (channels, img_size, img_size)
img_area = np.prod(img_shape)# 設置cuda:(cuda:0)
cuda = True if torch.cuda.is_available() else False
print(cuda)
二、下載數據
# mnist數據集下載
mnist = datasets.MNIST(root='./datasets/', train=True, download=True, transform=transforms.Compose([transforms.Resize(img_size), transforms.ToTensor(), transforms.Normalize([0.5], [0.5])]),
)
三、配置數據
# 配置數據到加載器
dataloader = DataLoader(mnist,batch_size=batch_size,shuffle=True,
)
四、定義鑒別器
# 將圖片28x28展開成784,然后通過多層感知器,中間經過斜率設置為0.2的LeakyReLU激活函數,
# 最后接sigmoid激活函數得到一個0到1之間的概率進行二分類
class Discriminator(nn.Module):def __init__(self):super(Discriminator, self).__init__()self.model = nn.Sequential(nn.Linear(img_area, 512), # 輸入特征數為784,輸出為512nn.LeakyReLU(0.2, inplace=True), # 進行非線性映射nn.Linear(512, 256), # 輸入特征數為512,輸出為256nn.LeakyReLU(0.2, inplace=True), # 進行非線性映射nn.Linear(256, 1), # 輸入特征數為256,輸出為1nn.Sigmoid(), # sigmoid是一個激活函數,二分類問題中可將實數映射到[0, 1],作為概率值, 多分類用softmax函數)def forward(self, img):img_flat = img.view(img.size(0), -1) # 鑒別器輸入是一個被view展開的(784)的一維圖像:(64, 784)validity = self.model(img_flat) # 通過鑒別器網絡return validity
五、訓練模型并保存
## 創建生成器,判別器對象
generator = Generator()
discriminator = Discriminator()## 首先需要定義loss的度量方式 (二分類的交叉熵)
criterion = torch.nn.BCELoss()## 其次定義 優化函數,優化函數的學習率為0.0003
## betas:用于計算梯度以及梯度平方的運行平均值的系數
optimizer_G = torch.optim.Adam(generator.parameters(), lr=lr, betas=(b1, b2))
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=lr, betas=(b1, b2))## 如果有顯卡,都在cuda模式中運行
if torch.cuda.is_available():generator = generator.cuda()discriminator = discriminator.cuda()criterion = criterion.cuda()## 進行多個epoch的訓練
for epoch in range(n_epochs): # epoch:50for i, (imgs, _) in enumerate(dataloader): # imgs:(64, 1, 28, 28) _:label(64)## =============================訓練判別器==================## view(): 相當于numpy中的reshape,重新定義矩陣的形狀, 相當于reshape(128,784) 原來是(128, 1, 28, 28)imgs = imgs.view(imgs.size(0), -1) # 將圖片展開為28*28=784 imgs:(64, 784)real_img = Variable(imgs).cuda() # 將tensor變成Variable放入計算圖中,tensor變成variable之后才能進行反向傳播求梯度real_label = Variable(torch.ones(imgs.size(0), 1)).cuda() ## 定義真實的圖片label為1fake_label = Variable(torch.zeros(imgs.size(0), 1)).cuda() ## 定義假的圖片的label為0## ---------------------## Train Discriminator## 分為兩部分:1、真的圖像判別為真;2、假的圖像判別為假## ---------------------## 計算真實圖片的損失real_out = discriminator(real_img) # 將真實圖片放入判別器中loss_real_D = criterion(real_out, real_label) # 得到真實圖片的lossreal_scores = real_out # 得到真實圖片的判別值,輸出的值越接近1越好## 計算假的圖片的損失## detach(): 從當前計算圖中分離下來避免梯度傳到G,因為G不用更新z = Variable(torch.randn(imgs.size(0), latent_dim)).cuda() ## 隨機生成一些噪聲, 大小為(128, 100)fake_img = generator(z).detach() ## 隨機噪聲放入生成網絡中,生成一張假的圖片。 fake_out = discriminator(fake_img) ## 判別器判斷假的圖片loss_fake_D = criterion(fake_out, fake_label) ## 得到假的圖片的lossfake_scores = fake_out ## 得到假圖片的判別值,對于判別器來說,假圖片的損失越接近0越好## 損失函數和優化loss_D = loss_real_D + loss_fake_D # 損失包括判真損失和判假損失optimizer_D.zero_grad() # 在反向傳播之前,先將梯度歸0loss_D.backward() # 將誤差反向傳播optimizer_D.step() # 更新參數## -----------------## Train Generator## 原理:目的是希望生成的假的圖片被判別器判斷為真的圖片,## 在此過程中,將判別器固定,將假的圖片傳入判別器的結果與真實的label對應,## 反向傳播更新的參數是生成網絡里面的參數,## 這樣可以通過更新生成網絡里面的參數,來訓練網絡,使得生成的圖片讓判別器以為是真的, 這樣就達到了對抗的目的## -----------------z = Variable(torch.randn(imgs.size(0), latent_dim)).cuda() ## 得到隨機噪聲fake_img = generator(z) ## 隨機噪聲輸入到生成器中,得到一副假的圖片output = discriminator(fake_img) ## 經過判別器得到的結果## 損失函數和優化loss_G = criterion(output, real_label) ## 得到的假的圖片與真實的圖片的label的lossoptimizer_G.zero_grad() ## 梯度歸0loss_G.backward() ## 進行反向傳播optimizer_G.step() ## step()一般用在反向傳播后面,用于更新生成網絡的參數## 打印訓練過程中的日志## item():取出單元素張量的元素值并返回該值,保持原元素類型不變if (i + 1) % 300 == 0:print("[Epoch %d/%d] [Batch %d/%d] [D loss: %f] [G loss: %f] [D real: %f] [D fake: %f]"% (epoch, n_epochs, i, len(dataloader), loss_D.item(), loss_G.item(), real_scores.data.mean(), fake_scores.data.mean()))## 保存訓練過程中的圖像batches_done = epoch * len(dataloader) + iif batches_done % sample_interval == 0:save_image(fake_img.data[:25], "./images/%d.png" % batches_done, nrow=5, normalize=True)## 保存模型
torch.save(generator.state_dict(), './save/generator.pth')
torch.save(discriminator.state_dict(), './save/discriminator.pth')
總結:
本項目中,我們實現了一個基于 MNIST 數據集的生成對抗網絡(GAN),主要流程從參數配置、數據準備,到模型構建與訓練,最后再到結果保存,形成了一個完整的生成式模型訓練管線。
首先,在超參數設置上,我們采用了經典 GAN 論文中推薦的組合:學習率設為 0.0002,Adam 優化器的 \beta_1 和 \beta_2 分別為 0.5 和 0.999,訓練 50 個周期,批量大小為 64。這些參數能在保證穩定訓練的同時,加快收斂速度。
數據部分選用了MNIST 手寫數字集,先將像素歸一化到 [-1, 1],再通過 DataLoader 按批次讀取并打亂順序。這一處理不僅保證了輸入分布的穩定性,也提升了訓練效率。
在模型結構方面,判別器(D)是一個多層全連接網絡,將 28×28 圖像展平為 784 維向量后輸入,激活函數使用 LeakyReLU,最后通過 Sigmoid 得到真假概率;生成器(G)則以 100 維隨機噪聲為輸入,經過多層全連接與 ReLU/Tanh 激活,輸出與真實圖像同尺寸的 28×28 結果。這樣的結構簡單直觀,適合入門實驗。
訓練時,判別器與生成器交替優化:
-
判別器的目標是最大化對真實圖像的判真概率、對生成圖像的判假概率;
-
生成器的目標則是讓判別器將其生成的圖像判為真。
損失函數統一采用二分類交叉熵(BCELoss),并為 D、G 分別設置優化器以避免梯度更新沖突。訓練過程中會定期輸出損失值并保存生成樣本,方便對生成效果進行直觀評估。
GAN 的核心思想,是讓生成器與判別器在對抗博弈中共同提升能力:G 學會捕捉數據分布特征,D 學會分辨真實與偽造,兩者在動態平衡中逼近真實分布。這種機制使得 GAN 特別適合用于圖像生成、數據增強和風格遷移等任務。
從實驗體驗來看,這套代碼的優點在于結構清晰、可視化直觀、參數穩定,非常適合作為學習 GAN 的起點。同時,經過適當修改,還能擴展到更復雜的生成任務,為后續的研究打下基礎。