今日はDCGANのディスクリミネータについて色々勉強していました。
新しい概念ばかりだね。
DCGANはニューラルネットワークの進化系という感覚で軽く勉強をはじめましたが、CNNや全結合のニューラルネットワークとは全然違うものであることがわかりました。また、ニューラルネットワーク自体の構成も厳密に研究されているみたいで、論文が基準になっていたりと、かなり基礎研究的な部分の技術で、本当に理解しようとすると、結構な難しさを感じています。
ということで、今回はPyTorchのDCGANのチュートリアルのディスクリミネータから勉強を進めていきます。
前回の記事はこちらです。
ディスクリミネータ
ディスクリミネータは、ジェネレータが生成した画像を偽物と見破るためのニューラルネットワークです。ディスクリミネータは本物の画像を入れた時は本物と判定し、ディスクリミネータの画像を入れた時に偽物と判定するような2クラス分類のためのニューラルネットワークです。出力として「本物である確率」を出力するように設計してあります。
PyTorchのDCGANでは具体的に、「3チャンネルの64×64」の画像を入力としてとります。
そして、畳み込み層(今度は転置ではない)を行い、バッチノーマライゼーション層を通り、「LealyReLU」という活性化関数で畳み込みを行います。この畳み込みを4回行い、最後にシグモイド関数を通すことで2クラス分類の確率の数値となります。
class Discriminator(nn.Module):
def __init__(self, ngpu):
super(Discriminator, self).__init__()
self.ngpu = ngpu
self.main = nn.Sequential(
#入力は64×64、3チャンネルの画像
nn.Conv2d(nc, ndf, 4, 2, 1, bias=False),
nn.LeakyReLU(0.2, inplace=True),
# state size. (ndf) x 32 x 32
nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 2),
nn.LeakyReLU(0.2, inplace=True),
# state size. (ndf*2) x 16 x 16
nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 4),
nn.LeakyReLU(0.2, inplace=True),
# state size. (ndf*4) x 8 x 8
nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 8),
nn.LeakyReLU(0.2, inplace=True),
# state size. (ndf*8) x 4 x 4
nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
nn.Sigmoid()
)
def forward(self, input):
return self.main(input)
そして、次のプログラムでディスクリミネータのネットワークの重みを初期化します。
最初のいくつかのプログラムは、GPUを利用するかどうかという条件式になっています。
重みの初期化は、「平均0、標準偏差0.02」の正規分布から数値を取得します。
# Create the Discriminator
netD = Discriminator(ngpu).to(device)
# Handle multi-gpu if desired
if (device.type == 'cuda') and (ngpu > 1):
netD = nn.DataParallel(netD, list(range(ngpu)))
# Apply the weights_init function to randomly initialize all weights
# to mean=0, stdev=0.2.
netD.apply(weights_init)
# Print the model
print(netD)
次のように出力されました。
Discriminator(
(main): Sequential(
(0): Conv2d(3, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(1): LeakyReLU(negative_slope=0.2, inplace=True)
(2): Conv2d(64, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(3): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(4): LeakyReLU(negative_slope=0.2, inplace=True)
(5): Conv2d(128, 256, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(6): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(7): LeakyReLU(negative_slope=0.2, inplace=True)
(8): Conv2d(256, 512, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(9): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(10): LeakyReLU(negative_slope=0.2, inplace=True)
(11): Conv2d(512, 1, kernel_size=(4, 4), stride=(1, 1), bias=False)
(12): Sigmoid()
)
)
損失関数と最適化手法
BCELossという2クラス問題に利用される損失関数を利用します。これはBinaryCrossEntoropyの略称です。
また、ジェネレータに入力するための潜在ベクトルzもここで準備しています。「fixed_noise」として準備されます。
その次に「real_label」「fake_label」とあります。これは、ディスクリミネータを訓練するときの答えとなります。GANは教師なし学習という分野に属しています。しかし、ディスクリミネータについては、本物、実際に準備できたデータに「1」という答えをつけ、ジェネレータが生成したデータに「0」という答えを定義しておくことで、2クラス問題を訓練させていきます。
最後に、ディスクリミネータ、ジェネレータ共に最適化手法の「Adam」で重みを更新していホームくように設定しています。
#誤差をバイナリクロスエントロピー関数とします。
criterion = nn.BCELoss()
#潜在ベクトル
fixed_noise = torch.randn(64, nz, 1, 1, device=device)
# Establish convention for real and fake labels during training
real_label = 1
fake_label = 0
# Setup Adam optimizers for both G and D
optimizerD = optim.Adam(netD.parameters(), lr=lr, betas=(beta1, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr=lr, betas=(beta1, 0.999))
ちょっとまだzのデータのサイズ感とかは理解できていないのですが、まずは訓練してみたいです。
訓練に向けてデータを準備
今回の訓練データは、データ量が1.4Gと多いので、パソコンにデータをダウンロードして訓練を行い、色々やっていましたが、GPUを積んだWindowsが一時的に利用できない状態にある中で、mac miniのローカル環境で行なっていました。しかし、CUDAが利用できないので、かなり訓練に時間がかかるようで・・・GoogleDriveにデータを保存することにしました。
手作業でアップロードだと時間がかかると感じ、PyTorchのリンク先のGoogleDriveから自分のDriveにコピーして、「ZipExtractor」で直接解凍を行なっています。
これが、データの解凍に40時間ほどかかっています 笑
手作業でアップロードの方が早かったのかもしれません・・・が、もうすぐでGoogleColaboで検証ができると信じています。
続きの記事はこちらです。