PyTorch教程之DCGAN

原文連接:DCGAN TUTORIAL

簡介

本教程通過例程來介紹 DCGANs 。我們使用名人照片來訓練 GAN 網絡使其能夠生成新的名人。 這裏使用的大部分代碼都來自pytorch/examples,本篇教程會出該實現的詳細介紹,並解釋此模型爲什麼有效。不用擔心,此教程不需要提前擁有 GAN 的相關知識,但是對第一個接觸 GAN網絡的人來說需要更多的時間來理解網絡中究竟發生了什麼。當然,爲了節省時間,最好有一個或兩個GPU來開始此教程。

Generative Adversarial Networks

什麼是GAN

GANs是一種訓練深度學習模型學習數據分佈規律的框架,我們可以通過學習到的分佈來生成新的數據。GANs是由Goodfellow在2014年提出,並且在Generative Adversarial Nets 進行了介紹。GAN 由兩個完全不同的模型組成,一個生成器,一個判別器。生成器的作用是產生和訓練樣本類似的 “假”(fake)圖片,判別器的工作是判別圖片是來自訓練集的真圖片(real)還是來自生成器的假圖片(fake)。在訓練的過程中,生成器不斷地使自己生成的假圖片更接近真圖片以便能夠欺騙判別器,而判別器也不斷提高自己的識別能力。這場博弈遊戲的平衡情況是,當生成器生成的數據和訓練數據足夠相似,判別器以50%左右的置信度判別樣本是真是假。

現在我們來說明在教程中將會用到的一些符號。xx 表示一張圖片,D(x)D(x)表示 xx 來自訓練集的概率,爲判別器的輸出。判別器的輸入爲 3x64x64 的圖片。易知,當 xx 來自訓練集時輸出概率高,當 xx 來自生成器時,輸入低。D(x)D(x) 也可視爲傳統的二元分類器。

對生成器來說,輸入 zz 爲來自標準正太分佈的向量,G(z)G(z) 使輸入zz映射到樣本圖像空間,爲生成器的輸出。

所以,D(G(z))D(G(z)) 表示生成器生成圖片爲真(real)的概率。在 Goodfellow的論文Generative Adversarial Nets中,D和G相互博弈,D 努力提高自己判別圖像真假的能力,G 努力降低其生成的圖片被識別爲假的概率 (log(1D(G(Z))))(log(1-D(G(Z)))),論文中給出了 GAN 網絡的損失函數如下:minGmaxDV(G,D)=Expdata(x)[logD(x)]+Ezpz(z)[log(1D(G(x)))] \mathop{min} \limits_G\mathop{max} \limits_ D V(G,D) = E_{x∼pdata(x)}[logD(x)]+E_{z∼pz(z)}[log(1−D(G(x)))]理論上來說,此博弈的平衡點爲 Pg=PdataP_g = P_data ,這時判別器無法分辨輸入的真假。然而,由於GAN 網絡的收斂問題,實際訓練時並不總是訓練到這個位置。

什麼是DCGAN

DCGAN是在GAN上進行的擴展,唯一的區別就是生成器和判別器分別使用轉置卷積層和卷積層。在論文Unsupervised Representation Learning With Deep Convolutional Generative Adversarial Networks中提出。判別器由 strided convolution layers, batch norm layers, 和 LeakyReLU 激活層構成。輸入爲 3x64x64的圖片,輸出爲此圖片來自真樣本的概率。判別器由轉置卷積層,batch norm layers, 和 ReLU 激活層構成。 輸入是取樣自標準正態分佈的隨機向量 zz,輸出是 3x64x64的RGB圖片。在論文中,作者給出了設置優化器的一些竅門,計算損失函數的方法,以及怎樣初始化模型的參數,這些我們都會在接下來的部分進行介紹。

from __future__ import print_function
#%matplotlib inline
import argparse
import os
import random
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.backends.cudnn as cudnn
import torch.optim as optim
import torch.utils.data
import torchvision.datasets as dset
import torchvision.transforms as transforms
import torchvision.utils as vutils
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from IPython.display import HTML

