TensorFlow/Kerasを用いて自己符号化器(AutoEncoder)を構築し、MNISTの手書き文字データに適用する実装例について解説します。
自己符号化器(AutoEncoder)
自己符号化器(AutoEncoder)とは、入力されたデータを圧縮し、その圧縮したデータから入力データを復元するようなニューラルネットワークの事を言います。入力データ自身を生成するので自己符号化と言われるわけです。
生成モデルとして有名な変分自己符号化器(VAE:VariationalAutoEncoder)がありますが、自己符号化器(AutoEncoder)はその基礎となるネットワークになっています。
自己符号化器(AutoEncoder)のネットワーク構造を示すと以下の用になります。
入力層の$x$が隠れ層$z$に入っていますが、ここでポイントなのは隠れ層$z$の次元数は入力よりも少ない次元であるということです。出力層$\hat{x}$は入力と同じ次元なので、一度少ない次元の特徴表現$z$を得た後に、入力と同じ出力を復元していることになります。$z$は入力$x$を圧縮した情報に相当するため、自己符号化器(AutoEncoder)はデータの次元削減やノイズ除去などに使用されます。
図における前半部分は入力データをエンコードしている部分であるので「Encoder」、後半部分は圧縮されたデータをデコードしている部分なので「Decoder」と言います。
Encoder、Decoder部分の数式としては以下のようになっており、ニューラルネットワークにより$\boldsymbol{W}$, $\boldsymbol{b}$, $\boldsymbol{\hat{W}}$, $\boldsymbol{\hat{b}}$のパラメータを学習します。
Encoder
\[
z = f(\boldsymbol{W} x + \boldsymbol{b})
\]
Decoder
\[
\hat{x} = g(\boldsymbol{\hat{W}} z + \boldsymbol{\hat{b}})
\]
本記事では、TensorFlow/Kerasを使用した自己符号化器(AutoEncoder)をMNISTの手書き文字データに適用する実装例について紹介したいと思います。
TensorFlow/Kerasでの自己符号化器(AutoEncoder)の実装
自己符号化器(AutoEncoder)を使用したTensorFlow/Kerasでの実装例を紹介します。今回は、MNISTの手書き文字データに対して適用する例を考えてみたいと思います。
今回実装するイメージは上記のような形になります。隠れ層を32として圧縮し、圧縮したデータから入力画像と同じ画像を復元してみます。
実装例
自己符号化器(AutoEncoder)のTensorFlow/Kerasを用いた実装例を以下に示します。
import matplotlib.pyplot as plt from tensorflow import keras from tensorflow.keras import layers from tensorflow.keras.datasets import mnist def main(): """メイン関数""" # ===== MNIST(エムニスト)データの読込 (train_imgs, _), (test_imgs, _) = mnist.load_data() train_imgs = train_imgs.astype("float32") / 255 test_imgs = test_imgs.astype("float32") / 255 train_imgs = train_imgs.reshape((60000, 784)) test_imgs = test_imgs.reshape((10000, 784)) # 訓練データの一部(20%)を評価データとして使う idx = int(train_imgs.shape[0] * 0.2) train_imgs, val_imgs = train_imgs[idx:], train_imgs[:idx] # ===== AutoEncoderの構築 # 隠れ層の次元 encoding_dim = 32 # 入力データ inputs = keras.Input(shape=(784,)) # エンコード部 encoded = layers.Dense(encoding_dim, activation="relu")(inputs) # デコード部 decoded = layers.Dense(784, activation="sigmoid")(encoded) # ===== モデルの構築 # AutoEncoderモデルの作成 autoencoder = keras.Model(inputs=inputs, outputs=decoded, name="AutoEncoder") print(autoencoder.summary()) keras.utils.plot_model(autoencoder, "autoencoder.png", show_shapes=True) # Encoderモデル encoder = keras.Model(inputs=inputs, outputs=encoded, name="Encoder") # print(encoder.summary()) # keras.utils.plot_model(encoder, "encoder.png", show_shapes=True) # Decoderモデル encoded_input = keras.Input(shape=(encoding_dim,)) decoder_layer = autoencoder.layers[-1](encoded_input) decoder = keras.Model(inputs=encoded_input, outputs=decoder_layer, name="Decoder") # print(decoder.summary()) # keras.utils.plot_model(decoder, "decoder.png", show_shapes=True) # ===== オプティマイザ、損失関数、指標を設定してコンパイル autoencoder.compile(optimizer="adam", loss="binary_crossentropy") # ===== fitを使ったモデルの訓練 num_epochs = 20 history = autoencoder.fit( train_imgs, train_imgs, epochs=num_epochs, batch_size=128, validation_data=(val_imgs, val_imgs), ) # ===== history情報の可視化 # 損失関数(loss)の履歴 loss = history.history["loss"] val_loss = history.history["val_loss"] # 損失関数の履歴描画 x_epoch = range(1, num_epochs + 1) plt.plot(x_epoch, loss, "r", label="training loss") plt.plot(x_epoch, val_loss, "b", label="validation loss") plt.xlabel("Epochs") plt.ylabel("Loss") plt.legend() plt.show() # ====== テスト画像を使ってエンコード/デコードの実行 # エンコード encoded_imgs = encoder.predict(test_imgs) # デコード decoded_imgs = decoder.predict(encoded_imgs) # ===== テスト画像のエンコード、デコード結果を表示 disp_n = 5 plt.figure(figsize=(10, 5)) for i in range(disp_n): # 元画像の表示 ax = plt.subplot(3, disp_n, i + 1) plt.imshow(test_imgs[i].reshape(28, 28)) plt.gray() ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) # エンコード値の表示 ax = plt.subplot(3, disp_n, i + 1 + disp_n) plt.imshow(encoded_imgs[i].reshape(1, encoding_dim)) plt.gray() ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) # 復元画像の表示 ax = plt.subplot(3, disp_n, i + 1 + 2 * disp_n) plt.imshow(decoded_imgs[i].reshape(28, 28)) plt.gray() ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) plt.show() if __name__ == "__main__": main()
実装内容の解説
上記で紹介した実装例の各部分ごとに内容を解説していきます。
必要モジュールのインポート
import matplotlib.pyplot as plt from tensorflow import keras from tensorflow.keras import layers from tensorflow.keras.datasets import mnist
今回TensorFlowのKerasを使用するので、kerasとlayersをインポートしておきます。また、データセットとしてはMNISTデータを使用します。matplotlibについては結果の描画用です。
データセットの準備
# ===== MNIST(エムニスト)データの読込 (train_imgs, _), (test_imgs, _) = mnist.load_data() train_imgs = train_imgs.astype("float32") / 255 test_imgs = test_imgs.astype("float32") / 255 train_imgs = train_imgs.reshape((60000, 784)) test_imgs = test_imgs.reshape((10000, 784)) # 訓練データの一部(20%)を評価データとして使う idx = int(train_imgs.shape[0] * 0.2) train_imgs, val_imgs = train_imgs[idx:], train_imgs[:idx]
上記の部分では、データセットのMNIST(エムニスト)データを読み込んでいます。データをfloat32に変換して、値が0~1になるように正規化しています。MNISTは28×28の画像ですが、今回は単純なニューラルネットワークを使うので784にreshapeしています。
また、訓練データの一部を評価データとして分割して、「訓練データ」「評価データ」「テストデータ」に分けています。
自己符号化器(AutoEncoder)モデル構築
さて、本題の自己符号化器(AutoEncoder)の構築部分です。順にみていきましょう。
# ===== AutoEncoderの構築 # 隠れ層の次元 encoding_dim = 32 # 入力データ inputs = keras.Input(shape=(784,)) # エンコード部 encoded = layers.Dense(encoding_dim, activation="relu")(inputs) # デコード部 decoded = layers.Dense(784, activation="sigmoid")(encoded)
各層の定義を行っています。入力は784の入力で、その後エンコード部としてencoding_dim(今回は32)の隠れ層に入ります。エンコード部ではReLUを活性化関数に使用しています。デコード部は、また784に戻して、0~1になるように活性化関数にsigmoidを使用しています。
# ===== モデルの構築 # AutoEncoderモデルの作成 autoencoder = keras.Model(inputs=inputs, outputs=decoded, name="AutoEncoder") print(autoencoder.summary()) keras.utils.plot_model(autoencoder, "autoencoder.png", show_shapes=True)
作成した層を使ってAutoEncoderを構築している部分です。inputに先ほど定義した入力層、outputsに出力層を設定します。訓練自体は、このAutoEncoderを使って実施し、パラメータを学習します。
層の状態が分かりやすいようにsummary()とplot_modelで表示しています。具体的には以下のようになります。
Model: "AutoEncoder" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) [(None, 784)] 0 dense (Dense) (None, 32) 25120 dense_1 (Dense) (None, 784) 25872 ================================================================= Total params: 50,992 Trainable params: 50,992 Non-trainable params: 0 _________________________________________________________________ None
Encoderに該当する部分のパラメータ数としては(784+1)×32=25,120で、これが数式でいうところの$\boldsymbol{W}$, $\boldsymbol{b}$に該当します。また、Decoderに該当する部分のパラメータ数としては(32+1)×784=25,872で、数式でいうところの$\boldsymbol{\hat{W}}$, $\boldsymbol{\hat{b}}$に該当します。+1と書いている部分はバイアス部分です。
上記のようにplot_modelにしてみると入出力が分かりやすいので便利です。
Encoder部のモデル構築
# Encoderモデル encoder = keras.Model(inputs=inputs, outputs=encoded, name="Encoder") # print(encoder.summary()) # keras.utils.plot_model(encoder, "encoder.png", show_shapes=True)
訓練後にEncoder部のみを使った圧縮が使えるようにEncoder部を構築しておきます。AutoEncoderと同じinputsとencodedを使用しています。このようにすることで、訓練後に訓練したパラメータを使ってEncoder部分の実行ができます。
Decoder部のモデル構築
# Decoderモデル encoded_input = keras.Input(shape=(encoding_dim,)) decoder_layer = autoencoder.layers[-1](encoded_input) decoder = keras.Model(inputs=encoded_input, outputs=decoder_layer, name="Decoder") # print(decoder.summary()) # keras.utils.plot_model(decoder, "decoder.png", show_shapes=True)
同じくDecoder部も構築します。Decoder部はencoding_dim(今回は32)の入力となるのでencoded_inputというものを作っています。それをautoencoderの層(layers)の-1番目につなげています。-1番目はAutoEncoderの最後の層になります。このようにすることで訓練後に訓練したパラメータを使ってDecoder部分の実行ができます。
自己符号化器(AutoEncoder)の訓練の実行
# ===== オプティマイザ、損失関数、指標を設定してコンパイル autoencoder.compile(optimizer="adam", loss="binary_crossentropy") # ===== fitを使ったモデルの訓練 num_epochs = 20 history = autoencoder.fit( train_imgs, train_imgs, epochs=num_epochs, batch_size=128, validation_data=(val_imgs, val_imgs), ) # ===== history情報の可視化 # 損失関数(loss)の履歴 loss = history.history["loss"] val_loss = history.history["val_loss"] # 損失関数の履歴描画 x_epoch = range(1, num_epochs + 1) plt.plot(x_epoch, loss, "r", label="training loss") plt.plot(x_epoch, val_loss, "b", label="validation loss") plt.xlabel("Epochs") plt.ylabel("Loss") plt.legend() plt.show()
実際に訓練を実行している部分になります。オプティマイザとしては’adam’、損失関数としては’binary_crossentoropy’を設定しています。
fitでモデルの訓練をしていますが、入力も正解もtrain_imgsになっていることがポイントです。同じく評価データとしてもval_imgsを入力と正解として共に設定しています。これにより、入力画像と出力画像を近づけるように重みの学習が実行されます。
損失関数の情報の可視化も行っていますが、大体20エポックぐらいで0.09あたりに収束していきます。
テストデータを使ってエンコード/デコードを実行
# ====== テスト画像を使ってエンコード/デコードの実行 # エンコード encoded_imgs = encoder.predict(test_imgs) # デコード decoded_imgs = decoder.predict(encoded_imgs)
訓練後にテストデータに対してエンコード/デコードを実行している部分です。それぞれ、predictメソッドを使うことで順伝播を実行しています。テスト画像をencoderに入力した結果のencoded_imgsが圧縮された特徴表現になります。さらにこの圧縮したencoded_imgsをdecoderに入れた結果のdecoded_imgsが復元画像になります。
エンコード/デコード結果の表示
# ===== テスト画像のエンコード、デコード結果を表示 disp_n = 5 plt.figure(figsize=(10, 5)) for i in range(disp_n): # 元画像の表示 ax = plt.subplot(3, disp_n, i + 1) plt.imshow(test_imgs[i].reshape(28, 28)) plt.gray() ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) # エンコード値の表示 ax = plt.subplot(3, disp_n, i + 1 + disp_n) plt.imshow(encoded_imgs[i].reshape(1, encoding_dim)) plt.gray() ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) # 復元画像の表示 ax = plt.subplot(3, disp_n, i + 1 + 2 * disp_n) plt.imshow(decoded_imgs[i].reshape(28, 28)) plt.gray() ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) plt.show()
この部分では、エンコード/デコードした結果を表示しています。
結果画像を再掲しますが、上段が元画像、中段が圧縮データ、下段が復元画像になります。復元画像については、おおむね元画像の画像を復元できていますが、若干ぼけた画像になってしまうのが、自己符号化器(AutoEncoder)の特徴になります。
以上が、TensorFlow/Kerasを用いた自己符号化器(AutoEncoder)の実装例でした。
まとめ
TensorFlow/Kerasを用いて自己符号化器(AutoEncoder)を構築し、MNISTの手書き文字データに適用する実装例について解説してきました。
自己符号化器(AutoEncoder)は、入力されたデータを圧縮して、その圧縮したデータから入力データを復元するようなニューラルネットワークです。
上記で紹介した実装例では、MNISTの手書き画像に対して自己符号化器(AutoEncoder)を構築し、圧縮表現の生成、圧縮表現から元画像の復元ができることを確認しています。
自己符号化器(AutoEncoder)は、生成モデルとして有名な変分自己符号化器(VAE:VariationalAutoEncoder)の基礎的な技術となりますので、VAEの理解につなげるためにも、一度自分で実装してみるとイメージがつかめるかと思います。
上記で紹介しているソースコードについてはgithubにて公開しています。参考にしていただければと思います。