天天看點

Generative Adversarial NetGenerative Adversarial Nets代碼閱讀

Generative Adversarial Nets

Abstract

  • 目的:以一種對抗的過程來估計生成式模型(generative models)

    這也是為什麼題目中并沒有 discriminative 的原因。該方法的目的是要以一種新的方式得到好的生成模型

  • 同時訓練兩個模型
    • A generative model G - 得到資料分布(data distribution)
    • a discriminative model D - 估計一個樣本是從訓練資料得到而不是由G生成的機率
  • A minimax two-player game
  • 解決方案(優化目标):G能夠恢複訓練資料的分布,且D的輸出恒為0.5

1 Introduction

  • Deep Learning 希望能夠發現豐富的分層模型來表示各種資料的機率分布,特别是結構資料(structured data, 例如圖像、文字、語音等)
  • Discriminative model - map a high-dimension, rich sensory input to a class label 例如分類的問題
  • Generative models - 目前的估計方法涉及許多概念的計算,以及難以與分段線性函數整合
  • Adversarial nets framework - 例如僞造假鈔和警察的關系,雙方在競争中各自改善自己的方法,直到難以警察難以分辨真假(也即生成模型效果達到最佳)
  • 在本文提出的方法中,G和D均使用多層感覺機模型,G以随機噪聲作為輸入

[補充] Discriminative model v.s. Generative model

【資料待查待整理】

2 Related work

估計生成模型的一系列算法,是看不懂的内容。

3 Adversarial nets

  • 一些符号表示
    • data x 的機率分布
    • 生成器通過噪聲z得到的機率分布
    • Discriminator輸出表示
  • Value function
    Generative Adversarial NetGenerative Adversarial Nets代碼閱讀
    • 對G的優化 - minmize後面一部分,G生成的資料越真實
    • 對D的優化 - maximize整個函數,給真實的資料高分,給G生成的資料低分
  • a less formal, more pedagogical explanation
    Generative Adversarial NetGenerative Adversarial Nets代碼閱讀

    a) 初始情況

    b) 對D進行了優化,此時D能夠準确分辨真實資料和生成的資料

    c) 對G進行了優化,G生成的資料進一步接近真實資料的分布

    d) 最後的結果,G生成的資料與真實資料分布一緻,D的輸出處處為0.5,難以分辨資料真假

  • Algorithm
    Generative Adversarial NetGenerative Adversarial Nets代碼閱讀
  • 在實際的訓練過程中,在早期訓練過程中,D能很容易地分辨真實資料和G生成的資料,是以難以去訓練G。在早期改變G的優化目标:

    maximize logD(G(z))

  • 先train G還是先train D?重要嗎

4 Theoretical Results

…大概是在理論上證明了最優解的存在以及算法的收斂性

5 Experiments

訓練的設定以及實驗結果

代碼閱讀

參數中不太懂的部分

  • b1, b2
parser.add_argument("--b1", type=float, default=0.5, help="adam: decay of first order momentum of gradient")
parser.add_argument("--b2", type=float, default=0.999, help="adam: decay of first order momentum of gradient")
           
Adam 裡面的兩個參數
           
  • latent dimension
目前主要不明白這裡的潛在空間(latent space)指的是什麼。generator 的輸入 z 的次元。
           

Generator

class Generator(nn.Module):
	def __init__(self):
		super(Generator, self).__init__()
		
		# 基礎層結構 : 全連接配接層 +(BatchNorm1d)+ LeakyReLU
		def block(in_feat, out_feat, normalize=True):
			layers = [nn.Linear(in_feat, out_feat)]
			if normalize:
				layers.append(nn.BatchNorm1d(out_feat, 0.8))
			layers.append(nn.LeakyReLU(0.2, inplace=True))
			return layers
		
		self.model = nn.Sequential(
			*block(opt.latent_dim, 128, normalize=False),
			*block(128, 256),
			*block(256, 512),
			*block(512, 1024),
			nn.Linear(1024, int(np.prod(img_shape))), # 生成圖像
			nn.Tanh() # 最後使用的是 Tanh 激活函數
		)
	
	def forward(self, z):
		img = self.model(z)
		img = img.view(img.size(0), *img_shape) # 對輸出的向量進行變換,變換為圖檔的大小(batch_size, channels, size, size)
		return img
           
  • latent_dim

    是以就是輸入噪聲的尺寸咯?

  • numpy prod

    Return the product of array elements over a given axis.

  • img.size(0)

    img 是個 Tensor,那麼img.size(0)應該是表示batch?

  • img_shape 此處img_shape的定義是使用括号而不是方括号,是以img_shape是一個元組(tuple),而不是數組。(元組内的元素不能進行修改)

    暫時不清楚

    *img_shape

    是什麼意思,但是輸出出來就是img_shape本身。

