【GAN-Keras入門】DCGANとSRGANのモデルを交換して遊んでみる♪

今回は、GANのモデルの勘所をつかむために、以下の参考のDCGANとSRGANのGeneratorモデルとDiscriminatorモデルを変更して、どのように変化するかを見た。
【参考】
・[DCGAN-Keras/keras-dcgan_original.py](https://github.com/MuAuan/DCGAN-Keras/blob/master/keras-dcgan_original.py)
・[DCGAN-Keras/SRGAN-Keras/srgan.py](https://github.com/MuAuan/DCGAN-Keras/blob/master/SRGAN-Keras/srgan.py)
###やったこと
・DCGAN-KerasのGeneratorとDiscriminatorを変更する
・SRGAN-KerasのGeneratorとDiscriminatorを変更する
###・DCGAN-KerasのGeneratorとDiscriminatorを変更する
コードは以下のとおり
元のモデル

```generator_model.py
def generator_model():
    model = Sequential()
    model.add(Dense(1024, input_shape=(10*10,))) #1024,100
    model.add(Activation('tanh'))
    model.add(Dense(128 * 16 * 16)) #128
    model.add(BatchNormalization())
    model.add(Activation('tanh'))
    model.add(Reshape((16, 16, 128)))
    model.add(Conv2DTranspose(64, (5, 5), activation='tanh', strides=2, padding='same'))
    model.add(Conv2DTranspose(32, (5, 5), activation='tanh', strides=2, padding='same'))
    model.add(Conv2DTranspose(n_colors,(5, 5), activation='tanh', strides=2, padding='same'))
    return model
```
これを以下のコードに置き換えています。
注意したのは、
①入力を上記はDense(1024,input_shape=(10*10,))であるので、以下では
`img_lr = Input(shape=(10,))`　`x=Dense(8*8*128, )(img_lr)`としています。
②出力は上記は`return model`としているので、以下でも`model=Model(img_lr, gen_hr)`
`return model`としました
③上記では`activation='tanh'`を利用しているので、以下でも同様に`tanh`を利用します
④上記では`BatchNormalization()`は第一段しか利用していませんが、下記では残しました

```generator_model.py
def generator_model():
    def residual_block(layer_input, filters):
        """Residual block described in paper"""
        d = Conv2D(filters, kernel_size=3, strides=1, padding='same')(layer_input)
        d = Activation('tanh')(d)  #relu
        d = BatchNormalization(momentum=0.8)(d)
        d = Conv2D(filters, kernel_size=3, strides=1, padding='same')(d)
        d = BatchNormalization(momentum=0.8)(d)
        d = Add()([d, layer_input])
        return d
    def deconv2d(layer_input):
        """Layers used during upsampling"""
        u = UpSampling2D(size=2)(layer_input)
        u = Conv2D(256, kernel_size=3, strides=1, padding='same')(u)
        u = Activation('tanh')(u)  #relu
        return u
    gf=64
    n_residual_blocks = 16
    # Low resolution image input
    img_lr = Input(shape=(10,))
    x=Dense(8*8*128, )(img_lr) #1024,100
    x=BatchNormalization()(x)
    x=Activation('tanh')(x)
    x=Reshape((8, 8, 128))(x)
    x=Conv2DTranspose(128, (5, 5), activation='tanh', strides=2, padding='same')(x)
    x=BatchNormalization()(x)
    x=Conv2DTranspose(64, (5, 5), activation='tanh', strides=2, padding='same')(x)
    x=BatchNormalization()(x)
    # Pre-residual block
    c1 = Conv2D(64, kernel_size=9, strides=1, padding='same')(x)
    c1 = Activation('tanh')(c1) #relu
    # Propogate through residual blocks
    r = residual_block(c1, gf)
    for _ in range(n_residual_blocks - 1):
        r = residual_block(r, gf)
    # Post-residual block
    c2 = Conv2D(64, kernel_size=3, strides=1, padding='same')(r)
    c2 = BatchNormalization(momentum=0.8)(c2)
    c2 = Add()([c2, c1])
    # Upsampling
    u1 = deconv2d(c2)
    u2 = deconv2d(u1)
    # Generate high resolution output
    gen_hr = Conv2D(3, kernel_size=9, strides=1, padding='same', activation='tanh')(u2)
    model=Model(img_lr, gen_hr)
    return model
```
discriminator_modelの元のモデルは以下のとおり

```discriminator_model.py
def discriminator_model():
    model = Sequential()
    model.add(Conv2D(16, (5, 5), input_shape=(128, 128, n_colors), padding='same'))
    model.add(Activation('tanh'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Conv2D(32, (5, 5), padding='same')) 
    model.add(Activation('tanh'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Conv2D(64, (5, 5), padding='same'))
    model.add(Activation('tanh'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Conv2D(128, (5, 5), padding='same'))
    model.add(Activation('tanh'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Conv2D(256, (5, 5), padding='same'))
    model.add(Activation('tanh'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Flatten())
    model.add(Dense(256*4*4))
    model.add(Activation('tanh'))
    model.add(Dense(1))
    model.add(Activation('sigmoid'))
    return model
```
以下を注意して変更しました。
①入力は`df=16`と`hr_shape=(128, 128, 3)`を定義しています
②`strides=2`での収束性が悪いので代わりに`d = MaxPooling2D(pool_size=(2, 2))(d)`としています
③BatchNormalizationも収束しないので削除しています
④`return model`を`model=Model(d0, validity)` `return model`としています
⑤`Activation('tanh')`を利用しています

```discriminator_model.py
def discriminator_model():
    df = 16
    hr_shape=(128, 128, 3)

    def d_block(layer_input, filters, strides=1, bn=True):
        #Discriminator layer
        d = Conv2D(filters, kernel_size=3, strides=1, padding='same')(layer_input)
        d = Activation('tanh')(d)
        if strides==2:
            d = MaxPooling2D(pool_size=(2, 2))(d)
        if bn:
            d = BatchNormalization(momentum=0.8)(d)
        return d

    # Input img
    d0 = Input(shape=hr_shape)

    d1 = d_block(d0, df , bn=False)
    d2 = d_block(d1, df, strides=2, bn=False)
    d3 = d_block(d2, df*2, bn=False)
    d4 = d_block(d3, df*2, strides=2, bn=False)
    d5 = d_block(d4, df*4, bn=False)
    d6 = d_block(d5, df*4, strides=2, bn=False)
    d7 = d_block(d6, df*8, bn=False)
    d8 = d_block(d7, df*8, strides=2, bn=False)
    
    x=Flatten()(d8)
    d9 = Dense(df*16)(x)
    d10 = Activation('tanh')(d9)
    validity = Dense(1, activation='sigmoid')(d10)
    model=Model(d0, validity)
    return model
```
上記のモデルで一応DCGANの出力はよくなってもいいなぁ～
と期待しましたが、10000epochで以下のようにどことなく怖い絵が出力されました。
![gen10000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/d84a0e4f-821f-6e8f-8952-42e2d9f84b58.png)
そして、上記のDiscriminatorの判定を弱くなることを期待して、以下のように変更してやってみました。ほぼ、元のDiscriminatorと同じようなモデルになりました。
ということで、これで学習するとほぼGeneratorの差だといえそうです。

```discriminator_model.py
def discriminator_model():
    df = 16
    hr_shape=(128, 128, 3)
    def d_block(layer_input, filters, strides=1, bn=True):
        #Discriminator layer
        d = Conv2D(filters, kernel_size=3, strides=1, padding='same')(layer_input)
        d = Activation('tanh')(d)
        if strides==2:
            d = MaxPooling2D(pool_size=(2, 2))(d)
        if bn:
            d = BatchNormalization(momentum=0.8)(d)
        return d
    # Input img
    d0 = Input(shape=hr_shape)
    d2 = d_block(d0, df, strides=2, bn=False)
    d4 = d_block(d2, df*2, strides=2, bn=False)
    d6 = d_block(d4, df*4, strides=2, bn=False)
    d8 = d_block(d6, df*8, strides=2, bn=False)
    d10 = d_block(d8, df*8, strides=2, bn=False)
    x=Flatten()(d10)
    d11 = Dense(df*16)(x)
    d12 = Activation('tanh')(d11)
    validity = Dense(1, activation='sigmoid')(d12)
    model=Model(d0, validity)
    return model
```
また、学習が発散気味なので、以下のように学習率Irを半減しました。

```py
opt = optimizer=Adam(lr=0.00005, beta_1=0.5) 
```
これによって、6000epochで以下のような画像が生成されました。
![gen06000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/6b5b397d-fb8f-4448-3bb5-020ce3afceae.png)
**これも怖い感じは残っているので、Generatorの性質からきているのかもしれない**
置き換え後のコード全体はいかに置きました。
・[DCGAN-Keras/keras-dcgan_trial_paper_impl.py](https://github.com/MuAuan/DCGAN-Keras/blob/master/keras-dcgan_trial_paper_impl.py)
###・SRGAN-KerasのGeneratorとDiscriminatorを変更する
上記と同様に置き換えます。
元のモデル

```build_generator.py
    def build_generator(self):
        def residual_block(layer_input, filters):
            """Residual block described in paper"""
            d = Conv2D(filters, kernel_size=3, strides=1, padding='same')(layer_input)
            d = Activation('relu')(d)
            d = BatchNormalization(momentum=0.8)(d)
            d = Conv2D(filters, kernel_size=3, strides=1, padding='same')(d)
            d = BatchNormalization(momentum=0.8)(d)
            d = Add()([d, layer_input])
            return d
        def deconv2d(layer_input):
            """Layers used during upsampling"""
            u = UpSampling2D(size=2)(layer_input)
            u = Conv2D(256, kernel_size=3, strides=1, padding='same')(u)
            u = Activation('relu')(u)
            return u
        # Low resolution image input
        img_lr = Input(shape=self.lr_shape)
        # Pre-residual block
        c1 = Conv2D(64, kernel_size=9, strides=1, padding='same')(img_lr)
        c1 = Activation('relu')(c1)
        # Propogate through residual blocks
        r = residual_block(c1, self.gf)
        for _ in range(self.n_residual_blocks - 1):
            r = residual_block(r, self.gf)
        # Post-residual block
        c2 = Conv2D(64, kernel_size=3, strides=1, padding='same')(r)
        c2 = BatchNormalization(momentum=0.8)(c2)
        c2 = Add()([c2, c1])
        # Upsampling
        u1 = deconv2d(c2)
        u2 = deconv2d(u1)
        # Generate high resolution output
        gen_hr = Conv2D(self.channels, kernel_size=9, strides=1, padding='same', activation='tanh')(u2)
        return Model(img_lr, gen_hr)
```
変更箇所は以下のとおり
①入力を`img_lr = Input(shape=self.lr_shape)` `c1 = Conv2D(64, kernel_size=9, strides=1, padding='same')(img_lr)`に合わせて、`x=Conv2D(64, (3, 3), ...)(img_lr)`に変更
②全体に`x=Conv2DTranspose(128, (3, 3), ．．．)(x)`の形式に変更
③`activation='relu'`を採用した
④`return Model(img_lr, gen_hr)`とした

```build_generator.py
    def build_generator(self):
        img_lr = Input(shape=self.lr_shape)
        x=Conv2D(64, (3, 3), activation='relu', strides=1, padding='same')(img_lr)
        x=Conv2DTranspose(128, (3, 3), activation='relu', strides=2, padding='same')(x)
        x=Conv2DTranspose(256, (3, 3), activation='relu', strides=2, padding='same')(x)
        gen_hr=Conv2D(self.channels,(3, 3), activation='tanh', strides=1, padding='same')(x)
        return Model(img_lr, gen_hr)
```
discriminator_modelの元のモデルは以下のとおり

```build_discriminator.py
    def build_discriminator(self):
        def d_block(layer_input, filters, strides=1, bn=True):
            """Discriminator layer"""
            d = Conv2D(filters, kernel_size=3, strides=strides, padding='same')(layer_input)
            d = LeakyReLU(alpha=0.2)(d)
            if bn:
                d = BatchNormalization(momentum=0.8)(d)
            return d
        # Input img
        d0 = Input(shape=self.hr_shape)
        d1 = d_block(d0, self.df, bn=False)
        d2 = d_block(d1, self.df, strides=2)
        d3 = d_block(d2, self.df*2)
        d4 = d_block(d3, self.df*2, strides=2)
        d5 = d_block(d4, self.df*4)
        d6 = d_block(d5, self.df*4, strides=2)
        d7 = d_block(d6, self.df*8)
        d8 = d_block(d7, self.df*8, strides=2)
        d9 = Dense(self.df*16)(d8)
        d10 = LeakyReLU(alpha=0.2)(d9)
        validity = Dense(1, activation='sigmoid')(d10)
        return Model(d0, validity)
```
以下のとおり、変更した
①img_hr = Input(shape=self.hr_shape)を定義して入力とした
②上記と同様に、`x=Conv2D(64, (5, 5), ．．．)(img_hr)`の形式に変更
③`activation='tanh'`を利用
④`x=Flatten()(x)`をやめて、`x=Dense(1024)(x)`とした
⑤`return Model(img_hr, validity)`とした

```build_discriminator.py
    def build_discriminator(self):
        img_hr = Input(shape=self.hr_shape)
        x=Conv2D(64, (5, 5), activation='tanh', padding='same')(img_hr)
        x=MaxPooling2D(pool_size=(2, 2))(x)
        x=Conv2D(128, (5, 5), activation='tanh', padding='same')(x)
        x=MaxPooling2D(pool_size=(2, 2))(x)
        x=Conv2D(256, (5, 5), activation='tanh', padding='same')(x)
        x=MaxPooling2D(pool_size=(2, 2))(x)
        x=Conv2D(512, (5, 5), activation='tanh', padding='same')(x)
        x=MaxPooling2D(pool_size=(2, 2))(x)
        x=Conv2D(1024, (5, 5), activation='tanh', padding='same')(x)
        x=Dense(1024)(x)
        x=Activation('tanh')(x)
        validity=Dense(1,activation='sigmoid')(x)
        return Model(img_hr, validity)
```
これで以下のような生成画像を得た。モデルはかなり簡単なものになったが、綺麗な画像が得られた。
![19700.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/dbf56a74-4094-5925-d32a-a7ab3f9bbaee.png)
変更後のコードはいかに置きました。
・[DCGAN-Keras/SRGAN-Keras/srgan_trial.py](https://github.com/MuAuan/DCGAN-Keras/blob/master/SRGAN-Keras/srgan_trial.py)
###まとめ
・DCGANとSRGANのモデルを相互に変更して遊んでみた
・SRGANは綺麗な出力が得られたが、DCGANではなんとなく固い画像しか生成されず、うまくいかなかった

・これはどうやらGeneratorの差であるようなのでさらに追及して最適な組み合わせの条件を明らかにしたい