今回は、Stanford University/CS231n/Assignment3/Generative Adversarial Networks(GANs/敵対的生成ネットワーク)パイトーチ編をやる。
Generative Adversarial Networks (GANs)¶
What is a GAN?¶
2014年、Goodfellow et alは、Generative Adversarial Networks(GANs/敵対的生成ネットワー)と呼ばれる生成モデルを訓練するための手法をプレゼンした。GANで、2種類のニューラルネットワークを構築する。最初のネットワークは、識別器(discriminator)と呼ばれる従来型の分類ネットワークだ。この識別器に画像を入力して、リアル(訓練セットに属する)もしくはフェイク(訓練セットに存在しない)であるかを分類させて訓練する。生成器(generator)と呼ばれるもう一方のネットワークは、入力として不規則ノイズを受け、それをニューラルネットを用いて変換して画像を生成する。生成器の目的は、それが生成した画像がリアルであると識別器を欺くことである。
生成器($G$)が識別器($D$)を欺こうとし、識別器は正確に本物と偽物を分類しようとするこのやりとりを以下のようなミニマックスゲームとして考えることができる。
$$\underset{G}{\text{minimize}}\; \underset{D}{\text{maximize}}\; \mathbb{E}_{x \sim p_\text{data}}\left[\log D(x)\right] + \mathbb{E}_{z \sim p(z)}\left[\log \left(1-D(G(z))\right)\right]$$ $x \sim p_\text{data}$が入力データのサンプル、$z \sim p(z)$が不規則ノイズサンプル、$G(z)$がニューラルネットワーク生成器$G$を使って生成された画像、$D$が識別器の出力で入力がリアルである確率を特定する。Goodfellow等は、このミニマックスゲームを解析し、それがいかにして、訓練データ分布と$G$の生成サンプル間のJensen-Shannon divergence(ジェンセン・シャノン・ダイバージェンス)を極小化することと関連しているのかを明らかにしている。
このミニマックスゲームを最適化するために、$G$の目的への勾配降下ステップと$D$の目的への勾配上昇ステップを繰り返す。
- 識別器が正しい選択をする確率を最小化するのに生成器($G$)を更新する。
- 識別器が正しい選択をする確率を最大化するのに識別器($D$)を更新する。
こういった更新は分析には有効である一方で、実際には上手く機能しない。その代わりとして、生成器を更新する際に別の目的を使用して、識別機が誤った選択をする確率を最大化する。この僅かな修正が、識別機の信頼度が高い時に生成器勾配が消失する問題を軽減するのに役立つ。これは、ほとんどのGAN研究論文で使われている標準的なアップデートで、グッドフェロー等による原論文の中でも使われていた。
この宿題では、以下のアプデを交互に行う。
- 識別器が生成画像に対して不正確な選択をする確率を最大化するように生成器($G$)をアプデする。$$\underset{G}{\text{maximize}}\; \mathbb{E}_{z \sim p(z)}\left[\log D(G(z))\right]$$
- 識別器が真・偽画像に対して正しい選択をする確率を最大化するように識別器($D$)をアプデする。$$\underset{D}{\text{maximize}}\; \mathbb{E}_{x \sim p_\text{data}}\left[\log D(x)\right] + \mathbb{E}_{z \sim p(z)}\left[\log \left(1-D(G(z))\right)\right]$$
What else is there?¶
2014年以降、GANsは多くの大規模研修会や何百もの新論文により、大人気の研究分野へと急速に発展している。生成モデルへの他のアプローチと比べ、それらは頻繁に最上級のサンプルを作り出すが、訓練するのが最も困難で細心の注意が必要なモデルになっている(モデルを機能させるのに役立つ17のハック集を含むこのgithub repoを参照のこと)。GAN訓練の安定性と堅牢性を向上することはオープンな研究課題になっており、毎日のように新たな研究論文が発表されている。GANの最新のチュートリアルに関してはここを参照。目的関数(objective function)をWasserstein distanc(ワッサースタイン距離)に変えてモデルアーキテクチャを越えてはるかに安定した結果をもたらす、いくつかのもっと最近の興味深い研究も存在する(WGAN, WGAN-GP)。
GANだけが生成モデルを訓練する方法ではない。生成モデリングへの他のアプローチに関しては、深層学習本の深層生成モデルの章を参照。生成モデルとしてニューラルネットワークを訓練するもう一つの人気の方法が変分オートエンコーダだ(Variational Autoencoders(VAE)はこことここで共発見された)。変分自己符号化器(VAE)は、深層生成モデルを訓練するために、ニューラルネットワークをvariational inference(変分推論)と組み合わせている。このようなモデルは、はるかに安定していて訓練しやすい傾向があるが、今のところはGANと同等の綺麗なサンプルは生成していない。
期待される画像のいくつかの見本(若干違うかもしれない)
Setup¶
import torch
import torch.nn as nn
from torch.nn import init
from torch.autograd import Variable
import torchvision
import torchvision.transforms as T
import torch.optim as optim
from torch.utils.data import DataLoader
from torch.utils.data import sampler
import torchvision.datasets as dset
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
plt.rcParams['figure.figsize'] = (18, 12) # set default size of plots
plt.rcParams["font.size"] = "16"
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
def show_images(images):
images = np.reshape(images, [images.shape[0], -1]) # images reshape to (batch_size, D)
sqrtn = int(np.ceil(np.sqrt(images.shape[0])))
sqrtimg = int(np.ceil(np.sqrt(images.shape[1])))
fig = plt.figure(figsize=(sqrtn*2, sqrtn*2))
gs = gridspec.GridSpec(sqrtn, sqrtn)
gs.update(wspace=0.05, hspace=0.05)
for i, img in enumerate(images):
ax = plt.subplot(gs[i])
plt.axis('off')
ax.set_xticklabels([])
ax.set_yticklabels([])
ax.set_aspect('equal')
plt.imshow(img.reshape([sqrtimg,sqrtimg]))
return
def preprocess_img(x):
return 2 * x - 1.0
def deprocess_img(x):
return (x + 1.0) / 2.0
def rel_error(x,y):
return np.max(np.abs(x - y) / (np.maximum(1e-8, np.abs(x) + np.abs(y))))
def count_params(model):
"""Count the number of parameters in the current TensorFlow graph """
param_count = np.sum([np.prod(p.size()) for p in model.parameters()])
return param_count
answers = np.load('gan-checks-tf.npz')
%matplotlib inline
Dataset¶
GANでハイパーパラメータを取り扱うのは非常に難しく、多くの訓練エポックも必要となる。今回のエクササイズをGPU無しで取っ付きやすくするために、6万訓練画像と1万テスト画像を持つMNISTデータセットを使って作業する。各画像は黒い背景に白い(0から9の)数字が中心にある画像を含んでいる。これは、畳み込みニューラルネットワークを訓練するのに使われる最初のデータセットの一つで非常に簡単であるため、標準的なCNNモデルなら余裕で正確度99%を超える。
コードを簡素化する目的で、MNISTデータセットをダウンロードしてロードしてくれるPyTorch MNIST wrapperを使用する。このインターフェースに関するさらなる情報はドキュメンテーションを参照されたし。デフォルトパラメーターは訓練標本のうち5000を取得し、それらを検証データセットに据える。データはMNISTデータと呼ばれるフォルダーに保存される。
class ChunkSampler(sampler.Sampler):
"""Samples elements sequentially from some offset.
Arguments:
num_samples: # of desired datapoints
start: offset where we should start selecting from
"""
def __init__(self, num_samples, start=0):
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
NUM_TRAIN = 50000
NUM_VAL = 5000
NOISE_DIM = 96
batch_size = 128
mnist_train = dset.MNIST('./cs231n/datasets/MNIST_data', train=True, download=True,
transform=T.ToTensor())
loader_train = DataLoader(mnist_train, batch_size=batch_size,
sampler=ChunkSampler(NUM_TRAIN, 0))
mnist_val = dset.MNIST('./cs231n/datasets/MNIST_data', train=True, download=True,
transform=T.ToTensor())
loader_val = DataLoader(mnist_val, batch_size=batch_size,
sampler=ChunkSampler(NUM_VAL, NUM_TRAIN))
imgs = loader_train.__iter__().next()[0].view(batch_size, 784).numpy().squeeze()
show_images(imgs)
Random Noise¶
shape [batch_size, dim]を持つ-1~1の均一ノイズを生成する。
Hint: torch.randを使用する。
def sample_noise(batch_size, dim):
"""
Generate a PyTorch Tensor of uniform random noise.
Input:
- batch_size: Integer giving the batch size of noise to generate.
- dim: Integer giving the dimension of noise to generate.
Output:
- A PyTorch Tensor of shape (batch_size, dim) containing uniform
random noise in the range (-1, 1).
"""
return torch.Tensor(batch_size, dim).uniform_(-1, 1)
ノイズが正しい形と型であるかを確かめる。
def test_sample_noise():
batch_size = 3
dim = 4
torch.manual_seed(231)
z = sample_noise(batch_size, dim)
np_z = z.cpu().numpy()
assert np_z.shape == (batch_size, dim)
assert torch.is_tensor(z)
assert np.all(np_z >= -1.0) and np.all(np_z <= 1.0)
assert np.any(np_z < 0.0) and np.any(np_z > 0.0)
print('All tests passed!')
test_sample_noise()
Flatten¶
前回のノートブックのFlatten演算を思い起こす。今回は、畳み込み生成器を実装する場合に使いたくなるかもしれないUnflattenも提供する。PyTorchの同型デフォルトの代わりにXavier initializationを使用するweight initializerを提供する(なおかつそれをコールする)。
class Flatten(nn.Module):
def forward(self, x):
N, C, H, W = x.size() # read in N, C, H, W
return x.view(N, -1) # "flatten" the C * H * W values into a single vector per image
class Unflatten(nn.Module):
"""
An Unflatten module receives an input of shape (N, C*H*W) and reshapes it
to produce an output of shape (N, C, H, W).
"""
def __init__(self, N=-1, C=128, H=7, W=7):
super(Unflatten, self).__init__()
self.N = N
self.C = C
self.H = H
self.W = W
def forward(self, x):
return x.view(self.N, self.C, self.H, self.W)
def initialize_weights(m):
if isinstance(m, nn.Linear) or isinstance(m, nn.ConvTranspose2d):
init.xavier_uniform(m.weight.data)
CPU / GPU¶
デフォルトでは、全てのコードはCPUで実行される。この宿題に関してはGPUは必要ないが、モデルの訓練を加速するのには役立つ。GPUでコードを実行したいなら、下のセルのdtype変数を変更する。
#dtype = torch.FloatTensor
dtype = torch.cuda.FloatTensor ## UNCOMMENT THIS LINE IF YOU'RE ON A GPU!
Discriminator¶
初めの一歩は識別器の構築。下の関数にnn.Sequential constructorの一部として仕様を書き込む。全ての全結合層はバイアス項を含んでいなければならない。
Architecture:
- サイズが784~256の全結合層
- LeakyReLU with alpha 0.01
- サイズが256~256の全結合層
- LeakyReLU with alpha 0.01
- サイズが256~1の全結合層
Leaky ReLU nonlinearity(非線形性)が、ある固定定数$\alpha$に対する$f(x) = \max(\alpha x, x)$を算出することを思い出す。上の仕様のLeakyReLU非線形性に対しては$\alpha=0.01$を設定する。
識別器の出力はshape [batch_size, 1]であり、各batch_size入力が実画像のスコアに対応する実数を含んでいなければならない。
def discriminator():
"""
Build and return a PyTorch model implementing the architecture above.
"""
model = nn.Sequential(Flatten(),
nn.Linear(784, 256),
nn.LeakyReLU(negative_slope=0.01, inplace=True),
nn.Linear(256, 256),
nn.LeakyReLU(negative_slope=0.01, inplace=True),
nn.Linear(256, 1))
return model
識別器のパラメーター数が正しいことを確認するためにテストする。
def test_discriminator(true_count=267009):
model = discriminator()
cur_count = count_params(model)
if cur_count != true_count:
print('Incorrect number of parameters in discriminator. Check your achitecture.')
else:
print('Correct number of parameters in discriminator.')
test_discriminator()
Generator¶
次に生成器ネットワークをビルドする。
- Fully connected layer from noise_dim to 1024
- ReLU
- Fully connected layer with size 1024
- ReLU
- Fully connected layer with size 784
- TanH (画像が[-1,1]になるようにクリップする)
def generator(noise_dim=NOISE_DIM):
"""
Build and return a PyTorch model implementing the architecture above.
"""
model = nn.Sequential(nn.Linear(noise_dim, 1024),
nn.ReLU(inplace=True),
nn.Linear(1024, 1024),
nn.ReLU(inplace=True),
nn.Linear(1024, 784),
nn.Tanh())
return model
生成器のパラメーター数が正しいことを確認するためにテストする。
def test_generator(true_count=1858320):
model = generator(4)
cur_count = count_params(model)
if cur_count != true_count:
print('Incorrect number of parameters in generator. Check your achitecture.')
else:
print('Correct number of parameters in generator.')
test_generator()
GAN Loss¶
生成器/識別器損失を算出する。生成器損失$$\ell_G = -\mathbb{E}_{z \sim p(z)}\left[\log D(G(z))\right]$$識別器損失$$ \ell_D = -\mathbb{E}_{x \sim p_\text{data}}\left[\log D(x)\right] – \mathbb{E}_{z \sim p(z)}\left[\log \left(1-D(G(z))\right)\right]$$生成器/識別器損失が最小化していくに連れて、これらは前出の式からネゲートされることに留意する。
HINTS: 識別器のlogit output(ロジット出力)に与えられる正解ラベルのlog probability(対数確率)算出に必要なbinary cross entropy lossを算出するのに下で定義されているbce_loss関数を使うべき。スコアを$s\in\mathbb{R}$、ラベルを$y\in\{0, 1\}$と仮定すると、バイナリ交差エントロピー損失は$$ bce(s, y) = y * \log(s) + (1 – y) * \log(1 – s) $$
この公式のnaive implementation(ナイーブ実装)は、数値的に不安定になる可能性があるので、下に数値的に安定した実装を用意してある。
真・偽に対応するラベルを算出し、それらのサイズを割り出すのにlogit arguments(ロジット引数)を使用する必要がある。global dtype variable(グローバルdtype変数)を使用して正しいデータ型にこれらのラベルを確実にキャストする。例えば、
true_labels = Variable(torch.ones(size)).type(dtype)
期待値を算出する代わりにミニバッチの要素を平均するので、合計するのではなく平均することで損失を組み合わせることを忘れないこと。
def bce_loss(input, target):
"""
Numerically stable version of the binary cross-entropy loss function.
As per https://github.com/pytorch/pytorch/issues/751
See the TensorFlow docs for a derivation of this formula:
https://www.tensorflow.org/api_docs/python/tf/nn/sigmoid_cross_entropy_with_logits
Inputs:
- input: PyTorch Variable of shape (N, ) giving scores.
- target: PyTorch Variable of shape (N,) containing 0 and 1 giving targets.
Returns:
- A PyTorch Variable containing the mean BCE loss over the minibatch of input data.
"""
neg_abs = - input.abs()
loss = input.clamp(min=0) - input * target + (1 + neg_abs.exp()).log()
return loss.mean()
def discriminator_loss(logits_real, logits_fake):
"""
Computes the discriminator loss described above.
Inputs:
- logits_real: PyTorch Variable of shape (N,) giving scores for the real data.
- logits_fake: PyTorch Variable of shape (N,) giving scores for the fake data.
Returns:
- loss: PyTorch Variable containing (scalar) the loss for the discriminator.
"""
N, _ = logits_real.size()
loss = (bce_loss(logits_real, Variable(torch.ones(N)).type(dtype)) +
bce_loss(logits_fake, Variable(torch.zeros(N)).type(dtype)))
return loss
def generator_loss(logits_fake):
"""
Computes the generator loss described above.
Inputs:
- logits_fake: PyTorch Variable of shape (N,) giving scores for the fake data.
Returns:
- loss: PyTorch Variable containing the (scalar) loss for the generator.
"""
N, _ = logits_fake.size()
loss = bce_loss(logits_fake, Variable(torch.ones(N)).type(dtype))
return loss
生成器/識別器損失をテストする。エラーは1e-7未満になる必要がある。
def test_discriminator_loss(logits_real, logits_fake, d_loss_true):
d_loss = discriminator_loss(Variable(torch.Tensor(logits_real)).type(dtype),
Variable(torch.Tensor(logits_fake)).type(dtype)).data.cpu().numpy()
print("Maximum error in d_loss: %g"%rel_error(d_loss_true, d_loss))
test_discriminator_loss(answers['logits_real'], answers['logits_fake'],
answers['d_loss_true'])
def test_generator_loss(logits_fake, g_loss_true):
g_loss = generator_loss(Variable(torch.Tensor(logits_fake)).type(dtype)).data.cpu().numpy()
print("Maximum error in g_loss: %g"%rel_error(g_loss_true, g_loss))
test_generator_loss(answers['logits_fake'], answers['g_loss_true'])
Optimizing our loss¶
学習率=1e-3、beta1=0.5、beta2=0.999の所与のモデルに対してoptim.Adam optimizerを返す関数を作る。今後このノートブックで使われる生成器と識別器用オプティマイザを構築するのにこの関数を使用することになる。
def get_optimizer(model):
"""
Construct and return an Adam optimizer for the model with learning rate 1e-3,
beta1=0.5, and beta2=0.999.
Input:
- model: A PyTorch model that we want to optimize.
Returns:
- An Adam optimizer for the model with the desired hyperparameters.
"""
optimizer = optim.Adam(model.parameters(), lr=1e-3, betas=(0., 0.999))
return optimizer
Training a GAN!¶
main training loopはこっちで用意してある。この関数を変える必要はないが、それに一通りじっくり目を通して完璧に理解することを強く奨励する。
def run_a_gan(D, G, D_solver, G_solver, discriminator_loss, generator_loss, show_every=250,
batch_size=128, noise_size=96, num_epochs=10):
"""
Train a GAN!
Inputs:
- D, G: PyTorch models for the discriminator and generator
- D_solver, G_solver: torch.optim Optimizers to use for training the
discriminator and generator.
- discriminator_loss, generator_loss: Functions to use for computing the generator and
discriminator loss, respectively.
- show_every: Show samples after every show_every iterations.
- batch_size: Batch size to use for training.
- noise_size: Dimension of the noise to use as input to the generator.
- num_epochs: Number of epochs over the training dataset to use for training.
"""
iter_count = 0
for epoch in range(num_epochs):
for x, _ in loader_train:
if len(x) != batch_size:
continue
D_solver.zero_grad()
real_data = Variable(x).type(dtype)
logits_real = D(2* (real_data - 0.5)).type(dtype)
g_fake_seed = Variable(sample_noise(batch_size, noise_size)).type(dtype)
fake_images = G(g_fake_seed).detach()
logits_fake = D(fake_images.view(batch_size, 1, 28, 28))
d_total_error = discriminator_loss(logits_real, logits_fake)
d_total_error.backward()
D_solver.step()
G_solver.zero_grad()
g_fake_seed = Variable(sample_noise(batch_size, noise_size)).type(dtype)
fake_images = G(g_fake_seed)
gen_logits_fake = D(fake_images.view(batch_size, 1, 28, 28))
g_error = generator_loss(gen_logits_fake)
g_error.backward()
G_solver.step()
if (iter_count % show_every == 0):
print('Iter: {}, D: {:.4}, G:{:.4}'.format(iter_count,d_total_error.data[0],g_error.data[0]))
imgs_numpy = fake_images.data.cpu().numpy()
show_images(imgs_numpy[0:16])
plt.show()
print()
iter_count += 1
まぁ、そんなに難しくはなかったはず。反復が数百程度だと黒い背景だけが見え、1000に近付くとぼやけた形、3000を超えると約半分はくっきり鮮明なまともな形に見えるはず。
# Make the discriminator
D = discriminator().type(dtype)
# Make the generator
G = generator().type(dtype)
# Use the function you wrote earlier to get optimizers for the Discriminator and the Generator
D_solver = get_optimizer(D)
G_solver = get_optimizer(G)
# Run it!
import time
start_time = time.time()
run_a_gan(D, G, D_solver, G_solver, discriminator_loss, generator_loss)
end_time = time.time()
print("Elapsed time was %g seconds" % (end_time - start_time))
Least Squares GAN¶
今度は、原型GANに代わる新型でより安定したLeast Squares GAN(最小二乗GAN)に注目する。この部分に関しては、損失関数を変えてモデルを再訓練するだけでいい。論文中の式(9)を以下の生成器損失と識別器損失を使って実装する。$$\ell_G = \frac{1}{2}\mathbb{E}_{z \sim p(z)}\left[\left(D(G(z))-1\right)^2\right]$$$$ \ell_D = \frac{1}{2}\mathbb{E}_{x \sim p_\text{data}}\left[\left(D(x)-1\right)^2\right] + \frac{1}{2}\mathbb{E}_{z \sim p(z)}\left[ \left(D(G(z))\right)^2\right]$$
HINTS: 期待値を算出する代わりにミニバッチの要素を平均するので、合計するのではなく平均することで損失を組み合わせることを忘れないこと。$D(x)$と$D(G(z))に代入する場合、識別器の直接出力(score_realとscore_fake)を使う。
def ls_discriminator_loss(scores_real, scores_fake):
"""
Compute the Least-Squares GAN loss for the discriminator.
Inputs:
- scores_real: PyTorch Variable of shape (N,) giving scores for the real data.
- scores_fake: PyTorch Variable of shape (N,) giving scores for the fake data.
Outputs:
- loss: A PyTorch Variable containing the loss.
"""
N, _ = scores_real.size()
loss_real = 0.5*torch.mean(torch.pow(scores_real-Variable(torch.ones(N)).type(dtype), 2))
loss_fake = 0.5*torch.mean(torch.pow(scores_fake, 2))
loss = loss_real + loss_fake
return loss
def ls_generator_loss(scores_fake):
"""
Computes the Least-Squares GAN loss for the generator.
Inputs:
- scores_fake: PyTorch Variable of shape (N,) giving scores for the fake data.
Outputs:
- loss: A PyTorch Variable containing the loss.
"""
N, _ = scores_fake.size()
loss = 0.5*torch.mean(torch.pow(scores_fake-Variable(torch.ones(N)).type(dtype), 2))
return loss
新しい損失関数でGANを実行する前に、それをチェックする。
def test_lsgan_loss(score_real, score_fake, d_loss_true, g_loss_true):
d_loss = ls_discriminator_loss(Variable(torch.Tensor(score_real)).type(dtype),
Variable(torch.Tensor(score_fake)).type(dtype)).data.cpu().numpy()
g_loss = ls_generator_loss(Variable(torch.Tensor(score_fake)).type(dtype)).data.cpu().numpy()
print("Maximum error in d_loss: %g"%rel_error(d_loss_true, d_loss))
print("Maximum error in g_loss: %g"%rel_error(g_loss_true, g_loss))
test_lsgan_loss(answers['logits_real'], answers['logits_fake'],
answers['d_loss_lsgan_true'], answers['g_loss_lsgan_true'])
D_LS = discriminator().type(dtype)
G_LS = generator().type(dtype)
D_LS_solver = get_optimizer(D_LS)
G_LS_solver = get_optimizer(G_LS)
start_time = time.time()
run_a_gan(D_LS, G_LS, D_LS_solver, G_LS_solver, ls_discriminator_loss, ls_generator_loss)
end_time = time.time()
print("Elapsed time was %g seconds" % (end_time - start_time))
訓練結果が明らかにおかしいのでやり直す。
D_LS = discriminator().type(dtype)
G_LS = generator().type(dtype)
D_LS_solver = get_optimizer(D_LS)
G_LS_solver = get_optimizer(G_LS)
start_time = time.time()
run_a_gan(D_LS, G_LS, D_LS_solver, G_LS_solver, ls_discriminator_loss, ls_generator_loss)
end_time = time.time()
print("Elapsed time was %g seconds" % (end_time - start_time))
Deeply Convolutional GANs¶
ノートブックの前半部分では、Ian GoodfellowのオリジナルGANのほぼ直接コピーを実装した。しかし、このネットワーク仕様では、real spatial reasoning(実空間推論)ができない。それには畳み込み層が実装されていないので、基本的に、”sharp edges”のようなものについて推論することができない。従って、この部分では、識別機と生成器として畳み込みネットワークを利用する、DCGANの着想の一部を導入する。
Discriminator¶
TensorFlow MNIST classificationチュートリアルにヒントを得た、かなり急速にMNISTデータセットで99%を超える正確度を得られる識別器を使う。
- Reshape into image tensor (Use Unflatten!)
- 32 Filters, 5×5, Stride 1, Leaky ReLU(alpha=0.01)
- Max Pool 2×2, Stride 2
- 64 Filters, 5×5, Stride 1, Leaky ReLU(alpha=0.01)
- Max Pool 2×2, Stride 2
- Flatten
- Fully Connected size 4 x 4 x 64, Leaky ReLU(alpha=0.01)
- Fully Connected size 1
def build_dc_classifier():
"""
Build and return a PyTorch model for the DCGAN discriminator implementing
the architecture above.
"""
return nn.Sequential(
###########################
######### TO DO ###########
###########################
Unflatten(batch_size, 1, 28, 28),
nn.Conv2d(1, 32, kernel_size=5, stride=1),
nn.LeakyReLU(negative_slope=0.01, inplace=True),
nn.MaxPool2d(2, stride=2),
nn.Conv2d(32, 64, kernel_size=5, stride=1),
nn.LeakyReLU(negative_slope=0.01, inplace=True),
nn.MaxPool2d(2, stride=2),
Flatten(),
nn.Linear(1024, 1024),
nn.LeakyReLU(negative_slope=0.01, inplace=True),
nn.Linear(1024, 1))
data = Variable(loader_train.__iter__().next()[0]).type(dtype)
b = build_dc_classifier().type(dtype)
out = b(data)
print(out.size())
サニティーチェックとして分類器のパラメーター数をチェックする。
def test_dc_classifer(true_count=1102721):
model = build_dc_classifier()
cur_count = count_params(model)
if cur_count != true_count:
print('Incorrect number of parameters in generator. Check your achitecture.')
else:
print('Correct number of parameters in generator.')
test_dc_classifer()
Generator¶
生成器に対しては、InfoGAN paperからの仕様を正確にコピーする。Appendix C.1 MNISTとtf.nn.conv2d_transposeを参照されたし。常にGANモードで訓練をすること。
- Fully connected of size 1024, ReLU
- BatchNorm
- Fully connected of size 7 x 7 x 128, ReLU
- BatchNorm
- Reshape into Image Tensor
- 64 conv2d^T filters of 4×4, stride 2, ‘same’ padding, ReLU
- BatchNorm
- 1 conv2d^T filter of 4×4, stride 2, ‘same’ padding, TanH
- Should have a 28x28x1 image, reshape back into 784 vector
def build_dc_generator(noise_dim=NOISE_DIM):
"""
Build and return a PyTorch model implementing the DCGAN generator using
the architecture described above.
"""
return nn.Sequential(
###########################
######### TO DO ###########
###########################
nn.Linear(noise_dim, 1024),
nn.ReLU(inplace=True),
nn.BatchNorm1d(1024),
nn.Linear(1024, 7*7*128),
nn.ReLU(inplace=True),
nn.BatchNorm1d(7*7*128),
Unflatten(batch_size, 128, 7, 7),
nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1),
nn.ReLU(inplace=True),
nn.BatchNorm2d(64),
nn.ConvTranspose2d(64, 1, kernel_size=4, stride=2, padding=1),
nn.Tanh(),
Flatten())
test_g_gan = build_dc_generator().type(dtype)
test_g_gan.apply(initialize_weights)
fake_seed = Variable(torch.randn(batch_size, NOISE_DIM)).type(dtype)
fake_images = test_g_gan.forward(fake_seed)
fake_images.size()
サニティーチェックとして生成器のパラメーター数をチェックする。
def test_dc_generator(true_count=6580801):
model = build_dc_generator(4)
cur_count = count_params(model)
if cur_count != true_count:
print('Incorrect number of parameters in generator. Check your achitecture.')
else:
print('Correct number of parameters in generator.')
test_dc_generator()
D_DC = build_dc_classifier().type(dtype)
D_DC.apply(initialize_weights)
G_DC = build_dc_generator().type(dtype)
G_DC.apply(initialize_weights)
D_DC_solver = get_optimizer(D_DC)
G_DC_solver = get_optimizer(G_DC)
start_time = time.time()
run_a_gan(D_DC, G_DC, D_DC_solver, G_DC_solver, discriminator_loss, generator_loss, num_epochs=5)
end_time = time.time()
print("Elapsed time was %g seconds" % (end_time - start_time))