GAN備忘録

GAN概要

潜在変数をGeneratorの入力として受け取り、画像データを生成する.その画像データをDiscriminatorが入力として受け取り本物であるか偽物であるかの真偽値を確率として連続な値で返す.

プログラムの大枠

  1. inputの定義
  2. discriminatorモデルの定義
  3. generatorモデルの定義

Generatorモデル

Generatorの役割としては一様乱数から画像を表現するための配列を生成することである. そのため、はじめのinputの次元数は潜在変数の次元数で出力の次元数は画像をの解像度、チャンネル数を示した次元数となる.

def build_generator(self):
        noise_shape = (self.z_dim,)
        model = Sequential()
        model.add(Dense(256, input_shape=noise_shape))
        model.add(LeakyReLU(alpha=0.2))
        model.add(BatchNormalization(momentum=0.8))
        model.add(Dense(512))
        model.add(LeakyReLU(alpha=0.2))
        model.add(BatchNormalization(momentum=0.8))
        model.add(Dense(1024))
        model.add(LeakyReLU(alpha=0.2))
        model.add(BatchNormalization(momentum=0.8))
        model.add(Dense(np.prod(self.img_shape), activation='tanh'))
        model.add(Reshape(self.img_shape))

        model.summary()

        return model
***generator***
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_4 (Dense)              (None, 256)               25856     
_________________________________________________________________
leaky_re_lu_3 (LeakyReLU)    (None, 256)               0         
_________________________________________________________________
batch_normalization_1 (Batch (None, 256)               1024      
_________________________________________________________________
dense_5 (Dense)              (None, 512)               131584    
_________________________________________________________________
leaky_re_lu_4 (LeakyReLU)    (None, 512)               0         
_________________________________________________________________
batch_normalization_2 (Batch (None, 512)               2048      
_________________________________________________________________
dense_6 (Dense)              (None, 1024)              525312    
_________________________________________________________________
leaky_re_lu_5 (LeakyReLU)    (None, 1024)              0         
_________________________________________________________________
batch_normalization_3 (Batch (None, 1024)              4096      
_________________________________________________________________
dense_7 (Dense)              (None, 784)               803600    
_________________________________________________________________
reshape_1 (Reshape)          (None, 28, 28, 1)         0         
=================================================================
Total params: 1,493,520
Trainable params: 1,489,936
Non-trainable params: 3,584

詳細

1層目

まず初めの以下の初めの層について見てみる.入力は潜在変数の次元数 *1で、出力は256次元となっている.

noise_shape = (self.z_dim,)
model = Sequential()
model.add(Dense(256, input_shape=noise_shape))
dense_4 (Dense)              (None, 256)               25856   

ここでなぜパラメータ数が25856になっているかメモして置く. ここの変換では100次元を256次元に変換させているので100*256=25600個のパラメータなんじゃないかって勘違いしやすいが、バイアスであるBの256次元分のパラメータもあるので25856個のパラメータとなっている.

f:id:takaishi78:20200215014249j:plain
Generator01

LeakyReLU(2層目)

活性化関数なので次元数に関しては変化はない.イメージとしては以下の感じ.

f:id:takaishi78:20200215020358j:plain
generator02

www.thothchildren.com

BatchNormalization

各層でのアクティベーションの分布を、適切な広がりをもつように調節することができる.また必ずアクティベーション層の後に挿入する必要がある.

利点 - 学習速度が上がる - 重みが初期値に依存しなくなる - 次元数の変化はない

最終ノード

最終ノードの手前でnp.prodを使うことで3次元配列である画像の要素数分の全要素数を算出し出力ノードの次元数に当てている.その後画像に転用できるようにreshapeを行なっている.

Discriminatorモデル

def build_discriminator(self):

        img_shape = (self.img_rows, self.img_cols, self.channels)

        print("***build_discriminator***")
        model = Sequential()
        model.add(Flatten(input_shape=img_shape))
        model.add(Dense(512))
        model.add(LeakyReLU(alpha=0.2))
        model.add(Dense(256))
        model.add(LeakyReLU(alpha=0.2))
        model.add(Dense(1, activation='sigmoid'))
        model.summary()

        return model
***build_discriminator***
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
flatten_1 (Flatten)          (None, 784)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 512)               401920    
_________________________________________________________________
leaky_re_lu_1 (LeakyReLU)    (None, 512)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 256)               131328    
_________________________________________________________________
leaky_re_lu_2 (LeakyReLU)    (None, 256)               0         
_________________________________________________________________
dense_3 (Dense)              (None, 1)                 257       
=================================================================
Total params: 533,505
Trainable params: 533,505
Non-trainable params: 0

詳細

第1層目

画像の次元数を1次元に変換した要素数分が初めの層のノード数となる.(やっていることとしてはGeneratorの最終層の真逆)

最終層

1次元に変換、この数値は本物か偽物かを判断する0 ~ 1の範囲で連続な確率値を算出する.そのため活性化関数にはsoftmax関数が用いられている.

無から始めるKeras 第2回 - Qiita

モデルの結合

結合したモデルに関してはdiscriminatorの重みは固定させた上でgeneratorを学習させる必要がある.

def build_combined1(self):
        self.discriminator.trainable = False
        model = Sequential([self.generator, self.discriminator])
        return model

訓練

def train(self, epochs=3000, batch_size=128, save_interval=100):

        print('Load Start')
        # mnistデータの読み込み
        (train_images, train_labels),(test_images, test_labels) = mnist.load_data()
        print('Load Done')

        # 値を-1 to 1に規格化
        train_images = train_images.astype('float32')
        train_images = train_images/127.5
        train_images -= np.ones((train_images.shape))
        train_images = np.expand_dims(train_images, axis=3)
        half_batch = int(batch_size / 2)

        for epoch in range(epochs):

            # ---------------------
            #  Discriminatorの学習
            # ---------------------

            # バッチサイズの半数をGeneratorから生成
            noise = np.random.normal(0, 1, (half_batch, self.z_dim))
            gen_imgs = self.generator.predict(noise)


            # バッチサイズの半数を教師データからピックアップ
            #0 ~ train_images.shape[0]未満の乱数をhalf_batch分の配列を用意
            idx = np.random.randint(0, train_images.shape[0], half_batch)
            imgs = train_images[idx]

            # discriminatorを学習
            # 本物データと偽物データは別々に学習させる
            d_loss_real = self.discriminator.train_on_batch(imgs, np.ones((half_batch, 1)))
            d_loss_fake = self.discriminator.train_on_batch(gen_imgs, np.zeros((half_batch, 1)))
            # それぞれの損失関数を平均
            d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)


            # ---------------------
            #  Generatorの学習
            # ---------------------

            noise = np.random.normal(0, 1, (batch_size, self.z_dim))

            # 生成データの正解ラベルは本物(1)
            valid_y = np.array([1] * batch_size)

            # Train the generator
            g_loss = self.combined.train_on_batch(noise, valid_y)

            # 進捗の表示
            print ("%d [D loss: %f, acc.: %.2f%%] [G loss: %f]" % (epoch, d_loss[0], 100*d_loss[1], g_loss))

            # 指定した間隔で生成画像を保存
            if epoch % save_interval == 0:
                self.sample_images(epoch)

前処理

ループを回す前までに学習画像に対して前処理をかける.

  1. 画素値に対して0.0~255.0の値のレンジを-1.0~1.0へ変換する.
  2. channel数を次元に追加している.(*expand_dims)

補足expand_dimsについて

expand_dimsでは第2引数に指定した場所の直前にdim=1を挿入するメソッドである

print(train_images.shape)#(6000, 28, 28)
train_images = np.expand_dims(train_images, axis=3)
print(train_images.shape)#(6000, 28, 28, 1)

Discriminatorの学習

batchの半数分の100次元乱数をGeneratorに与えたことで生成されたfake画像と、訓練画像の正しい画像ランダムに選択(ミニバッチ学習)を元に学習を行う.

(*batchとは1epochあたりの1まとまりの入力データのことを指す)

Generator

generatorは単体で学習しないのでコンパイルは必要ない

結合モデルの学習

Generatorによって生成されたデータを正しい結果として結合モデルには渡す. Generatorの目的としては偽物を正しいと認識させることで損失関数を少なくする方向へ進めることができるので、生成した画像に対するラベルは1にしている.

疑問点

  • train_on_batch
  • batch_normalization

参考

*1:, 100