TensorFlow

【TensorFlow/Keras】自己符号化器(AutoEncoder)の実装

【TensorFlowKeras】自己符号化器(AutoEncoder)の実装

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の手書き文字データに対して適用する例を考えてみたいと思います。

自己符号化器(AutoEncoder)

今回実装するイメージは上記のような形になります。隠れ層を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()
損失関数の推移
自己符号化器(AutoEncoder)実行結果

実装内容の解説

上記で紹介した実装例の各部分ごとに内容を解説していきます。

必要モジュールのインポート

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と書いている部分はバイアス部分です。

自己符号化器(AutoEncoder)モデル構成

上記のように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)実行結果

結果画像を再掲しますが、上段が元画像、中段が圧縮データ、下段が復元画像になります。復元画像については、おおむね元画像の画像を復元できていますが、若干ぼけた画像になってしまうのが、自己符号化器(AutoEncoder)の特徴になります。

以上が、TensorFlow/Kerasを用いた自己符号化器(AutoEncoder)の実装例でした。

まとめ

TensorFlow/Kerasを用いて自己符号化器(AutoEncoder)を構築し、MNISTの手書き文字データに適用する実装例について解説してきました。

自己符号化器(AutoEncoder)は、入力されたデータを圧縮して、その圧縮したデータから入力データを復元するようなニューラルネットワークです。

上記で紹介した実装例では、MNISTの手書き画像に対して自己符号化器(AutoEncoder)を構築し、圧縮表現の生成、圧縮表現から元画像の復元ができることを確認しています。

自己符号化器(AutoEncoder)は、生成モデルとして有名な変分自己符号化器(VAE:VariationalAutoEncoder)の基礎的な技術となりますので、VAEの理解につなげるためにも、一度自分で実装してみるとイメージがつかめるかと思います。

Pythonによるディープラーニング」はTensorFlow/Kerasの中~上級者向けの本ですが非常におすすめできる書籍です。CNN, RNN, Transformer, GAN等高度なモデルも扱っており面白く、TensorFlow/Kerasの実装力をつけることができますので是非読んでみてもらえるといいと思います。