1. 程式人生 > >【Gans入門】Pytorch實現Gans程式碼詳解【70+程式碼】

【Gans入門】Pytorch實現Gans程式碼詳解【70+程式碼】

簡述

由於科技論文老師要求閱讀Gans論文並在網上找到類似的程式碼來學習。

程式碼來源

程式碼含義概覽

這個大致講講這個程式碼實現了什麼。

這個模型的輸入為:一些資料夾雜在x2x^22x2+12x^2+1這個兩個函式之間的一些資料。這個用線性函式的隨機生成來生成這個東西
輸出: 這是一個生成模型,生成模型的結果就是生成通過上面的輸入資料輸出這樣的資料來畫一條曲線

  • 我們每次只取15個在x方向上等距的點。然後畫出這條曲線來。
    經過學習之後,我們要求這個模型能自己畫出一條在其中的曲線來。

  • 當然,由於我們設定的區間是有弧線的,即區間的概率上是有偏差的。經過足夠多的擬合,有較高的概率使得整個模型畫出來的曲線也是一個弧線。

程式碼分段解釋

匯入包:

import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt

設定引數:

  • LR_G:生成器的學習率
  • LR_D:判別器的學習率
  • N_IDEAS:生成器的啟發因子(就是生成器這個神經網路的初始輸入層的節點數)
  • ART_COMPONENTS:觀測節點–每次用於畫線的那些輸出點的數量
  • BATCH_SIZE:其實是輸入資料的數量。
  • PAINT_POINTS :就是把重複的那麼多資料(將x區間等分為觀測節點數量等分的x節點)疊起來而已。這樣之後就直接代入就可以知道資料了。
BATCH_SIZE = 64
LR_G = 0.0001  # learning rate for generator
LR_D = 0.0001  # learning rate for discriminator
N_IDEAS = 5  # think of this as number of ideas for generating an art work (Generator)
ART_COMPONENTS = 15  # it could be total point G can draw in the canvas
PAINT_POINTS = np.vstack([np.linspace(
-1, 1, ART_COMPONENTS) for _ in range(BATCH_SIZE)])

給出標準資料:

這個函式,會給出特定規模的標準資料

  • 先建立一個(BATCH_SIZE,1)規模的來自於(1,2)均勻分佈的隨機數。
  • 再用這個資料構建 ax2+(a1)a*x ^2 + (a - 1) 其中a來自於(1,2)(1,2)的均勻分佈。然後有BATCH_SIZE 個結果,所以,我們會在前面說到,這個引數表示輸入集合的大小
def artist_works():  # painting from the famous artist (real target)
    a = np.random.uniform(1, 2, size=BATCH_SIZE)[:, np.newaxis]
    paintings = a * np.power(PAINT_POINTS, 2) + (a - 1)
    paintings = torch.from_numpy(paintings).float()
    return paintings

構建模型:

搭建神經網路

  • 這裡搭建的神經網路,只需要構建對映層就好了。
  • 生成器模型:先通過一個線性函式構建一個從N_IDEAS到128的對映。再通過啟用函式ReLU()函式來做一個對映。最後,再用一個線性函式搭建從128到觀測點的對映。(這些對映都是用矩陣乘法來實現的,所以,其實引數空間是三個不同的矩陣)
  • 判別式模型:先通過一個觀測點的到128的模型。再通過一個ReLU啟用函式。之後,再用一個線性函式使得從128到1維度。一維就是常數,再做一個sigmoid的啟用函式對映到(0,1)(0,1)空間。表示概率。
G = nn.Sequential(  # Generator
    nn.Linear(N_IDEAS, 128),  # random ideas (could from normal distribution)
    nn.ReLU(),
    nn.Linear(128, ART_COMPONENTS),  # making a painting from these random ideas
)

D = nn.Sequential(  # Discriminator
    nn.Linear(ART_COMPONENTS, 128),  # receive art work either from the famous artist or a newbie like G
    nn.ReLU(),
    nn.Linear(128, 1),
    nn.Sigmoid(),  # tell the probability that the art work is made by artist
)