# Set random seed for reproducibility
manualSeed = 999
#manualSeed = random.randint(1, 10000) # use if you want new results
print("Random Seed: ", manualSeed)
random.seed(manualSeed)
torch.manual_seed(manualSeed)

Out:

Random Seed:  999

輸入

下面是程序運行中的輸入信息:

  • dataroot - 數據集的根目錄,後面數據集部分會有詳細討論
  • workers - 使用DataLoader 加載數據的線程數
  • batch_size - 每個batch中的圖片數量,論文中爲128
  • image_size - 圖片的分辨率,默認爲64x64。如果使用了其它尺寸,需要更改判別器與生成器的結構。可參考詳細操作
  • nc - 輸入圖片的色彩通道,對彩色圖片來說爲 3
  • nz - 生成器的輸入zz 的維度
  • ngf - 生成器特徵圖的深度
  • ndf - 判別器特徵圖的深度
  • num_epochs - 訓練的輪數
  • lr - 訓練的 learning rate。論文中取值爲0.0002
  • beta1 - Adam optimizers 的超參beta1。論文中取值爲 0.5
  • ngpu - 使用的gpu個數,若爲0表示使用cpu模式。
# Root directory for dataset
dataroot = "data/celeba"

# Number of workers for dataloader
workers = 2

# Batch size during training
batch_size = 128

# Spatial size of training images. All images will be resized to this
#   size using a transformer.
image_size = 64

# Number of channels in the training images. For color images this is 3
nc = 3

# Size of z latent vector (i.e. size of generator input)
nz = 100

# Size of feature maps in generator
ngf = 64

# Size of feature maps in discriminator
ndf = 64

# Number of training epochs
num_epochs = 5

# Learning rate for optimizers
lr = 0.0002

# Beta1 hyperparam for Adam optimizers
beta1 = 0.5

# Number of GPUs available. Use 0 for CPU mode.
ngpu = 1

數據

此教程中我們使用Celeb-A Faces dataset, 也可以通過百度雲盤下載。我們下載其中的 img_align_celeba.zip。下載之後,創建名爲 celeba 的文件夾,將壓縮文件解壓至此文件夾。然後,設置 dataroot 爲你剛剛建立的celeba路徑。最終的文件結構如下:

/path/to/celeba
    -> img_align_celeba
        -> 188242.jpg
        -> 173822.jpg
        -> 284702.jpg
        -> 537394.jpg
           ...

這是一個重要的步驟,因爲我們後面用到的 ImageFolder類,要求數據存於根目錄的子目錄中。現在我們進行數據的處理加載工作。

# We can use an image folder dataset the way we have it setup.
# Create the dataset
dataset = dset.ImageFolder(root=dataroot,
                           transform=transforms.Compose([
                               transforms.Resize(image_size),
                               transforms.CenterCrop(image_size),
                               transforms.ToTensor(),
                               transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
                           ]))
                       
# creat the dataloader
dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size,
	shuffle=True, num_workers=workers)

# device which device we want to run on
divice = torch.device("cuda:0" if (torch.cuda.is_available() and ngpu > 0) else "cpu")

# Plot some training images
real_batch = next(iter(dataloader))
plt.figure(figsize=(8,8))
plt.axis("off")
plt.title("Training Images")
plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:64],
			 padding=2, normalize=True).cpu(),(1,2,0)))

Out:

部署

在完成數據集的準備與超參設置工作後,我們現在開始網絡的部署。我們將逐步介紹 權重初始化,生成器,判別器,損失函數,訓練過程的實現細節。

權重初始化

在DCGAN的論文中,作者指明所有的權重都以均值爲0,標準差爲0.2的正態分佈隨機初始化。weights_init 函數讀取一個已初始化的模型並重新初始化卷積層,轉置卷積層,batch normalization 層。這個函數在模型初始化之後使用。

# custom weights initialization called on netG and netD
def weights_init(m):
    classname = m.__class__.__name__
    if classname.find('Conv') != -1:
        nn.init.normal_(m.weight.data, 0.0, 0.02)
    elif classname.find('BatchNorm') != -1:
        nn.init.normal_(m.weight.data, 1.0, 0.02)
        nn.init.constant_(m.bias.data, 0)

