Auto Encoder

Auto Encoderに関するチュートリアル

このチュートリアルでは, ReNomを用いたAuto encoderの構築方法を説明します. Auto encoderは特徴量抽出や深層学習における重みパラメータの事前学習に使用されます.

以下の2種類のAuto encoderについて紹介していきます.

  • Vanilla auto encoder
  • Denoising auto encoder

このチュートリアルでは以下に示す二種類のAuto encoderについて述べます. さらに, それぞれのauto encoderを事前学習に用いてクラス分類モデルの構築を行いますそしてクラス分類の結果から, それぞれのauto encoderの違いを確認します.

Auto encoderは教師なし学習手法にあたり, 特徴抽出を行うアルゴリズムです. Auto encoderは入出力層のユニットサイズが等しい構造をしており, 更にいくつかの隠れ層を持ちます. Auto encoderには入力データと等しい教師データを与えます.

入力データを再構成させることを学習させます.一般的に隠れ層のユニット数は入出力層よりも少なく設定します. これにより隠れ層で情報の圧縮が行われます.

学習済みauto encoderの重みパラメータを他のニューラルネットワークの初期重みとして使用することもでき, ランダムに設定した初期値よりも汎化性能や学習の速さで勝ることがあります.

他にも学習済みauto encoderにデータを入力し, 隠れ層の出力をサポートベクターマシンなどの機械学習アルゴリズムに入力する方法があります. Auto encoderを通して得られたデータを使用する場合, 元のデータを扱うよりもデータの次元数を削減出来たり, カテゴリーデータを扱えたりする利点があります.

Requirements

このチュートリアルでは以下のモジュールが必要となります.

  • Numpy 1.12.1
  • Matplotlib 2.0.2
  • Scikit-learn 0.18.2
In [1]:
import numpy as np
np.seterr(all="ignore")

import matplotlib.pyplot as plt
%matplotlib inline

from sklearn.datasets import fetch_mldata
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelBinarizer
from sklearn.metrics import classification_report

import renom as rm
from renom.optimizer import Adam
from renom.utility.initializer import Gaussian

データの読み込み

このチュートリアルではMNISTデータセットを使用します. MNISTデータセットは以下のwebサイトからダウンロード可能です. もしくは以下のようにscikit-learnの関数を使用してダウンロードすることが出来ます.

ダウンロード後はデータセットを学習用データとテスト用データに分割します.

In [2]:
# 'data_path' must point to the directory containing the data folder.
data_path = "../dataset"
mnist = fetch_mldata('MNIST original', data_home=data_path)

X = mnist.data
y = mnist.target

# Binarize ("one-hot") the image data.
X = X.astype(np.float32)
y = y.astype(np.float32)
X = np.array(X > 128, dtype=np.float32)

x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.1)

binarizer = LabelBinarizer()
label_train = binarizer.fit_transform(y_train)
label_test = binarizer.transform(y_test)

# Training data size.
N = len(x_train)

Vanilla Auto Encoder

このセクションでは, auto encoderモデルをReNomのSequentialモデルを用いて構築します. 上記で述べたとおり, auto encoderは入出力層に同じユニット数を持ちます.

今回はMNISTデータの次元数が784次元(=28x28)であることから, 入出力層のユニットサイズは784となります.

モデルの定義

モデルは2つのレイヤとrelu活性化関数からなります.

さらに勾配降下手法としてAdam[1]を使用します.

In [3]:
model_ae = rm.Sequential([
        rm.Dense(100),
        rm.Relu(),
        rm.Dense(784),
    ])
optimizer = Adam()

Train Loop

学習ループは以下のようになります. ここでは, 元データと再構成されたデータ間の誤差を交差エントロピーで測ります.

学習ループの後に, 元データと再構成されたデータを可視化するコードを追加しています. 再構成されたデータが元データと同じ形をしていることを確認出来ます.

In [4]:
batch = 64
epoch = 10

