理解和創建GANs|使用PyTorch來做深度學習

人工智能 Python 大學 AI公園 2019-06-20
作者:Venkatesh Tata編譯:ronghuaiyang

生成對抗網絡的一篇實踐文章,使用PyTorch,用很簡單的代碼搭建了一個GANs,非常通俗易懂。

我們創建了一個生成對抗網絡,可以生成顯示世界中沒有的鳥。

理解和創建GANs|使用PyTorch來做深度學習

理解和創建GANs|使用PyTorch來做深度學習

在我們實際創建GAN之前,我們先看看GANs背後的思想。GANs是Ian Goodfellow發明的,他在斯坦福獲得了本科和碩士學位,在蒙特利爾大學獲得了博士學位。這是深度學習領域的一個新的大事。Yann LeCun說過:

"生成對抗網絡是近年來機器學習領域最有趣的想法"

什麼是GANs?我們為什麼要創造GANs?

神經網絡很擅長分類和預測事情,但是AI的研究者想要讓神經網絡更加像人類,通過創造東西而不僅僅是看見東西。 Ian Goodfellow成功的發明了這樣一類深度學習模型,可以用來創造東西。

GANs是怎麼工作的?

GANs有兩個獨立的神經網絡。一個叫做“G”,代表了生成器,另外一個叫做“D”,代表了判別器。生成器首先隨機的產生圖像,判別器通過觀察這些圖像告訴生成器這些圖片有多真實。

理解和創建GANs|使用PyTorch來做深度學習

讓我們考慮一個生成器

在開始的時候,生成器用一個隨機噪聲信號作為輸入,產生一個隨機圖像作為輸出,通過判別器的幫助,開始產生越來越真實的圖像。

判別器

判別器是生成器的一個對手,它的輸入即有真實的圖像,同時也有生成器生成的圖像,判別器輸出這個圖像的真實程度。

到了某個點的時候,判別器無法判斷出這個圖像是否是真實圖像了,這時我們可以發現某個由生成器輸出的圖像是之前從沒有存在過的了。

GANs的應用

  • 超分辨率
理解和創建GANs|使用PyTorch來做深度學習

  • 藝術輔助
理解和創建GANs|使用PyTorch來做深度學習

  • 元素抽取
理解和創建GANs|使用PyTorch來做深度學習

開始寫代碼 !

注意:下面的代碼並不適合深度學習的新手,我希望你有一些python深度學習的經驗。

開始我們先導入一些GAN需要的包。首先需要確保PyTorch已安裝。

#importing required libraries
from __future__ import print_function
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.optim as optim
import torch.utils.data
import torchvision.datasets as dset
import torchvision.transforms as transforms
import torchvision.utils as vutils
from torch.autograd import Variable

設置一些超參數,batch-size和圖像的尺寸:

# Setting hyperparameters
batchSize = 64
imageSize = 64

第一行我們設置了batchsize為64,第二行設置了輸出圖像的尺寸為64x64。

然後我們創建一個圖像的轉換器的對象,如下:

# Creating the transformations
transform = transforms.Compose([transforms.Scale(imageSize),transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),])

上面的轉化器是將圖像作為判別器的輸入所必須的。

注意:如果需要獲取數據集,點擊這裡:https://github.com/venkateshtata/GAN_Medium.git>,clone這個倉庫,然後替換 “dcgan.py” 文件為你需要寫入的python文件, “data” 文件夾存儲的是數據集。

現在我們加載數據集。這裡我們使用的是 CIFAR-10的數據集。我們批量加載,確保你的python文件和你導入的數據集在同一個文件夾。

# Loading the dataset
dataset = dset.CIFAR10(root = './data', download = True, transform = transform)
dataloader = torch.utils.data.DataLoader(dataset, batch_size = batchSize, shuffle =True, num_workers = 2)

我們將數據集下載後放在./data目錄下,應用我們之前定義的轉化器。然後使用dataLoader 來獲取訓練圖像。其中‘num_workers’ 表示的是讀取數據用的線程的數量,其他的參數可以從字面意思理解。

由於這裡我們需要處理兩個神經網絡,我們會定義一個全局的函數來初始化給定的神經網絡,只要將神經網絡模型通過參數傳給這個函數即可。

def weights_init(m):
classname = m.__class__.__name__
if classname.find('Conv') != -1:
m.weight.data.normal_(0.0, 0.02)
elif classname.find('BatchNorm') != -1:
m.weight.data.normal_(1.0, 0.02)
m.bias.data.fill_(0)

上面的函數獲取神經網絡的模型作為參數,初始化所有的參數。這個函數在訓練開始時在每個迭代都會調用。

理解和創建GANs|使用PyTorch來做深度學習

第一步就是定義我們的生成器神經網絡。我們創建一個生成器的類,裡面包含了一系列的層。

