目錄
前言
1.導入數據及數據可視化
2.構建模型
3.訓練模型
4.模型分析并生成指定圖像
總結
前言
- ???🍨?本文為🔗365天深度學習訓練營中的學習記錄博客
- 🍖?原作者:K同學啊
1.導入數據及數據可視化
from torchvision import datasets, transforms
from torch.autograd import Variable
from torchvision.utils import save_image, make_grid
from torchsummary import summary
import matplotlib.pyplot as plt
import numpy as np
import torch.nn as nn
import torch.optim as optim
import torchdevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
devicebatch_size = 128train_transform = transforms.Compose([transforms.Resize(128),transforms.ToTensor(),transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])])train_dataset = datasets.ImageFolder(root='data/data/rps/', transform=train_transform)train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True,num_workers=6)# 可視化第一個 batch 的數據
def show_images(dl):for images, _ in dl:fig, ax = plt.subplots(figsize=(10, 10))ax.set_xticks([]); ax.set_yticks([])ax.imshow(make_grid(images.detach(), nrow=16).permute(1, 2, 0))breakshow_images(train_loader)
2.構建模型
latent_dim = 100
n_classes = 3
embedding_dim = 100# 自定義權重初始化函數,用于初始化生成器和判別器的權重
def weights_init(m):# 獲取當前層的類名classname = m.__class__.__name__# 如果當前層是卷積層(類名中包含 'Conv' )if classname.find('Conv') != -1:# 使用正態分布隨機初始化權重,均值為0,標準差為0.02torch.nn.init.normal_(m.weight, 0.0, 0.02)# 如果當前層是批歸一化層(類名中包含 'BatchNorm' )elif classname.find('BatchNorm') != -1:# 使用正態分布隨機初始化權重,均值為1,標準差為0.02torch.nn.init.normal_(m.weight, 1.0, 0.02)# 將偏置項初始化為全零torch.nn.init.zeros_(m.bias)class Generator(nn.Module):def __init__(self):super(Generator, self).__init__()# 定義條件標簽的生成器部分,用于將標簽映射到嵌入空間中# n_classes:條件標簽的總數# embedding_dim:嵌入空間的維度self.label_conditioned_generator = nn.Sequential(nn.Embedding(n_classes, embedding_dim), # 使用Embedding層將條件標簽映射為稠密向量nn.Linear(embedding_dim, 16) # 使用線性層將稠密向量轉換為更高維度)# 定義潛在向量的生成器部分,用于將噪聲向量映射到圖像空間中# latent_dim:潛在向量的維度self.latent = nn.Sequential(nn.Linear(latent_dim, 4*4*512), # 使用線性層將潛在向量轉換為更高維度nn.LeakyReLU(0.2, inplace=True) # 使用LeakyReLU激活函數進行非線性映射)# 定義生成器的主要結構,將條件標簽和潛在向量合并成生成的圖像self.model = nn.Sequential(# 反卷積層1:將合并后的向量映射為64x8x8的特征圖nn.ConvTranspose2d(513, 64*8, 4, 2, 1, bias=False),nn.BatchNorm2d(64*8, momentum=0.1, eps=0.8), # 批標準化nn.ReLU(True), # ReLU激活函數# 反卷積層2:將64x8x8的特征圖映射為64x4x4的特征圖nn.ConvTranspose2d(64*8, 64*4, 4, 2, 1, bias=False),nn.BatchNorm2d(64*4, momentum=0.1, eps=0.8),nn.ReLU(True),# 反卷積層3:將64x4x4的特征圖映射為64x2x2的特征圖nn.ConvTranspose2d(64*4, 64*2, 4, 2, 1, bias=False),nn.BatchNorm2d(64*2, momentum=0.1, eps=0.8),nn.ReLU(True),# 反卷積層4:將64x2x2的特征圖映射為64x1x1的特征圖nn.ConvTranspose2d(64*2, 64*1, 4, 2, 1, bias=False),nn.BatchNorm2d(64*1, momentum=0.1, eps=0.8),nn.ReLU(True),# 反卷積層5:將64x1x1的特征圖映射為3x64x64的RGB圖像nn.ConvTranspose2d(64*1, 3, 4, 2, 1, bias=False),nn.Tanh() # 使用Tanh激活函數將生成的圖像像素值映射到[-1, 1]范圍內)def forward(self, inputs):noise_vector, label = inputs# 通過條件標簽生成器將標簽映射為嵌入向量label_output = self.label_conditioned_generator(label)# 將嵌入向量的形狀變為(batch_size, 1, 4, 4),以便與潛在向量進行合并label_output = label_output.view(-1, 1, 4, 4)# 通過潛在向量生成器將噪聲向量映射為潛在向量latent_output = self.latent(noise_vector)# 將潛在向量的形狀變為(batch_size, 512, 4, 4),以便與條件標簽進行合并latent_output = latent_output.view(-1, 512, 4, 4)# 將條件標簽和潛在向量在通道維度上進行合并,得到合并后的特征圖concat = torch.cat((latent_output, label_output), dim=1)# 通過生成器的主要結構將合并后的特征圖生成為RGB圖像image = self.model(concat)return image
generator = Generator().to(device)
generator.apply(weights_init)
print(generator)from torchinfo import summarysummary(generator)a = torch.ones(100)
b = torch.ones(1)
b = b.long()
a = a.to(device)
b = b.to(device)import torch
import torch.nn as nnclass Discriminator(nn.Module):def __init__(self):super(Discriminator, self).__init__()# 定義一個條件標簽的嵌入層,用于將類別標簽轉換為特征向量self.label_condition_disc = nn.Sequential(nn.Embedding(n_classes, embedding_dim), # 嵌入層將類別標簽編碼為固定長度的向量nn.Linear(embedding_dim, 3*128*128) # 線性層將嵌入的向量轉換為與圖像尺寸相匹配的特征張量)# 定義主要的鑒別器模型self.model = nn.Sequential(nn.Conv2d(6, 64, 4, 2, 1, bias=False), # 輸入通道為6(包含圖像和標簽的通道數),輸出通道為64,4x4的卷積核,步長為2,padding為1nn.LeakyReLU(0.2, inplace=True), # LeakyReLU激活函數,帶有負斜率,增加模型對輸入中的負值的感知能力nn.Conv2d(64, 64*2, 4, 3, 2, bias=False), # 輸入通道為64,輸出通道為64*2,4x4的卷積核,步長為3,padding為2nn.BatchNorm2d(64*2, momentum=0.1, eps=0.8), # 批量歸一化層,有利于訓練穩定性和收斂速度nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(64*2, 64*4, 4, 3, 2, bias=False), # 輸入通道為64*2,輸出通道為64*4,4x4的卷積核,步長為3,padding為2nn.BatchNorm2d(64*4, momentum=0.1, eps=0.8),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(64*4, 64*8, 4, 3, 2, bias=False), # 輸入通道為64*4,輸出通道為64*8,4x4的卷積核,步長為3,padding為2nn.BatchNorm2d(64*8, momentum=0.1, eps=0.8),nn.LeakyReLU(0.2, inplace=True),nn.Flatten(), # 將特征圖展平為一維向量,用于后續全連接層處理nn.Dropout(0.4), # 隨機失活層,用于減少過擬合風險nn.Linear(4608, 1), # 全連接層,將特征向量映射到輸出維度為1的向量nn.Sigmoid() # Sigmoid激活函數,用于輸出范圍限制在0到1之間的概率值)def forward(self, inputs):img, label = inputs# 將類別標簽轉換為特征向量label_output = self.label_condition_disc(label)# 重塑特征向量為與圖像尺寸相匹配的特征張量label_output = label_output.view(-1, 3, 128, 128)# 將圖像特征和標簽特征拼接在一起作為鑒別器的輸入concat = torch.cat((img, label_output), dim=1)# 將拼接后的輸入通過鑒別器模型進行前向傳播,得到輸出結果output = self.model(concat)return outputdiscriminator = Discriminator().to(device)
discriminator.apply(weights_init)
print(discriminator)summary(discriminator)a = torch.ones(2,3,128,128)
b = torch.ones(2,1)
b = b.long()
a = a.to(device)
b = b.to(device)c = discriminator((a,b))
c.size()
3.訓練模型
adversarial_loss = nn.BCELoss() def generator_loss(fake_output, label):gen_loss = adversarial_loss(fake_output, label)return gen_lossdef discriminator_loss(output, label):disc_loss = adversarial_loss(output, label)return disc_losslearning_rate = 0.0002G_optimizer = optim.Adam(generator.parameters(), lr = learning_rate, betas=(0.5, 0.999))
D_optimizer = optim.Adam(discriminator.parameters(), lr = learning_rate, betas=(0.5, 0.999))# 設置訓練的總輪數
num_epochs = 300
# 初始化用于存儲每輪訓練中判別器和生成器損失的列表
D_loss_plot, G_loss_plot = [], []# 循環進行訓練
for epoch in range(1, num_epochs + 1):# 初始化每輪訓練中判別器和生成器損失的臨時列表D_loss_list, G_loss_list = [], []# 遍歷訓練數據加載器中的數據for index, (real_images, labels) in enumerate(train_loader):# 清空判別器的梯度緩存D_optimizer.zero_grad()# 將真實圖像數據和標簽轉移到GPU(如果可用)real_images = real_images.to(device)labels = labels.to(device)# 將標簽的形狀從一維向量轉換為二維張量(用于后續計算)labels = labels.unsqueeze(1).long()# 創建真實目標和虛假目標的張量(用于判別器損失函數)real_target = Variable(torch.ones(real_images.size(0), 1).to(device))fake_target = Variable(torch.zeros(real_images.size(0), 1).to(device))# 計算判別器對真實圖像的損失D_real_loss = discriminator_loss(discriminator((real_images, labels)), real_target)# 從噪聲向量中生成假圖像(生成器的輸入)noise_vector = torch.randn(real_images.size(0), latent_dim, device=device)noise_vector = noise_vector.to(device)generated_image = generator((noise_vector, labels))# 計算判別器對假圖像的損失(注意detach()函數用于分離生成器梯度計算圖)output = discriminator((generated_image.detach(), labels))D_fake_loss = discriminator_loss(output, fake_target)# 計算判別器總體損失(真實圖像損失和假圖像損失的平均值)D_total_loss = (D_real_loss + D_fake_loss) / 2D_loss_list.append(D_total_loss.item())# 反向傳播更新判別器的參數D_total_loss.backward()D_optimizer.step()# 清空生成器的梯度緩存G_optimizer.zero_grad()# 計算生成器的損失G_loss = generator_loss(discriminator((generated_image, labels)), real_target)G_loss_list.append(G_loss.item())# 反向傳播更新生成器的參數G_loss.backward()G_optimizer.step()# 打印當前輪次的判別器和生成器的平均損失print('Epoch: [%d/%d]: D_loss: %.3f, G_loss: %.3f' % ((epoch), num_epochs, torch.mean(torch.FloatTensor(D_loss_list)), torch.mean(torch.FloatTensor(G_loss_list))))# 將當前輪次的判別器和生成器的平均損失保存到列表中D_loss_plot.append(torch.mean(torch.FloatTensor(D_loss_list)))G_loss_plot.append(torch.mean(torch.FloatTensor(G_loss_list)))if epoch%10 == 0:# 將生成的假圖像保存為圖片文件save_image(generated_image.data[:50], './images/sample_%d' % epoch + '.png', nrow=5, normalize=True)# 將當前輪次的生成器和判別器的權重保存到文件torch.save(generator.state_dict(), './training_weights/generator_epoch_%d.pth' % (epoch))torch.save(discriminator.state_dict(), './training_weights/discriminator_epoch_%d.pth' % (epoch))
4.模型分析并生成指定圖像
G_loss_list = [i.item() for i in G_loss_plot]
D_loss_list = [i.item() for i in D_loss_plot]import matplotlib.pyplot as plt
#隱藏警告
import warnings
warnings.filterwarnings("ignore") #忽略警告信息
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用來正常顯示中文標簽
plt.rcParams['axes.unicode_minus'] = False # 用來正常顯示負號
plt.rcParams['figure.dpi'] = 100 #分辨率plt.figure(figsize=(8,4))
plt.title("Generator and Discriminator Loss During Training")
plt.plot(G_loss_list,label="G")
plt.plot(D_loss_list,label="D")
plt.xlabel("iterations")
plt.ylabel("Loss")
plt.legend()
plt.show()# 導入所需的庫
from numpy.random import randint, randn
from numpy import linspace
from matplotlib import pyplot, gridspec# 導入生成器模型
generator.load_state_dict(torch.load('./training_weights/generator_epoch_300.pth'), strict=False)
generator.eval() interpolated = randn(100) # 生成兩個潛在空間的點
# 將數據轉換為torch張量并將其移至GPU(假設device已正確聲明為GPU)
interpolated = torch.tensor(interpolated).to(device).type(torch.float32)label = 0 # 手勢標簽,可在0,1,2之間選擇
labels = torch.ones(1) * label
labels = labels.to(device).unsqueeze(1).long()# 使用生成器生成插值結果
predictions = generator((interpolated, labels))
predictions = predictions.permute(0,2,3,1).detach().cpu()#隱藏警告
import warnings
warnings.filterwarnings("ignore") #忽略警告信息
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用來正常顯示中文標簽
plt.rcParams['axes.unicode_minus'] = False # 用來正常顯示負號
plt.rcParams['figure.dpi'] = 100 #分辨率plt.figure(figsize=(8, 3))pred = (predictions[0, :, :, :] + 1 ) * 127.5
pred = np.array(pred)
plt.imshow(pred.astype(np.uint8))
plt.show()
總結
在本次實驗中,我們完成了數據準備、模型構建、訓練與結果分析的完整流程。
數據準備方面,采用 torchvision.datasets.ImageFolder 從自定義文件夾中加載圖像數據,并依次進行縮放、張量化和歸一化等預處理操作。為了直觀了解輸入情況,利用 show_images 函數展示了訓練集中首個批次的圖像,這有助于確認數據管道是否正常。
模型設計部分包括生成器和判別器兩個核心模塊。生成器接收潛在向量與類別標簽作為輸入,其中類別標簽先經過嵌入層映射為特征向量,再與潛在向量結合,經由多層反卷積逐步生成目標圖像。判別器則接收圖像和類別標簽作為輸入,將標簽轉化為特征圖并與圖像拼接,隨后通過多層卷積進行特征提取,最終輸出一個標量用于判別真偽。此外,使用自定義的 weights_init 方法對模型參數進行初始化,保證了訓練的穩定性。
在訓練過程中,損失函數選擇了二元交叉熵(BCELoss),優化器采用 Adam,對生成器與判別器分別進行參數更新。具體流程為:在每個 epoch 內,判別器和生成器交替更新;對于每個 batch,先更新判別器,使其更好地區分真實與偽造圖像,再更新生成器,使其生成的結果更具迷惑性。訓練過程中,每隔 10 個 epoch 保存生成樣本和模型權重,方便后續分析與復現。
結果分析環節,通過記錄生成器和判別器的損失曲線,可以直觀反映訓練進展和收斂趨勢。隨著對抗博弈的進行,生成器不斷提高欺騙判別器的能力,而判別器也在努力提升辨別水平,二者的競爭推動了模型整體性能的提升。