スギノウエメモ

機械学習とか雑記とか

Keras で AutoEncoder 実行するまでのメモ

python を触り始めて半年、色々学んだ事を記録しておく。
環境はGoogle Collaborators 使用。

実装してみる

データ概要

100点のxから生成したa*sin(x) を作る。
a は平均0,標準偏差3の正規分布とする。
試しに5つ描いてみる。

import numpy as np
from matplotlib import pyplot as plt
import random

fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)
n = 100
x = np.linspace(0, 2*np.pi, n)
for i in range(5):
    a = np.random.normal(0, 3)
    y = a * np.sin(x)
    ax1.plot(y)

f:id:sugino_ue:20191110000858p:plain
こんな感じのデータ

学習データ、テストデータ作成

学習データ : 平均0,標準偏差3の正規分布 * sin(x) を200データ
テストデータ: -20~20 の一様分布 * sin(x)を200データ
上記データを作成

train_list = []
test_list = []
for i in range(200):
    a = np.random.normal(0, 3)
    b = random.uniform(-20, 20)
    y_train = a * np.sin(x)
    y_test = b * np.sin(x)
    train_list.append(y_train)
    test_list.append(y_test)
x_train = np.vstack(train_list)
x_test = np.vstack(test_list)
print("x_train.shape=", x_train.shape)
print("x_test.shape=", x_test.shape)

x_train.shape= (200, 100)
x_test.shape= (200, 100)

for文の中で、list を append、最後に結合というのよくやる。

正規化

学習データ、テストデータ共に、最大値、最小値が-1~1の範囲を超えている。
この後、非線形関数の活性化関数を使って学習するが、活性化関数では最大、最小で表現できる値が決まっているので、正規化するのが一般的。
今回はtanhを使うので、-1〜1 の範囲に値が収まるようにMinMaxScalerで正規化する。
活性化関数の例:
ReLU:最小値=0, 最大値は幾つでも。
tanh:最小値=-1, 最大値=1

from sklearn.preprocessing import MinMaxScaler

fig = plt.figure(figsize=(10, 10))
ax1 = fig.add_subplot(2, 1, 1)
ax2 = fig.add_subplot(2, 1, 2)
for i in range(len(x_train)):
    ax1.plot(x_train[i], color="blue")
    ax1.plot(x_test[i], color="red")
scaler = MinMaxScaler(feature_range=(-1, 1))
all_val = np.hstack((x_train, x_test)).reshape(-1, 1)
scaler.fit(all_val)
x_train = scaler.transform(x_train)
x_test = scaler.transform(x_test)
for i in range(len(x_train)):
    ax2.plot(x_train[i], color="blue")
    ax2.plot(x_test[i], color="red")  

学習データ、テストデータ合わせた全部で、-1~1にしたいので、all_valで結合し、fitの型に整形している。
今回、テストデータがある想定なので全部対象にできたが、テストデータが手元にない場合は、正規化の方法をもっと工夫する必要がありそう。
f:id:sugino_ue:20191110131331p:plain
青が学習データ、赤がテストデータ。
上が正規化する前、下が正規化した後。
値の縮尺のみが変わっている。

AutoEncoderで学習

AutoEncoderのモデルを定義。
100 -> 50 -> 100 で次元を50にした後復元している。
損失関数はMSE(最小二乗誤差)。
EarlyStoppingを指定する事で、学習を良い所で止めている。

from keras.layers import Input, Dense
from keras.models import Model
from keras.callbacks import EarlyStopping

input_sin = Input(name="input_sin", shape=(100,))
encoder = Dense(50, name="encoder1", activation='tanh')(input_sin)
decoder = Dense(100, name="decoder1", activation='tanh')(encoder)
autoenc = Model(inputs=input_sin, outputs=decoder)
autoenc.summary()
autoenc.compile(loss='mean_squared_error', optimizer='adam')

early_stop = EarlyStopping(monitor='val_loss', patience=5,
                           verbose=1, mode='auto')