class G(nn.Module):
def __init__(self):
super(G, self).__init__()
self.main = nn.Sequential(
nn.ConvTranspose2d(100, 512, 4, 1, 0, bias = False),
nn.BatchNorm2d(512),
nn.ReLU(True),
nn.ConvTranspose2d(512, 256, 4, 2, 1, bias = False),
nn.BatchNorm2d(256),
nn.ReLU(True),
nn.ConvTranspose2d(256, 128, 4, 2, 1, bias = False),
nn.BatchNorm2d(128),
nn.ReLU(True),
nn.ConvTranspose2d(128, 64, 4, 2, 1, bias = False),
nn.BatchNorm2d(64),
nn.ReLU(True),
nn.ConvTranspose2d(64, 3, 4, 2, 1, bias = False),
nn.Tanh()
)

分解上面的代碼:

  • 我們創建了一個類‘G’,繼承了 ‘nn.module’,這個類裡有構建模型所需要的各種功能,只要將各種應用和連接放到神經網絡裡即可。
  • 然後我們創建了一個模型,包含了一系列的模塊,如卷積,全連接等。
  • 這裡從圖中可以看大,生成器和判別器是相互倒著的。生成器的輸入時一個向量,所以這裡我們使用了轉置卷積 ‘ConvTranspose2d’。
  • 然後我們在batch的維度上對所有的特徵進行了歸一化,然後使用ReLU進行了非線性變換。
  • 我們重複上面的操作,輸入的節點從100變到了512,特徵數從512變到了256,bias保持為False。
  • 在最後的 ‘ConvTranspose2d’ 中,我們輸出了3個通道,因為輸出的是‘RGB’的圖像,使用了‘Tanh’作為激活函數。

現在我們創建一個forward函數來進行生成器信號的前向傳播。

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

上面的函數的輸入時長度為100的隨機向量。返回的是一個生成的圖像。隨機向量產生隨機圖像。

創建生成器:

netG = G() 
netG.apply(weights_init)

這裡我們創建了一個生成器,然後進行了參數初始化。

現在我們再定義一個判別器類:

class D(nn.Module):
def __init__(self):
super(D, self).__init__()
self.main = nn.Sequential(
nn.Conv2d(3, 64, 4, 2, 1, bias = False),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(64, 128, 4, 2, 1, bias = False),
nn.BatchNorm2d(128),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(128, 256, 4, 2, 1, bias = False),
nn.BatchNorm2d(256),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(256, 512, 4, 2, 1, bias = False),
nn.BatchNorm2d(512),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(512, 1, 4, 1, 0, bias = False),
nn.Sigmoid()
)

判別器分解:

  • 和G類似,判別器也是繼承了‘nn.module’,輸入是生成器生成的圖像,返回一個0~1之間的數字。
  • 由於用生成器的輸出作為輸入,第一個操作時卷積,我們的激活函數使用了LeakyReLU。
  • 可以看到,不同於生成器,我們這裡使用了LeakyReLU,這個是經驗得來的。
  • 我們使用了‘BatchNorm2d’ 來進行特徵歸一化。
  • 最後,我們使用了sigmoid函數,輸入0~1之間的概率。

為了進行前向傳播,我們定義一個forward函數,使用生成器的輸出作為輸入:

def forward(self, input):
output = self.main(input)
return output.view(-1)

最後一行,我們的輸出值在0~1之間,由於我們需要把向量鋪平,確保向量有相同的維度。

創建判別器 :

netD = D() 
netD.apply(weights_init)

上面我們創建了判別器,初始化所有的參數:

現在,我們開始訓練生成對抗網絡。開始之前,我們需要得到一個損失函數,用來評價判別器的損失。我們使用 BCE Loss,非常適合對抗網絡。然後生成器和判別器我們都需要一個優化器。