for i in range(epoch):
    for j in range(N//batch):
        train_batch=x_train[j*batch:(j+1)*batch]
        with model_ae.train():
            z = model_ae(train_batch)
            loss = rm.sigmoid_cross_entropy(z, train_batch)
        loss.grad().update(optimizer)
    if i%2 == 0:print("epoch %02d train_loss:%f"%(i, loss))

# Show raw img and reconstructed img.
test_img = x_test[0]
fig, ax = plt.subplots(ncols=2, nrows=1, figsize=(8, 6))
ax[0].set_title("Raw image")
ax[0].imshow(test_img.reshape(28, 28), cmap="gray")
ax[1].set_title("Reconstructed image")
ax[1].imshow(rm.sigmoid(model_ae(test_img)).reshape(28, 28), cmap="gray")
plt.show()
epoch 00 train_loss:51.009064
epoch 02 train_loss:21.207394
epoch 04 train_loss:14.829244
epoch 06 train_loss:12.308509
epoch 08 train_loss:11.139716
../../../_images/notebooks_basic_algorithm_autoencoder_notebook_9_1.png

Denoising auto encoder

Denoising auto encoder はほとんど上記で説明したauto encoderと変わりませんが, 入力データにノイズを加えて学習を行う点が異なります.

加えるノイズの分布としてはガウスノイズやごま塩ノイズなどのノイズ分布を使用します.

今回はごま塩ノイズを使用します.具体的には, 入力データの書くピクセルを50%の確率で0にするようノイズを加えます.

In [5]:
test_img = x_test[0].reshape(28, 28)
sp_noise = np.array(np.random.rand(*test_img.shape) > 0.5, dtype=np.bool)
fig, ax = plt.subplots(ncols=2, nrows=1, figsize=(8, 6))
ax[0].set_title("Raw image")
ax[0].imshow(test_img, cmap="gray")
ax[1].set_title("Added salt and pepper noise")
ax[1].imshow(test_img*sp_noise, cmap="gray")
plt.show()
../../../_images/notebooks_basic_algorithm_autoencoder_notebook_11_0.png

モデルの定義

Denoising auto encoder モデルはauto encoderモデルと同様に以下のように定義します.

In [6]:
model_denoise_ae = rm.Sequential([
        rm.Dense(100),
        rm.Relu(),
        rm.Dense(784),
    ])
optimizer = Adam()

Train Data

学習ループもauto encoderとほとんど変わりませんが, 8, 9行目でノイズを加えています.

In [7]:
batch = 64
epoch = 10

for i in range(epoch):
    for j in range(N//batch):
        train_batch=x_train[j*batch:(j+1)*batch]
        with model_denoise_ae.train():
            sp_noise = np.array(np.random.rand(*train_batch.shape) > 0.5, dtype=np.bool)
            z = model_denoise_ae(train_batch*sp_noise)
            loss = rm.sigmoid_cross_entropy(z, train_batch)
        loss.grad().update(optimizer)
    if i%2 == 0:print("epoch %02d train_loss:%f"%(i, loss))

# Show raw img and reconstructed img.
test_img = x_test[0]
fig, ax = plt.subplots(ncols=2, nrows=1, figsize=(8, 6))
ax[0].set_title("Raw image")
ax[0].imshow(test_img.reshape(28, 28), cmap="gray")
ax[1].set_title("Reconstructed image")
ax[1].imshow(rm.sigmoid(model_denoise_ae(test_img)).reshape(28, 28), cmap="gray")
plt.show()
epoch 00 train_loss:91.471199
epoch 02 train_loss:73.140144
epoch 04 train_loss:72.041679
epoch 06 train_loss:71.260941
epoch 08 train_loss:69.084564
../../../_images/notebooks_basic_algorithm_autoencoder_notebook_15_1.png

2つの事前学習方法を比較

このセクションでは上記で説明下した2つのauto encoderを事前学習器として使用し, 数字の識別を行うクラス分類モデルを構築します.

ここでも入力データに対しごま塩ノイズを加えます.入力データにノイズが乗っていると一般的にクラス識別率は低下しますが, denoising auto encoderによる事前学習で得た初期重みパラメータを用いて学習と行った場合, 識別精度を改善できることを示します.

まず, 以下のコードのように識別モデルを作成します. これら2つのモデルは同じハイパーパラメータを持ちますが, それぞれ異なる事前学習方法で得られた重みパラメータをセットします.

In [8]:
pretrained_ae = rm.Sequential([
        rm.Dense(100),
        rm.Relu(),
        rm.Dense(10),
    ])

pretrained_dae = rm.Sequential([
        rm.Dense(100),
        rm.Relu(),
        rm.Dense(10),
    ])

# Copy first weight parameters of first layer.
pretrained_ae[0].params = model_ae[0].params
pretrained_dae[0].params = model_denoise_ae[0].params

opt1 = Adam()
opt2 = Adam()

Train loop

分類モデルの学習を行います.

In [9]:
batch = 64
epoch = 40

train_loss1 = []
train_loss2 = []
validation_loss1 = []
validation_loss2 = []

for i in range(epoch):
    for j in range(N//batch):
        train_batch = x_train[j*batch:(j+1)*batch]
        response_batch = label_train[j*batch:(j+1)*batch].astype(np.float32)
        sp_noise = np.array(np.random.rand(*train_batch.shape) > 0.5)
        train_batch = train_batch*sp_noise

        with pretrained_ae.train():
            z = pretrained_ae(train_batch)
            loss1 = rm.softmax_cross_entropy(z, response_batch)

        with pretrained_dae.train():
            z = pretrained_dae(train_batch)
            loss2 = rm.softmax_cross_entropy(z, response_batch)

        loss1.grad().update(opt1)
        loss2.grad().update(opt2)

    validation1 = rm.softmax_cross_entropy(pretrained_ae(x_test), label_test)
    validation2 = rm.softmax_cross_entropy(pretrained_dae(x_test), label_test)

    train_loss1.append(loss1)
    train_loss2.append(loss2)
    validation_loss1.append(validation1)
    validation_loss2.append(validation2)

    strs = "epoch:%02d AE_loss:%f AE_validation:%f DAE_loss:%f DAE_validation:%f"
    if i%2 == 0:print(strs%(i, loss1, validation1, loss2, validation2))

epoch:00 AE_loss:0.561384 AE_validation:0.665277 DAE_loss:0.377859 DAE_validation:0.400579
epoch:02 AE_loss:0.330235 AE_validation:0.532445 DAE_loss:0.231801 DAE_validation:0.270267
epoch:04 AE_loss:0.377993 AE_validation:0.477424 DAE_loss:0.220577 DAE_validation:0.202235
epoch:06 AE_loss:0.284053 AE_validation:0.381976 DAE_loss:0.096848 DAE_validation:0.167779
epoch:08 AE_loss:0.256713 AE_validation:0.295453 DAE_loss:0.161372 DAE_validation:0.152546
epoch:10 AE_loss:0.186659 AE_validation:0.248264 DAE_loss:0.088071 DAE_validation:0.142935
epoch:12 AE_loss:0.280057 AE_validation:0.224904 DAE_loss:0.119961 DAE_validation:0.137183
epoch:14 AE_loss:0.178850 AE_validation:0.207520 DAE_loss:0.071178 DAE_validation:0.134815
epoch:16 AE_loss:0.141422 AE_validation:0.180905 DAE_loss:0.076573 DAE_validation:0.125874
epoch:18 AE_loss:0.116089 AE_validation:0.172424 DAE_loss:0.123805 DAE_validation:0.120988
epoch:20 AE_loss:0.109084 AE_validation:0.155472 DAE_loss:0.034230 DAE_validation:0.121413
epoch:22 AE_loss:0.082346 AE_validation:0.151591 DAE_loss:0.132201 DAE_validation:0.118642
epoch:24 AE_loss:0.178457 AE_validation:0.146817 DAE_loss:0.138417 DAE_validation:0.122302
epoch:26 AE_loss:0.050109 AE_validation:0.145061 DAE_loss:0.026600 DAE_validation:0.115971
epoch:28 AE_loss:0.118149 AE_validation:0.145276 DAE_loss:0.029089 DAE_validation:0.123674
epoch:30 AE_loss:0.190159 AE_validation:0.132439 DAE_loss:0.167682 DAE_validation:0.114807
epoch:32 AE_loss:0.064115 AE_validation:0.137775 DAE_loss:0.043263 DAE_validation:0.120619
epoch:34 AE_loss:0.089808 AE_validation:0.134122 DAE_loss:0.035184 DAE_validation:0.121039
epoch:36 AE_loss:0.083881 AE_validation:0.129205 DAE_loss:0.051456 DAE_validation:0.113429
epoch:38 AE_loss:0.042798 AE_validation:0.131029 DAE_loss:0.043206 DAE_validation:0.112431

学習曲線

2つのモデルの学習曲線を比べると, denoising auto encoderを使用して事前学習を行ったモデルでは, 一環してvalidation誤差が低くなっています. このように, 初期重みパラメータの違いにより汎化性能に差がでることを確認することが出来ました.

In [10]:
plt.figure(figsize=(8, 5))
plt.grid()
plt.plot(train_loss1, label="AE_train_loss", linestyle="--", linewidth=3)
plt.plot(validation_loss1, label="AE_validation_loss", linewidth=3)
plt.plot(train_loss2, label="DAE_train_loss", linestyle="--", linewidth=3)
plt.plot(validation_loss2, label="DAE_validation_loss", linewidth=3)
plt.ylabel("loss")
plt.xlabel("epoch")
plt.legend()
plt.show()
../../../_images/notebooks_basic_algorithm_autoencoder_notebook_21_0.png
In [11]:
prediction1 = np.argmax(pretrained_ae(x_test).as_ndarray(), axis = 1)
prediction2 = np.argmax(pretrained_dae(x_test).as_ndarray(), axis = 1)

print("///////////// AE pretrained model //////////////")
print(classification_report(np.argmax(label_test, axis = 1), prediction1))

print("///////////// DAE pretrained model //////////////")
print(classification_report(np.argmax(label_test, axis = 1), prediction2))
///////////// AE pretrained model //////////////
             precision    recall  f1-score   support

          0       0.97      0.99      0.98       685
          1       0.98      0.99      0.99       809
          2       0.97      0.96      0.97       723
          3       0.94      0.98      0.96       695
          4       0.99      0.97      0.98       660
          5       0.99      0.93      0.96       646
          6       0.97      0.99      0.98       688
          7       0.98      0.98      0.98       756
          8       0.97      0.97      0.97       668
          9       0.96      0.95      0.96       670

avg / total       0.97      0.97      0.97      7000

///////////// DAE pretrained model //////////////
             precision    recall  f1-score   support

          0       0.98      0.99      0.99       685
          1       0.98      0.99      0.99       809
          2       0.97      0.98      0.97       723
          3       0.95      0.97      0.96       695
          4       0.99      0.97      0.98       660
          5       0.99      0.93      0.96       646
          6       0.97      0.99      0.98       688
          7       0.98      0.97      0.98       756
          8       0.96      0.97      0.96       668
          9       0.97      0.97      0.97       670

avg / total       0.97      0.97      0.97      7000

References

[1] Diederik P. Kingma, Jimmy Ba. Adam: A Method for Stochastic Optimization. 3rd International Conference for Learning Representations, San Diego, 2015.
[2] Pascal Vincent, Hugo Larochelle, Yoshua Bengio and Pierre-Antoine Manzagol. Extracting and Composing Robust Features with Denoising Autoencoders. Proc. of ICML, 2008.