【CGAN】櫻坂メンバーの推しメン画像を生成してモチベを上げたかった

# はじめに

[コチラ](https://qiita.com/okateru/items/8bd518736cda6043a5be)の記事でDCGANとFastGAN(LightweightGAN)を使ってポケモンを生成してみたの続きです。

今回はCGAN(conditional GAN)モデルを構築して、推しメンの画像を生成するようにしてみました。

# きっかけ

![20210829_220118 (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/f7ac5d27-db23-0b97-1866-1de47c04c412.jpeg)

推しメンが公式ブログで新しい自撮りを載せてくれると嬉しいですよね。生きるモチベがガンガン上がります。

そこで新しい推しメン画像を自分で生成できれば、無限にモチベーション上げられて勉強がより進むのではないかという不純な動機です（）

ただ結論から述べると、生成画像の質としては満足できるものではなくて、失敗してます。

一人の櫻坂ファンが奮闘する姿を見ていただければ幸いです。

# CGANとは

自分の[GANについてまとめた記事](https://qiita.com/okateru/items/3eacbf858081fa3cf77e)に簡単ですがどういうイメージなのかはまとめております。

詳しい説明は[論文](https://arxiv.org/pdf/1411.1784.pdf)を参考にして頂くのがいいかと思います。

ざっくりと言うと、通常のGANの入力はノイズベクトルだけですが、ここにさらにラベル情報も入力して学習させることで、生成する画像をコントロールできるようにしたGANモデルです。

# いざ、実装
実装にあたっては[コチラ](https://learnopencv.com/conditional-gan-cgan-in-pytorch-and-tensorflow/)のサイトを参考にしました。正直いろんな実装方法があって迷いましたが（[コチラ](https://qiita.com/merry1221/items/b73884ee830d1791c792)や[コチラ](https://qiita.com/o93/items/96ca7dbcc82a8dd873ad)など）面白そうなモデル構造なので、今回採用させて頂きました。

環境としてはGoogle Colaboratoryを使用しています。

## 1. モデル概要図
### Generator
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/181a7bd8-c5ce-ca44-e99a-8ac323760d8f.png)


### Discriminator
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/5719eb9c-876e-43ac-e4d5-9633b6a35609.png)


個人的に面白いなと思ったのは、Generatorの方でラベル情報を埋め込んだ後に全結合層につなげている所です。

東大松尾研で無償公開されている[DL4US](https://weblab.t.u-tokyo.ac.jp/dl4us/)の中のCGANモデルの章では、ラベル情報をone-hotエンコーディングしてノイズ情報と重ね合わせたものを入力とする形式を取っていたりするので、合わせ方も複数あるなぁと興味深かったです。

## 2. importとデータの準備
```python
# 必要なものimport
import numpy as np
import time
import os
import glob
import cv2
import matplotlib.pyplot as plt
import datetime as dt
import tensorflow as tf

from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import *
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.utils import plot_model
```

```python
# 画像データとラベルデータに分けて保存したnpzファイルからデータをロード
data = np.load('/content/drive/MyDrive/sakurazaka_make_face/sakurazaka_face.npz')

# 画像データとラベルデータの取得
Images = data['x']
labels = data['y']
```
[コチラ](https://qiita.com/okateru/items/6f9daf1094ef8c2d6d68)の記事で紹介させて頂いた櫻坂メンバーの顔分類アプリを作成する際に用意した顔画像セットを`npz`ファイルにして保存してたので、そちらからデータをダウンロードしてます。

この後でミニバッチデータ化します。

```python
# 前処理
train_images = Images.astype('float32')
train_images = (train_images - 127.5) / 127.5

# データセット化
dataset = tf.data.Dataset.from_tensor_slices((train_images, labels))
dataset = dataset.shuffle(train_images.shape[0])
dataset = dataset.batch(128, drop_remainder=True)
```
例によってまず[-1,1]スケールに正規化しています。その後で`TensorFlow`の`from_tensor_slices`を使ってデータセット化、シャッフル・バッチしてバッチサイズ128のミニバッチデータセットを作成しています。

ちなみに画像枚数は計11331枚なのでバッチサイズ128だと割り切れません。そのため

```python
dataset = dataset.batch(128, drop_remainder=True)
```
`drop_remainder = True`にすることで割り切れないものは切り捨てしてバッチサイズ128に統一しています。

最初この処理を忘れていて、学習の時に**「データの次元がちゃうから損失計算できひんぞ！」**ってエラー吐かれたので要注意です。

## 3. Generator
```python
# ノイズの次元数
z_size = 100

# クラスの数
n_classes = 25

# ノイズの入力層を定義
z_in = Input(shape=(z_size, ))

# ラベルの入力層を定義
label_in = Input(shape=(1, ), dtype="int32")

# モデル概要図の右側の処理を行う関数
# ラベル情報の層を作成
def make_gen_label_input(n_classes=25, embedding_dim=100):
    # ラベルを埋め込む
    label_embedding = Embedding(n_classes, embedding_dim)(label_in)
    # 全結合層に接続
    label_dense = Dense(16)(label_embedding)
    # 変形[4,4,1]
    label_reshape_layer = Reshape((4, 4, 1))(label_dense)
    return label_reshape_layer

# モデル概要図の左側の処理を行う関数
def make_z_input(z_size=100):
    z_dense = Dense(512 * 4 * 4)(z_in)
    z_dense = ReLU()(z_dense)
    z_reshape = Reshape((4, 4, 512))(z_dense)
    return z_reshape
```

ノイズベクトルとラベル情報の入力をまとめるわけですが、まとめる前の処理をする関数を定義しています。

ラベルの埋め込み出力は[4,4,1]の形に
ノイズベクトルの出力は[4,4,512]の形になるようにしています。

```python
def build_cgan_generator():
    
    # ラベルの埋め込み出力を取得
    label_output = make_gen_label_input() # 4 × 4 × 1

    # ノイズベクトルの出力を取得
    z_output = make_z_input() # 4 × 4 × 512

    # 上記2つを連結
    merge = Concatenate()([z_output, label_output]) # 4 × 4 × 513
    
    # 重みを初期化するinitializerを定義
    # 論文より平均0,標準偏差0.02の正規分布から初期化する（より早く収束するらしい）
    initializer = tf.keras.initializers.RandomNormal(mean=0.0, stddev=0.02)

    x = Conv2DTranspose(64 * 8, kernel_size=4, strides= 2, padding='same', 
                        kernel_initializer=initializer, use_bias=False, name='conv_transpose_1')(merge)
    x = BatchNormalization(momentum=0.1, epsilon=0.8, center=1.0, scale=0.02, name='bn_1')(x)
    x = ReLU(name='relu_1')(x)
    
    x = Conv2DTranspose(64 * 4, kernel_size=4, strides= 2, padding='same', 
                        kernel_initializer=initializer,use_bias=False, name='conv_transpose_2')(x)
    x = BatchNormalization(momentum=0.1, epsilon=0.8, center=1.0, scale=0.02, name='bn_2')(x)
    x = ReLU(name='relu_2')(x)
    
    x = Conv2DTranspose(64 * 2, kernel_size=4, strides=2, padding='same', 
                        kernel_initializer=initializer, use_bias=False, name='conv_transpose_3')(x)
    x = BatchNormalization(momentum=0.1, epsilon=0.8,  center=1.0, scale=0.02, name='bn_3')(x)
    x = ReLU(name='relu_3')(x)
  
    x = Conv2DTranspose(64 * 1, kernel_size=4, strides=2, padding='same', 
                        kernel_initializer=initializer, use_bias=False, name='conv_transpose_4')(x)
    x = BatchNormalization(momentum=0.1, epsilon=0.8,  center=1.0, scale=0.02, name='bn_4')(x)
    x = ReLU(name='relu_4')(x) 
    
    out_layer = Conv2DTranspose(3, kernel_size=4, strides=2,padding='same', 
                                kernel_initializer=initializer, use_bias=False, activation='tanh', name='conv_transpose_6')(x)
    
    
    # 今までの入力層と出力層を結合
    model = Model(inputs=[z_in, label_in], outputs=out_layer)
    return model
```

```python
# Generatorインスタンスを作成、モデル概要、モデル図も作成
generator = build_cgan_generator()
generator.summary()
plot_model(generator, to_file='generator.png')
```

```
Model: "model"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            [(None, 100)]        0                                            
__________________________________________________________________________________________________
input_2 (InputLayer)            [(None, 1)]          0                                            
__________________________________________________________________________________________________
dense_1 (Dense)                 (None, 8192)         827392      input_1[0][0]                    
__________________________________________________________________________________________________
embedding (Embedding)           (None, 1, 100)       2500        input_2[0][0]                    
__________________________________________________________________________________________________
re_lu (ReLU)                    (None, 8192)         0           dense_1[0][0]                    
__________________________________________________________________________________________________
dense (Dense)                   (None, 1, 16)        1616        embedding[0][0]                  
__________________________________________________________________________________________________
reshape_1 (Reshape)             (None, 4, 4, 512)    0           re_lu[0][0]                      
__________________________________________________________________________________________________
reshape (Reshape)               (None, 4, 4, 1)      0           dense[0][0]                      
__________________________________________________________________________________________________
concatenate (Concatenate)       (None, 4, 4, 513)    0           reshape_1[0][0]                  
                                                                 reshape[0][0]                    
__________________________________________________________________________________________________
conv_transpose_1 (Conv2DTranspo (None, 8, 8, 512)    4202496     concatenate[0][0]                
__________________________________________________________________________________________________
bn_1 (BatchNormalization)       (None, 8, 8, 512)    2048        conv_transpose_1[0][0]           
__________________________________________________________________________________________________
relu_1 (ReLU)                   (None, 8, 8, 512)    0           bn_1[0][0]                       
__________________________________________________________________________________________________
conv_transpose_2 (Conv2DTranspo (None, 16, 16, 256)  2097152     relu_1[0][0]                     
__________________________________________________________________________________________________
bn_2 (BatchNormalization)       (None, 16, 16, 256)  1024        conv_transpose_2[0][0]           
__________________________________________________________________________________________________
relu_2 (ReLU)                   (None, 16, 16, 256)  0           bn_2[0][0]                       
__________________________________________________________________________________________________
conv_transpose_3 (Conv2DTranspo (None, 32, 32, 128)  524288      relu_2[0][0]                     
__________________________________________________________________________________________________
bn_3 (BatchNormalization)       (None, 32, 32, 128)  512         conv_transpose_3[0][0]           
__________________________________________________________________________________________________
relu_3 (ReLU)                   (None, 32, 32, 128)  0           bn_3[0][0]                       
__________________________________________________________________________________________________
conv_transpose_4 (Conv2DTranspo (None, 64, 64, 64)   131072      relu_3[0][0]                     
__________________________________________________________________________________________________
bn_4 (BatchNormalization)       (None, 64, 64, 64)   256         conv_transpose_4[0][0]           
__________________________________________________________________________________________________
relu_4 (ReLU)                   (None, 64, 64, 64)   0           bn_4[0][0]                       
__________________________________________________________________________________________________
conv_transpose_6 (Conv2DTranspo (None, 128, 128, 3)  3072        relu_4[0][0]                     
==================================================================================================
Total params: 7,793,428
Trainable params: 7,791,508
Non-trainable params: 1,920
__________________________________________________________________________________________________
```

モデル構造は参考記事とほぼ一緒です。モデル概要図になるように結合させています。

個人的にはモデルを書くときは`model.add`で上から順番にどんどん層を積んでいく書き方が分かりやすくて好きなのですが、コチラの書き方の方が後で`model.summary()`した時に、どの層と層が繋がってるかが分かりやすいので、この書き方も練習していこうと思います。

## 4. Discriminator
```python
# 画像の入力をそのまま出力として返す関数
def make_image_input(input_shape=(128,128,3)):
    input_image = Input(shape=input_shape)
    return input_image

# モデル図の左側の処理を行う関数
def make_disc_label_input(input_shape=(128,128,3), n_classes=25, embedding_dim=100):
    # ラベルの入力
    label_in = Input(shape=(1, ))

    # ラベルの埋め込み
    label_embedding = Embedding(n_classes, embedding_dim)(label_in)
    
    # 全結合層につなぐ
    label_dense = Dense(128 * 128 * 3)(label_embedding)
    
    # 128 × 128 × 3に変形
    label_reshape_layer = Reshape((input_shape[0], input_shape[1], 3))(label_dense)

    return label_in, label_reshape_layer
```
Generator同様、最終的にラベルデータと実際の画像データを結合させる前の処理を行う関数を定義しています。

```python
def build_cgan_discriminator():
    # ラベルの入力層と、そのラベルデータの出力層を取得(128 × 128 × 3)
    label_in, label_output = make_disc_label_input()

    # 画像データの出力を取得(128 × 128 × 3)
    input_image_output = make_image_input()
    
    # 上記、ラベルデータと画像データを連結(128 × 128 × 6)
    merge = Concatenate()([input_image_output, label_output])
    
    # 重みを初期化するinitializerを定義
    # 論文より平均0,標準偏差0.02の正規分布から初期化する（より早く収束するらしい）
    initializer = tf.keras.initializers.RandomNormal(mean=0.0, stddev=0.02)

    x = Conv2D(64, kernel_size=4, strides= 2, padding='same', 
               kernel_initializer=initializer, use_bias=False, name='conv_1')(merge)
    x = BatchNormalization(momentum=0.1,  epsilon=0.8, center=1.0, scale=0.02, name='bn_1')(x)
    x = LeakyReLU(0.2, name='leaky_relu_1')(x)
    x = Dropout(0.3, name = 'dropout_1')(x)
    
    x = Conv2D(64 * 2, kernel_size=4, strides= 3, padding='same', 
               kernel_initializer=initializer, use_bias=False, name='conv_2')(x)
    x = BatchNormalization(momentum=0.1,  epsilon=0.8, center=1.0, scale=0.02, name='bn_2')(x)
    x = LeakyReLU(0.2, name='leaky_relu_2')(x)
    x = Dropout(0.3, name = 'dropout_2')(x)
    
    x = Conv2D(64 * 4, kernel_size=4, strides=3, padding='same', 
               kernel_initializer=initializer, use_bias=False, name='conv_3')(x)
    x = BatchNormalization(momentum=0.1,  epsilon=0.8, center=1.0, scale=0.02, name='bn_3')(x)
    x = LeakyReLU(0.2, name='leaky_relu_3')(x)
    x = Dropout(0.3, name = 'dropout_3')(x)
      
    x = Conv2D(64 * 8, kernel_size=4, strides=3,padding='same', 
               kernel_initializer=initializer, use_bias=False, name='conv_5')(x)
    x = BatchNormalization(momentum=0.1,  epsilon=0.8, center=1.0, scale=0.02, name='bn_4')(x)
    x = LeakyReLU(0.2, name='leaky_relu_5')(x)
  
    flattened_out = Flatten()(x)

    dropout = Dropout(0.4, name = 'dropout_4')(flattened_out)

    dense_out = Dense(1, activation='sigmoid')(dropout)

    # 出力と入力をまとめる
    model = Model([input_image_output, label_in], dense_out)
    return model
```

画像とラベルの出力を連結させて[128,128,6]の形で次の層に送ります。
最終的に色々な畳み込み層を通って本物か偽物かを判断します。

```python
# Discriminatorをインスタンス化、モデル概要、モデル図も作成
discriminator = build_cgan_discriminator()
discriminator.summary()
plot_model(discriminator)
```

```python
Model: "model_1"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_3 (InputLayer)            [(None, 1)]          0                                            
__________________________________________________________________________________________________
embedding_1 (Embedding)         (None, 1, 100)       2500        input_3[0][0]                    
__________________________________________________________________________________________________
dense_2 (Dense)                 (None, 1, 49152)     4964352     embedding_1[0][0]                
__________________________________________________________________________________________________
input_4 (InputLayer)            [(None, 128, 128, 3) 0                                            
__________________________________________________________________________________________________
reshape_2 (Reshape)             (None, 128, 128, 3)  0           dense_2[0][0]                    
__________________________________________________________________________________________________
concatenate_1 (Concatenate)     (None, 128, 128, 6)  0           input_4[0][0]                    
                                                                 reshape_2[0][0]                  
__________________________________________________________________________________________________
conv_1 (Conv2D)                 (None, 64, 64, 64)   6144        concatenate_1[0][0]              
__________________________________________________________________________________________________
bn_1 (BatchNormalization)       (None, 64, 64, 64)   256         conv_1[0][0]                     
__________________________________________________________________________________________________
leaky_relu_1 (LeakyReLU)        (None, 64, 64, 64)   0           bn_1[0][0]                       
__________________________________________________________________________________________________
dropout_1 (Dropout)             (None, 64, 64, 64)   0           leaky_relu_1[0][0]               
__________________________________________________________________________________________________
conv_2 (Conv2D)                 (None, 22, 22, 128)  131072      dropout_1[0][0]                  
__________________________________________________________________________________________________
bn_2 (BatchNormalization)       (None, 22, 22, 128)  512         conv_2[0][0]                     
__________________________________________________________________________________________________
leaky_relu_2 (LeakyReLU)        (None, 22, 22, 128)  0           bn_2[0][0]                       
__________________________________________________________________________________________________
dropout_2 (Dropout)             (None, 22, 22, 128)  0           leaky_relu_2[0][0]               
__________________________________________________________________________________________________
conv_3 (Conv2D)                 (None, 8, 8, 256)    524288      dropout_2[0][0]                  
__________________________________________________________________________________________________
bn_3 (BatchNormalization)       (None, 8, 8, 256)    1024        conv_3[0][0]                     
__________________________________________________________________________________________________
leaky_relu_3 (LeakyReLU)        (None, 8, 8, 256)    0           bn_3[0][0]                       
__________________________________________________________________________________________________
dropout_3 (Dropout)             (None, 8, 8, 256)    0           leaky_relu_3[0][0]               
__________________________________________________________________________________________________
conv_5 (Conv2D)                 (None, 3, 3, 512)    2097152     dropout_3[0][0]                  
__________________________________________________________________________________________________
bn_4 (BatchNormalization)       (None, 3, 3, 512)    2048        conv_5[0][0]                     
__________________________________________________________________________________________________
leaky_relu_5 (LeakyReLU)        (None, 3, 3, 512)    0           bn_4[0][0]                       
__________________________________________________________________________________________________
flatten (Flatten)               (None, 4608)         0           leaky_relu_5[0][0]               
__________________________________________________________________________________________________
dropout_4 (Dropout)             (None, 4608)         0           flatten[0][0]                    
__________________________________________________________________________________________________
dense_3 (Dense)                 (None, 1)            4609        dropout_4[0][0]                  
==================================================================================================
Total params: 7,733,957
Trainable params: 7,732,037
Non-trainable params: 1,920
__________________________________________________________________________________________________
```

3日くらいかけて最終的にこのモデルになったわけですが、まだまだ改善の余地ありです。このモデルでも200エポックぐらいからDiscriminatorが強くなりすぎて、D-lossが0に収束してしまいました。

もう少しDropout層の率を上げたりとかすれば良くなるのかな。

Google Colaboratoryが学習のしすぎで悲鳴あげてそうだったので、ここで妥協しました（）

余力できたら、改善したい。

## 5. Optimizer
```python
# optimizerの定義
generator_optimizer = tf.keras.optimizers.Adam(learning_rate=0.0002, beta_1 = 0.5, beta_2 = 0.999 )
discriminator_optimizer = tf.keras.optimizers.Adam(learning_rate=0.0001, beta_1 = 0.1, beta_2 = 0.999 )
```
Generator,Discriminator共にAdamで。

ただDiscriminatorが強くなりがちなので調整をしています。

## 6. 損失関数
```python
# 損失関数の定義
loss_fn = tf.keras.losses.BinaryCrossentropy()

def generator_loss(label, fake_output):
    gen_loss = loss_fn(label, fake_output)
    return gen_loss

def discriminator_loss(label, output):
    disc_loss = loss_fn(label, output)
    return disc_loss
```

## 7. 学習ステップの定義
```python
# 学習効率化のためtf.function
@tf.function
def train_step(images,label):
    # ランダムにノイズを取得
    noise = tf.random.normal([label.shape[0], z_size])

    # discriminatorを本物の画像で学習
    with tf.GradientTape() as disc_tape1:
        generated_images = generator([noise,label], training=True)

        real_output = discriminator([images,label], training=True)
        real_labels = tf.ones_like(real_output)
        disc_loss1 = discriminator_loss(real_labels, real_output)
        
    # 本物のラベルで勾配を計算   
    gradients_of_disc1 = disc_tape1.gradient(disc_loss1, discriminator.trainable_variables)
      
    # 本物のラベルでdiscriminatorのパラメータ更新
    discriminator_optimizer.apply_gradients(zip(gradients_of_disc1,\
    discriminator.trainable_variables))
    
    # disctiminatorを偽物の画像で学習
    with tf.GradientTape() as disc_tape2:
        fake_output = discriminator([generated_images,label], training=True)
        fake_labels = tf.zeros_like(fake_output)
        disc_loss2 = discriminator_loss(fake_labels, fake_output)
    # 偽物のラベルで勾配を計算
    gradients_of_disc2 = disc_tape2.gradient(disc_loss2, discriminator.trainable_variables)
    
    # 偽物のラベルでdiscriminatorのパラメータ更新
    discriminator_optimizer.apply_gradients(zip(gradients_of_disc2,\
    discriminator.trainable_variables))
    
    # Generatorを本物のラベルで学習
    with tf.GradientTape() as gen_tape:
        generated_images = generator([noise,label], training=True)
        fake_output = discriminator([generated_images,label], training=True)
        real_labels = tf.ones_like(fake_output)
        gen_loss = generator_loss(real_labels, fake_output)

    # generatorの勾配を計算    
    gradients_of_gen = gen_tape.gradient(gen_loss, generator.trainable_variables)
    
    # 本物のラベルでgeneratorのパラメータ更新
    generator_optimizer.apply_gradients(zip(gradients_of_gen,\
    generator.trainable_variables))

    d_loss = disc_loss1 + disc_loss2
    
    return d_loss, gen_loss
```
ほぼ参考通りです。
`tf.GradientTape`を使って、GeneratorとDiscriminatorのパラメータを更新しています。

正直なところを言うと損失の計算や勾配、パラメータ更新の流れはまだ正確には理解できていないので、勉強します。

## 8. 学習前に必要な関数を定義
```python
# ランダムにラベルベクトルを生成する関数
def label_gen(n_classes):
    lab = tf.random.uniform((1,), minval=0, maxval=n_classes, dtype=tf.dtypes.int32, seed=None, name=None)
    return tf.repeat(lab, [25], axis=None, name=None)

# 学習結果を表示・保存する関数
def save_imgs(log_path, epoch):
    # 何枚画像を生成するか
    num_example_gen = 25
    
    # ノイズの生成
    seed = tf.random.normal([num_example_gen, z_size])
    
    # ラベルの生成
    labels = label_gen(num_example_gen)
    
    # 画像を生成して、[0,1]スケールにする
    gen_imgs = generator([seed, labels], training=False)
    gen_imgs = 0.5 * gen_imgs + 0.5

    fig = plt.figure(figsize = (8, 8))
    for i in range(gen_imgs.shape[0]):
        plt.subplot(5,5,i+1)
        plt.imshow(gen_imgs[i])
        plt.axis('off')
    fig.savefig("{}/{}.png".format(log_path, str(epoch+1).zfill(4)))
    plt.show()
    plt.close()
```

## 9. いざ学習
```python
def train(ds, epochs, save_interval=1):
    # 学習結果を保存する場所を定義
    log_path = '/content/drive/MyDrive/sakurazaka_make_face/save_image5'
    os.makedirs(log_path, exist_ok=True)
    for epoch in range(epochs):
        D_loss_list, G_loss_list = [], []
        start_time = time.time()
        for image, label in ds:
            D_loss, G_loss = train_step(image, label)
            D_loss_list.append(D_loss)
            G_loss_list.append(G_loss)

        if epoch % save_interval == 0:
            # 生成画像の表示と保存
            per_1_time = time.time() - start_time
            print ("epoch:%d, Time:%.2f seconds,  End: %.2f min,  [D loss: %f / G loss: %f]" % 
                   (epoch+1, per_1_time, (epochs- epoch)*per_1_time/60, np.mean(D_loss_list), np.mean(G_loss_list)))
            save_imgs(log_path, epoch)
```
データセットをミニバッチごとに学習させます。
全部のミニバッチの学習が終わって1エポックです。

学習時間が膨大なので個人的には一回あたりの学習時間と、残り学習時間の表示は必須だと思います。

またD-loss と G-lossの表示も必須です。
うまくいっているときはD-lossの値が1.4付近になります。
$$ln(0.5)+ln(1-0.5)≒-1.4$$
なので。

まずそうなら途中で学習ストップするとかしないと時間が無駄に...。


また残り学習時間 = 1回あたりの学習時間 × 残りエポック数
で算出しています。

でとりあえず、1000エポックで学習してみたところ
1エポックあたり40秒、大体10時間ほどかかりました。

## 学習結果
グロいやつもあるので苦手な方はごめんなさい。
### 1エポック目

![0001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/e224d71e-e541-22c5-d14b-92a8107cf115.png)

### 10エポック目
![0010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/7da6e7c2-a140-28b1-1ef9-17b98450c9e5.png)
なんか顔っぽいのが生成されてます。

### 50エポック目
![0050.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/5b425bbf-8f36-f4c3-8633-48b9dc3b9c2d.png)

怖いですね。

### 100エポック目
![0100.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/f927dd28-4e05-19d8-1b2b-6d60fe1c606c.png)

だいぶ整ってきた？

### 200エポック目
![0200.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/47a313a1-caa8-1837-adc1-0aa371a9e0c7.png)

うーん...。

### 300エポック目
![0300.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/93a9ee41-10fc-040f-c147-05d0f0f28062.png)

よさそうなやつが生成されてますね。

### 500エポック目
![0500 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/01bd85f6-9894-fae0-983f-9748dd55d5d3.png)

この辺りからDiscriminatorが強くなってきました。

### 800エポック目
![0800.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/b49ff118-bc46-99f7-4092-6d1b0885b1d9.png)

### 1000エポック目
![1000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/94e73f14-4bc4-bcd0-304a-5c1073109d3b.png)


微妙な感じですね、、、


ただ本番はここから。
ラベルノイズを調整して狙ったメンバーの画像を生成してもらいます。

```python
members = ['uemura_rina', 'ozeki_rika', 'koike_minami', 'kobayashi_yui', 'saito_fuyuka',
         'sugai_yuka', 'habu_miduho', 'harada_aoi', 'moriya_akane', 'watanabe_rika', 'watanabe_risa',
         'inoue_rina', 'endo_hikari', 'oozono_rei', 'oonuma_akiho', 'kousaka_marino', 'seki_yumiko',
         'takemoto_yui', 'tamura_hono', 'fuziyoshi_karin', 'masumoto_kira', 'matuda_rina','morita_hikaru',
         'moriya_rena', 'yamasaki_ten']
         
def get_class_label(class_code):
    class_labels = {i:member_name for i, member_name in enumerate(members)}
    return class_labels[class_code]

number = 18
name = get_class_label(number)
noise = tf.random.normal([5, 100])
label = tf.ones(5) * number
gen_imgs = generator([noise, label], training=False)
gen_imgs = 0.5 * gen_imgs + 0.5
fig = plt.figure(figsize = (8, 8))
for i in range(gen_imgs.shape[0]):
    plt.subplot(1,5,i+1)
    plt.imshow(gen_imgs[i])
    if i == 2:
        plt.title(name)
    plt.axis('off')
plt.show()
plt.close()
```

こんな感じのコードで`number`の値を変えればメンバーを変えれます。

我が推しメンは田村保乃ちゃんと森田ひかるちゃんなのでこの2人を生成してもらいましょう。

個人的にうまくいったものだけ載せます。

![hono.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/27eafdf4-fe6b-6a8b-d961-3f6308a5eecd.png)

![ダウンロード (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/f305d307-f953-cd77-808d-55f1bcf33151.png)

![ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/8eae4f55-27dd-2ce4-1d00-624f0eb37b40.png)

![hikaru.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/484db4e5-39bf-e001-9d47-b56e53a01011.png)

![hikaru2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/09c26950-0d9f-4bdf-71ab-d08fd6098519.png)

![hikaru3.png.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/03435464-c1e4-c49a-ea3c-988c94b76739.png)

妥協できるラインのものを載せましたが、まだまだって感じですね。これでは僕のモチベは上がらないです（）

# まとめ
CGANモデルで推しメンの画像をたくさん拝めると思ったけど、ダメでした。
まだDiscriminatorが強くなりすぎるのと、学習回数重ねてないからっていうのもあるのかな。

伸び代はまだまだあるので、気が向いたら調整かけて、また紹介できればと思います。

ただ、本当にラベルを調整するだけで生成画像が変わるのは面白くて、より勉強のモチベになったのでやって良かったです。

個人的にはStyleGAN2のような高性能モデルで推しメン生成してみたいですね。

結果は失敗ですが、参考になれば幸いです。