GAN備忘録
GAN概要
潜在変数をGeneratorの入力として受け取り、画像データを生成する.その画像データをDiscriminatorが入力として受け取り本物であるか偽物であるかの真偽値を確率として連続な値で返す.
プログラムの大枠
- inputの定義
- discriminatorモデルの定義
- 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個のパラメータとなっている.
LeakyReLU(2層目)
活性化関数なので次元数に関しては変化はない.イメージとしては以下の感じ.
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関数が用いられている.
モデルの結合
結合したモデルに関しては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)
前処理
ループを回す前までに学習画像に対して前処理をかける.
- 画素値に対して0.0~255.0の値のレンジを-1.0~1.0へ変換する.
- 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