尋夢新聞LINE@每日推播熱門推薦文章,趣聞不漏接❤️
生成式對抗網路(Generative Adversarial Network, GAN)是近些年計算機視覺領域非常常見的一類方法,其強大的從已有數據集中生成新數據的能力令人驚嘆,甚至連人眼都無法進行分辨。本文將會介紹基於最原始的DCGAN的動漫人物生成任務,通過定義生成器和判別器,並讓這兩個網路在參數優化過程中不斷「打架」,最終得到較好的生成結果。
01
生成動漫人物任務概述
日本動漫中會出現很多的卡通人物,這些卡通人物都是漫畫家花費大量的時間設計繪制出來的,那麼,假設已經有了一個卡通人物的集合,那麼深度學習技術可否幫助漫畫家們根據已有的動漫人物形象,設計出新的動漫人物形象呢?
本文使用的數據集包含已經裁減完成的頭像如圖 17-1所示,每張圖像的大小為96*96*3像素,總數為51000張。
圖 17-1 動漫人物數據集
數據集鏈接:
https://pan.baidu.com/s/1P7cpoN2vlvXBsTD8y7_yuQ
提取碼:k33j
這項任務與之前的有監督任務不同之處在於,監督任務是有明確的輸入和輸出來對模型進行優化調整,而這一項任務是基於已有的數據集生成新的與原有數據集相似的新的數據。這是一個典型生成式任務,即假設原始數據集中所有的動漫圖像都服從於某一分布,數據集中的圖片是從這個分布隨機采樣得到的,倘若可以獲得這個分布是什麼,那麼就可以獲得與數據集中圖片分布相同但完全不同的新的動漫形象。因此,生成式任務最重要核心任務就在於如何去獲得這個分布。現有的基於圖像的生成式框架有VAE和GAN兩大分支,GAN的大名想必很多人都有所耳聞,其實驗效果也是要由於VAE分支,本文將介紹基於GAN的動漫人物生成任務。
02
反卷積網路
反卷積層是GAN網路的非常重要的一個部件。大多數卷積層會使特征圖的尺寸不斷變小,但反卷積層是為了使得特征圖逐漸變大,甚至與最初的輸入圖片一致。反卷積層最開始用於分割任務,後來也被廣泛應用於生成式任務中,如圖 17-2所示,為一個反卷積層的正向傳播時的計算過程,下層藍色色塊的為輸入,白色虛線色塊為padding的部分,上層綠色的為反卷積層的輸出,原本3×3大小的特征圖經過反卷積可以得到5×5的輸出。本文的網路結構中也使用了反卷積層作為重要的一環。
圖 17-2 反卷積示意圖
在分類或者分割等計算機視覺的任務當中,最終損失函數都需要對網路的輸出與標簽的差異進行量化,比如常見的L1、L2、交叉熵等損失函數,那麼在生成式任務當中,當網路輸出一張新的圖片,如何去評判這張圖片與原始數據集的分布是否一致?這是非常困難的一項事情,而GAN用很巧妙的思路規避了直接去判斷分布是否一致,通過引入另一個網路(判別器)實現了判斷兩張圖片是否一致這一任務。
具體來說,假設原始的分布為 Pdata(X) , PG(X;θ) 指參數值為θ的卷積網路,其以隨機數x作為初入,輸出一張圖像,該卷積網路稱為生成器,根據最大似然定理,希望每個樣例出現的概率的乘積最大,即最大化:
對 θ 進行求解,可得:
即GAN的生成器目標是找到 PG(X;θ)的一組參數,使其接近 Pdata(X)分布,從而最小化生成器G生成結果與原始數據之間的差異
,
為了解決這個問題,GAN引入了判別器的概念,使用判別器 D(X),來判斷 PG(X;θ)生成的結果與 Pdata(X)分布是否一致,判別器的目標是給真樣本獎勵,假樣本懲罰,判別器的目的在於盡可能的區分生成器生成的樣本與數據集的樣本,當輸入為數據集的樣本時,判別器輸出為真,當輸入為生成器生成的樣本時,判別器輸出為假,GAN的結構如圖 17-3所示。
圖 17-3 GAN模型結構
判別器希望最大化的目標函數,就是
,
這一優化目標與交叉熵函數的形式非常相似,需要注意的是,在優化判別器時,生成器中的參數是不變的。生成器與判別器的目標不同,由於沒有像監督學習那樣的標簽用於生成器,因此,生成器的目標為盡可能的騙過判別器,使判別器認為生成器生成的樣本與原始數據集分布一致,即生成器的目標函數為
至此,GAN的損失函數可寫為
03
DCGAN
本文中使用DCGAN作為網路模型,其核心思想與GAN一致,只是將原始GAN的多層感知器替換為了卷積神經網路,從而更符合圖像的性質。下面介紹DCGAN的結構。
圖 17-4 DCGAN生成器網路結構
如圖 17-4可知,DCGAN的生成器從一個100維的隨機變量開始,不斷疊加使用反卷積層,最終得到的64*64*3的輸出層。
其判別器為一個5層的卷積結構,以64*64*3大小作為輸入,單獨一個值作為輸出,為輸入判別器的圖像與數據集圖像同分布的概率。
訓練步驟與損失函數與上文中GAN的一致,通過交替更新參數的方式,使生成器和判別器逐漸收斂。在下文中將具體介紹如何構建DCGAN並實現動漫人物生成。
04
基於DCGAN的動漫人物生成
新建GanModel.py文件,並在這個腳本中構建DCGAN的生成器和判別器模型,首先是生成器模型,由於本數據集的圖片大小為96*96,因此對原始DCGAN的參數做了一些調整,使得最終經過生成器得到的圖片大小也是96*96。
如代碼清單 17-1所示為經過調整後的生成器網路,同樣包含有5層,出去最後一層,每層中都有一個卷積層、一個歸一化層以及一個激活函數。
代碼清單 17-1 調整後的生成器網路
1.import torch.nn as nn
2.# 定義生成器網路G
3.class Generator(nn.Module):
4. def __init__(self, nz=100):
5. super(Generator, self). __init__
6. # layer1輸入的是一個100x1x1的隨機噪聲, 輸出尺寸1024x4x4
7. self.layer1 = nn.Sequential(
8. nn.ConvTranspose2d(nz, 1024, kernel_size=4, stride=1, padding=0, bias=False),
9. nn.BatchNorm2d(1024),
10. nn.ReLU(inplace=True)
11. )
12. # layer2輸出尺寸512x8x8
13. self.layer2 = nn.Sequential(
14. nn.ConvTranspose2d(1024, 512, 4, 2, 1, bias=False),
15. nn.BatchNorm2d(512),
16. nn.ReLU(inplace=True)
17. )
18. # layer3輸出尺寸256x16x16
19. self.layer3 = nn.Sequential(
20. nn.ConvTranspose2d(512, 256, 4, 2, 1, bias=False),
21. nn.BatchNorm2d(256),
22. nn.ReLU(inplace=True)
23. )
24. # layer4輸出尺寸128x32x32
25. self.layer4 = nn.Sequential(
26. nn.ConvTranspose2d(256, 128, 4, 2, 1, bias=False),
27. nn.BatchNorm2d(128),
28. nn.ReLU(inplace=True)
29. )
30. # layer5輸出尺寸 3x96x96
31. self.layer5 = nn.Sequential(
32. nn.ConvTranspose2d(128, 3, 5, 3, 1, bias=False),
33. nn.Tanh
34. )
35.
36. # 定義Generator的前向傳播
37. def forward(self, x):
38. out = self.layer1(x)
39. out = self.layer2(out)
40. out = self.layer3(out)
41. out = self.layer4(out)
42. out = self.layer5(out)
43. return out
定義判別器模型及前向傳播過程如代碼清單 17-2所示。
代碼清單 17-2 判別器模型的定義與前向傳播過程
1.# 定義辨別器網路D
2.class Discriminator(nn.Module):
3. def __init__(self):
4. super(Discriminator, self). __init__
5. # layer1 輸入 3 x 96 x 96, 輸出 64 x 32 x 32
6. self.layer1 = nn.Sequential(
7. nn.Conv2d(3, 64, kernel_size=5, stride=3, padding=1, bias=False),
8. nn.BatchNorm2d(64),
9. nn.LeakyReLU(0.2, inplace=True)
10. )
11. # layer2 輸出 128 x 16 x 16
12. self.layer2 = nn.Sequential(
13. nn.Conv2d(64, 128, 4, 2, 1, bias=False),
14. nn.BatchNorm2d(128),
15. nn.LeakyReLU(0.2, inplace=True)
16. )
17. # layer3 輸出 256 x 8 x 8
18. self.layer3 = nn.Sequential(
19. nn.Conv2d(128, 256, 4, 2, 1, bias=False),
20. nn.BatchNorm2d(256),
21. nn.LeakyReLU(0.2, inplace=True)
22. )
23. # layer4 輸出 512 x 4 x 4
24. self.layer4 = nn.Sequential(
25. nn.Conv2d(256, 512, 4, 2, 1, bias=False),
26. nn.BatchNorm2d(512),
27. nn.LeakyReLU(0.2, inplace=True)
28. )
29. # layer5 輸出預測結果概率
30. self.layer5 = nn.Sequential(
31. nn.Conv2d(512, 1, 4, 1, 0, bias=False),
32. nn.Sigmoid
33. )
34.
35. # 前向傳播
36. def forward(self, x):
37. out = self.layer1(x)
38. out = self.layer2(out)
39. out = self.layer3(out)
40. out = self.layer4(out)
41. out = self.layer5(out)
42. return out
定義完模型的基本結構後,新建另一個python腳本DCGAN.py,並將數據集放在同一目錄下。如代碼清單 17-3所示,首先是引入會用到的各種包以及超參數,將超參數寫在最前面方便後續需要修改的時候進行調整。其中超參數主要包含,一次迭代的batchsize大小,這個參數視GPU的性能而定,一般建議8以上,如果顯存足夠大,可以增大batchsize,batchsize越大,訓練的速度也會越快。ImageSize為輸入的圖片大小,Epoch為訓練要在數據集上訓練幾個輪次,Lr是優化器最開始的學習率的大小,Beta1為Adam優化器的一階矩估計的指數衰減率,以及DataPath為數據集存放位置,OutPath為最終結果存放位置。
代碼清單 17-3 DCGAN超參數定義
1.importtorch
2.importtorchvision
3.importtorchvision.utils asvutils
4.importtorch.nn asnn
5.fromGanModel importGenerator, Discriminator
6.
7.# 設置超參數
8.BatchSize = 8
9.ImageSize = 96
10.Epoch = 25
11.Lr = 0.0002
12.Beta1 = 0.5
13.DataPath = ‘./faces/’
14.OutPath = ‘./imgs/’
15.# 定義是否使用GPU
16.device = torch.device( “cuda”iftorch.cuda.is_available else”cpu”)
接下來定義train函數,如代碼清單 17-4所示。以數據集、生成器、辨別器作為函數輸入,首先設置優化器以及損失函數。
代碼清單 17-4 train函數定義
1.def train(netG, netD, dataloader):
2. criterion = nn.BCELoss
3. optimizerG = torch.optim.Adam(netG.parameters, lr=Lr, betas=(Beta1, 0.999))
4. optimizerD = torch.optim.Adam(netD.parameters, lr=Lr, betas=(Beta1, 0.999))
5.
6. label = torch.FloatTensor(BatchSize)
7. real_label = 1
8. fake_label = 0
再開始一輪一輪的迭代訓練並輸出中間結果,方便debug。
代碼清單 17-5 辨別器訓練
1.for epoch in range(1, Epoch + 1):
2. for i, (imgs, _) in enumerate(dataloader):
3. # 固定生成器G,訓練辨別器D
4. optimizerD.zero_grad
5. # 讓D盡可能的把真圖片判別為1
6. imgs = imgs.to(device)
7. output = netD(imgs)
8. label.data.fill _(real_label)
9. label = label.to(device)
10. errD_real = criterion(output, label)
11. errD_real.backward
12. # 讓D盡可能把假圖片判別為0
13. label.data.fill _(fake_label)
14. noise = torch.randn(BatchSize, 100, 1, 1)
15. noise = noise.to(device)
16. fake = netG(noise)
17. # 避免梯度傳到G,因為G不用更新
18. output = netD(fake.detach)
19. errD_fake = criterion(output, label)
20. errD_fake.backward
21. errD = errD _fake + errD_real
22. optimizerD.step
如代碼清單 17-5所示,首先固定生成器的參數,並隨機一組隨機數送入生成器得到一組假圖片,同時從數據集中抽取同樣數目的真圖片,假圖片對應標簽為0,真圖片對應標簽為1,將這組數據送入判別器進行參數更新。
代碼清單 17-6 生成器訓練
1. # 固定辨別器D,訓練生成器G
2. optimizerG.zero_grad
3. # 讓D盡可能把G生成的假圖判別為1
4. label.data.fill _(real_label)
5. label = label.to(device)
6. output = netD(fake)
7. errG = criterion(output, label)
8. errG.backward
9. optimizerG.step
10. if i % 50 == 0:
11. print(‘[ %d/%d][ %d/%d] Loss _D: %.3f Loss_G %.3f’
12. % (epoch, Epoch, i, len(dataloader), errD.item, errG.item))
13.
14.vutils.save_image(fake.data,
15. ‘%s/fake _samples_epoch_%03d.png’ % (OutPath, epoch),
16. normalize=True)
17.torch.save(netG.state _dict, ‘%s/netG_%03d.pth’ % (OutPath, epoch))
18.torch.save(netD.state _dict, ‘%s/netD_%03d.pth’ % (OutPath, epoch))
如代碼清單 17-6所示,接下來固定判別器參數,訓練生成器,生成器的目標是根據隨機數生成得到的圖片能夠騙過判別器,使之認為這些圖片為真,因此將生成得到的假圖經過判別器得到判別結果,並設置標簽全部為1,計算損失函數並反向傳播對生成器參數進行更新。
在訓練過程中,不斷列印生成器和判別器Loss的變化情況,從而方便進行觀察並調整參數。每訓練完一個Epoch,則將該Epoch中生成器得到的假圖保存下來,同時存儲生成器和判別器的參數,防止訓練過程突然被終止,可以使用存儲的參數進行恢復,不需要再從頭進行訓練。
最後完成mian函數主程序入口代碼的編寫,其包含了加載數據集、定義模型、訓練等步驟,如代碼清單 17-7所示。
Transforms定義了對數據集中輸入圖片進行預處理的步驟,主要包含scale對輸入圖片大小進行調整,ToTensor轉化為PyTorch的Tensor類型以及Normalize中使用均值和標準差來進行圖片的歸一化。
代碼清單 17-7 主程序
1.if __name__== ” __main__”:
2. # 圖像格式轉化與歸一化
3. transforms = torchvision.transforms.Compose([
4. torchvision.transforms.Scale(ImageSize),
5. torchvision.transforms.ToTensor,
6. torchvision.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
7. dataset = torchvision.datasets.ImageFolder(DataPath, transform=transforms)
8.
9. dataloader = torch.utils.data.DataLoader(
10. dataset=dataset,
11. batch_size=BatchSize,
12. shuffle=True,
13. drop_last=True,
14. )
15.
16. netG = Generator.to(device)
17. netD = Discriminator.to(device)
18. train(netG, netD, dataloader)
開始訓練後,在命令行可得類似於如圖 17-5的輸出。
圖 17-5 訓練過程命令行輸出
讓我們來看一下經過一個Epoch迭代後的生成器得到的結果如圖 17-6所示。
圖 17-6 Epoch1測試結果可視化
好像已經有了那麼一些輪廓,但又像戴了近視鏡一樣看不清,頗有些印象派作家的畫風,繼續訓練網路,如圖 17-7所示,等到第5,第10個Epoch,會發現生成器生成的質量越來越高。
圖 17-7 Epoch15測試結果可視化
一直到第25個Epoch,得到的結果如圖 17-8所示,盡管生成的圖片中還是存在一些結構性問題,但也有一些圖片逐漸開始接近於我們的期待。當然,本文迭代次數較少,僅有25次,若進一步升高迭代次數,最終可獲得更加真實的動漫頭像。
圖 17-8 Epoch25測試結果可視化
05
參考教材
《Python機器學習實戰-微課視訊版》
ISBN:978-7-302-57641-9
呂雲翔 王淥汀 袁琪 張凡 韓雪婷 編著
定價:59.8元
掃碼,優惠購書
選書太糾結?推薦幾本必買的 Python好書
06
精彩推薦
-
鯤鵬架構入門與實戰︱鯤鵬應用遷移(附代碼)
-
《機器學習》實驗指導書(附實驗參考+代碼)
-
Python爬蟲綜合實戰 │ 創建雲起書院爬蟲(附代碼)
-
Python爬蟲實戰 │ Email提醒(附代碼)
-
Python深度學習 │一文掌握卷積神經網路
-
Python邊做邊學︱商品列表資訊爬取(附代碼)
-
P ython爬蟲實戰│狀態521網頁的爬取
-
Python爬蟲實戰│爬取天氣數據的實例詳解(附源碼)
-
Python實訓:用貪婪算法分析業務員路徑問題|附源碼
鯤鵬架構入門與實戰︱鯤鵬應用遷移(附代碼)
《機器學習》實驗指導書(附實驗參考+代碼)
Python爬蟲綜合實戰 │ 創建雲起書院爬蟲(附代碼)
Python爬蟲實戰 │ Email提醒(附代碼)
Python深度學習 │一文掌握卷積神經網路
Python邊做邊學︱商品列表資訊爬取(附代碼)
P ython爬蟲實戰│狀態521網頁的爬取
Python爬蟲實戰│爬取天氣數據的實例詳解(附源碼)
Python實訓:用貪婪算法分析業務員路徑問題|附源碼