history = autoenc.fit(x_train, x_train,
                      epochs=1000,
                      batch_size=100,
                      shuffle=True,
                      validation_split=0.1,
                      verbose=0,
                      callbacks=[early_stop]
                      )
loss = history.history['loss']
val_loss = history.history['val_loss']
nb_epoch = len(loss)

fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)
ax1.plot(range(nb_epoch), loss, label='loss')
ax1.plot(range(nb_epoch), val_loss, label='val_loss')
ax1.set_xlabel('epoch')
ax1.set_ylabel('loss')
ax1.legend()

________________________________________________________
Layer (type) Output Shape Param #
=======================================
input_sin (InputLayer) (None, 100) 0
________________________________________________________
encoder1 (Dense) (None, 50) 5050
________________________________________________________
decoder1 (Dense) (None, 100) 5100
=======================================
Total params: 10,150
Trainable params: 10,150
Non-trainable params: 0
________________________________________________________
Epoch 00079: early stopping
f:id:sugino_ue:20191110131853p:plain
79 epoch で学習終了している。
以前ハマったのが、

  • Dense の nameは、表示だけのもので、引数指定には使えない。
  • Input の shape には、データのセット数は入らない。

 x_train.shape= (200, 100)なので、100個のsin(x)データが200セット。

  • trainとtestは、データのセット数以外は同じshapeでないとエラー。

 上記モデルは、trainだけでなく、test時も同じように使うので。

テストデータで評価

テストデータを入れ、結果を見てみる。
評価としては、損失関数と同じ、MSEの値で、INPUTしたデータがどれだけOUTPUTしたデータと差があるかを確認する。

pre = autoenc.predict(x_test)
pre = scaler.inverse_transform(pre)
x_test = scaler.inverse_transform(x_test)

基本

  • model定義 -> fit()で学習 -> 学習曲線で収束しているか確認 -> predict()でテストデータを入れる

の流れは、AutoEncoderだけでなく、他DNNのモデルや scikit-learnの機械学習なども同様。

わかりやすいように、inverse_transformで正規化を元に戻してから
以下、predictした結果をグラフ化して確認。

from sklearn.metrics import mean_squared_error

fig = plt.figure(figsize=(10, 20))
ax1 = fig.add_subplot(4, 1, 1)
ax2 = fig.add_subplot(4, 1, 2)
ax3 = fig.add_subplot(4, 1, 3)
ax4 = fig.add_subplot(4, 1, 4)
mse_red = []
mse_blue = []
for i in range(len(pre)):
    mse_val = mean_squared_error(x_test[i], pre[i])
    if np.max(x_test[i]) > 9:
        ax1.plot(x_test[i], color="red")
        ax2.plot(pre[i], color="red")
        ax3.scatter(i, mse_val, color="red")
        mse_red.append(mse_val)
    else:
        ax1.plot(x_test[i], color="blue")
        ax2.plot(pre[i], color="blue")
        ax3.scatter(i, mse_val, color="blue")
        mse_blue.append(mse_val)
ax1.set_xlabel('x')
ax1.set_ylabel('sin(x)')
ax2.set_xlabel('x')
ax2.set_ylabel('after autoenc sin(x)')
ax3.set_xlabel('file')
ax3.set_ylabel('mse value')
# ax3.set_ylim(0, 0.5)
ax4.hist([mse_blue, mse_red], bins=50, color=["blue", "red"], stacked=True)
ax4.set_xlabel('mse value')
ax4.set_ylabel('file count')

f:id:sugino_ue:20191110141124p:plain
標準偏差3の範囲のx_trainを学習させたので、3\sigmaにあたる±9の範囲を青で、それ以外の範囲を赤で示している。
x_testは一様分布なのだが、3つ目、4つ目のグラフをみる通り、MSEの値は青の範囲はほとんど0となり、期待通り。
sin波が、学習したデータから外れるほどMSEの値が大きくなっている。