PyTorch對DCGANs網絡的實現

原文鏈接:https://github.com/L1aoXingyu/code-of-learn-deep-learning-with-pytorch/blob/master/chapter6_GAN/gan.ipynb

​ 生成對抗網絡(GANs)是現在深度學習的熱點之一,下面我們通過PyTorch實現深度卷積生成對抗網絡(DCGANs),數據集使用最爲經典的MNIST手寫數據集。

1.導入需要使用到的包

import os
import torch
import torch.nn as nn
from torch.autograd import Variable
from torch.utils.data import DataLoader,sampler
import torch.nn.functional as F
from torchvision.datasets import MNIST
from torchvision import transforms
from torchvision.utils import save_image
import time
import numpy as np

2.定義判別網絡(判別器)和生成網絡(生成器)

定義深層卷積判別網絡,它就是一般的卷積網絡

class DC_Discriminator(nn.Module):
	def __init__(self):
		super(DC_Discriminator,self).__init__()

		self.conv = nn.Sequential(
			nn.Conv2d(1,32,5,1), # 輸入深度1,輸出深度32,濾波器5*5,步長1
			nn.LeakyReLU(0.01), # 斜率爲0.01
			nn.MaxPool2d(2,2), # 濾波器2*2,步長2
			nn.Conv2d(32,64,5,1), # 輸入深度32,輸出深度64,濾波器5*5,步長1
			nn.LeakyReLU(0.01),
			nn.MaxPool2d(2,2)
			)

		self.fc = nn.Sequential(
			nn.Linear(1024,1024),
			nn.LeakyReLU(0.01),
			nn.Linear(1024,1)
			)

	def forward(self,x):
		x = self.conv(x)
		x = x.view(x.size(0),-1)
		x = self.fc(x)
		return x

定義深度卷積生成網絡,其需要將一個低維的噪聲向量變成一個圖片數據

class DC_Generator(nn.Module):
	def __init__(self,noise_size=NOISE_DIM):
		super(DC_Generator,self).__init__()

		self.fc = nn.Sequential(
			nn.Linear(noise_size,1024), # noise_size表示輸入的維度
			nn.ReLU(True),
			nn.BatchNorm1d(1024),
			nn.Linear(1024,7*7*128),
			nn.ReLU(True),
			nn.BatchNorm1d(7*7*128)
			)

		self.conv = nn.Sequential(
			nn.ConvTranspose2d(128,64,4,2,padding=1), # 轉置卷積
			nn.ReLU(True),
			nn.BatchNorm2d(64),
			nn.ConvTranspose2d(64,1,4,2,padding=1),
			nn.Tanh()
			)

	def forward(self,x):
		x = self.fc(x)
		x = x.view(x.size(0),128,7,7) # reshape通道是128,大小是7*7
		x = self.conv(x)
		return x

3.定義圖片預處理函數,及取樣函數

訓練圖片時的預處理函數:

def preprocess_img(x): # 訓練測試圖片的預處理
	x = transforms.ToTensor()(x)
	return (x-0.5)/0.5 # 這一步是標準化處理

存儲圖片時的預處理函數:

def deprocess_img(x):
	return (x+1.0)/2.0

def to_img(x):
	x = 0.5*(x+1.)
	x = x.clamp(0,1) # 將輸入張量每個元素夾緊到區間中,並返回結果到一個新張量
	x = x.view(x.size(0),1,28,28)
	return x

定義一個取樣的函數,即取樣器

class ChunkSampler(sampler.Sampler):
	def __init__(self,num_samples,start=0):
		# num_samples:表示我們需要的樣本數量
		# start:表示我們從哪(下標)開始取樣本
		self.num_samples = num_samples
		self.start = start

	def __iter__(self): # 迭代器
		return iter(range(self.start,self.start+self.num_samples))

	def __len__(self): # 返回取樣的樣本總數
		return self.num_samples

4.定義超參數,及加載數據集

# 定義超參數
NUM_TRAIN = 50000 # 訓練樣本數
learning_rate = 3e-4 # 學習率
NOISE_DIM = 96 # 噪音維度,就是生成器中的noise_size
batch_size = 128 # 一批次中的樣本數

# 加載數據集,參數root表示數據集的路徑,trainsform表示數據的預處理
train_set = MNIST(root='./data/',train=True,transform=preprocess_img)
train_loader = DataLoader(train_set,batch_size=batch_size,sampler=ChunkSampler(NUM_TRAIN,0))

5.定義損失函數和優化方法

# 定義判別網絡的損失函數
def ls_discriminator_loss(scores_real,scores_fake):
    # scores_real:數據集裏圖片輸入到判別網絡得到的輸出
    # scores_fake:生成器生成的圖片輸入到判別網絡得到的輸出
	loss = 0.5*((scores_real - 1)**2).mean() + 0.5*(scores_fake**2).mean()
	return loss

# 定義生成網絡的損失函數
def ls_generator_loss(scores_fake):
	loss = 0.5*((scores_fake - 1)**2).mean()
	return loss

# 使用adam來進行優化,學習率是3e-4,beta1是0.5,beta2是0.999
def get_optimizer(net):
	optimizer = torch.optim.Adam(net.parameters(),lr=learning_rate,betas=(0.5,0.999))
    # betas (Tuple[float, float], 可選) – 用於計算梯度以及梯度平方的運行平均值的係數(默認:0.9,0.999)
	return optimizer

