當前位置:網站首頁>DCGAN 源碼解析

DCGAN 源碼解析

2022-01-28 11:46:37 周周周周周大帥

為什麼寫Blog現在還沒找到理由。不過用心看下去你會覺得更有意義。

我們以生成圖片為例子:

  1. G就是一個生成圖片的網絡,它接受一個隨機的噪聲z,然後通過這個噪聲生成圖片,生成的數據記做G(z)
  2. D是一個判別網絡,判別一張圖片是不是“真實的”(是否是捏造的)。它的輸入參數是xx代錶一張圖片,輸出D(x)代錶x為真實圖片的概率,如果為1,就代錶絕逼是真實的圖片,而輸出為0,就代錶不可能是真實的圖片。

在訓練的過程中,生成網絡G的目標就是生成假的圖片去騙過判別網絡D,而判別網絡D的目標就是能够分辨出某一張圖片是不是由G生成的。這就變成了一個博弈的過程。同時GD的能力也在訓練的過程中逐漸提高。在最理想的情况下, 則就是D(G(z))=0.5

一、實現 Implementation

1.權重初始化

在DCGAN的論文中,作者指定所有模型的初始化權重是一個均值為0,標准差為0.02的正態分布。weights_init函數的輸入是一個初始化的模型,然後按此標准重新初始化模型的卷積層、卷積轉置層和BN層的權重。模型初始化後應立即應用此函數。

# custom(自定義) initial weight,called on netG and netD
def weights_init(m):
    classname = m.__class__.__name__  # return m's name
    if classname.find('Conv') != -1:  # find():find 'classname' whether contains "Conv" character,if not,return -1;otherwise,return 0
        torch.nn.init.normal_(m.weight, 0.0, 0.02)  # nn.init.normal_():the initialization weigts used Normally Distributed,mean=0.0,std=0.02
    elif classname.find('BatchNorm') != -1:
        torch.nn.init.normal_(m.weight, 1.0, 0.02)
        torch.nn.init.zeros_(m.bias)

2. 生成器Generator

生成器G, 用於將隱向量 (z)映射到數據空間。 由於我們的數據是圖片,也就是通過隱向量z生成一張與訓練圖片大小相同的RGB圖片 (比如 3x64x64). 在實踐中,這是通過一系列的ConvTranspose2d,BatchNorm2d,ReLU完成的。 生成器的輸出,通過tanh激活函數把數據映射到[−1,1]。需要注意的是,在卷積轉置層之後緊跟BN層,這是DCGAN論文的重要貢獻。這些層(即BN層)有助於訓練過程中梯度的流動。DCGAN論文中的生成器如下圖所示:

生成器代碼如下:

class Generator(nn.Module):  # father:nn.Module   son:Generator
    def __init__(self, ngpu):  # initialize the properties of the father
        super(Generator, self).__init__()  # jointly father and son,call the method of father's __init__(),let son include all the properties of father
        self.ngpu = ngpu
        self.main = nn.Sequential(  # construct neural layers in order
            # input is Z, going into a convolution;state size:4 x 4 x (ngf*8)
            nn.ConvTranspose2d(nz, ngf * 8, 4, 1, 0, bias=False),  # in_channels=nz, out_channels=ngf*8, kernel_size=4, stride=1, padding=0
            nn.BatchNorm2d(ngf * 8),
            nn.ReLU(True),
            # state size: 8 x 8 x (ngf*4)
            nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 4),
            nn.ReLU(True),
            # state size: 16 x 16 x (ngf*2)
            nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 2),
            nn.ReLU(True),
            # state size: 32 x 32 x (ngf)
            nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf),
            nn.ReLU(True),
            # state size: 64 x 64 x (nc)
            nn.ConvTranspose2d(ngf, nc, 4, 2, 1, bias=False),
            nn.Tanh()
        )

    def forward(self, input):
        if input.is_cuda and self.ngpu > 1:
            output = nn.parallel.data_parallel(self.main, input, range(self.ngpu))
        else:
            output = self.main(input)
        return output

然後,我們可以實例化生成器,並應用weights_init方法,代碼如下:

netG = Generator(ngpu).to(device)  # create the generator
netG.apply(weights_init)  # apply the weight_init function,randomly initialize all weights to means=0,std=0.2
if opt.netG != '':
    netG.load_state_dict(torch.load(opt.netG))