生成器

生成器的目的是將輸入向量zz 映射到真的數據空間。這兒我們的數據爲圖片,意味着我們需要將輸入向量zz轉換爲 3x64x64的RGB圖像。實際操作時,我們通過一系列的二維轉置卷,每次轉置卷積後跟一個二維的batch norm層和一個relu激活層。生成器的輸出接入 tanhtanh函數以便滿足輸出範圍爲[1,1][-1,1]。值得一提的是,每個轉置卷積後面跟一個 batch norm 層,是DCGAN論文的一個主要貢獻。這些網絡層有助於訓練時的梯度計算。整個過程如下圖所示。

需要注意的是,上面我們設置的參數(nz, ngf, nc)會直接影響到生成器的結構。 nz是輸入向量zz的維度,ngf 是作用於生成器卷積核的邊,nc 是輸出圖片的通道數(我們是 RGB圖像,所以爲3),下面是生成器的代碼。

# Generator Code

class Generator(nn.Module):
    def __init__(self, ngpu):
        super(Generator, self).__init__()
        self.ngpu = ngpu
        self.main = nn.Sequential(
            # input is Z, going into a convolution
            nn.ConvTranspose2d( nz, ngf * 8, 4, 1, 0, bias=False),
            nn.BatchNorm2d(ngf * 8),
            nn.ReLU(True),
            # state size. (ngf*8) x 4 x 4
            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),
            # state size. (ngf) x 32 x 32
            nn.ConvTranspose2d( ngf, nc, 4, 2, 1, bias=False),
            nn.Tanh()
            # state size. (nc) x 64 x 64
        )

    def forward(self, input):
        return self.main(input)

現在我們可以實例化生成器並調用weights_init函數。仔細觀察輸出的模型結構以便知道生成器是怎樣構建起來的。

# Create the generator
netG = Generator(ngpu).to(device)

# Handle multi-gpu if desired
if (device.type == 'cuda') and (ngpu > 1):
    netG = nn.DataParallel(netG, list(range(ngpu)))

# Apply the weights_init function to randomly initialize all weights
#  to mean=0, stdev=0.2.
netG.apply(weights_init)

# Print the model
print(netG)

out:

Generator(
  (main): Sequential(
    (0): ConvTranspose2d(100, 512, kernel_size=(4, 4), stride=(1, 1), bias=False)
    (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (2): ReLU(inplace)
    (3): ConvTranspose2d(512, 256, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (4): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (5): ReLU(inplace)
    (6): ConvTranspose2d(256, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (7): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (8): ReLU(inplace)
    (9): ConvTranspose2d(128, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (10): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (11): ReLU(inplace)
    (12): ConvTranspose2d(64, 3, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (13): Tanh()
  )
)

判別器

上面已經提到了,判別器是一個二元分類器( binary classification)輸出圖片爲真的概率。這兒,D的輸入爲 3x64x64 的圖片,依次通過卷繼層,BN層,LeakyReLU層,然後通過sigmoid激活函數輸出圖片爲真的概率,可根據具體的情況改變判別器的具體結構。但是其中使用的 strided卷積層,BN層,LeakyReLUs層是必要的。DCGAN的論文提到,使用strided卷積比池化層具有更好的下采樣效果,因爲strided卷積讓網絡學習到了自己的池化函數(而非固定的池化函數)。而且BN層和leaky relu層使得梯度傳播更加高效,這對判別器和生成器的學習過程很有幫助。
判別器代碼:

class Discriminator(nn.Module):
    def __init__(self, ngpu):
        super(Discriminator, self).__init__()
        self.ngpu = ngpu
        self.main = nn.Sequential(
            # input is (nc) x 64 x 64
            nn.Conv2d(nc, ndf, 4, 2, 1, bias=False),
            nn.LeakyReLU(0.2, inplace=True),
            # state size. (ndf) x 32 x 32
            nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 2),
            nn.LeakyReLU(0.2, inplace=True),
            # state size. (ndf*2) x 16 x 16
            nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 4),
            nn.LeakyReLU(0.2, inplace=True),
            # state size. (ndf*4) x 8 x 8
            nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 8),
            nn.LeakyReLU(0.2, inplace=True),
            # state size. (ndf*8) x 4 x 4
            nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
            nn.Sigmoid()
        )

    def forward(self, input):
        return self.main(input)

和上面一樣,現在實例化判別器並輸出網絡結構。

# Create the Discriminator
netD = Discriminator(ngpu).to(device)

# Handle multi-gpu if desired
if (device.type == 'cuda') and (ngpu > 1):
    netD = nn.DataParallel(netD, list(range(ngpu)))

# Apply the weights_init function to randomly initialize all weights
#  to mean=0, stdev=0.2.
netD.apply(weights_init)

# Print the model
print(netD)

out:

Discriminator(
  (main): Sequential(
    (0): Conv2d(3, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): LeakyReLU(negative_slope=0.2, inplace)
    (2): Conv2d(64, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (3): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (4): LeakyReLU(negative_slope=0.2, inplace)
    (5): Conv2d(128, 256, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (6): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (7): LeakyReLU(negative_slope=0.2, inplace)
    (8): Conv2d(256, 512, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (9): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (10): LeakyReLU(negative_slope=0.2, inplace)
    (11): Conv2d(512, 1, kernel_size=(4, 4), stride=(1, 1), bias=False)
    (12): Sigmoid()
  )
)

損失函數及優化器

通過以下的設置,我們可以瞭解到整個優化的過程。我們使用二元交叉熵損失(BCELoss) ,其定義如下:l(x,y)=L={l1,...lN}T,ln=[ynlogxn+(1yn)log(1xn)]l(x,y)=L= \{ l_1,...l_N \}^T , l_n= -[y_n·logx_n+(1-y_n)·log(1-x_n)] 觀察此函數,就能發現對於正例(yn=1y_n=1)(1yn)log(1xn)(1-y_n)·log(1-x_n)爲0,ynlogxny_n·logx_n起作用。反例時加號後面部分起作用,前面爲零。
接下來我們定義真圖片標籤爲1,假圖片標籤爲0。這些標籤在計算損失函數時會用到,這也是GAN原論文中用到的方法。在DCGAN論文中使用Adam優化器,學習率設置爲 0.0002,Beta1=0.5Beta1 = 0.5,爲了追蹤生成器的學習過程,我們生成改進後的高斯分佈向量(i.e. fixed_noise)。在訓練過程中,我們將改進的隨機向量輸入生成器,經過迭代之後,我們可以看到由噪聲生成的圖片。

# Initialize BCELoss function
criterion = nn.BCELoss()

# Create batch of latent vectors that we will use to visualize
#  the progression of the generator
fixed_noise = torch.randn(64, nz, 1, 1, device=device)

# Establish convention for real and fake labels during training
real_label = 1
fake_label = 0

# Setup Adam optimizers for both G and D
optimizerD = optim.Adam(netD.parameters(), lr=lr, betas=(beta1, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr=lr, betas=(beta1, 0.999))

訓練

至此,我們完成了GAN網絡各個模塊的定義,現在我們可以開始進行訓練。需要注意的是,GAN的訓練需要很細心,超參的一點不正確的設置都可能導致整個模型的崩潰,然而我們卻不能解釋導致模型崩潰的具體原因。在這裏,我們將密切關注Goodfellow論文中的算法,同時遵守 ganhacks 中展示的一些最佳實踐。也就是說,我們將爲 "real"和"fake"圖片使用不一樣的mini-batch。同時調整生成器的目標函數使 logD(G(Z))logD(G(Z))最大。訓練可以分爲兩個主要的部分。1.更新判別器 2.更新生成器

第一部分—更新判別器

前面已經說到,訓練判別器的目的是爲了提高識別真假圖片的正確率。我們通過隨機梯度下降法來更新判別器。實際中我們通過最大化 log(D(x))+log(1D(G(z)))log(D(x))+log(1−D(G(z))) 來訓練模型。根據 ganhacks 中的建議,我們的訓練分爲兩個步驟。搜先,我們從真樣本中抽取一個batch的圖片輸入判別器 D ,計算損失 log(D(x))log(D(x)),然後反向傳播計算梯度。 第二步,我們在 假樣本(生成的樣本)中抽取一個 batch的圖片輸入判別器D,計算損失log(1D(G(z)))log(1−D(G(z))),然後反向傳播計算梯度。兩個步驟完成之後,把真假樣本計算的梯度累積,做一步優化。

第二部分—更新生成器

論文中提到,我們通過最小化 log(1D(G(z)))log(1−D(G(z)))來訓練生成器以便能生成更好的假樣本。如上所述,Goodfellow表明這不能提供足夠的梯度,尤其是在訓練的初始階段,所以我們通過採用最大化log(D(G(z)))log(D(G(z)))來進行訓練。在代碼實現中,我們使用第一部分中的判別器來判別生成器的輸出,。。。。。。

最後,我們會在每一輪epoch後做一次統計報告,我們會展示輸入噪聲通過生成器的過程,以便觀察生成器訓練的軌跡。統計報告包括以下部分:

  • Loss_D - 判別器損失,包含真樣本和假樣本 log(D(x))+log(D(G(z)))log(D(x))+log(D(G(z)))
  • Loss_G - 生成器損失 log(D(G(z)))log(D(G(z)))
  • D(x) - 真樣本一個batch的平均輸出,理論上此值應該首先接近於1,隨着訓練接近 0.5
  • D(G(z)) - 假樣本一個batch的平均輸出,第一個值產生於 D 更新之前,第二個值產生於 D更新之後。理論上此值開始時接近 0 ,隨着訓練接近 0.5
# Training Loop

# Lists to keep track of progress
img_list = []
G_losses = []
D_losses = []
iters = 0

print("Starting Training Loop...")
# For each epoch
for epoch in range(num_epochs):
    # For each batch in the dataloader
    for i, data in enumerate(dataloader, 0):

        ############################
        # (1) Update D network: maximize log(D(x)) + log(1 - D(G(z)))
        ###########################
        ## Train with all-real batch
        netD.zero_grad()
        # Format batch
        real_cpu = data[0].to(device)
        b_size = real_cpu.size(0)
        label = torch.full((b_size,), real_label, device=device)
        # Forward pass real batch through D
        output = netD(real_cpu).view(-1)
        # Calculate loss on all-real batch
        errD_real = criterion(output, label)
        # Calculate gradients for D in backward pass
        errD_real.backward()
        D_x = output.mean().item()

        ## Train with all-fake batch
        # Generate batch of latent vectors
        noise = torch.randn(b_size, nz, 1, 1, device=device)
        # Generate fake image batch with G
        fake = netG(noise)
        label.fill_(fake_label)
        # Classify all fake batch with D
        output = netD(fake.detach()).view(-1)
        # Calculate D's loss on the all-fake batch
        errD_fake = criterion(output, label)
        # Calculate the gradients for this batch
        errD_fake.backward()
        D_G_z1 = output.mean().item()
        # Add the gradients from the all-real and all-fake batches
        errD = errD_real + errD_fake
        # Update D
        optimizerD.step()

        ############################
        # (2) Update G network: maximize log(D(G(z)))
        ###########################
        netG.zero_grad()
        label.fill_(real_label)  # fake labels are real for generator cost
        # Since we just updated D, perform another forward pass of all-fake batch through D
        output = netD(fake).view(-1)
        # Calculate G's loss based on this output
        errG = criterion(output, label)
        # Calculate gradients for G
        errG.backward()
        D_G_z2 = output.mean().item()
        # Update G
        optimizerG.step()

        # Output training stats
        if i % 50 == 0:
            print('[%d/%d][%d/%d]\tLoss_D: %.4f\tLoss_G: %.4f\tD(x): %.4f\tD(G(z)): %.4f / %.4f'
                  % (epoch, num_epochs, i, len(dataloader),
                     errD.item(), errG.item(), D_x, D_G_z1, D_G_z2))

        # Save Losses for plotting later
        G_losses.append(errG.item())
        D_losses.append(errD.item())

        # Check how the generator is doing by saving G's output on fixed_noise
        if (iters % 500 == 0) or ((epoch == num_epochs-1) and (i == len(dataloader)-1)):
            with torch.no_grad():
                fake = netG(fixed_noise).detach().cpu()
            img_list.append(vutils.make_grid(fake, padding=2, normalize=True))

        iters += 1

out:

Starting Training Loop...
[0/5][0/1583]   Loss_D: 1.7410  Loss_G: 4.7765  D(x): 0.5343    D(G(z)): 0.5771 / 0.0136
[0/5][50/1583]  Loss_D: 0.2913  Loss_G: 27.3206 D(x): 0.8725    D(G(z)): 0.0000 / 0.0000
[0/5][100/1583] Loss_D: 0.8845  Loss_G: 9.8764  D(x): 0.9949    D(G(z)): 0.4462 / 0.0002
.........
.........
.........
[4/5][1150/1583]        Loss_D: 0.5826  Loss_G: 2.2118  D(x): 0.7190    D(G(z)): 0.1875 / 0.1417
[4/5][1200/1583]        Loss_D: 0.7822  Loss_G: 3.6914  D(x): 0.8733    D(G(z)): 0.4308 / 0.0338
[4/5][1250/1583]        Loss_D: 1.4110  Loss_G: 0.3719  D(x): 0.3172    D(G(z)): 0.0488 / 0.7127
[4/5][1300/1583]        Loss_D: 0.6790  Loss_G: 1.2476  D(x): 0.5989    D(G(z)): 0.0965 / 0.3366
[4/5][1350/1583]        Loss_D: 0.8312  Loss_G: 4.2156  D(x): 0.9318    D(G(z)): 0.4816 / 0.0213
[4/5][1400/1583]        Loss_D: 1.5377  Loss_G: 0.3751  D(x): 0.2823    D(G(z)): 0.0394 / 0.7148
[4/5][1450/1583]        Loss_D: 0.6127  Loss_G: 3.6128  D(x): 0.9081    D(G(z)): 0.3650 / 0.0354
[4/5][1500/1583]        Loss_D: 0.6328  Loss_G: 1.9700  D(x): 0.6545    D(G(z)): 0.1332 / 0.1859
[4/5][1550/1583]        Loss_D: 1.2825  Loss_G: 0.8622  D(x): 0.3590    D(G(z)): 0.0437 / 0.4739

結果分析

最後,看一下我們是怎麼做到的。我們將會看到三個不同的結果。首先,我們看到的是訓練過程中D,G損失是怎麼變化的。然後我們觀察生成器的輸出。最後我們將同時觀察真圖和生成的圖。

訓練過程的損失變化

下面代碼畫出訓練過程損失函數的變化過程。

plt.figure(figsize=(10,5))
plt.title("Generator and Discriminator Loss During Training")
plt.plot(G_losses,label="G")
plt.plot(D_losses,label="D")
plt.xlabel("iterations")
plt.ylabel("Loss")
plt.legend()
plt.show()
### 生成器輸出 前面我們保存了生成器的輸出,現在我們用動畫的形式來觀察結果,按下播放鍵開始動畫。
#%%capture
fig = plt.figure(figsize=(8,8))
plt.axis("off")
ims = [[plt.imshow(np.transpose(i,(1,2,0)), animated=True)] for i in img_list]
ani = animation.ArtistAnimation(fig, ims, interval=1000, repeat_delay=1000, blit=True)

HTML(ani.to_jshtml())

真假圖片對照

最後我們對比觀察真假圖片

# Grab a batch of real images from the dataloader
real_batch = next(iter(dataloader))

# Plot the real images
plt.figure(figsize=(15,15))
plt.subplot(1,2,1)
plt.axis("off")
plt.title("Real Images")
plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:64], padding=5, normalize=True).cpu(),(1,2,0)))

# Plot the fake images from the last epoch
plt.subplot(1,2,2)
plt.axis("off")
plt.title("Fake Images")
plt.imshow(np.transpose(img_list[-1],(1,2,0)))
plt.show()

附錄

完整代碼下載:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章