總的來說,Generator 是使用多層感覺機模型,在中間過程中使用了 LeakyReLU 函數,在輸出層使用了 Tanh 函數。Generator以某一長度的向量作為輸入,最後輸出圖像矩陣。

Discriminator

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

		self.model = nn.Sequential(
			nn.Linear(int(np.prod(img_shape)), 512),
			nn.LeakyReLU(0.2, inplace=True),
			nn.Linear(512, 256),
			nn.LeakyReLU(0.2, inplace=True),
			nn.Linear(256, 1),
			nn.Sigmoid(),
		)

	def forward(self, img):
		img_flat = img.view(img.size(0), -1)
		validity = self.model(img_flat)

		return validity
           

與 Generator 相比,同樣是多層感覺機,但是層數較少。在輸出層使用了 Sigmoid 函數。輸入圖像矩陣轉換得到的向量,輸出一個标量。

損失函數

BCELoss

Optimizers

optimizer_G = torch.optim.Adam(generator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))
optimizer_D = torch.optim.Adam(generator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))
           

可以看到G和D的優化政策是相同的。

b1和b2代表了 Adam 裡的兩個參數。

Training

Tensor = torch.cuda.FloatTensor if cuda else torch.FloatTensor

for epoch in range(opt.n_epochs):
	for i, (imgs, _) in enumerate(dataloader):
		# Adversarial ground truths 
		# 前者是對真實圖檔的給分,後者是對生成圖檔的給分 1-0
		valid = Variable(Tensor(imgs.size(0), 1).fill_(1.0), requires_grad=False) # (batch_size, 1) init=1.0
		fake = Variable(Tensor(imgs.size(0), 1).fill_(0.0), requires_grad=False)
	
		# Configure input 
		# 将圖像資料以FloatTensor的格式複制到了real_imgs
		# 經過實驗發現,改動real_imgs, imgs的值同樣會發生變化(共享記憶體位址)
		real_imgs = Variable(imgs.type(Tensor))

		# ----------------
		#  Train Generator
		# ----------------
		optimizer_G.zero_grad()

		# Sample noise as generator input
		z = Variable(Tensor(np.random.normal(0, 1, (imgs.shape[0], opt.latent_dim))))

		# Generate a batch of images
		gen_imgs = generator(z)

		# Loss measures generator's ability to fool the discriminator
		g_loss = adversarial_loss(discriminator(gen_imgs), valid) # generator 的輸出要盡量得到高分(1)

		g_loss.backward()
		optimizer_G.step()

		# ---------------------
		#  Train Discriminator
		# ---------------------

		optimizer_D.zero_grad()

		# Measure discriminator's ability to classify real from generated samples
		real_loss = adversarial_loss(discriminator(real_imgs), valid) # 真實圖檔高分
		fake_loss = adversarial_loss(discriminator(gen_imgs.detach()), fake) # 生成的圖檔低分
		d_loss = (real_loss + fake_loss) / 2 # 包含了兩方面的 loss 

		d_loss.backward()
		optimizer_D.step()

		print(
			"[Epoch %d/%d] [Batch %d/%d] [D loss: %f] [G loss: %f]"
			% (epoch, opt.n_epochs, i, len(dataloader), d_loss.item(), g_loss.item())
		)

		batches_done = epoch * len(dataloader) + i
		if batches_done % opt.sample_interval == 0:
			# from torchvision.utils import save_image
			save_image(gen_imgs.data[:25], "images/%d.png" % batches_done, nrow=5, normalize=True)
		
           
  • G 和 D 疊代頻率是一緻的,k=1
  • 先訓練G還是D沒什麼關系,都看到過
  • 真實資料和生成資料的給分,1-0

繼續閱讀