print(netG)

3.判別器Discriminator 

 如前所述,判別器D是一個二分類網絡,它將圖片作為輸入,輸出其為真的標量概率。這裏,D的輸入是一個3*64*64的圖片,通過一系列的 Conv2d, BatchNorm2d,和 LeakyReLU 層對其進行處理,最後通過Sigmoid 激活函數輸出最終概率。如有必要,你可以使用更多層對其擴展。DCGAN 論文提到使用跨步卷積而不是池化進行降采樣是一個很好的實踐,因為它可以讓網絡自己學習池化方法。BatchNorm2d層和LeakyReLU層也促進了梯度的健康流動,這對生成器G和判別器D的學習過程都是至關重要的。判別器代碼如下:

class Discriminator(nn.Module):  # father:nn.Module   son:Discriminator
    def __init__(self, ngpu):  # initialize the properties of the father
        super(Discriminator, self).__init__()  # jointly father and son,call the method of father's __init__(),let son include all the properties of father
        self.ngpu = ngpu
        self.main = nn.Sequential(  # construct neural layers in order
            # input is 64 x 64 x (nc)
            nn.Conv2d(nc, ndf, 4, 2, 1, bias=False),  # in_channels=nz, out_channels=ngf, kernel_size=4, stride=2, padding=1
            nn.LeakyReLU(0.2, inplace=True),
            # state size. 32 x 32 x (ndf)
            nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 2),
            nn.LeakyReLU(0.2, inplace=True),
            # state size.  16 x 16 x (ndf*2)
            nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 4),
            nn.LeakyReLU(0.2, inplace=True),
            # state size.  8 x 8 x (ndf*4)
            nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 8),
            nn.LeakyReLU(0.2, inplace=True),
            # state size.   4 x 4 x (ndf*8)
            nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
            nn.Sigmoid()
        )

    def forward(self, input):
        if input.is_cuda and self.ngpu > 1:
            output = nn.parallel.data_parallel(self.main, input, range(self.ngpu))
        else:
            output = self.main(input)

        return output.view(-1, 1).squeeze(1)  # .view(-1,1):redefining the shape of the matrix;  .squeeze(1):remove the dimensions of 1 in the matrix


然後,我們同樣可以實例化判別器,並應用weights_init方法,代碼如下: 

netD = Discriminator(ngpu).to(device)  # # create the discriminator
netD.apply(weights_init)  # apply the weight_init function,randomly initialize all weights to means=0,std=0.2
if opt.netD != '':
    netD.load_state_dict(torch.load(opt.netD))
print(netD)

 4.損失函數和優化器

有了生成器D和判別器G,我們可以為其指定損失函數和優化器來進行學習。這裏將使用Binary Cross Entropy損失函數 (BCELoss)。其在PyTorch中的定義為:

其中,y_i是標簽,p_yi是樣本為positive的概率,我們約定1代錶positive points,0代錶negative points。我們來分析一下這個公式:

①對於positive樣本,y=1,loss=-logp_yi,當p_yi越大,loss越小;理想情况下,p_yi=1,loss=0;

②對於negative樣本,y=0,loss=-log(1-p_yi) ,當p_yi越大,loss越大;理想情况下,p_yi=0,loss=0;

 DCGAN中咱們的損失函數為:

關於這個公式的含義,我在上篇博客已經講過了,這裏不再贅述。注意這個損失函數需要你提供兩個log組件 (比如 log(D(x))和log(1−D(G(z))))。我們可以指定BCE的哪個部分使用輸入y標簽。但我們可以通過改變y標簽來指定使用哪個log部分。

接下來,我們定義真實標簽為1,假標簽為0。這些標簽用來計算生成器D和判別器G的損失,這也是原始GAN論文的慣例。最後,我們將設置兩個獨立的優化器,一個用於生成器G,另一個判別器D。如DCGAN 論文所述,兩個Adam優化器學習率都為0.0002,Beta1都為0.5。為了記錄生成器的學習過程,我們將會生成一批符合高斯分布的固定的隱向量(即fixed_noise)。在訓練過程中,我們將周期性地把固定噪聲作為生成器G的輸入,通過輸出看到由噪聲生成的圖像。

