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