構建優化器

opt_D = torch.optim.Adam(D.parameters(), lr=LR_D)
opt_G = torch.optim.Adam(G.parameters(), lr=LR_G)

構建了兩個優化器。其實就是把對應模型的引數放進來了而已,之後,再設定一下學習率。

這裡採用的是Adam模型來做優化。

迭代細節

其實這上面應該還有一些畫圖而加上的函式,但是對於模型不是很重要,這裡就不看了。最後會有一個整體的模型。

for step in range(10000):

明顯看出,使用了10000次的迭代。

  • 先呼叫標準資料生成函式,生成標準資料。
  • 再用pytorch的隨機數來生特定大小的生成器啟發因子。
  • 之後,再把這個隨機數丟給生成器。
  • 明顯,通過這樣的訓練,其實逐漸的訓練這個生成器模型,在隨機給輸入的情況下,漸漸掌握輸出正確的結果(個人感覺這裡有提高的可能
artist_paintings = artist_works()  # real painting from artist
G_ideas = torch.randn(BATCH_SIZE, N_IDEAS)  # random ideas
G_paintings = G(G_ideas)  # fake painting from G (random ideas)

再把假畫和真畫都丟給判別式模型。給出一個概率來。

之後構建兩個模型的交叉熵,需要降低的損失函式

D_loss = - torch.mean(torch.log(prob_artist0) + torch.log(1. - prob_artist1))
G_loss = torch.mean(torch.log(1. - prob_artist1))

這個其實是根據論文中的公式給出的。

  • 注意到,這裡跟下面演算法中給出的梯度是相同的。就是前面少了個係數,但是有沒係數,對於這個不影響的。

在這裡插入圖片描述

其實上面只是把整個模型搭建起來,其實都還沒有執行的。
真正執行的部分是下面這裡

opt_D.zero_grad()
D_loss.backward(retain_graph=True)  # reusing computational graph
opt_D.step()

opt_G.zero_grad()
G_loss.backward(retain_graph=True)
opt_G.step()

注意到,其實非常重複的。

  • 第一步的zero_grad()函式:

原因:
In PyTorch, we need to set the gradients to zero before starting to do backpropragation because PyTorch accumulates the gradients on subsequent backward passes. This is convenient while training RNNs. So, the default action is to accumulate the gradients on every loss.backward() call.
在PyTorch中,我們需要設定這個梯度到0,在開始反向傳播的訓練之前,因為Pytorch會累積這個梯度在之後的反向傳播過程中。這是非常方便的當訓練RNNs的時候,所以預設就這麼設定了。
Because of this, when you start your training loop, ideally you should zero out the gradients so that you do the parameter update correctly. Else the gradient would point in some other directions than the intended direction towards the minimum (or maximum, in case of maximization objectives).
由於這個,當你開始你的訓練迴圈的時候,比較聰明的一點就是先把這個梯度設定為0,以確保你的訓練的引數會是正確的。否則的話,這個梯度會指向一些其他地方(亂跑)

  • 第二步:反向傳播,這裡設定保留整個圖的情況下。
  • 第三步:.step() 其實這個函式才真正表示這個模型被訓練了。

畫圖

由於我們每次生成時候後,其實都是生成了一個BATCH_SIZE個。但是我們一次畫太多的圖的話,會顯得很醜,所以就只畫第一個圖就好了。

這裡取模的原因就在於避免畫太多的圖,導致耗費太多資源。

    if step % 500 == 0:  # plotting
        plt.cla()
        plt.plot(PAINT_POINTS[0], G_paintings.data.numpy()[0], c='#4AD631', lw=3, label='Generated painting', )
        # 2x^2 + 1
        plt.plot(PAINT_POINTS[0], 2 * np.power(PAINT_POINTS[0], 2) + 1, c='#74BCFF', lw=3, label='upper bound')
        # x^2
        plt.plot(PAINT_POINTS[0], 1 * np.power(PAINT_POINTS[0], 2) + 0, c='#FF9359', lw=3, label='lower bound')
        plt.text(-.5, 2.3, 'D accuracy=%.2f (0.5 for D to converge)' % prob_artist0.data.numpy().mean(),
                 fontdict={'size': 13})
        plt.text(-.5, 2, 'D score= %.2f (-1.38 for G to converge)' % -D_loss.data.numpy(), fontdict={'size': 13})
        plt.ylim((0, 3))
        plt.legend(loc='upper right', fontsize=10)
        plt.draw()
        plt.pause(0.01)

全部程式碼:

import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt

# Hyper Parameters
BATCH_SIZE = 64
LR_G = 0.0001  # learning rate for generator
LR_D = 0.0001  # learning rate for discriminator
N_IDEAS = 5  # think of this as number of ideas for generating an art work (Generator)
ART_COMPONENTS = 15  # it could be total point G can draw in the canvas
PAINT_POINTS = np.vstack([np.linspace(-1, 1, ART_COMPONENTS) for _ in range(BATCH_SIZE)])


def artist_works():  # painting from the famous artist (real target)
    a = np.random.uniform(1, 2, size=BATCH_SIZE)[:, np.newaxis]
    paintings = a * np.power(PAINT_POINTS, 2) + (a - 1)
    paintings = torch.from_numpy(paintings).float()
    return paintings


G = nn.Sequential(  # Generator
    nn.Linear(N_IDEAS, 128),  # random ideas (could from normal distribution)
    nn.ReLU(),
    nn.Linear(128, ART_COMPONENTS),  # making a painting from these random ideas
)

D = nn.Sequential(  # Discriminator
    nn.Linear(ART_COMPONENTS, 128),  # receive art work either from the famous artist or a newbie like G
    nn.ReLU(),
    nn.Linear(128, 1),
    nn.Sigmoid(),  # tell the probability that the art work is made by artist
)

opt_D = torch.optim.Adam(D.parameters(), lr=LR_D)
opt_G = torch.optim.Adam(G.parameters(), lr=LR_G)

plt.ion()  # something about continuous plotting

for step in range(10000):
    artist_paintings = artist_works()  # real painting from artist
    G_ideas = torch.randn(BATCH_SIZE, N_IDEAS)  # random ideas
    G_paintings = G(G_ideas)  # fake painting from G (random ideas)

    prob_artist0 = D(artist_paintings)  # D try to increase this prob
    prob_artist1 = D(G_paintings)  # D try to reduce this prob

    D_loss = - torch.mean(torch.log(prob_artist0) + torch.log(1. - prob_artist1))
    G_loss = torch.mean(torch.log(1. - prob_artist1))

    opt_D.zero_grad()
    D_loss.backward(retain_graph=True)  # reusing computational graph
    opt_D.step()

    opt_G.zero_grad()
    G_loss.backward(retain_graph=True)
    opt_G.step()

    if step % 500 == 0:  # plotting
        plt.cla()
        plt.plot(PAINT_POINTS[0], G_paintings.data.numpy()[0], c='#4AD631', lw=3, label='Generated painting', )
        # 2x^2 + 1
        plt.plot(PAINT_POINTS[0], 2 * np.power(PAINT_POINTS[0], 2) + 1, c='#74BCFF', lw=3, label='upper bound')
        # x^2
        plt.plot(PAINT_POINTS[0], 1 * np.power(PAINT_POINTS[0], 2) + 0, c='#FF9359', lw=3, label='lower bound')
        plt.text(-.5, 2.3, 'D accuracy=%.2f (0.5 for D to converge)' % prob_artist0.data.numpy().mean(),
                 fontdict={'size': 13})
        plt.text(-.5, 2, 'D score= %.2f (-1.38 for G to converge)' % -D_loss.data.numpy(), fontdict={'size': 13})
        plt.ylim((0, 3))
        plt.legend(loc='upper right', fontsize=10)
        plt.draw()
        plt.pause(0.01)

plt.ioff()
plt.show()

參考並學習的連結