こんにちは。Toruです。
最近、MNISTというデータセットを使ってAutoEncoderを実装しているのですが、
他の人もたくさんやっているっていうことと、いまいち画像データが白黒ということもあり面白く無くなってしまいました。
なので今回はCIFAR10というデータセットとKerasというディープラーニングライブラリを使ってAutoEncoderを実装しました。
まあまあいい感じの結果が出たので、これを紹介していきたいと思います。
なるべく初心者の方にもわかりやすく説明していきますので、皆さんの参考にしていただけたら幸いです。
- 50000の学習データと10000の評価データからなる32×32×3のデータセット
- 中身は飛行機、車、鳥などの画像があり、それぞれを10種類のクラスに分かれている。
- Kerasのデータセットに付属しているためmnistと同様、簡単に呼び出すことができる。
それではいきましょう
【結論】実装したAutoEncoderのプログラム
その前に、私の開発環境
AutoEncoderを実装していくにあたって、まずは私の開発環境について紹介しておきます。
今回はこの様な開発環境で進めていきます。
- CPU:Intel Core i7 9700
- GPU:NVIDIA GeForce RTX 2070 Super
- OS:Windows 10 Home
- Python:3.6.1
- TensorFlow-GPU:1.12.0
- Keras:2.2.4
- TensorFlow-GPUの環境の整え方については「CUDA cuDNN TensorFlow GPU版をWindowsにインストールした話」で紹介してますので、こちらを参照してください。
作成したAuto Encoderプログラム
早速結果となりますが、作成したプログラムはこちらになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | import numpy as np import tensorflow as tf from keras import backend as K from keras import callbacks, layers, models from keras.datasets import cifar10 from keras.layers import (BatchNormalization, Conv2D, Dense, LeakyReLU, MaxPooling2D, UpSampling2D) from keras.layers.core import Activation, Dense from keras.optimizers import Adam from sklearn.model_selection import train_test_split def normalizer(x, axis = None): min = x.min(axis = axis, keepdims = True) max = x.max(axis = axis, keepdims = True) result = (x - min) / (max - min) return result def main(): (x_train, y_train), (x_test, y_test) = cifar10.load_data() image_height, image_width = 32, 32 epochs = 150 learn_rate = 0.0002 batch_size = 8 train_data = np.concatenate([x_train, x_test]) label_data = np.concatenate([y_train, y_test]) x_train, x_test, y_train, y_test = train_test_split(train_data, label_data, \ test_size = 0.2, shuffle = True) model = models.Sequential() model.add(Conv2D(64, kernel_size = 6, padding = 'same', input_shape = (32, 32, 3),\ kernel_initializer = 'random_uniform', bias_initializer = 'zeros')) model.add(Activation(LeakyReLU(alpha = 0.5623815057565213))) model.add(BatchNormalization()) model.add(MaxPooling2D(pool_size = (2, 2))) model.add(Conv2D(128, kernel_size = 6, padding = 'same')) model.add(Activation(LeakyReLU(alpha = 0.3949936266626689))) model.add(BatchNormalization()) model.add(MaxPooling2D(pool_size = (2, 2))) model.add(Conv2D(64, kernel_size = 6, padding = "same")) model.add(Activation(LeakyReLU(alpha = 0.9758185183456943))) model.add(BatchNormalization()) model.add(UpSampling2D(size = (2, 2))) model.add(Conv2D(32, kernel_size = 6, padding = "same")) model.add(Activation(LeakyReLU(alpha = 0.288662535902546))) model.add(BatchNormalization()) model.add(Conv2D(16, kernel_size = 6, padding = "same")) model.add(Activation(LeakyReLU(alpha = 0.3376263809413643))) model.add(BatchNormalization()) model.add(UpSampling2D(size = (2, 2))) model.add(Conv2D(3, kernel_size = 6, padding = "same", activation = "sigmoid")) model.compile(optimizer = Adam(lr = learn_rate), loss = 'mse', metrics = ['accuracy']) model.summary() fit_callbacs = [callbacks.EarlyStopping(monitor = 'val_loss', patience = 5, \ mode = 'min')] hist = model.fit(x_train, x_train, epochs = epochs, batch_size = batch_size, \ shuffle = True, validation_data = (x_test, x_test), \ callbacks = fit_callbacs, verbose = 1) val_loss, val_acc = model.evaluate(x_test, x_test, verbose = 1) if __name__ == '__main__': main() |
そして上記プログラムを実行すると、結果はこうなりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | Using TensorFlow backend. _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_1 (Conv2D) (None, 32, 32, 64) 6976 _________________________________________________________________ activation_1 (Activation) (None, 32, 32, 64) 0 _________________________________________________________________ batch_normalization_1 (Batch (None, 32, 32, 64) 256 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 16, 16, 64) 0 _________________________________________________________________ conv2d_2 (Conv2D) (None, 16, 16, 128) 295040 _________________________________________________________________ activation_2 (Activation) (None, 16, 16, 128) 0 _________________________________________________________________ batch_normalization_2 (Batch (None, 16, 16, 128) 512 _________________________________________________________________ max_pooling2d_2 (MaxPooling2 (None, 8, 8, 128) 0 _________________________________________________________________ conv2d_3 (Conv2D) (None, 8, 8, 64) 294976 _________________________________________________________________ activation_3 (Activation) (None, 8, 8, 64) 0 _________________________________________________________________ batch_normalization_3 (Batch (None, 8, 8, 64) 256 _________________________________________________________________ up_sampling2d_1 (UpSampling2 (None, 16, 16, 64) 0 _________________________________________________________________ conv2d_4 (Conv2D) (None, 16, 16, 32) 73760 _________________________________________________________________ activation_4 (Activation) (None, 16, 16, 32) 0 _________________________________________________________________ batch_normalization_4 (Batch (None, 16, 16, 32) 128 _________________________________________________________________ conv2d_5 (Conv2D) (None, 16, 16, 16) 18448 _________________________________________________________________ activation_5 (Activation) (None, 16, 16, 16) 0 _________________________________________________________________ batch_normalization_5 (Batch (None, 16, 16, 16) 64 _________________________________________________________________ up_sampling2d_2 (UpSampling2 (None, 32, 32, 16) 0 _________________________________________________________________ conv2d_6 (Conv2D) (None, 32, 32, 3) 1731 ================================================================= Total params: 692,147 Trainable params: 691,539 Non-trainable params: 608 _________________________________________________________________ Train on 48000 samples, validate on 12000 samples Epoch 39/150 48000/48000 [==============================] - 47s 984us/step - loss: 7.9963e-04 - acc: 0.8497 - val_loss: 7.8588e-04 - val_acc: 0.8687 12000/12000 [==============================] - 1s 123us/step val loss : 0.0007858769219989578 val_acc : 0.868697509765625 10/10 [==============================] - 0s 11ms/step |
それでは詳しく説明していきます。
プログラムの詳細
importライブラリとdefine関数
まずは定義したライブラリと関数について説明します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import numpy as np import tensorflow as tf from keras import backend as K from keras import callbacks, layers, models from keras.datasets import cifar10 from keras.layers import (BatchNormalization, Conv2D, Dense, LeakyReLU, MaxPooling2D, UpSampling2D) from keras.layers.core import Activation, Dense from keras.optimizers import Adam from sklearn.model_selection import train_test_split def normalizer(x, axis = None): min = x.min(axis = axis, keepdims = True) max = x.max(axis = axis, keepdims = True) result = (x - min) / (max - min) return result |
まず初めにこれらのライブラリをインポートします。
これについては詳しく説明する必要はないと思います。
その下のnormalizer関数では画像のデータを0~1の間にノーマライズ(正規化)するために定義します。
なぜノーマライズが必要なのかというと、機械が学習するときに扱いやすい数字にするために変換します。
扱いやすい数字にするとどうなるかというと、重みの更新が効率化されたり、誤差が収束しやすくなります。
機械学習・ディープラーニングをする上では重要になってきますので正規化については理解できるようにしておきましょう。
- 画像データを0~1の間にノーマライズ(正規化)する。
- 正規化することで学習のスピードを上げることができる。
- 正規化することで損失関数の誤差が収束しやすくなる。
main()関数内
main()関数内はネットワークのモデルを定義しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | def main(): (x_train, y_train), (x_test, y_test) = cifar10.load_data() image_height, image_width = 32, 32 epochs = 150 learn_rate = 0.0002 batch_size = 8 train_data = np.concatenate([x_train, x_test]) label_data = np.concatenate([y_train, y_test]) x_train, x_test, y_train, y_test = train_test_split(train_data, label_data, \ test_size = 0.2, shuffle = True) model = models.Sequential() model.add(Conv2D(64, kernel_size = 6, padding = 'same', input_shape = (32, 32, 3),\ kernel_initializer = 'random_uniform', bias_initializer = 'zeros')) model.add(Activation(LeakyReLU(alpha = 0.5623815057565213))) model.add(BatchNormalization()) model.add(MaxPooling2D(pool_size = (2, 2))) model.add(Conv2D(128, kernel_size = 6, padding = 'same')) model.add(Activation(LeakyReLU(alpha = 0.3949936266626689))) model.add(BatchNormalization()) model.add(MaxPooling2D(pool_size = (2, 2))) model.add(Conv2D(64, kernel_size = 6, padding = "same")) model.add(Activation(LeakyReLU(alpha = 0.9758185183456943))) model.add(BatchNormalization()) model.add(UpSampling2D(size = (2, 2))) model.add(Conv2D(32, kernel_size = 6, padding = "same")) model.add(Activation(LeakyReLU(alpha = 0.288662535902546))) model.add(BatchNormalization()) model.add(Conv2D(16, kernel_size = 6, padding = "same")) model.add(Activation(LeakyReLU(alpha = 0.3376263809413643))) model.add(BatchNormalization()) model.add(UpSampling2D(size = (2, 2))) model.add(Conv2D(3, kernel_size = 6, padding = "same", activation = "sigmoid")) model.compile(optimizer = Adam(lr = learn_rate), loss = 'mse', metrics = ['accuracy']) model.summary() fit_callbacs = [callbacks.EarlyStopping(monitor = 'val_loss', patience = 5, \ mode = 'min')] hist = model.fit(x_train, x_train, epochs = epochs, batch_size = batch_size, \ shuffle = True, validation_data = (x_test, x_test), \ callbacks = fit_callbacs, verbose = 1) val_loss, val_acc = model.evaluate(x_test, x_test, verbose = 1) |
3行目でデータセットの読み込みをしています。
5〜8行目ではそれぞれのパラメータの設定をしています。
10,11行目では、28,29行目でデータを8:2に割り振るために、学習データと評価データをそれぞれ結合しています。
もともとの(学習データ:評価データ)=(50000:10000)で学習させていたのですが、これでも評価データが少なかったようで、損失関数の値が安定しなかったのでこの様にしました。
33行目以下はモデルの定義になります。
モデル内ではCNNを使い、過学習防止のために BatchNormalizetion() を使っています。
過学習防止策は BatchNormalizetion() の他に Dropout() が有名ですがBatchNormalizetionはDropoutよりも有効であると言われています。
- Batch Normalizeについての詳しい説明は「Deep LearningにおけるBatch Normalizationの理解メモと、実際にその効果を見てみる」で紹介されていますのでこちらを参照してください。
35行目はInput層です。
少し工夫して重み、バイアスベクトルを初期化しています。
説明すると長くなるのでここでは割愛します。
後は普通のCNNですが、 BatchNormalizetion() は Activation(LeakyReLU) の次に定義する様にしています。
Convolutionのフィルターは64→128→64→32→16→3というふうに増減させています。
学習したときになかなか学習率が上がらなかったらフィルター数を増やすのがセオリーです。
また、このモデル内のパラメータは全てhyperasを使ってパラメータチューニングをし求めました。
- hyperasの紹介やインストール方法、使い方については「【Dense】hyperasを使ってパラメータチューニングする-part1」に書きましたのでこちらをご覧ください。
おまけ
ちなみに、実行した結果などをグラフ化したり、学習した画像などを保存したりするプログラムを作成したのでそちらも合わせてご覧ください。
画像を保存するフォルダーを日時と時間から作成
1 2 3 4 5 6 | def into_dir(): dt_now = datetime.datetime.now() date = str(dt_now.year) + str(dt_now.month) + str(dt_now.day) + "-" + str(dt_now.hour) + "-" + str(dt_now.minute) + "-" + str(dt_now.second) os.mkdir('./cifar_model_result/{}'.format(date)) return dt_now, date |
学習した結果を保存するとき、ファイル名が被ってしまったりするのめんどくさいので学習した時間をフォルダー名にして学習ごとの結果をそのフォルダーに入れる様にします。
学習にかかった時間を表示
1 2 3 4 5 6 7 8 | finish_time = time.time() processing_time = (finish_time - start_time) / 60 print("===============================") print("Processing time:{:.2f}".format(processing_time), "min") print("===============================") |
これは有名ですが、処理にかかった時間を表示します。
学習した画像や損失、正解率を画像、グラフで表示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | p = np.random.random_integers(0, len(x_test), 10) x_test_sampled = x_test[p] y_test_sample_lable = y_test[p] x_test_sampled_pred = model.predict_proba(x_test_sampled, verbose = 1) fig, axes = plt.subplots(2, 10) for i, label in enumerate(y_test[p]): img = x_test_sampled[i].reshape(image_height, image_width, 3) axes[0][i].imshow(img, cmap = cm.gray_r) axes[0][i].axis('off') axes[0][i].set_title(label, color = 'red') pred_img = x_test_sampled_pred[i].reshape(image_height, image_width, 3) axes[1][i].imshow(pred_img, cmap = cm.gray_r) axes[1][i].axis('off') plt.savefig('./cifar_model_result/{}/Probacation_Image.png'.format(date)) #plt.show() #Accuracy Graph plt.rc('font', family = 'serif') fig = plt.figure(figsize = (13, 6)) plt.subplot(1, 2, 1) plt.plot(hist.history['acc'], label = 'Train Acc', color = 'red') plt.plot(hist.history['val_acc'], label = 'Test Acc', color = 'm') plt.title('Model Accuracy') plt.xlabel('Epochs') plt.ylabel('accuracy') plt.legend(bbox_to_anchor = (0.7, 0.5), loc = 'center left', borderaxespad = 0, fontsize = 8) #Loss Graph plt.rc('font', family = 'serif') plt.subplot(1, 2, 2) plt.plot(hist.history['loss'], label = 'Train Loss', color = 'blue') plt.plot(hist.history['val_loss'], label = 'Test Loss', color = 'c') plt.title('Model Loss') plt.xlabel('Epochs') plt.ylabel('loss') plt.legend(bbox_to_anchor = (0.7, 0.5), loc = 'center left', borderaxespad = 0, fontsize = 8) plt.savefig ('./cifar_model_result/{}/Accuracy_Graph.png'.format(date)) #plt.show() |
上記のプログラム1行目~18行目では、ランダムに選んだ画像10枚の推論前、推論後を出力します。
出力された結果
こんな感じで出力されます。
上の段が入力された画像で、下の段がAutoEncoderされた画像です。
22行目以下は損失関数のグラフと正解率のグラフを出力するプログラムです。
こんな感じで出力されます。
モデルや学習結果、エポック数、学習率などの情報をテキストファイルで保存
1 2 3 4 5 6 7 8 9 | dt_now, date = into_dir() with open("./cifar_model_result/{}/Summary.txt".format(date), "w") as fp: model.summary(print_fn = lambda x: fp.write(x + "\r\n")) write_date = str(dt_now) + "\n\n" + "Data_Name:CIFAR10" + "\n\n" + "x_train:{}".format(x_train.shape) + "\n\n" + "x_test:{}".format(x_test.shape) + "\n\n" + "y_train:{}".format(y_train.shape) + "\n\n" + "y_test:{}".format(y_test.shape) + "\n\n" + "Epochs:{}".format(epochs) + "epochs" + "\n\n" + "Learn Rate:{}".format(learn_rate) + "%" + "\n\n" + "Batch_Size:{}".format(batch_size) + "\n\n" + "Loss Value:"+ str(f'{val_loss:.10f}') + "\n\n" + "Acc Value:" + str(f'{val_acc:.4f}') + "%" + "\n\n" + "Processing Time:{:.2} minuites".format(processing_time) with open("./cifar_model_result/{}/Result.txt".format(date), "w") as f: f.write(write_date) |
ここでは先ほど作った学習時間ごとにフォルダーにモデルの情報と、パラメータなどの情報が記載されたテキストファイルが保存される様になっています。
こんな感じです。(アイコンが星になってるやつもありますが気にしないでください。)
まとめ
今回はCIFAR10を使ったAuto Encoderについて紹介しました。
昨今、AutoEncoder自体はそこまで使用するシーンは減ってきています。
私も、「Auto Eocderって何に使われてるの?」って聞かれたら少し悩んでしまいます。(笑)
しかし、これを応用すれば、今最先端の研究の一つである「超解像」ー例えば画素数の荒い画像を高画質な画像に変換したりーに利用できるのではないかと思います。(現在超解像度に関するサイトや文献は少ないので確かなことは言えませんが涙。)
現在私も超解像のプログラムを探求しています。
どこか情報交換できる場所とかないかな〜。あったら知りたいな〜。