オートエンコーダーを用いた事前学習
はじめに
通常、多層パーセプトロンの層を深くしていけば、より複雑なパターンを認識することが期待される。しかし、層の数を増やしていくほど関数の最適化は困難になり、初期条件の選択によって学習効率が左右される。オートエンコーダーは重み関数についてのより良い初期条件を得るために提案された教師なし事前学習法である。今回はよりロバストな学習モデルを作るためにStacked denoising Autoencoderを使って事前学習を行い、層数を増やしても学習効率が下がらないことを示す。
理論
1) Denoising Autoencoder (dA)
オートエンコーダーの模式図を下に示す。
オートエンコーダーは与えられた入力を隠れ層に渡し(encode)、再び入力を再現する(decode)ように重み関数を最適化するネットワークである。encodeおよびdecodeは次式で表される。
ここで、は入力を一部損傷させたデータ、は損傷された入力から隠れ層を経て復元されたデータである(denoising)。また、今回は[tex: \tilde{W} = WT]として議論を進める。誤差関数として交差エントロピー誤差関数を用いると
で与えられる。したがって、に対しての誤差関数の傾きは
で与えられる(導出は下図の計算グラフを参照)。ここで、はベクトルの要素ごとの掛け算を表している。上式の傾きをもとには更新される。
2) Stacked denoising Autoencoder (SdA)
SdAはdAを積み重ねて学習する方法で、多層パーセプトロンのための事前学習である。SdAの模式図を下に示す。左のdA層から1層ずつ学習を進めていく。1層目で事前学習を行い、得られた結果(encodeの結果)は2層目が入力値として受け取り、事前学習を行う。以下、3層目、4層目へと事前学習が行われる。各層で事前学習が完了したら、得られた重み関数を初期値として通常の教師あり学習が行われる。
実装
- モジュール、およびデータ(MNIST)の読み込み
%matplotlib inline import numpy as np import matplotlib.pyplot as plt from sklearn.utils import shuffle from sklearn.model_selection import train_test_split from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets('MNIST_data/', one_hot=True) mnist_X, mnist_y = mnist.train.images, mnist.train.labels train_X, test_X, train_y, test_y = train_test_split(mnist_X, mnist_y, test_size=0.1, random_state=random_state)
- denoising Autoencoderの実装、活性化関数にはシグモイド関数を使用
class Autoencoder_sig: def __init__(self, vis_dim, W, b): self.W = W self.b = b self.a = np.zeros(vis_dim).astype('float64') self.params = [self.W, self.b] def encode(self, x): u = np.dot(x, self.W) + self.b return sigmoid(u) def decode(self, x): u = np.dot(x, self.W.T) + self.a return sigmoid(u) def reconst_error(self, t, y): return -np.sum(t * np.log(y + 1.e-6) + (1.0 - t) * np.log(1.0 - y + 1.e-6), axis=1) def train(self, x, noise, eta=np.float32(0.01)): # forward prop tilde_x = x * noise z = self.encode(tilde_x) reconst_x = self.decode(z) error = self.reconst_error(x, reconst_x) # backward prop delta_2 = reconst_x - x delta_1 = np.dot(delta_2, self.W) * z * (1.0 - z) da = np.dot(np.ones(len(reconst_x)), delta_2) db = np.dot(np.ones(len(z)), delta_1) dW = np.dot(tilde_x.T, delta_1) + np.dot(z.T, delta_2).T self.W -= eta * dW self.a -= eta * da self.b -= eta * db return error, reconst_x
- Stacked Autoencoderの実装
class Dense: def __init__(self, in_dim, out_dim): self.W = rng.uniform(low=-0.08, high=0.08, size=(in_dim, out_dim)).astype('float64') self.b = np.zeros([out_dim]).astype('float64') self.params = [self.W, self.b] self.AE = Autoencoder_sig(in_dim, self.W, self.b) def fwd_prop(self, x): u = np.dot(x, self.W) + self.b self.z = sigmoid(u) return self.z def pretrain(self, x, noise): error, reconst_x = self.AE.train(x, noise) return error, reconst_x
- モデルの構築
layers = [ Dense(784, 300), Dense(300, 300), Dense(300, 300), Dense(300, 300) ]
- 事前学習
X = np.copy(train_X) for layer in layers: n_epochs = 10 batch_size = 100 n_batches = X.shape[0] // batch_size corruption_level = np.float64(0.3) for epoch in range(n_epochs): X = shuffle(X) err_all = [] for i in range(n_batches): start = i * batch_size end = start + batch_size _noise = rng.binomial(size=X[start:end].shape, n=1, p=1-corruption_level) err, _ = layer.pretrain(X[start:end], _noise) err_all.append(err) print('EPOCH:%d, ERROR:%lf' % (epoch+1, np.mean(err_all))) X = layer.fwd_prop(X)
- 事前学習から得た重み関数を用いた多層パーセプトロンの学習
mlp = MLP_MNIST(layers[0].W, layers[0].b, layers[1].W, layers[1].b, layers[2].W, layers[2].b, layers[3].W, layers[3].b) n_epochs = 10 batch_size = 100 n_batches = train_X.shape[0] // batch_size cost_history = [] for epoch in range(n_epochs): train_X, train_y = shuffle(train_X, train_y) cost = 0 for i in range(n_batches): start = i * batch_size end = start + batch_size cost += mlp.train(train_X[start:end], train_y[start:end], eps=0.01) cost_history.append(cost/(n_batches*batch_size)) pred_y = mlp.test(test_X) print(f1_score(np.argmax(test_y, 1), pred_y, average='macro'))
結果
(1) 一般的なMLPによる学習効率
SdAによる事前学習を用いない場合についてMLPの学習効率を調べた。以下にネットワークを1-4層まで変えた場合のF1値および交差エントロピー誤差の変化を示す。層の数を増やすに連れてF1値が低い値をとることがわかった。10エポックの学習が終了した時には層数による影響はF1値には見られなかった(One-layer: 0.9709, Two-layer: 0.9652, Three-layer: 0.9664, Four-layer: 0.9528)。しかし、5エポックの学習時点では著しく4層のネットワークの値が悪くなっている。誤差関数においても層数を増やすに連れて収束しにくくなっている。
(2) SdAによる事前学習を用いた場合の学習効率
SdAを用いて事前学習を行い、得られた重み関数(バイアスを含む)を初期値としてMLPの学習を行った。以下に、3および4層についてのF1値および交差エントロピー誤差を示す。事前学習を行ったことで、1エポックの学習終了時点でより高いF1値を示した(Three-layer: 0.9467, Four-layer: 0.9483)。層数による違いがほとんどないことから、いずれの場合でも事前学習が十分に行われたと思われる。
メモ
ネットワークの層を深くすることで学習効率が落ちる原因の一つとして勾配消失問題が挙げられる。この問題は活性化関数を変更することで避けることも可能である。今回用いているシグモイド関数の勾配は高々0.25であるから、層を深くするほど入力層に近くなる程伝搬する誤差が0に近くなり学習効率が下がるわけである。一方、tanhやReLU関数は勾配の最大値は1であるから誤差が0になるような状況を避けることが可能になる。
参考文献
岡谷貴之, 「深層学習」第5章, 講談社, 2017.