DCGANやってみた
年末にパソコンを自作してグラボが使えるようになったのでCPUだけだときつそうだったGANを試してみることにした。
丁寧に解説してるサイトはいくらでもあるので適当に説明する。
↓パクった参考にしたサイト
elix-tech.github.io
GAN(Generative Adversarial Network)
生成器(generator)と識別器(discriminator)の二つのモデルを同時に学習させて、お互いがお互いに勝てるように競争してくイメージらしい。ガンとギャンどっちなんでしょうか。
DCGAN(Deep Convolutional GAN)はその名の通りCNNを取り入れたGANのことで、様々なテクニックで学習がうまくいくように工夫している。例えば一般的にCNNで使われるプーリングの代わりにDCGANのdiscriminatorでは代わりにストライド2の畳み込み使ったり、batch normalization使ったり、Leaky ReLU使ったり。これらの工夫は一長一短らしいからちゃんと論文読まないと。
ソースコード
from keras.models import Sequential from keras.layers import Dense,Activation,Reshape from keras.layers.normalization import BatchNormalization from keras.layers.convolutional import UpSampling2D,Conv2D # 生成モデル def generator_model(): model = Sequential() # 入力は100次元のノイズ model.add(Dense(1024,input_dim=100)) model.add(BatchNormalization()) model.add(Activation('relu')) # あとで(128,7,7)にreshapeするため model.add(Dense(128*7*7)) model.add(BatchNormalization()) model.add(Activation('relu')) model.add(Reshape((128,7,7),input_shape=(128*7*7,))) # UpSamplingで画像を2倍に拡大 model.add(UpSampling2D((2,2))) model.add(Conv2D(64,(5,5),padding='same')) model.add(BatchNormalization()) model.add(Activation('relu')) # UpSamplingで画像を2倍に拡大 model.add(UpSampling2D((2,2))) model.add(Conv2D(1,(5,5),padding='same')) model.add(Activation('tanh')) # 二回UpSamplingを行うことにより最終的に28*28の画像になる return model from keras.layers.advanced_activations import LeakyReLU from keras.layers import Flatten,Dropout # 識別モデルCNN) def discriminator_model(): model = Sequential() # プーリングの代わりにストライド2の畳み込みを行う model.add(Conv2D( 64,(5,5),strides=(2,2),padding='same',input_shape=(1,28,28) )) # 活性化関数はLeakyReLUを使用 model.add(LeakyReLU(0.2)) model.add(Conv2D(128,(5,5),strides=(2,2))) model.add(LeakyReLU(0.2)) model.add(Flatten()) model.add(Dense(256)) model.add(LeakyReLU(0.2)) model.add(Dropout(0.5)) model.add(Dense(1)) model.add(Activation('sigmoid')) return model import math import numpy as np # 生成画像表示用の関数 def combine_images(generated_images): total = generated_images.shape[0] cols = int(math.sqrt(total)) rows = math.ceil(float(total)/cols) width,height = generated_images.shape[2:] combined_image = np.zeros((height*rows,width*cols),dtype=generated_images.dtype) for index,image in enumerate(generated_images): i = int(index/cols) j = index%cols combined_image[width*i:width*(i+1),height*j:height*(j+1)] = image[0,:,:] return combined_image import os from keras.datasets import mnist from keras.optimizers import Adam from PIL import Image BATCH_SIZE = 32 NUM_EPOCH = 20 GENERATED_IMAGE_PATH = 'keras_dcgan_generated_images/' def train(): # 訓練データのみ必要になる (X_train, _), (_, _) = mnist.load_data() # -1~1の範囲にする X_train = (X_train.astype(np.float32) - 127.5)/127.5 # おそらくRGBとかなら第二引数は3になる X_train = X_train.reshape(X_train.shape[0], 1, X_train.shape[1], X_train.shape[2]) discriminator = discriminator_model() d_opt = Adam(lr=1e-5, beta_1=0.1) discriminator.compile(loss='binary_crossentropy', optimizer=d_opt) # generatorの学習時はdiscriminatorの学習は行わない discriminator.trainable = False generator = generator_model() # 生成モデルの訓練はdiscriminatorも用いて行う dcgan = Sequential([generator, discriminator]) g_opt = Adam(lr=2e-4, beta_1=0.5) dcgan.compile(loss='binary_crossentropy', optimizer=g_opt) num_batches = int(X_train.shape[0] / BATCH_SIZE) print('Number of batches:', num_batches) for epoch in range(NUM_EPOCH): for index in range(num_batches): noise = np.array([np.random.uniform(-1, 1, 100) for _ in range(BATCH_SIZE)]) image_batch = X_train[index*BATCH_SIZE:(index+1)*BATCH_SIZE] generated_images = generator.predict(noise, verbose=0) if index % 500 == 0: image = combine_images(generated_images) image = image*127.5 + 127.5 if not os.path.exists(GENERATED_IMAGE_PATH): os.mkdir(GENERATED_IMAGE_PATH) Image.fromarray(image.astype(np.uint8))\ .save(GENERATED_IMAGE_PATH+"%04d_%04d.png" % (epoch, index)) X = np.concatenate((image_batch, generated_images)) y = [1]*BATCH_SIZE + [0]*BATCH_SIZE d_loss = discriminator.train_on_batch(X, y) noise = np.array([np.random.uniform(-1, 1, 100) for _ in range(BATCH_SIZE)]) # generatorの学習時にはラベルはすべて1 g_loss = dcgan.train_on_batch(noise, [1]*BATCH_SIZE) print("epoch: %d, batch: %d, g_loss: %f, d_loss: %f" % (epoch, index, g_loss, d_loss)) # 重みの保存 generator.save_weights('generator.h5') discriminator.save_weights('discriminator.h5') if __name__ == "__main__": train()
ほとんど写経でConvolution2DをConv2Dにしたりコメントをつけ足したりした。discriminatorへの入力を-1から1にしたのはLeakyReLUの特性を生かすためなのかな。学習終了時にはしっかり数字画像が生成できている。
おまけでfashion-mnistとひらがなデータセットを使ってやってみた。fashion-mnistはTシャツのみを取り出し、ひらがなの方はグレースケールに変換したりしてから学習を始めた。
Tシャツの方は結構わかりやすい。ひらがなは50文字以上もあるから難しいかな。