(機器學習大作業)Pytorch 使用DCGAN實現二次元人物頭像生成(資料集+實現程式碼+數學原理)
Pytorch 使用DCGAN實現二次元人物頭像生成(實現程式碼+公式推導)
GAN介紹
演算法主體
推導證明(之後將補全完整過程)
隨機梯度下降訓練D,G
DCGAN介紹及相關原理
Pytorch實現二次元人物頭像生成
如何使用GAN生成二次元頭像
資料準備
程式碼實現
判別、生成模型均一輪迭代
判別每一輪迭代,生成模型每五輪迭代
圖片生成
總結
本篇文章主要是關於李宏毅教授授課視訊中的作業進行介紹,小白博主所作工作只是將現有的知識內容結合網路上一些優秀作者的總結以博文的形式加上自己的理解複述一遍。本文主要還是我的學習總結,因為網上的一些知識分佈比較零散故作整理敘述。如果有不對的地方,還請幫忙指正,如有出現禁止轉載的圖片,文字內容請聯絡我修改。
相關參考:
李宏毅2020機器學習深度學習(完整版)國語:連結: link.
何之源:GAN學習指南:從原理入門到製作生成Demo連結: link
張先生-您好.Pytorch實現GAN 生成動漫頭像 連結: link.
Dean0Winchester.深度學習—影像卷積與反摺積(最完美的解釋)連結: link.
GAN介紹
生成對抗網路( G A N GAN GAN, G e n e r a t i v e A d v e r s a r i a l N e t w o r k s Generative Adversarial Networks GenerativeAdversarialNetworks )是深度學習中一種無監督(即不需要給樣本資料打標籤)的學習方法, G A N GAN GAN的核心思想源自博弈論中的納什均衡,博弈的雙方分別是 G e n e r a t o Generato Generator(負責生成圖片的生成器 G G G)和 D i s c r i m i n a t o r Discriminator Discriminator(負責判斷圖片真假的判別器 D D D)。 D D D的目的在於揪出由 G G G “偽造”的“假”圖片給它打低分, G G G的目的在於盡最大可能地模模擬圖片從而“欺騙” D D D獲取高分。
參與這場遊戲雙方再不斷地較量中完成自身地優化,從而實現了各自判別能力和生成能力的提升,直到雙方達到一種動態的平衡。
如上圖所示,蝴蝶扮演
G
A
N
GAN
GAN中的生成器,而波波鳥則扮演判別器,蝴蝶為了逃避波波鳥(判別器)的捕食(識別)需要不斷的朝著樹葉(真樣本)進化,而波波鳥為了能夠捕食(識別)出蝴蝶(用假樣本偽裝自己的生成器)也需要不斷進化,比方說,最開始波波鳥理解的葉子(真樣本)只停留在不是彩色這一層面上,隨著訓練,波波鳥的認知開始進化,從不是彩色轉向棕色是葉子(真樣本),而蝴蝶得到反饋之後為了繼續欺騙波波鳥開始學會讓生成的翅膀是棕色。
這個過程將反覆進行下去,最後可以達到,波波鳥可以真正認出葉子,蝴蝶可以讓生成的翅膀(假樣本)與葉子(真樣本)近乎一致為止。
演算法主體
演算法總流程:
G A N GAN GAN演算法簡單來說,就是固定生成器,訓練判別器;然後固定訓練好的判別器,再訓練生成器的反覆過程。
逐條解讀演算法流程:
首先初始化
D
D
D,
G
G
G的引數,先固定生成器,進入判別器的學習過程。
判別器學習過程
-
取樣 :從資料集中抽樣出 m m m張樣本,同時藉助某種分佈(通常使用正態分佈)生成 m m m個 n n n維噪聲樣本。
-
獲取假樣本:將噪聲樣本作為輸入,得到生成器輸出的假樣本。
-
判別器D的目標函式 :直觀上來看,我們要達到的目的,就是讓判別器對生成器生成的假樣本儘可能地嚴格,對資料集抽樣出來的真樣本儘可能地寬鬆,那麼我們可以構建這樣一個目標函式,讓判別器給真樣本儘可能高的分數,給假樣本儘可能低的分數。
m a x D max_D maxD v ∗ v^* v∗ = 1 m \dfrac{1}{m} m1 ∑ i = 1 m \sum_{i=1}^{m} ∑i=1m l o g log log( D D D( x i x^i xi)) + + + 1 m \dfrac{1}{m} m1 ∑ i = 1 m \sum_{i=1}^{m} ∑i=1m l o g log log( D D D(1 − - − x ∗ i x*^i x∗i))
D D D( x x x)代表 x x x為真實圖片的概率,如果為 1 1 1,就代表100%是真實的圖片,而輸出為 0 0 0,就代表不可能是真實的圖片。程式碼實現中通常藉由 s i g m o i d sigmoid sigmoid函式來表達
要讓 l o g log log( D D D( x ∗ i x*^i x∗i))儘可能小,等效於讓 l o g log log( D D D(1 − - − x ∗ i x*^i x∗i))儘可能大。以此為我們的目的函式進行求導做梯度上升(亦可加負號求 m i n min min做梯度下降)從而更新判別器引數。
生成器學習過程 -
噪聲取樣 :藉助同種分佈(通常使用正態分佈)生成另外 m m m個 n n n維噪聲樣本。
-
生成器G的目標函式:
v ∗ v^* v∗ = 1 m \dfrac{1}{m} m1 ∑ i = 1 m \sum_{i=1}^{m} ∑i=1m l o g log log( D D D( G G G( z i z^i zi)))
先直觀的理解為什麼要這樣定義 G G G目標函式,根據之前的分析,要讓生成器生成的圖片足夠接近真樣本,那麼就需要騙過判別器,從而我們可以讓最大化生成圖片在 D D D的打分為目標來引導我們的生成器完成引數迭代。
在最理想的狀態下, G G G可以生成足以“以假亂真”的圖片 G G G( z z z)。對於 D D D來說,它難以判定 G G G生成的圖片究竟是不是真實的,因此 D D D( G G G( z z z)) = 0.5 0.5 0.5
推導證明
說了那麼多,那麼我們如何用一條數學語言來描述
G
A
N
GAN
GAN的核心原理呢?
G
e
n
e
r
a
t
i
v
e
A
d
v
e
r
s
a
r
i
a
l
N
e
t
w
o
r
k
s
Generative Adversarial Networks
GenerativeAdversarialNetworks 的作者
I
a
n
Ian
Ian
G
o
o
d
f
e
l
l
o
w
Goodfellow
Goodfellow大佬給出了答案:
分析這個公式:
1.整個式子由兩項構成。
x
x
x表示真實圖片,
z
z
z表示輸入
G
G
G網路的噪聲,而
G
G
G(
z
z
z)表示
G
G
G網路生成的圖片。
2.
D
D
D(
x
x
x)表示
D
D
D網路判斷真實圖片是否真實的概率(因為
x
x
x就是真實的,所以對於
D
D
D來說,這個值越接近
1
1
1越好)。而
D
D
D(
G
G
G(
z
z
z))是
D
D
D網路判斷
G
G
G生成的圖片的是否真實的概率。
3.
D
D
D的目的:
D
D
D的能力越強,
D
D
D(
x
x
x)應該越大,
D
D
D(
G
G
G(
x
x
x))應該越小。這時
V
V
V(
D
D
D,
G
G
G)會變大。因此式子對於
D
D
D來說是求最大(
m
a
x
max
max
D
D
D)
4.
G
G
G的目的: 上面提到過,
D
D
D(
G
G
G(
z
z
z))是
D
D
D網路判斷
G
G
G生成的圖片是否真實的概率,
G
G
G應該希望自己生成的圖片“越接近真實越好”。也就是說,(實際上,我們不是同時訓練兩者,當
D
D
D訓練好時,第一項好樣本的得分期望是定值),
G
G
G希望
D
D
D(
G
G
G(
z
z
z))儘可能得大,那麼
V
V
V(
D
D
D,
G
G
G)會變小。因此我們看到式子的最前面的記號是
m
i
n
min
min
G
G
G。
5. 為什麼是期望: 我們假設我們取出的樣本的統計特性可以代表整個資料分佈(包括資料集資料分佈和
G
G
G生成器生成資料的分佈),那麼我們之前表示的均值可以近似等價於各自資料分佈的期望。
我們可以用張形象的圖來描述我們是如何選擇我們的生成器和判別器的。
我們選取判別器的標準,是取
m
a
x
max
max
V
V
V(
D
D
D,
G
G
G),那麼對應到圖片就是找到函式的最高點,在我們完成判別器的選取後(即找到函式最高點後),我們下一步的目標(更新生成器),就是找出
m
i
n
min
min
m
a
x
max
max
V
V
V(
D
D
D,
G
G
G)(此時
m
a
x
max
max
V
V
V(
D
D
D,
G
G
G)即為紅點),我們要做的就是選取紅點中最低的,選出的
G
i
G_i
Gi即為我們想要的生成器。
隨機梯度下降訓練D,G
隨機梯度下降法用於減少陷入區域性最優的風險,,進而可以更接近全域性最優的方法。
該怎麼做,論文也給出了演算法(不愧是大佬),本文程式碼使用的是
A
d
a
m
優
化
算
法
Adam優化演算法
Adam優化算法(利用”慣性“來跳出區域性最優的方法),此處不做詳細說明:
這裡紅框圈出的部分是我們要額外注意的。第一步我們訓練
D
D
D,
D
D
D是希望
V
V
V(
G
G
G,
D
D
D)越大越好,所以是加上梯度(
a
s
c
e
n
d
i
n
g
ascending
ascending)。第二步訓練
G
G
G時,
V
V
V(
G
G
G,
D
D
D)越小越好,所以是減去梯度(
d
e
s
c
e
n
d
i
n
g
descending
descending)。整個訓練過程交替進行。
DCGAN介紹及相關原理
使用過
C
N
N
CNN
CNN的朋友一定不會對運用
C
N
N
CNN
CNN進行影像樣本分類的方法感到陌生,我們很容易就會想到,
G
A
N
GAN
GAN中的判別器是否可以用
C
N
N
CNN
CNN做二元分類器來進行替代,那麼如何把
C
N
N
CNN
CNN與
G
A
N
GAN
GAN很好的結合起來?
《
U
n
s
u
p
e
r
v
i
s
e
d
R
e
p
r
e
s
e
n
t
a
t
i
o
n
L
e
a
r
n
i
n
g
w
i
t
h
D
e
e
p
C
o
n
v
o
l
u
t
i
o
n
a
l
G
e
n
e
r
a
t
i
v
e
A
d
v
e
r
s
a
r
i
a
l
N
e
t
w
o
r
k
s
Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks
UnsupervisedRepresentationLearningwithDeepConvolutionalGenerativeAdversarialNetworks》論文告訴了我們答案。
論文作者將
G
A
N
GAN
GAN中的生成器,判別器都用兩個卷積神經網路來進行替換。用二元分類的
C
N
N
CNN
CNN來做判別器,用反摺積代替卷積操作的卷積神經網路來做生成器,反摺積操作可簡單理解為往原有的小畫素矩陣中進行填充,之後再進行卷積操作,從而實現從”小到大”(從而可以實現用一組列向量生成一個畫素矩陣)的變化。
D
C
G
A
N
DCGAN
DCGAN對卷積神經網路的結構做了一些改變,以提高樣本的質量和收斂的速度,這些改變有:
1.取消所有
p
o
o
l
i
n
g
pooling
pooling層。
G
G
G網路中使用轉置卷積(
t
r
a
n
s
p
o
s
e
d
c
o
n
v
o
l
u
t
i
o
n
a
l
l
a
y
e
r
transposed convolutional layer
transposedconvolutionallayer)進行上取樣,
D
D
D網路中用加入
s
t
r
i
d
e
stride
stride的卷積代替
p
o
o
l
i
n
g
pooling
pooling。
2.在
D
D
D和
G
G
G中均使用
b
a
t
c
h
n
o
r
m
a
l
i
z
a
t
i
o
n
batch normalization
batchnormalization
3.去掉
F
C
FC
FC層,使網路變為全卷積網路
4.
G
G
G網路中使用
R
e
L
U
ReLU
ReLU作為啟用函式,最後一層使用
t
a
n
h
tanh
tanh
5.
D
D
D網路中使用
L
e
a
k
y
R
e
L
U
LeakyReLU
LeakyReLU作為啟用函式
D
C
G
A
N
DCGAN
DCGAN中的
G
G
G網路示意:
Pytorch實現二次元人物頭像生成
如何使用GAN生成二次元頭像
藉助我們之前的推導快速地理解一下動漫頭像生成的
G
A
N
GAN
GAN原理。如下圖所示,判別器將資料集中的取樣做真樣本,由噪聲取樣經生成器反摺積生成的影像做假樣本進行每代的更新,生成器則以讓判別器儘可能地給自己生成的影像打高分為目的進行模型的更新迭代。
我們也可以用合作的思想去理解生成器同判別器的關係:
初始化兩個模型後,再依照我們之前提及的模型
G
G
G,
D
D
D固定一方,更新另一方的方法進行反覆迭代。資料集提供的樣本做真樣本,由
G
G
G生成的樣本做假樣本。
之所以要這樣做,是因為,如果我們只用資料集抽樣出的樣本訓練
D
D
D,(而我們收集的圖片都是由真人繪製的好圖片),換言之,我們只能為判別器提供標籤為
1
1
1的樣本,除此之外沒有反例,這樣訓練出來的
D
D
D從來沒有見過反例,此時給他一張由
G
G
G生成的假圖片,它也會給它打高分,可以這樣理解,在它看來假圖片和真圖片都是圖片,都應該打高分(
D
D
D並沒有學會我們想要它學會的特徵)。
所以,負面的例子對判別模型來說至關重要,於是我們可以讓資料集中抽樣出來的樣本做真樣本(得分為1,real),讓生成器生成的樣本做假樣本(得分為0,fake)。
生成演算法:給出一系列的正例,以及隨機生成的反例。在每一個輪次中讓
D
D
D學習這些正,反例,訓練好判別器之後,固定住。接著訓練我們的生成器,使它生成的反例能夠儘可能地在判別器中得高分。
演算法主體:和之前提及的一樣,初始引數,固定一方,訓練另一方,讓
G
G
G學會“欺騙”判別器。
另一種思路,我們可以認為所有的畫手在畫二次元美少女的時候都服從一種固定的規律,比如眼睛的位置,頭髮的走向,依照一些讓人感覺舒服的比例畫出好的圖片,又因為機器理解圖片是藉助畫素矩陣實現的(所以你喜歡的不是二次元美少女是矩陣啊),用數學的思維來理解,就是所有的美少女頭像都是服從一種複雜分佈的。如圖所示,我們在這個分佈裡面取樣本可以得到我們想要的五官分佈正常的好樣本,而脫離這個分佈之外,我們可以獲取各種“抽象派”畫作。
那麼生成器
G
G
G要做的就是找出這個分佈,並完成對它的模擬,從而讓隨機噪聲經過生成器後能夠得到滿足這個好分佈的影像。
那麼我們可以從極大似然估計的方法去計算這個分佈的引數,通過圖中的數學變化可以發現,求引數的最大似然等價於求
P
d
a
t
a
P_data
Pdata分佈與
P
G
P_G
PG分佈的
K
L
KL
KL散度。那麼我們求似然最大就等價於求散度最小。
同時我們再來看判別器的目標函式,經過求導帶入極值點後我們發現,判別器的目標函式最大值就是兩個
K
L
KL
KL散度之和。從而判別器,生成器聯絡了起來,生成器的目標函式可以從
m
i
n
min
min
K
L
KL
KL改寫成
m
i
n
min
min
m
a
x
max
max
V
V
V (
G
G
G,
D
D
D)。到這各位可以回過頭去再看看推到證明中的那個式子。
資料準備
本次採用的資料集約有
16000
16000
16000張,畫素為96X96,可以看到一些熟悉的角色(諸如涼宮,長門),下文提供下載連結,網路上也有很多其他的資料集,諸君也可以藉助爬蟲放自己喜歡的角色進去(京都粉可以嘗試爬京都的作畫,生成圖片會比較接近京都臉)。
網盤連結:連結:https://pan.baidu.com/s/1MFulwMQJ78U2MCqRUYjkMg
提取碼:58v6
複製這段內容後開啟百度網盤手機App,操作更方便哦
程式碼實現
程式碼部分主要參考:張先生-您好.Pytorch實現GAN 生成動漫頭像
from tqdm import tqdm
import torch
import torchvision as tv
from torch.utils.data import DataLoader
import torch.nn as nn
# config類中定義超引數,
class Config(object):
"""
定義一個配置類
"""
# 0.引數調整
data_path = '/root/PycharmProjects/untitled/'
virs = "result"
num_workers = 4 # 多執行緒
img_size = 96 # 剪下圖片的畫素大小
batch_size = 256 # 批處理數量
max_epoch = 400 # 最大輪次
lr1 = 2e-4 # 生成器學習率
lr2 = 2e-4 # 判別器學習率
beta1 = 0.5 # 正則化係數,Adam優化器引數
gpu = False # 是否使用GPU運算(建議使用)
nz = 100 # 噪聲維度
ngf = 64 # 生成器的卷積核個數
ndf = 64 # 判別器的卷積核個數
# 1.模型儲存路徑
save_path = 'imgs2/' # opt.netg_path生成圖片的儲存路徑
# 判別模型的更新頻率要高於生成模型
d_every = 1 # 每一個batch 訓練一次判別器
g_every = 5 # 每1個batch訓練一次生成模型
save_every = 5 # 每save_every次儲存一次模型
netd_path = None
netg_path = None
# 測試資料
gen_img = "result.png"
# 選擇儲存的照片
# 一次生成儲存64張圖片
gen_num = 64
gen_search_num = 512
gen_mean = 0 # 生成模型的噪聲均值
gen_std = 1 # 噪聲方差
# 例項化Config類,設定超引數,並設定為全域性引數
opt = Config()
# 定義Generation生成模型,通過輸入噪聲向量來生成圖片
class NetG(nn.Module):
# 構建初始化函式,傳入opt類
def __init__(self, opt):
super(NetG, self).__init__()
# self.ngf生成器特徵圖數目
self.ngf = opt.ngf
self.Gene = nn.Sequential(
# 假定輸入為1*1*opt.nz維的資料,opt.nz維的向量
# output = (input - 1)*stride + output_padding - 2*padding + kernel_size
nn.ConvTranspose2d(in_channels=opt.nz, out_channels=self.ngf * 8, kernel_size=4, stride=1, padding=0, bias =False),
nn.BatchNorm2d(self.ngf * 8),
nn.ReLU(inplace=True),
# 輸入一個4*4*ngf*8
nn.ConvTranspose2d(in_channels=self.ngf * 8, out_channels=self.ngf * 4, kernel_size=4, stride=2, padding=1, bias =False),
nn.BatchNorm2d(self.ngf * 4),
nn.ReLU(inplace=True),
# 輸入一個8*8*ngf*4
nn.ConvTranspose2d(in_channels=self.ngf * 4, out_channels=self.ngf * 2, kernel_size=4, stride=2, padding=1,bias=False),
nn.BatchNorm2d(self.ngf * 2),
nn.ReLU(inplace=True),
# 輸入一個16*16*ngf*2
nn.ConvTranspose2d(in_channels=self.ngf * 2, out_channels=self.ngf, kernel_size=4, stride=2, padding=1, bias =False),
nn.BatchNorm2d(self.ngf),
nn.ReLU(inplace=True),
# 輸入一張32*32*ngf
nn.ConvTranspose2d(in_channels=self.ngf, out_channels=3, kernel_size=5, stride=3, padding=1, bias =False),
# Tanh收斂速度快於sigmoid,遠慢於relu,輸出範圍為[-1,1],輸出均值為0
nn.Tanh(),
)# 輸出一張96*96*3
def forward(self, x):
return self.Gene(x)
# 構建Discriminator判別器
class NetD(nn.Module):
def __init__(self, opt):
super(NetD, self).__init__()
self.ndf = opt.ndf
### 為什麼不做池化??為什麼不加全連線層??
self.Discrim = nn.Sequential(
# 卷積層
# 輸入通道數in_channels,輸出通道數(即卷積核的通道數)out_channels,此處設定filer過濾器有64個,輸出通道自然也就是64。
# 因為對圖片作了灰度處理,此處通道數為1,
# 卷積核大小kernel_size,步長stride,對稱填0行列數padding
# input:(bitch_size, 3, 96, 96),bitch_size = 單次訓練的樣本量
# output:(bitch_size, ndf, 32, 32), (96 - 5 +2 *1)/3 + 1 =32
# LeakyReLu= x if x>0 else nx (n為第一個函式引數),開啟inplace(覆蓋)可以節省記憶體,取消反覆申請記憶體的過程
# LeakyReLu取消了Relu的負數硬飽和問題,是否對模型優化有效有待考證
nn.Conv2d(in_channels=3, out_channels= self.ndf, kernel_size= 5, stride= 3, padding= 1, bias=False),
nn.LeakyReLU(negative_slope=0.2, inplace= True),
# input:(ndf, 32, 32)
nn.Conv2d(in_channels= self.ndf, out_channels= self.ndf * 2, kernel_size= 4, stride= 2, padding= 1, bias=False),
nn.BatchNorm2d(self.ndf * 2),
nn.LeakyReLU(0.2, True),
# input:(ndf *2, 16, 16)
nn.Conv2d(in_channels= self.ndf * 2, out_channels= self.ndf *4, kernel_size= 4, stride= 2, padding= 1,bias=False),
nn.BatchNorm2d(self.ndf * 4),
nn.LeakyReLU(0.2, True),
# input:(ndf *4, 8, 8)
nn.Conv2d(in_channels= self.ndf *4, out_channels= self.ndf *8, kernel_size= 4, stride= 2, padding= 1, bias=False),
nn.BatchNorm2d(self.ndf *8),
nn.LeakyReLU(0.2, True),
# input:(ndf *8, 4, 4)
# output:(1, 1, 1)
nn.Conv2d(in_channels= self.ndf *8, out_channels= 1, kernel_size= 4, stride= 1, padding= 0, bias=True),
# 呼叫sigmoid函式解決分類問題
# 因為判別模型要做的是二分類,故用sigmoid即可,因為sigmoid返回值區間為[0,1],
# 可作判別模型的打分標準
nn.Sigmoid()
)
def forward(self, x):
# 展平後返回
return self.Discrim(x).view(-1)
def train(**kwargs):
# 配置屬性
# 如果函式無字典輸入則使用opt中設定好的預設超引數
for k_, v_ in kwargs.items():
setattr(opt, k_, v_)
# device(裝置),分配裝置
if opt.gpu:
device = torch.device("cuda")
else:
device = torch.device('cpu')
# 資料預處理1
# transforms 模組提供一般影像轉換操作類的功能,最後轉成floatTensor
# tv.transforms.Compose用於組合多個tv.transforms操作,定義好transforms組合操作後,直接傳入圖片即可進行處理
# tv.transforms.Resize,對PIL Image物件作resize運算, 數值儲存型別為float64
# tv.transforms.CenterCrop, 中心裁剪
# tv.transforms.ToTensor,將opencv讀到的圖片轉為torch image型別(通道,畫素,畫素),且把畫素範圍轉為[0,1]
# tv.transforms.Normalize,執行image = (image - mean)/std 資料歸一化操作,一引數是mean,二引數std
# 因為是三通道,所以mean = (0.5, 0.5, 0.5),從而轉成[-1, 1]範圍
transforms = tv.transforms.Compose([
# 3*96*96
tv.transforms.Resize(opt.img_size), # 縮放到 img_size* img_size
# 中心裁剪成96*96的圖片。因為本實驗資料已滿足96*96尺寸,可省略
tv.transforms.CenterCrop(opt.img_size),
# ToTensor 和 Normalize 搭配使用
tv.transforms.ToTensor(),
tv.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
# 載入資料並使用定義好的transforms對圖片進行預處理,這裡用的是直接定義法
# dataset是一個包裝類,將資料包裝成Dataset類,方便之後傳入DataLoader中
# 寫法2:
# 定義類Dataset(Datasets)包裝類,重寫__getitem__(進行transforms系列操作)、__len__方法(獲取樣本個數)
# ### 兩種寫法有什麼區別
dataset = tv.datasets.ImageFolder(root=opt.data_path, transform=transforms)
# 資料預處理2
# 檢視drop_last操作,
dataloader = DataLoader(
dataset, # 資料載入
batch_size=opt.batch_size, # 批處理大小設定
shuffle=True, # 是否進行洗牌操作
#num_workers=opt.num_workers, # 是否進行多執行緒載入資料設定
drop_last=True # 為True時,如果資料集大小不能被批處理大小整除,則設定為刪除最後一個不完整的批處理。
)
# 初始化網路
netg, netd = NetG(opt), NetD(opt)
# 判斷網路是否有權重數值
# ### storage儲存
map_location = lambda storage, loc: storage
# torch.load模型載入,即有模型載入模型在該模型基礎上進行訓練,沒有模型則從頭開始
# f:類檔案物件,如果有模型物件路徑,則載入返回
# map_location:一個函式或字典規定如何remap儲存位置
# net.load_state_dict將載入出來的模型資料載入到構建好的net網路中去
if opt.netg_path:
netg.load_state_dict(torch.load(f=opt.netg_path, map_location=map_location))
if opt.netd_path:
netd.load_state_dict(torch.load(f=opt.netd_path, map_location=map_location))
# 搬移模型到之前指定裝置,本文采用的是cpu,分配裝置
netd.to(device)
netg.to(device)
# 定義優化策略
# torch.optim包內有多種優化演算法,
# Adam優化演算法,是帶動量的慣性梯度下降演算法
optimize_g = torch.optim.Adam(netg.parameters(), lr=opt.lr1, betas=(opt.beta1, 0.999))
optimize_d = torch.optim.Adam(netd.parameters(), lr=opt.lr2, betas=(opt.beta1, 0.999))
# 計算目標值和預測值之間的交叉熵損失函式
# BCEloss:-w(p(x)log x +(1 - p(x))log(1 - x))
# to(device),用於指定CPU/GPU
criterions = nn.BCELoss().to(device)
# 定義標籤,並且開始注入生成器的輸入noise
true_labels = torch.ones(opt.batch_size).to(device)
fake_labels = torch.zeros(opt.batch_size).to(device)
# 生成滿足N(1,1)標準正態分佈,opt.nz維(100維),opt.batch_size個數的隨機噪聲
noises = torch.randn(opt.batch_size, opt.nz, 1, 1).to(device)
# 用於儲存模型時作生成影像示例
fix_noises = torch.randn(opt.batch_size, opt.nz, 1, 1).to(device)
# 訓練網路
# 設定迭代
for epoch in range(opt.max_epoch):
# tqdm(iterator()),函式內嵌迭代器,用作迴圈的進度條顯示
for ii_, (img, _) in tqdm((enumerate(dataloader))):
# 將處理好的圖片賦值
real_img = img.to(device)
# 開始訓練生成器和判別器
# 注意要使得生成的訓練次數小於一些
# 每一輪更新一次判別器
if ii_ % opt.d_every == 0:
# 優化器梯度清零
optimize_d.zero_grad()
# 訓練判別器
# 把判別器的目標函式分成兩段分別進行反向求導,再統一優化
# 真圖
# 把所有的真樣本傳進netd進行訓練,
output = netd(real_img)
# 用之前定義好的交叉熵損失函式計算損失
error_d_real = criterions(output, true_labels)
# 誤差反向計算
error_d_real.backward()
# 隨機生成的假圖
# .detach() 返回相同資料的 tensor ,且 requires_grad=False
# ,但能通過 in-place 操作報告給 autograd 在進行反向傳播的時候
noises = noises.detach()
# 通過生成模型將隨機噪聲生成為圖片矩陣資料
fake_image = netg(noises).detach()
# 將生成的圖片交給判別模型進行判別
output = netd(fake_image)
# 再次計算損失函式的計算損失
error_d_fake = criterions(output, fake_labels)
# 誤差反向計算
# 求導和優化(權重更新)是兩個獨立的過程,只不過優化時一定需要對應的已求取的梯度值。
# 所以求得梯度值很關鍵,而且,經常會累積多種loss對某網路引數造成的梯度,一併更新網路。
error_d_fake.backward()
# 計算一次Adam演算法,完成判別模型的引數迭代
# 多個不同loss的backward()來累積同一個網路的grad,計算一次Adam即可
optimize_d.step()
# 訓練判別器
if ii_ % opt.g_every == 0:
optimize_g.zero_grad()
# 用於netd作判別訓練和用於netg作生成訓練兩組噪聲需不同
noises.data.copy_(torch.randn(opt.batch_size, opt.nz, 1, 1))
fake_image = netg(noises)
output = netd(fake_image)
error_g = criterions(output, true_labels)
error_g.backward()
# 計算一次Adam演算法,完成判別模型的引數迭代
optimize_g.step()
# 儲存模型
if (epoch + 1) % opt.save_every == 0:
fix_fake_image = netg(fix_noises)
tv.utils.save_image(fix_fake_image.data[:64], "%s/%s.png" % (opt.save_path, epoch), normalize=True)
torch.save(netd.state_dict(), 'imgs2/' + 'netd_{0}.pth'.format(epoch))
torch.save(netg.state_dict(), 'imgs2/' + 'netg_{0}.pth'.format(epoch))
# @torch.no_grad():資料不需要計算梯度,也不會進行反向傳播
@torch.no_grad()
def generate(**kwargs):
# 用訓練好的模型來生成圖片
for k_, v_ in kwargs.items():
setattr(opt, k_, v_)
device = torch.device("cuda") if opt.gpu else torch.device("cpu")
# 載入訓練好的權重資料
netg, netd = NetG(opt).eval(), NetD(opt).eval()
### ?? 兩個引數返回第一個
map_location = lambda storage, loc: storage
# opt.netd_path等引數有待修改
netd.load_state_dict(torch.load('imgs2/netd_399.pth', map_location=map_location), False)
netg.load_state_dict(torch.load('imgs2/netg_399.pth', map_location=map_location), False)
netd.to(device)
netg.to(device)
# 生成訓練好的圖片
noise = torch.randn(opt.gen_search_num, opt.nz, 1, 1).normal_(opt.gen_mean, opt.gen_std)
noise.to(device)
fake_image = netg(noise)
score = netd(fake_image).detach()
# 挑選出合適的圖片
# 取出得分最高的圖片
indexs = score.topk(opt.gen_num)[1]
result = []
for ii in indexs:
result.append(fake_image.data[ii])
# 以opt.gen_img為檔名儲存生成圖片
tv.utils.save_image(torch.stack(result), opt.gen_img, normalize=True, range=(-1, 1))
def main():
# 訓練模型
train()
# 生成圖片
generate()
if __name__ == '__main__':
main()
判別、生成模型均一輪迭代,200輪次
每
50
50
50輪展示一次
判別每一輪迭代,生成模型每五輪迭代,400輪次
每
100
100
100輪展示一次
理論上,生成器的更新頻率應該小於判別器,因為生成器變化太快,會導致
V
V
V(
G
G
G,
D
D
D)變化劇烈,從而最開始的最高點不再滿足最高要求。
圖片生成
用訓練好的生成器,隨機輸入一組噪聲,生成結果如下。效果不理想,希望大佬們能夠給出意見讓我能夠學習改進一下。
總結
博主第一次寫博文,很多不足之處希望多多包涵,之後會陸續補齊李教授的其他作業講解,希望能夠幫助到一些和我一樣剛入門的朋友,也希望大佬可以幫忙提意見幫助我改進不足之處。
相關文章
- 基於celeba資料集和pytorch框架實現dcgan的人臉影像生成PyTorch框架
- 《機器學習Python實現_10_09_整合學習_bagging_stacking原理及實現》機器學習Python
- 【整合學習】:Stacking原理以及Python程式碼實現Python
- 【機器學習PAI實戰】—— 玩轉人工智慧之利用GAN自動生成二次元頭像機器學習AI人工智慧二次元
- 簡單介紹Pytorch實現WGAN用於動漫頭像生成PyTorch
- 【機器學習】--xgboost初始之程式碼實現分類機器學習
- 機器學習實戰原始碼和資料集下載機器學習原始碼
- 吳恩達機器學習作業程式碼和資料集吳恩達機器學習
- 如何實現實時機器學習? - huyenchip機器學習
- 【機器學習】求解邏輯迴歸引數(三種方法程式碼實現)機器學習邏輯迴歸
- Vue原始碼學習(二十):$emit、$on實現原理Vue原始碼MIT
- 深度學習之Pytorch(一)神經網路基礎及程式碼實現深度學習PyTorch神經網路
- Maui Blazor 使用攝像頭實現UIBlazor
- 飛漿(paddle)實現機器學習機器學習
- 用機器學習實現情感分析機器學習
- 《機器學習Python實現_10_02_整合學習_boosting_adaboost分類器實現》機器學習Python
- 機器學習經典聚類演算法 —— k-均值演算法(附python實現程式碼及資料集)機器學習聚類演算法Python
- 使用pmml實現跨平臺部署機器學習模型機器學習模型
- 《機器學習Python實現_10_06_整合學習_boosting_gbdt分類實現》機器學習Python
- 機器學習|決策樹-sklearn實現機器學習
- 將強化學習引入NLP:原理、技術和程式碼實現強化學習
- 機器學習系列文章:Apriori關聯規則分析演算法原理分析與程式碼實現機器學習演算法
- 《機器學習Python實現_10_10_整合學習_xgboost_原理介紹及迴歸樹的簡單實現》機器學習Python
- 教你基於MindSpore用DCGAN生成漫畫頭像
- 機器學習經典分類演算法 —— k-近鄰演算法(附python實現程式碼及資料集)機器學習演算法Python
- 【深度學習 01】線性迴歸+PyTorch實現深度學習PyTorch
- NNLM原理及Pytorch實現PyTorch
- 一個純前端實現的頭像生成網站前端網站
- 深度學習:多層感知機和異或問題(Pytorch實現)深度學習PyTorch
- 基於Sklearn機器學習程式碼實戰機器學習
- 機器學習——決策樹模型:Python實現機器學習模型Python
- 【機器學習】線性迴歸python實現機器學習Python
- 【機器學習】線性迴歸sklearn實現機器學習
- [原始碼解析] 機器學習引數伺服器 Paracel (2)--------SSP控制協議實現原始碼機器學習伺服器協議
- Pytorch筆記之 多層感知機實現MNIST資料集分類PyTorch筆記
- PHP 實現機器學習挖掘使用者的購物習慣PHP機器學習
- 數學推導+Python實現機器學習演算法:線性迴歸Python機器學習演算法
- 【小白學PyTorch】13 EfficientNet詳解及PyTorch實現PyTorch