criterion = nn.BCELoss()
optimizerD = optim.Adam(netD.parameters(), lr = 0.0002, betas = (0.5, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr = 0.0002, betas = (0.5, 0.999))

我們創建了一個評價函數用來度量預測和目標之間的差別。我們為判別器和生成器各創建了一個優化器。

我們使用了 ‘Adam’ 優化器,這是個SGD的升級版。

我們訓練神經網絡25個epochs:

for epoch in range(25):

我們從數據集中循環讀取圖像 :

for i, data in enumerate(dataloader, 0):

第一步需要更新判別器中的參數,我們把判別器中所有的梯度清零。

netD.zero_grad()

我們知道,判別器需要用真實和虛假的圖像同時訓練。這裡我們先用一個真實圖像來訓練

real, _ = data
input = Variable(real)
target = Variable(torch.ones(input.size()[0]))
output = netD(input)
errD_real = criterion(output, target)

我們從數據集中獲取一個真實圖像訓練判別器,然後包裝成一個變量。然後前向傳播,得到預測值,然後計算loss。

現在,使用生成器輸出的虛假圖像訓練判別器:

noise = Variable(torch.randn(input.size()[0], 100, 1, 1))
fake = netG(noise)
target = Variable(torch.zeros(input.size()[0]))
output = netD(fake.detach())
errD_fake = criterion(output, target)

這裡,我們先讓一個隨機向量通過生成器,得到一個虛假的圖像。然後將這個虛假圖像通過判別器,得到預測,計算損失。

誤差反向傳播:

errD = errD_real + errD_fake
errD.backward()
optimizerD.step()

這裡我們計算判別器總的loss作為判別器的loss,更新判別器的時候,不更新生成器的權值。最後我們通過優化器來判別器更新權值。

下面我們更新生成器的權值:

netG.zero_grad()
target = Variable(torch.ones(input.size()[0]))
output = netD(fake)
errG = criterion(output, target)
errG.backward()
optimizerG.step()

就像之前一樣,我們先將所有的梯度清零。然後將loss是通過計算生成器的梯度來反向傳播,然後通過生成器的優化器來更新生成器的權值。

現在,我們最後的步驟就是在每100個steps時打印loss,存儲真實的圖像和生成的圖像,可以這麼做:

print('[%d/%d][%d/%d] Loss_D: %.4f Loss_G: %.4f' % (epoch, 25, i, len(dataloader),errD.data[0], errG.data[0]))
if i % 100 == 0:
vutils.save_image(real, '%s/real_samples.png' % "./results", normalize = True)
fake = netG(noise)
vutils.save_image(fake.data, '%s/fake_samples_epoch_%03d.png' %("./results", epoch), normalize = True)

完整代碼 :

from __future__ import print_function
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.optim as optim
import torch.utils.data
import torchvision.datasets as dset
import torchvision.transforms as transforms
import torchvision.utils as vutils
from torch.autograd import Variable
batchSize = 64
imageSize = 64
transform = transforms.Compose([transforms.Scale(imageSize),transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),]) # We create a list of transformations (scaling, tensor conversion, normalization) to apply to the input images.
dataset = dset.CIFAR10(root = './data', download = True, transform = transform)
dataloader = torch.utils.data.DataLoader(dataset, batch_size = batchSize, shuffle =True, num_workers = 2)
def weights_init(m):
classname = m.__class__.__name__
if classname.find('Conv') != -1:
m.weight.data.normal_(0.0, 0.02)
elif classname.find('BatchNorm') != -1:
m.weight.data.normal_(1.0, 0.02)
m.bias.data.fill_(0)
class G(nn.Module):
def __init__(self):
super(G, self).__init__()
self.main = nn.Sequential(
nn.ConvTranspose2d(100, 512, 4, 1, 0, bias = False),
nn.BatchNorm2d(512),
nn.ReLU(True),
nn.ConvTranspose2d(512, 256, 4, 2, 1, bias = False),
nn.BatchNorm2d(256),
nn.ReLU(True),
nn.ConvTranspose2d(256, 128, 4, 2, 1, bias = False),
nn.BatchNorm2d(128),
nn.ReLU(True),
nn.ConvTranspose2d(128, 64, 4, 2, 1, bias = False),
nn.BatchNorm2d(64),
nn.ReLU(True),
nn.ConvTranspose2d(64, 3, 4, 2, 1, bias = False),
nn.Tanh()
)
def forward(self, input):
output = self.main(input)
return output
netG = G()
netG.apply(weights_init)
class D(nn.Module):
def __init__(self):
super(D, self).__init__()
self.main = nn.Sequential(
nn.Conv2d(3, 64, 4, 2, 1, bias = False),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(64, 128, 4, 2, 1, bias = False),
nn.BatchNorm2d(128),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(128, 256, 4, 2, 1, bias = False),
nn.BatchNorm2d(256),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(256, 512, 4, 2, 1, bias = False),
nn.BatchNorm2d(512),
nn.LeakyReLU(0.2, inplace = True),
nn.Conv2d(512, 1, 4, 1, 0, bias = False),
nn.Sigmoid()
)
def forward(self, input):
output = self.main(input)
return output.view(-1)
netD = D()
netD.apply(weights_init)
criterion = nn.BCELoss()
optimizerD = optim.Adam(netD.parameters(), lr = 0.0002, betas = (0.5, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr = 0.0002, betas = (0.5, 0.999))
for epoch in range(25):
for i, data in enumerate(dataloader, 0):

netD.zero_grad()

real, _ = data
input = Variable(real)
target = Variable(torch.ones(input.size()[0]))
output = netD(input)
errD_real = criterion(output, target)

noise = Variable(torch.randn(input.size()[0], 100, 1, 1))
fake = netG(noise)
target = Variable(torch.zeros(input.size()[0]))
output = netD(fake.detach())
errD_fake = criterion(output, target)

errD = errD_real + errD_fake
errD.backward()
optimizerD.step()
netG.zero_grad()
target = Variable(torch.ones(input.size()[0]))
output = netD(fake)
errG = criterion(output, target)
errG.backward()
optimizerG.step()
print('[%d/%d][%d/%d] Loss_D: %.4f Loss_G: %.4f' % (epoch, 25, i, len(dataloader),errD.data[0], errG.data[0]))
if i % 100 == 0:
vutils.save_image(real, '%s/real_samples.png' % "./results", normalize = True)
fake = netG(noise)
vutils.save_image(fake.data, '%s/fake_samples_epoch_%03d.png' %("./results", epoch), normalize = True)

你可以從我的GitHub倉庫看到代碼:

https://github.com/venkateshtata/GAN_Medium

如果有好的建議,可以隨便fork或者拉代碼,謝謝!

更多文章,請關注微信公眾號:AI公園

相關推薦

推薦中...