# Initialize BCELoss Function
criterion = nn.BCELoss()

fixed_noise = torch.randn(opt.batchSize, nz, 1, 1, device=device)  # torch.randn(*size):return a tensor containing a set of random numbers drawn from the standard Normal Distribution
real_label = 1
fake_label = 0

# set up optimizer for both netG and netD
optimizerD = optim.Adam(netD.parameters(), lr=opt.lr, betas=(opt.beta1, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr=opt.lr, betas=(opt.beta1, 0.999))

5.訓練Training 

最後,我們已經定義了GAN網絡的所有結構,可以開始訓練它了。請注意,訓練GAN有點像一種藝術形式,因為不正確的超參數會導致模式崩潰,卻不會提示超參數錯誤的信息。接下來,我們將會“為真假數據構造不同的mini-batches數據”,同時調整判別器G的目標函數以最大化logD(G(z))。訓練分為兩個部分。第一部分更新判別器,第二部分更新生成器。

第一部分——訓練判別器(Part 1 - Train the Discriminator)

回想一下,判別器的訓練目的是最大化輸入正確分類的概率。實際上,我們想要最大化log(D(x))+log(1−D(G(z)))。為了區別mini-batches,分兩步計算。第一步,我們將會構造一個來自訓練數據的真圖片batch,作為判別器D的輸入,計算其損失loss(log(D(x)),調用backward方法計算梯度。第二步,我們將會構造一個來自生成器G的假圖片batch,作為判別器D的輸入,計算其損失loss(log(1−D(G(z))),調用backward方法累計梯度。最後,調用判別器D優化器的step方法更新一次模型(即判別器D)的參數。

第二部分——訓練生成器(Part 2 - Train the Generator) 

如原論文所述,我們希望通過最小化log(1−D(G(z)))訓練生成器G來創造更好的假圖片。作為解决方案,我們希望最大化log(D(G(z)))。通過以下方法來實現這一點:使用判別器D來分類在第一部分G的輸出圖片,計算損失函數的時候用真實標簽(記做GT),調用backward方法更新生成器G的梯度,最後調用生成器G優化器的step方法更新一次模型(即生成器G)的參數。使用真實標簽作為GT來計算損失函數看起來有悖常理,但是這允許我們可以使用BCELoss的log(x)部分而不是log(1−x)部分,這正是我們想要的。 

for epoch in range(opt.niter):  # For each epoch
    for i, data in enumerate(dataloader, 0):  # For each batch in the dataloader
        ############################
        # (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)  # data[a,b]:a means to take all the data in row a, b means take all data in column b
        batch_size = real_cpu.size(0)
        label = torch.full((batch_size,), real_label,
                           dtype=real_cpu.dtype, device=device)
        #############################
        output = netD(real_cpu)  # Forward,pass all-real batch through netD
        errD_real = criterion(output, label)  # calculate loss on all-real batch
        errD_real.backward()  # calculate gradient for netD in backward propagation
        D_x = output.mean().item()  # .item():return a float point data when output loss

        # train with fake
        noise = torch.randn(batch_size, nz, 1, 1, device=device)  # generate batch of z-vectors
        fake = netG(noise)  # generate fake image batch with netG
        label.fill_(fake_label)
        output = netD(fake.detach())  # Forward,pass all-fake image batch through netD
        errD_fake = criterion(output, label)  # calculate loss on all-fake image batch
        errD_fake.backward()  # calculate gradient for this batch(all-fake image)
        D_G_z1 = output.mean().item()
        errD = errD_real + errD_fake  # add the loss
        optimizerD.step()  # update the netD model by update discriminator's optimizer's step() method

        ############################
        # (2) Update G network: maximize log(D(G(z)))
        ###########################
        netG.zero_grad()
        label.fill_(real_label)  # fill real_label
        output = netD(fake)  # Forward,pass fake images through netD
        errG = criterion(output, label)  # calculate the loss between output and real_lable,why?because we want use BECLOSS's logP_y part
        errG.backward()
        D_G_z2 = output.mean().item()
        optimizerG.step()

參考:https://www.codetd.com/article/13228021

 

 

 

 

版權聲明
本文為[周周周周周大帥]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2022/01/202201281146372957.html

隨機推薦