​ 注意,這裏並沒有像平常一樣使用PyTorch已經集成的損失函數,而是自己定義的。而這個自己定義的損失函數其實是最基本的生成對抗網絡的一個變式,這是在損失函數上的一個變式,叫做Least Squares GAN。

​ Least Squares GAN比最原始的GANs的loss更加穩定,因此這裏我們使用了它。

6.定義訓練函數

def train_a_gan(D_net,G_net,D_optimizer,G_optimzer,discriminator_loss,generator_loss,show_every=250,noise_size=NOISE_DIM,num_epoches=100):
    # 參數分別是判別網絡、生成網絡、判別網絡優化方法、生成網絡優化方法、判別網絡損失函數、生成網絡損失函數
    # show_every用於計數爲了多少次保存一次圖片、noise_size輸入生成網絡的噪音維度、訓練輪數
	iter_count = 0 # 定義的計數器,爲了和show_every一起用,目的是顯示圖片
	now = time.clock()
	for epoch in range(num_epoches):
		for img,_ in train_loader:
			bs = img.size(0) # batch_size的簡寫,爲什麼不直接使用batch_size的來代替,是因爲有時到最後一個batch的size不一定夠128

			# 判別網絡
			# 判別網絡的前向傳播
			real_data = Variable(img).cuda() # 用於深度卷積生成對抗網絡的真實數據
			logits_real = D_net(real_data) # 判別網絡得分,判別真圖片

			sample_noise = (torch.rand(bs,noise_size)-0.5) / 0.5 # -1~1的均勻分佈
			g_fake_seed = Variable(sample_noise).cuda() # 生成噪聲維度
			fake_data = G_net(g_fake_seed) # 生成假的數據,即生成假圖片
			logits_fake = D_net(fake_data) # 判別網絡得分,判別假圖片
            
			d_total_error = discriminator_loss(logits_real,logits_fake) # 計算判別器的loss

			# 判別網絡的反向傳播
			D_optimizer.zero_grad()
			d_total_error.backward()
			D_optimizer.step() # 優化判別器,更新參數


			# 生成網絡
			# 生成網絡的前向傳播
			g_fake_seed = Variable(sample_noise).cuda() # 這裏爲什麼不用上面的數據,而重新生成一下???
			fake_data = G_net(g_fake_seed) # 生成假數據
			gen_logits_fake = D_net(fake_data) # 判別網絡得分(就是網絡的輸出結果)
            
			g_error = generator_loss(gen_logits_fake) # 計算生成網絡的loss

			# 生成網絡的反向傳播
			G_optimzer.zero_grad()
			g_error.backward()
			G_optimzer.step() # 優化生成器,更新參數

			if(iter_count % show_every == 0): # 每訓練250批次就讓生成器生成一次圖片,看看生成器的效果
				print("Iter:{},D_Loss:{:.4f},G_Loss:{:.4f},Time:{:.4f}".format(iter_count,d_total_error.item(),g_error.item(),time.clock()-now))
				imgs_numpy = deprocess_img(fake_data.data.cpu().numpy())
				imgs_variable = Variable(torch.from_numpy(imgs_numpy))
				pic = to_img(imgs_variable.cpu().data) # 將數據轉化爲圖片
                
				if not os.path.exists('./dc_gan_imgs'):
					os.mkdir('./dc_gan_imgs')
				save_image(pic,'./dc_gan_imgs/image_{}.png'.format(iter_count))
			iter_count += 1

7.定義主程序及結果顯示

# 定義深度卷積生成對抗網絡的訓練過程
D_DC = DC_Discriminator().cuda()
G_DC = DC_Generator().cuda()

D_DC_optim = get_optimizer(D_DC)
G_DC_optim = get_optimizer(G_DC)

train_a_gan(D_DC,G_DC,D_DC_optim,G_DC_optim,ls_discriminator_loss,ls_generator_loss,num_epoches=10)

Iter-500Iter-1500Iter-2500Iter-3500

圖片依次是Iter-500、Iter-1500、Iter-2500和Iter-3500的生成器生成的圖片(注:訓練10輪,圖片保存到Iter-3750)。

可以發現圖片生成的越來越清晰,已經跟數據集中的圖片沒多少區別了,已經基本達到以假亂真的境界了。

8.總結

  • 上面我們只用深度卷積生成對抗網絡(DCGANs)訓練10輪,就已經達到這麼好的效果了(輸入數據雖然只是普通的灰度圖)。這差不多能超過普通的GANs訓練30輪的效果。

  • 生成對抗網絡的目的是,判別網絡盡力將輸入的真實圖片判斷成1,把生成網絡生成的圖片判斷成0。而生成網絡則是盡力生成假圖片使得判別網絡將其判斷成1,以達到以假亂真的目的。所以二者間存在這種對抗的關係。

  • 雖然DCGANs的效果非常好,但是其實它並沒有真正地學習到它要表示的物體,通過對抗它只是生成了一張儘可能真的圖片。這意味着我們沒辦法決定哪種噪聲能夠生成想要的圖片。不過,在GANs的變式中變相解決了這個問題,它是Conditional GAN,在訓練的時候,將一句話(可以是描述)和噪聲一起輸入到生成網絡,這樣訓練之後,我們就可以通過輸入一句話,得到差不多我們想要的圖片了。

9.參考資料

來自於:https://github.com/L1aoXingyu/code-of-learn-deep-learning-with-pytorch/blob/master/chapter6_GAN/gan.ipynb

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