DCGAN

#DCGAN （Deep Convolutional GAN)

##DCGANとは？
[前回の記事](https://qiita.com/merry1221/items/cb0027b6290671fc7f5b)で作ったシンプルなGANの生成器と識別器の双方に関して、単純な２層フィードフォワードを用いるのではなく、畳み込みニューラルネットワークを用いたGANをDCGANと言います。

##バッチ正規化（Batch Normalization)
今回のDCGANではバッチ正規化を使用しています。詳しい説明はこちらの方の[記事](https://qiita.com/t-tkd3a/items/14950dbf55f7a3095600)が大変わかりやすいです。

簡単にバッチ正規化の導入メリットだけ紹介すると

1. 学習を早く進行させることが可能
2. 初期値にそれほど依存しなくなる
3. 過学習を抑制することができる

などが挙げられます。
今回の実装ではkeras.layers.BatchNormalization関数がミニバッチの計算や更新を裏でうまくやってくれています。
それでは実際にDCGANを実装していきましょう！大まかな流れは前回の記事とほぼ同じです。

##いざ実装！！！

###1.諸々import
```python
#まずは諸々import

%matplotlib inline

import matplotlib.pyplot as plt
import numpy as np

from keras.datasets import mnist
from keras.layers import Activation, BatchNormalization, Dense, Dropout, Flatten, Reshape
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.convolutional import Conv2D, Conv2DTranspose
from keras.models import Sequential
from keras.optimizers import Adam
```
###2.モデルの入力次元の設定
```python
#モデルの入力次元の設定

img_rows = 28
img_cols = 28
channels = 1

img_shape = (img_rows, img_cols, channels)

#生成器への入力として使われるノイズベクトルの次元
z_dim = 100
```

###3.生成器の実装
生成器では、ノイズベクトルzから画像を生成するので、転置畳み込みを用いることになります。つまり下の図で、一番右のzベクトルから一番左の画像を生成するということですね。

![スクリーンショット 2020-06-13 15.16.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404591/d6290edb-c2f3-43c3-e411-08a8cc33904b.png)

具体的なステップを以下にまとめます。
1. ノイズベクトルを作り全結合層に通すことで7×7×256のテンソルに変換
2. 転置畳み込み層によって、7×7×256を14×14×128に変換
3. バッチ正規化を行い、Leaky ReLUを適用
4. 転置畳み込み層により、14×14×128を14×14×64に変換。このステップでは高さと幅は不変
5. バッチ正規化を行い、Leaky ReLUを適用
6. 転置畳み込み層により、14×14×64を出力画像サイズ28×28×1に変換
7. tanh関数を適用

Conv2DTransposeのパラメータに関しては[こちらの記事](https://qiita.com/takurooo/items/9a9f387390f5fcf5a516)を参考にさせていただきました。

```python
#生成器

def build_generator(z_dim):
  model = Sequential()

  model.add(Dense(256*7*7, input_dim = z_dim))
  model.add(Reshape((7, 7, 256)))

  model.add(Conv2DTranspose(128, kernel_size=3, strides=2,padding='same'))
  model.add(BatchNormalization())
  model.add(LeakyReLU(alpha=0.01))

  model.add(Conv2DTranspose(1, kernel_size=3, strides=2, padding="same"))
  model.add(Activation('tanh'))

  return model
```
###4.識別器の実装
識別器ではCNNでおなじみのネットワーク構造をとります。やっていることをざっくりと説明すると、画像データを入力し畳み込みを行うことで最終的にその画像が本物かどうかの確率を計算します。詳しい内容は下のコードで確認してください。
#識別器
```python
def build_discriminator(img_shape):

  model = Sequential()

  model.add(Conv2D(32, kernel_size=3, strides=2, input_shape=img_shape, padding='same'))
  model.add(LeakyReLU(alpha=0.01))

  model.add(Conv2D(64, kernel_size=3, strides=2, padding="same"))
  model.add(BatchNormalization())
  model.add(LeakyReLU(alpha=0.01))

  model.add(Conv2D(128, kernel_size=3, strides=2, padding="same"))
  model.add(BatchNormalization())
  model.add(BatchNormalization())
  model.add(LeakyReLU(alpha=0.01))

  model.add(Flatten())
  model.add(Dense(1, activation="sigmoid"))

  return model
```
###5.DCGANのコンパイル
``` python
#DCGANコンパイル
def build_gan(generator, discriminator):

  model = Sequential()

  model.add(generator)
  model.add(discriminator)

  return model

discriminator = build_discriminator(img_shape)
discriminator.compile(loss="binary_crossentropy", optimizer=Adam(), metrics=["accuracy"])

generator = build_generator(z_dim)
discriminator.trainable = False

gan = build_gan(generator, discriminator)
gan.compile(loss="binary_crossentropy", optimizer=Adam())
```
###6.学習の設定
``` python
#Training

losses = []
accuracies = []
iteration_checkpoints = []

def train(iterations, batch_size, sample_interval):
  (X_train, _),(_, _) = mnist.load_data()

  X_train = X_train / 127.5 -1.0
  X_train = np.expand_dims(X_train, 3)

  real = np.ones((batch_size, 1))
  fake = np.zeros((batch_size, 1))

  for iteration in range(iterations):

    idx = np.random.randint(0, X_train.shape[0], batch_size)
    imgs = X_train[idx]

    z = np.random.normal(0, 1, (batch_size, 100))
    gen_imgs = generator.predict(z)

    d_loss_real = discriminator.train_on_batch(imgs, real)
    d_loss_fake = discriminator.train_on_batch(gen_imgs, fake)
    d_loss,  accuracy = 0.5 * np.add(d_loss_real, d_loss_fake)

    z = np.random.normal(0, 1, (batch_size, 100))
    gen_imgs = generator.predict(z)

    g_loss = gan.train_on_batch(z, real)
    if iteration == 0:
      sample_images(generator)

    if ((iteration + 1) % sample_interval == 0):

      losses.append((d_loss, g_loss))
      accuracies.append(100 * accuracy)
      iteration_checkpoints.append(iteration+1)

      print("%d [D loss: %f, acc.: %.2f%%] [G loss: %f]" %
                  (iteration + 1, d_loss, 100.0 * accuracy, g_loss))
      sample_images(generator)
```
###7.画像表示
``` python
def sample_images(generator, image_grid_rows=4, image_grid_columns=4):

  z = np.random.normal(0, 1, (image_grid_rows * image_grid_columns, z_dim))
  gen_imgs = generator.predict(z)
  gen_imgs = 0.5 * gen_imgs + 0.5

  fig, axs = plt.subplots(image_grid_rows,
                           image_grid_columns,
                           figsize=(4,4),
                           sharey=True,
                           sharex=True
                           )
  cnt = 0
  for i in range(image_grid_rows):
    for j in range(image_grid_columns):
      axs[i, j].imshow(gen_imgs[cnt, :, :, 0], cmap='gray')
      axs[i, j].axis('off')
      cnt += 1
```
###8. いざ学習！
```python
iterations = 20000
batch_size = 128
sample_interval = 1000
train(iterations, batch_size, sample_interval)
```
##結果
↓初期のノイズ
<img width="239" alt="スクリーンショット 2020-06-13 19.26.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404591/84f9777e-da8c-0de6-c973-97eae3cacf0d.png">
↓1000iterations
<img width="244" alt="スクリーンショット 2020-06-13 19.26.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404591/cd864515-db1c-0eee-be87-21612c66cab3.png">
↓10000iterations
<img width="252" alt="スクリーンショット 2020-06-13 19.27.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404591/63be17bc-b200-42b4-a272-4d99ba14c8fd.png">
↓20000iterations
<img width="255" alt="スクリーンショット 2020-06-13 19.27.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404591/3ca6b1e9-b7b1-d4e1-1e83-fa5018c0ceb5.png">

どうでしょうか、データセットのmnistから取ってきた本物の手書き文字と見分けがつかないレベルの画像を生成することができました。また、[前回](https://qiita.com/merry1221/items/cb0027b6290671fc7f5b)のシンプルなGANでは、画像にピクセル単位のノイズが入ってしまっていましたが、今回DCGANを用いることでピクセル間の関係性を埋め込むことができ、ノイズのない綺麗な画像が生成されました。