導讀:深度學習不僅在於其強大的學習能力,更在於它的創新能力。我們通過構建判別模型來提升模型的學習能力,通過構建生成模型來發揮其創新能力。判別模型通常利用訓練樣本訓練模型,然後利用該模型,對新樣本 x,進行判別或預測。而生成模型正好反過來,根據一些規則 y,來生成新樣本 x。
生成式模型很多,本章主要介紹常用的兩種:變分自動編碼器 ( VAE ) 和生成式對抗網絡 ( GAN ) 及其變種。雖然兩者都是生成模型,並且通過各自的生成能力展現其強大的創新能力,但他們在具體實現上有所不同。GAN 是基於博弈論,目的是找到達到納什均衡的判別器網絡和生成器網絡。而 VAE 基本根植貝葉斯推理,其目標是潛在地建模,從模型中採樣新的數據。
本章主要介紹多種生成式網絡,具體內容如下:
- 用變分自編碼器生成圖像
- GAN 簡介
- 如何用 GAN 生成圖像
- 比較 VAE 與 GAN 的異同
- CGAN、DCGAN 簡介
8.1 用變分自編碼器生成圖像
變分自編碼器是自編碼器的改進版本,自編碼器是一種無監督學習,但它無法產生新的內容,變分自編碼器對其潛在空間進行拓展,使其滿足正態分佈,情況就大不一樣了。
8.1.1 自編碼器
自編碼器是通過對輸入 X 進行編碼後得到一個低維的向量 z,然後根據這個向量還原出輸入 X。通過對比 X 與 X~ 的誤差,再利用神經網絡去訓練使得誤差逐漸減小,從而達到非監督學習的目的。
圖8-1 自編碼器的架構圖
自編碼器因不能隨意產生合理的潛在變量,從而導致它無法產生新的內容。因爲潛在變量Z都是編碼器從原始圖片中產生的。爲解決這一問題,研究人員對潛在空間 Z ( 潛在變量對應的空間 ) 增加一些約束,使Z滿足正態分佈,由此就出現了 VAE 模型,VAE 對編碼器添加約束,就是強迫它產生服從單位正態分佈的潛在變量。正是這種約束,把 VAE 和自編碼器區分開來。
8.1.2 變分自編碼器
變分自編碼器關鍵一點就是增加一個對潛在空間 Z 的正態分佈約束,如何確定這個正態分佈就成主要目標,我們知道要確定正態分佈,只要確定其兩個參數均值 u 和標準差。那麼如何確定 u、σ?用一般的方法或估計比較麻煩效果也不好,研究人員發現用神經網絡去擬合,簡單效果也不錯。圖8-2爲 AVE 的架構圖。
圖8-2 AVE 架構圖
在圖8-2中,模塊①的功能把輸入樣本 X 通過編碼器輸出兩個 m 維向量 ( mu、log_var ),這兩個向量是潛在空間 ( 假設滿足正態分佈 ) 的兩個參數 ( 相當於均值和方差 )。那麼如何從這個潛在空間採用一個點 Z?
這裏假設潛在正態分佈能生成輸入圖像,從標準正態分佈 N(0, I) 中採樣一個 ( 模塊②的功能 ),然後使
**Z = mu + exp(log_var)\*(8-1)**
這也是模塊③的主要功能。
Z 是從潛在空間抽取的一個向量,Z 通過解碼器生成一個樣本 X~,這是模塊④的功能。
這裏是隨機採樣的,這就可保證潛在空間的連續性、良好的結構性。而這些特性使得潛在空間的每個方向都表示數據中有意義的變化方向。
以上這些步驟構成整個網絡的前向傳播過程,那反向傳播應如何進行?要確定反向傳播就會涉及損失函數,損失函數是衡量模型優劣的主要指標。這裏我們需要從以下兩個方面進行衡量:
- 生成的新圖像與原圖像的相似度;
- 隱含空間的分佈與正態分佈的相似度。
度量圖像的相似度一般採用交叉熵 ( 如 nn.BCELoss ),度量兩個分佈的相似度一般採用 KL 散度 ( Kullback-Leibler divergence )。這兩個度量的和構成了整個模型的損失函數。
以下是損失函數的具體代碼,AVE 損失函數的推導過程,有興趣的讀者可參考原論文:
https://arxiv.org/pdf/1606.05908.pdf
# 定義重構損失函數及KL散度 reconst_loss = F.binary_cross_entropy(x_reconst, x, size_average=False) kl_div = - 0.5 * torch.sum(1 + log_var - mu.pow(2) - log_var.exp()) #兩者相加得總損失 loss= reconst_loss+ kl_div
8.1.3 用變分自編碼器生成圖像
前面已經介紹了 AVE 的架構和原理,至此對 AVE 的“藍圖”就有了大致瞭解,如何實現這個藍圖?本節我們將結合代碼,用 PyTorch 實現 AVE。此外,還包括在實現過程中需要注意的一些問題,爲便於說明起見,數據集採用 MNIST,整個網絡結構如圖8-3所示。
先簡單介紹一下實現的具體步驟,然後,結合代碼詳細說明,如何用 PyTorch 一步步實現 AVE。具體步驟如下:
- 導入必要的包。
import os import torch import torch.nn as nn import torch.nn.functional as F import torchvision from torchvision import transforms from torchvision.utils import save_image
圖8-3 AVE 網絡結構圖
- 定義一些超參數。
image_size = 784 h_dim = 400 z_dim = 20 num_epochs = 30 batch_size = 128 learning_rate = 0.001
- 對數據集進行預處理,如轉換爲 Tensor,把數據集轉換爲循環、可批量加載的數據集。
# 下載MNIST訓練集,這裏因已下載,故download=False # 如果需要下載,設置download=True將自動下載 dataset = torchvision.datasets.MNIST(root='data', train=True, transform=transforms.ToTensor(), download=False) shuffle=True) #數據加載 data_loader = torch.utils.data.DataLoader(dataset=dataset, batch_size=batch_size,
- 構建 AVE 模型,主要由 Encode 和 Decode 兩部分組成。
# 定義AVE模型 class VAE(nn.Module): def __init__(self, image_size=784, h_dim=400, z_dim=20): super(VAE, self).__init__() self.fc1 = nn.Linear(image_size, h_dim) self.fc2 = nn.Linear(h_dim, z_dim) self.fc3 = nn.Linear(h_dim, z_dim) self.fc4 = nn.Linear(z_dim, h_dim) self.fc5 = nn.Linear(h_dim, image_size) def encode(self, x): h = F.relu(self.fc1(x)) return self.fc2(h), self.fc3(h) #用mu,log_var生成一個潛在空間點z,mu,log_var爲兩個統計參數,我們假設#這個假設分佈能生成圖像。 def reparameterize(self, mu, log_var): std = torch.exp(log_var/2) eps = torch.randn_like(std) return mu + eps * std def decode(self, z): h = F.relu(self.fc4(z)) return F.sigmoid(self.fc5(h)) def forward(self, x): mu, log_var = self.encode(x) z = self.reparameterize(mu, log_var) x_reconst = self.decode(z) return x_reconst, mu, log_var
- 選擇 GPU 及優化器。
# 設置PyTorch在哪塊GPU上運行,這裏假設使用序號爲1的這塊GPU. torch.cuda.set_device(1) device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = VAE().to(device) optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
- 訓練模型,同時保存原圖像與隨機生成的圖像。
with torch.no_grad(): # 保存採樣圖像,即潛在向量Z通過解碼器生成的新圖像 z = torch.randn(batch_size, z_dim).to(device) out = model.decode(z).view(-1, 1, 28, 28) save_image(out, os.path.join(sample_dir, 'sampled-{}.png'.format(epoch+1))) # 保存重構圖像,即原圖像通過解碼器生成的圖像 out, _, _ = model(x) x_concat = torch.cat([x.view(-1, 1, 28, 28), out.view(-1, 1, 28, 28)], dim=3) save_image(x_concat, os.path.join(sample_dir, 'reconst-{}.png'.format(epoch+1)))'
- 展示原圖像及重構圖像。
reconsPath = './ave_samples/reconst-30.png' Image = mpimg.imread(reconsPath) plt.imshow(Image) # 顯示圖像 plt.axis('off') # 不顯示座標軸 plt.show()
這是迭代30次的結果,如圖8-4所示。
圖8-4 AVE 構建圖像
圖8-4中,奇數列爲原圖像,偶數列爲原圖像重構的圖像。從這個結果可以看出重構圖像效果還不錯。圖8-5爲由潛在空間通過解碼器生成的新圖像,這個圖像效果也不錯。
圖8-5 AVE 新圖像
- 顯示由潛在空間點 Z 生成的新圖像。
genPath = './ave_samples/sampled-30.png' Image = mpimg.imread(genPath) plt.imshow(Image) # 顯示圖像 plt.axis('off') # 不顯示座標軸 plt.show()
這裏構建網絡主要用全連接層,有興趣的讀者,可以把卷積層,如果編碼層使用卷積層 ( 如 nn.Conv2d ),解碼器需要使用反捲積層 ( nn.ConvTranspose2d )。接下來我們介紹生成式對抗網絡,並用該網絡生成新數字,其效果將好於 AVE 生成的數字。
8.2 GAN 簡介
8.1節介紹了基於自動編碼器的 AVE,根據這個網絡可以生成新的圖像。本節我們將介紹另一種生成式網絡,它是基於博弈論的,所以又稱爲生成式對抗網絡 ( Generative Adversarial Nets,GAN )。它是2014年由 Ian Goodfellow 提出的,它要解決的問題是如何從訓練樣本中學習出新樣本,訓練樣本就是圖像就生成新圖像,訓練樣本是文章就輸出新文章等。
GAN 既不依賴標籤來優化,也不是根據對結果獎懲來調整參數。它是依據生成器和判別器之間的博弈來不斷優化。打個不一定很恰當的比喻,就像一臺驗鈔機和一臺製造假幣的機器之間的博弈,兩者不斷博弈,博弈的結果假幣越來越像真幣,直到驗鈔機無法識別一張貨幣是假幣還是真幣爲止。這樣說,還是有點抽象,接下來我們將從多個側面進行說明。
8.2.1 GAN 架構
VAE 利用潛在空間,可以生成連續的新圖像,不過因損失函數採用像素間的距離,所以圖像有點模糊。那能否生成更清晰的新圖像呢?可以的,這裏採用 GAN 替換 VAE 的潛在空間,它能夠迫使生成圖像與真實圖像在統計上幾乎無法區別的逼真合成圖像。
GAN 的直觀理解,可以想象一個名畫僞造者想僞造一幅達芬奇的畫作,開始時,僞造者技術不精,但他將自己的一些贗品和達芬奇的作品混在一起,請一個藝術商人對每一幅畫進行真實性評估,並向僞造者反饋,告訴他哪些看起來像真跡、哪些看起來不像真跡。
僞造者根據這些反饋,改進自己的贗品。隨着時間的推移,僞造者技能越來越高,藝術商人也變得越來越擅長找出贗品。最後,他們手上就擁有了一些非常逼真的贗品。
這就是 GAN 的基本原理。這裏有兩個角色,一個是僞造者,另一個是技術鑑賞者。他們訓練的目的都是打敗對方。
因此,GAN 從網絡的角度來看,它由兩部分組成。
- 生成器網絡:它一個潛在空間的隨機向量作爲輸入,並將其解碼爲一張合成圖像。
- 判別器網絡:以一張圖像 ( 真實的或合成的均可 ) 作爲輸入,並預測該圖像來自訓練集還是來自生成器網絡。圖8-6爲其架構圖。
如何不斷提升判別器辨別是非的能力?如何使生成的圖像越來越像真圖像?這些都通過控制它們各自的損失函數來控制。
訓練結束後,生成器能夠將輸入空間中的任何點轉換爲一張可信圖像。與 VAE 不同的是,這個潛空間無法保證帶連續性或有特殊含義的結構。
GAN 的優化過程不像通常的求損失函數的最小值,而是保持生成與判別兩股力量的動態平衡。因此,其訓練過程要比一般神經網絡難很多。
圖8-6 GAN 架構圖
8.2.2 GAN 的損失函數
從 GAN 的架構圖 ( 圖8-6 ) 可知,控制生成器或判別器的關鍵是損失函數,而如何定義損失函數就成爲整個 GAN 的關鍵。我們的目標很明確,既要不斷提升判斷器辨別是非或真假的能力,又要不斷提升生成器不斷提升圖像質量,使判別器越來越難判別。那這些目標如何用程序體現?損失函數就能充分說明。
爲了達到判別器的目標,其損失函數既要考慮識別真圖像能力,又要考慮識別假圖像能力,而不能只考慮一方面,故判別器的損失函數爲兩者的和,具體代碼如下:D 表示判別器、G 爲生成器、real_labels、fake_labels 分別表示真圖像標籤、假圖像標籤。images 是真圖像,z 是從潛在空間隨機採樣的向量,通過生成器得到假圖像。
# 定義判斷器對真圖像的損失函數 outputs = D(images) d_loss_real = criterion(outputs, real_labels) real_score = outputs # 定義判別器對假圖像(即由潛在空間點生成的圖像)的損失函數 z = torch.randn(batch_size, latent_size).to(device) fake_images = G(z) outputs = D(fake_images) d_loss_fake = criterion(outputs, fake_labels) fake_score = outputs # 得到判別器總的損失函數 d_loss = d_loss_real + d_loss_fake
生成器的損失函數如何定義,才能使其越來越向真圖像靠近?以真圖像爲標杆或標籤即可。具體代碼如下:
z = torch.randn(batch_size, latent_size).to(device) fake_images = G(z) outputs = D(fake_images) g_loss = criterion(outputs, real_labels)
8.3 用 GAN 生成圖像
爲便於說明 GAN 的關鍵環節,這裏我們弱化了網絡和數據集的複雜度。數據集爲 MNIST、網絡用全連接層。後續將用一些卷積層的實例來說明。
8.3.1 判別器
獲取數據,導入模塊基本與 AVE 的類似,這裏就不展開來說,詳細內容讀者可參考 pytorch-08-01.ipynb 代碼模塊。
定義判別器網絡結構,這裏使用 LeakyReLU 爲激活函數,輸出一個節點並經過 Sigmoid 後輸出,用於真假二分類。
# 構建判斷器 D = nn.Sequential( nn.Linear(image_size, hidden_size), nn.LeakyReLU(0.2), nn.Linear(hidden_size, hidden_size), nn.LeakyReLU(0.2), nn.Linear(hidden_size, 1), nn.Sigmoid())
8.3.2 生成器
生成器與 AVE 的生成器類似,不同的地方是輸出爲 nn.tanh,使用 nn.tanh 將使數據分佈在 [–1,1] 之間。其輸入是潛在空間的向量 z,輸出維度與真圖像相同。
# 構建生成器,這個相當於AVE中的解碼器 G = nn.Sequential( nn.Linear(latent_size, hidden_size), nn.ReLU(), nn.Linear(hidden_size, hidden_size), nn.ReLU(), nn.Linear(hidden_size, image_size), nn.Tanh())
8.3.3 訓練模型
for epoch in range(num_epochs): for i, (images, _) in enumerate(data_loader): images = images.reshape(batch_size, -1).to(device) # 定義圖像是真或假的標籤 real_labels = torch.ones(batch_size, 1).to(device) fake_labels = torch.zeros(batch_size, 1).to(device) #==================================================================== # # 訓練判別器 # #==================================================================== # # 定義判別器對真圖像的損失函數 outputs = D(images) d_loss_real = criterion(outputs, real_labels) real_score = outputs # 定義判別器對假圖像(即由潛在空間點生成的圖像)的損失函數 z = torch.randn(batch_size, latent_size).to(device) fake_images = G(z) outputs = D(fake_images) d_loss_fake = criterion(outputs, fake_labels) fake_score = outputs # 得到判別器總的損失函數 d_loss = d_loss_real + d_loss_fake # 對生成器、判別器的梯度清零 reset_grad() d_loss.backward() d_optimizer.step() #==================================================================== # # 訓練生成器 # #==================================================================== # # 定義生成器對假圖像的損失函數,這裏我們要求 #判別器生成的圖像越來越像真圖片,故損失函數中 #的標籤改爲真圖像的標籤,即希望生成的假圖像, #越來越靠近真圖像 z = torch.randn(batch_size, latent_size).to(device) fake_images = G(z) outputs = D(fake_images) g_loss = criterion(outputs, real_labels) # 對生成器、判別器的梯度清零 #進行反向傳播及運行生成器的優化器 reset_grad() g_loss.backward() g_optimizer.step() if (i+1) % 200 == 0: print('Epoch [{}/{}], Step [{}/{}], d_loss: {:.4f}, g_loss: {:.4f}, D(x): {:.2f}, D(G(z)): {:.2f}' .format(epoch, num_epochs, i+1, total_step, d_loss.item(), g_loss.item(), real_score.mean().item(), fake_score.mean().item())) # 保存真圖像 if (epoch+1) == 1: images = images.reshape(images.size(0), 1, 28, 28) save_image(denorm(images), os.path.join(sample_dir, 'real_images.png')) # 保存假圖像 fake_images = fake_images.reshape(fake_images.size(0), 1, 28, 28) save_image(denorm(fake_images), os.path.join(sample_dir, 'fake_images-{}.png'.format(epoch+1))) # 保存模型 torch.save(G.state_dict(), 'G.ckpt') torch.save(D.state_dict(), 'D.ckpt')
8.3.4 可視化結果
可視化每次由生成器得到假圖像,即潛在向量 z 通過生成器得到的圖像,其可視化結果如圖8-7所示。
reconsPath = './gan_samples/fake_images-200.png' Image = mpimg.imread(reconsPath) plt.imshow(Image) # 顯示圖片 plt.axis('off') # 不顯示座標軸 plt.show()
圖8-7 GAN 的新圖像
可見圖8-7明顯好於圖8-5。AVE 生成圖像主要依據原圖像與新圖像的交叉熵,而 GAN 真假圖片的交叉熵,同時還兼顧了不斷提升判別器和生成器本身的性能上。
8.4 VAE與GAN的優缺點
VAE 和 GAN 都是生成模型 ( Generative Model )。所謂生成模型,即能生成樣本的模型,利用這類模型,我們可以完成圖像自動生成 ( 採樣 )、圖像信息補全等工作。
VAE 是利用已有圖像在編碼器生成潛在向量,這個向量在服從高斯分佈的情況下很好地保留了原圖像的特徵,在解碼器得到的圖片會更加的合理與準確。
VAE 適合於學習具有良好結構的潛在空間,潛在空間有比較好的連續性,其中存在一些有特定意義的方向。VAE 能夠捕捉到圖像的結構變化 ( 傾斜角度、圈的位置、形狀變化、表情變化等 )。這也是 VAE 的一大優點,它有顯式的分佈,能夠容易地可視化圖像的分佈,具體如圖8-8所示。
圖8-8 AVE 得到的數據流形分佈圖
但是圖像在訓練的時候損失函數只能用均方誤差 ( MSE ) 之類的粗略誤差衡量,這就導致生成的圖像不能很好地保留原圖像的清晰度,就會使得圖片看上去有點模糊。
GAN 生成的潛在空間可能沒有良好結構,但 GAN 生成的圖像一般比 VAE 的更清晰。
在 GAN 的訓練過程中容易發生崩潰,以及訓練時梯度消失情況的發生。生成對抗網絡的博弈理論只是單純的讓 G 生成的圖像騙過 D,這個會讓 G 鑽空子一旦騙過了 D 不論圖像的合不合理就作爲輸出,於是模型坍塌 ( Generative Model ) 就發生了。
GAN 生成器的損失函數 ( Loss ) 依賴於判別器 Loss 後向傳遞,而不是直接來自距離,因而若判別器總是能準確地判別出真假,則向後傳遞的信息就非常少,導致生成器無法形成自己的 Loss,這是 GAN 比較難訓練的原因。當然,針對這一不足,近些年人們採用一個新的距離定義 ( Wasserstein Distance ) 應用於判別器,而不是原型中簡單粗暴的對真僞樣本的分辨正確的概率。
綜上所述,兩者的優缺點可歸結爲以下兩點:
- GAN 生成的效果優於 VAE。
- GAN 比 VAE 更難訓練。
8.5 ConditionGAN
AVE 和 GAN 都能基於潛在空間的隨機向量z生成新圖片,GAN 生成的圖像比 AVE 的更清晰,質量更好些。不過它們生成的都是隨機的,無法預先控制你要生成的哪類或哪個數。
如果在生成新圖像的同時,能加上一個目標控制那就太好了,如果希望生成某個數字,生成某個主題或類別的圖像,實現按需生成的目的,這樣的應用應該非常廣泛。需求就是最大的生產力,經過研究人員的不懈努力,提出一個基於條件的 GAN,即 Condition GAN,簡稱爲 CGAN。
8.5.1 CGAN 的架構
在 GAN 這種完全無監督的方式加上一個標籤或一點監督信息,使整個網絡就可看成半監督模型。其基本架構與 GAN 類似,只要添加一個條件 y 即可,y 就是加入的監督信息,比如說 MNIST 數據集可以提供某個數字的標籤信息,人臉生成可以提供性別、是否微笑、年齡等信息,帶某個主題的圖像等標籤信息。以下用圖8-9來描述 CGAN 的架構。
圖8-9 CGAN 架構圖
對生成器輸入一個從潛在空間隨機採樣的一個向量 z 及一個條件 y,生成一個符合該條件的圖像 G(z/y)。對判別器來說,輸入一張圖像 x 和條件 y,輸出該圖像在該條件下的概率 D(x/y)。這只是 CGAN 的一個藍圖,那如何實現這個藍圖?接下來採用 PyTorch 具體實現。
8.5.2 CGAN 生成器
定義生成器 ( Generator ) 及前向傳播函數。
class Generator(nn.Module): def __init__(self): super().__init__() self.label_emb = nn.Embedding(10, 10) self.model = nn.Sequential( nn.Linear(110, 256), nn.LeakyReLU(0.2, inplace=True), nn.Linear(256, 512), nn.LeakyReLU(0.2, inplace=True), nn.Linear(512, 1024), nn.LeakyReLU(0.2, inplace=True), nn.Linear(1024, 784), nn.Tanh() ) def forward(self, z, labels): z = z.view(z.size(0), 100) c = self.label_emb(labels) x = torch.cat([z, c], 1) out = self.model(x) return out.view(x.size(0), 28, 28)
8.5.3 CGAN 判別器
定義判斷器 ( Discriminator ) 及前向傳播函數。
class Discriminator(nn.Module): def __init__(self): super().__init__() self.label_emb = nn.Embedding(10, 10) self.model = nn.Sequential( nn.Linear(794, 1024), nn.LeakyReLU(0.2, inplace=True), nn.Dropout(0.4), nn.Linear(1024, 512), nn.LeakyReLU(0.2, inplace=True), nn.Dropout(0.4), nn.Linear(512, 256), nn.LeakyReLU(0.2, inplace=True), nn.Dropout(0.4), nn.Linear(256, 1), nn.Sigmoid() ) def forward(self, x, labels): x = x.view(x.size(0), 784) c = self.label_emb(labels) x = torch.cat([x, c], 1) out = self.model(x) return out.squeeze()
8.5.4 CGAN 損失函數
定義判別器對真、假圖像的損失函數。
#定義判別器對真圖像的損失函數 real_validity = D(images, labels) d_loss_real = criterion(real_validity, real_labels) # 定義判別器對假圖像(即由潛在空間點生成的圖像)的損失函數 z = torch.randn(batch_size, 100).to(device) fake_labels = torch.randint(0,10,(batch_size,)).to(device) fake_images = G(z, fake_labels) fake_validity = D(fake_images, fake_labels) d_loss_fake = criterion(fake_validity, torch.zeros(batch_size).to(device)) #CGAN總的損失值 d_loss = d_loss_real + d_loss_fake
8.5.5 CGAN 可視化
利用網格(10×10)的形式顯示指定條件下生成的圖像,如圖8-10所示。
圖8-10 CGAN 生成的圖像
from torchvision.utils import make_grid z = torch.randn(100, 100).to(device) labels = torch.LongTensor([i for i in range(10) for _ in range(10)]).to(device) images = G(z, labels).unsqueeze(1) grid = make_grid(images, nrow=10, normalize=True) fig, ax = plt.subplots(figsize=(10,10)) ax.imshow(grid.permute(1, 2, 0).detach().cpu().numpy(), cmap='binary') ax.axis('off')
8.5.6 查看指定標籤的數據
可視化指定單個數字條件下生成的數字。
def generate_digit(generator, digit): z = torch.randn(1, 100).to(device) label = torch.LongTensor([digit]).to(device) img = generator(z, label).detach().cpu() img = 0.5 * img + 0.5 return transforms.ToPILImage()(img) generate_digit(G, 8)
運行結果如下:
8.5.7 可視化損失值
記錄判別器、生成器的損失值代碼:
writer.add_scalars('scalars', {'g_loss': g_loss, 'd_loss': d_loss}, step)
運行結果如圖8-11所示。
圖8-11 CGAN 損失值
由圖8-11可知,CGAN 的訓練過程不像一般神經網絡的過程,它是判別器和生成器互相競爭的過程,最後兩者達成一個平衡。
8.6 DCGAN
DCGAN 在 GAN 的基礎上優化了網絡結構,加入了卷積層 ( Conv )、轉置卷積 ( ConvTranspose )、批量正則 ( Batch_norm ) 等層,使得網絡更容易訓練,圖8-12爲使用卷積層的 DCGAN 的生成器網絡結構示意圖。
圖8-12 使用卷積層的 DCGAN 的結構圖
pytorch-08-01.ipynb 代碼中含有使用卷積層的實例,有興趣的讀者可參考一下。下面是使用卷積層的判別器及使用轉置卷積的生成器的一個具體代碼。
- 使用卷積層、批規範層的判別器:
class Discriminator(nn.Module): def __init__(self): super(Discriminator, self).__init__() self.main = nn.Sequential( # 輸入大致爲 (nc) x 64 x 64,nc表示通道數 nn.Conv2d(nc, ndf, 4, 2, 1, bias=False), nn.LeakyReLU(0.2, inplace=True), # ndf表示判別器特徵圖的大小 nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf * 2), nn.LeakyReLU(0.2, inplace=True), nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf * 4), nn.LeakyReLU(0.2, inplace=True), nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf * 8), nn.LeakyReLU(0.2, inplace=True), nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False), nn.Sigmoid() ) def forward(self, input): return self.main(input)
- 使用轉置卷積、批規範層的生成器:
class Generator(nn.Module): def __init__(self): super(Generator, self).__init__() self.main = nn.Sequential( # 輸入Z,nz表示Z的大小。 nn.ConvTranspose2d( nz, ngf * 8, 4, 1, 0, bias=False), nn.BatchNorm2d(ngf * 8), nn.ReLU(True), # ngf爲生成器特徵圖大小 nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf * 4), nn.ReLU(True), # state size. (ngf*4) x 8 x 8 nn.ConvTranspose2d( ngf * 4, ngf * 2, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf * 2), nn.ReLU(True), # state size. (ngf*2) x 16 x 16 nn.ConvTranspose2d( ngf * 2, ngf, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf), nn.ReLU(True), #nc爲通道數 nn.ConvTranspose2d( ngf, nc, 4, 2, 1, bias=False), nn.Tanh() ) def forward(self, input): return self.main(input)
8.7 提升 GAN 訓練效果的一些技巧
訓練 GAN 是生成器和判別器互相競爭的動態過程,比一般的神經網絡挑戰更大。爲了克服訓練 GAN 模型的一些問題,人們從實踐中總結一些常用方法,這些方法在一些情況下,效果不錯。當然,這些方法不一定適合所有情況,方法如下。
- 批量加載和批規範化,有利於提升訓練過程中博弈的穩定性。
- 使用 tanh 激活函數作爲生成器最後一層,將圖像數據規範在–1和1之間,一般不用 sigmoid。
- 選用 Leaky ReLU 作爲生成器和判別器的激活函數,有利於改善梯度的稀疏性,稀疏的梯度會妨礙 GAN 的訓練。
- 使用卷積層時,考慮卷積核的大小能被步幅整除,否則,可能導致生成的圖像中存在棋盤狀僞影。
8.8 小結
變分自編碼和對抗生成器是生成式網絡的兩種主要網絡,本章介紹了這兩種網絡的主要架構及原理,並用具體實例實現這兩種網絡,此外還簡單介紹了 GAN 的多種變種,如 CGAN、DCGAN 等對抗性網絡,後續章節還將介紹 GAN 的其他一些實例。
文章摘自:《 Python 深度學習:基於Pytorch 》 機械工業出版社.2019.11
作者介紹:
吳茂貴
資深大數據和人工智能技術專家,就職於中國外匯交易中心,在 BI、數據挖掘與分析、數據倉庫、機器學習等領域工作超過20年。在基於Spark、TensorFlow、PyTorch、Keras 等的機器學習和深度學習方面有大量的工程實踐實踐。著有《 Python 深度學習:基於 TensorFlow 》《深度實踐 Spark 機器學習》《自己動手做大數據系統》等著作。
鬱明敏
資深商業分析師,從事互聯網金融算法研究工作,專注於大數據、機器學習以及數據可視化的相關領域,擅長 Python、Hadoop、Spark 等技術,擁有豐富的實戰經驗。曾獲“江蘇省TI杯大學生電子競技大賽”二等獎和“華爲杯全國大學生數學建模大賽”二等獎。
楊本法
高級算法工程師,在流程優化、數據分析、數據挖掘等領域有10餘年實戰經驗,熟悉Hadoop和Spark技術棧。有大量工程實踐經驗,做過的項目包括:推薦系統、銷售預測系統、輿情監控系統、揀貨系統、報表可視化、配送路線優化系統等。
李濤
資深AI技術工程師,對PyTorch、Caffe、TensorFlow等深度學習框架以及計算機視覺技術有深刻的理解和豐富的實踐經驗,曾經參與和主導過服務機器人、無人售後店、搜索排序等多個人工智能相關的項目。
張粵磊
資深大數據技術專家,飛谷雲創始人,有10餘年一線數據數據挖掘與分析實戰經驗。先後在諮詢、金融、互聯網行業擔任大數據平臺的技術負責人或架構師。
本文來自 DataFun 社區
原文鏈接: