動漫頭像素材,項目中新建data文件夾放進去
具體思路是,生成器是將一個噪點生成一副假圖片,然後將假圖片傳給判別器進行判斷,如果判別器判斷爲真,則代碼生成器性能很好,而判別器是從真實圖片中學習模型,對生成的假圖片進行判斷,如果判斷出來爲假則代碼判別器性能很好。
關於代碼的具體註釋已經寫上,需要單獨開博客講的內容在前幾篇博客裏寫了,直接上代碼吧:
train.py主運行頁面:
import argparse
import torch
import torchvision
import torchvision.utils as vutils
import torch.nn as nn
from model import NetD, NetG
# 定義是否使用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") #判斷是否能用GPU
# 圖像讀入與預處理
transforms = torchvision.transforms.Compose([
torchvision.transforms.Resize(96), #處理圖像尺寸,短邊變爲96,長邊對應比例縮放
torchvision.transforms.ToTensor(), #數據集加載時,默認的圖片格式是 numpy,所以通過 transforms 轉換成 Tensor,圖像範圍[0, 255] -> [0.0,1.0]
torchvision.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), ]) #使用公式進行歸一化channel=(channel-mean)/std,因爲transforms.ToTensor()已經把數據處理成[0,1],那麼(x-0.5)/0.5就是[-1.0, 1.0]
dataset = torchvision.datasets.ImageFolder('data/', transform=transforms)
dataloader = torch.utils.data.DataLoader(
dataset=dataset,
batch_size=64, #因爲需要生成包含64張小圖的大圖片,所以把64定位一個批度
shuffle=True,
drop_last=True, #如何處理數據集長度除於batch_size餘下的數據。True就拋棄,否則保留
)
netG = NetG(64, 100).to(device) #操作過程中通道數都是64的倍數,最後由64一下子變爲3,100是噪聲維度,噪聲用來生成假圖片
netD = NetD(64).to(device) #操作過程中通道數都是64的倍數,最後由64一下子變爲1
criterion = nn.BCELoss() #用於二分類
optimizerG = torch.optim.Adam(netG.parameters(), lr=0.0002, betas=(0.5, 0.999)) #生成器的優化器
optimizerD = torch.optim.Adam(netD.parameters(), lr=0.0002, betas=(0.5, 0.999)) #判別器的優化器
label = torch.FloatTensor(64) #創建一個元素個數爲64的tensor標籤,隨機賦值
real_label = 1 #真圖片標籤爲1
fake_label = 0 #假圖片標籤爲0
for epoch in range(1, 25 + 1):
for i, (imgs, _) in enumerate(dataloader):
# 固定生成器G,訓練鑑別器D(判別器的好壞要通過是否能判斷出假圖片來體現)
optimizerD.zero_grad() #判別器優化器梯度全部降爲0
## 讓D儘可能的把真圖片判別爲1
imgs = imgs.to(device) #將圖片數據copy一份到device指定的GPU上去
output = netD(imgs) #判別器輸出
label.data.fill_(real_label) #標籤全部改爲1,一開始判斷真實圖片
label = label.to(device) #將labelcopy放入GPU
output=output.squeeze() #output.shape爲(64,1,1,1)維度,label爲(64),這一句是將output中所有維度爲一的去掉,不加這一句運行不會出錯,但是會有警告
errD_real = criterion(output, label) #計算判斷真實圖片的損失值
errD_real.backward() #反向傳播
## 讓D儘可能把假圖片判別爲0
label.data.fill_(fake_label) #標籤全部改爲0,一開始假圖片
noise = torch.randn(64, 100, 1, 1) #隨機生成64個噪點,每個噪點維度爲(100,1,1)
noise = noise.to(device) #將噪點放入GPU
fake = netG(noise) # 生成假圖
output = netD(fake.detach()) # 使用判別器對一個批次假圖片進行分類
output=output.squeeze()
errD_fake = criterion(output, label) #計算判斷假圖片爲假的損失值
errD_fake.backward() #反向傳播
errD = errD_fake + errD_real #判斷真圖片和判斷假圖片的損失值加和作爲總損失
optimizerD.step() #對判別器進行優化
# 固定鑑別器D,訓練生成器G(生成器性能的好壞要通過判別器是否判斷爲真來體現)
optimizerG.zero_grad() #生成器梯度全部降爲0
# 讓D儘可能把G生成的假圖判別爲1
label.data.fill_(real_label) #標籤全部改爲1,一開始判斷真實圖片
label = label.to(device)
output = netD(fake) #判別剛纔生成的假圖片
output=output.squeeze()
errG = criterion(output, label) #計算判斷假圖片爲真的損失值
errG.backward() #反向傳播
optimizerG.step() #對生成器進行優化
print('[%d/%d][%d/%d] Loss_D: %.3f Loss_G %.3f'
% (epoch, 25, i, len(dataloader), errD.item(), errG.item()))
vutils.save_image(fake.data,
'%s/fake_samples_epoch_%03d.png' % ('imgs/', epoch), #保存圖像
normalize=True)
model.py模型結構頁面:
import torch.nn as nn
# 定義生成器網絡G,從噪聲中生成一張彩色圖片
class NetG(nn.Module): #注意這裏圖片都是四維,第一維都是批度64,爲了方便將第一個省去
def __init__(self, ngf, nz): #操作過程中通道數都是ngf的倍數,最後由ngf一下子變爲3,nz是噪點維度,噪聲用來生成假圖片
super(NetG, self).__init__()
# layer1輸入的是一個100x1x1的隨機噪聲, 輸出尺寸(ngf*8)x4x4
self.layer1 = nn.Sequential( #(100,1,1)-->(64*8,4,4)
nn.ConvTranspose2d(nz, ngf * 8, kernel_size=4, stride=1, padding=0, bias=False), #o=(i-1)*s-2*p+k
nn.BatchNorm2d(ngf * 8), #參數爲輸出通道數
nn.ReLU(inplace=True) #implace=True是原地操作,直接替換掉以前的變量,比如x=x+5
)
# layer2輸出尺寸(ngf*4)x8x8
self.layer2 = nn.Sequential( #(64*8,4,4)-->(64*4,8,8)
nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf * 4),
nn.ReLU(inplace=True)
)
# layer3輸出尺寸(ngf*2)x16x16
self.layer3 = nn.Sequential( #(64*4,8,8)-->(64*2,16,16)
nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf * 2),
nn.ReLU(inplace=True)
)
# layer4輸出尺寸(ngf)x32x32
self.layer4 = nn.Sequential( #(64*2,16,16)-->(64,32,32)
nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf),
nn.ReLU(inplace=True)
)
# layer5輸出尺寸 3x96x96
self.layer5 = nn.Sequential( #(64,32,32)-->(3,96,96)
nn.ConvTranspose2d(ngf, 3, 5, 3, 1, bias=False),
nn.Tanh()
)
# 定義NetG的前向傳播
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = self.layer5(out)
return out
# 定義鑑別器網絡D
class NetD(nn.Module):
def __init__(self, ndf): #操作過程中圖片的通道數一直是ndf的倍數
super(NetD, self).__init__()
# layer1 輸入 3 x 96 x 96, 輸出 (ndf) x 32 x 32
self.layer1 = nn.Sequential( #(3,96,96)-->(64,32,32)
nn.Conv2d(3, ndf, kernel_size=5, stride=3, padding=1, bias=False),
nn.BatchNorm2d(ndf),
nn.LeakyReLU(0.2, inplace=True) #relu中f=maxy(0,x),而leakyrelu中f=x>0?x:ax(a=欄目大)
)
# layer2 輸出 (ndf*2) x 16 x 16
self.layer2 = nn.Sequential( #(64,32,32)-->(128,16,16)
nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 2),
nn.LeakyReLU(0.2, inplace=True)
)
# layer3 輸出 (ndf*4) x 8 x 8
self.layer3 = nn.Sequential( #(128,16,16)-->(256,8,8)
nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 4),
nn.LeakyReLU(0.2, inplace=True)
)
# layer4 輸出 (ndf*8) x 4 x 4
self.layer4 = nn.Sequential( #(256,8,8)-->(512,4,4)
nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 8),
nn.LeakyReLU(0.2, inplace=True)
)
# layer5 輸出一個數(概率)
self.layer5 = nn.Sequential( #(512,4,4)-->(1,1,1)
nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
nn.Sigmoid()
)
# 定義NetD的前向傳播
def forward(self,x):
out = self.layer1(x)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = self.layer5(out)
return out
生成的假圖片:
epoch=1:
epoch=10:
epoch=20: