はじめに
前回はPCAについて書きました。
PCAは比較的レガシーなやり方だと思いますが、非常に強力な手法で応用範囲が広いということは、色々なところの記載や実際に試してみてある程度わかった気がします。
では次にということでNeuralNetworkを活用した次元削減技術であるAutoEncoderを試してみようと思います。
実は昔にKerasで書いているのですが、やっぱりnumpyで作ってこそだと思うので改めてnumpy(GPU使いたかったので正確にはcupy)で作ってみました。
単純に実装
まずは構造
今回は入力層、隠れ層1つ、出力層1つで組んでみようと思います。
なお、活性化関数は隠れ層側はReLU, 出力層側はsigmoidです。
また、損失関数は今回は入力=出力としたいので平均二乗誤差を使います。
利用するデータ
まずはお決まり通りmnistの手書き文字を利用して次元削減->復元という流れをやってみようと思います。
実装
コードを記載します。colaboratoryで動作させています。
import cupy as cp import pandas as pd import matplotlib.pyplot as plt from sklearn.datasets import fetch_openml mnist = fetch_openml('mnist_784', version=1,) label = mnist.target.astype("int") x_train = cp.asarray(mnist.data).astype('float32') / 255 # 活性化関数とその微分を定義 def relu(x): return cp.maximum(0, x) def relu_d(u): return cp.where( u > 0, 1, 0) def sigmoid(x): return 1.0 / (1.0 + cp.exp(-x)) def sigmoid_d(u): return sigmoid(u) * (1.0-sigmoid(u)) # init params latent_dim = 32 # 潜在変数の次元 = 圧縮後の次元数 input_dim = 784 # 入力サイズ max_iter = 50 # 繰り返し回数 eta = 0.001 # 学習率 batch_size=256 # ミニバッチのサイズ output_dim = input_dim cp.random.seed(3) we = cp.random.normal(loc=0, scale=1, size=(input_dim, latent_dim)) wd = cp.random.normal(loc=0, scale=1, size=(latent_dim, output_dim)) be = cp.zeros((latent_dim,)) bd = cp.zeros((output_dim,)) # 活性化関数を変えたいときいちいち面倒なので、_1がencoder側、_2がdecoder側としてセットしておく activation_1 = relu activation_2 = sigmoid activation_d_1 = relu_d activation_d_2 = sigmoid_d mses = [] # ミニバッチと言いつつ、ちょっとランダムにしている idx = cp.array([True] * batch_size + [False] * (x_train.shape[0] - batch_size)) for i in range(max_iter): for j in range(x_train.shape[0] // batch_size + 1): train = x_train[cp.random.permutation(idx)] #print(train.shape) # forward h = train.dot(we) + be y = activation_1(h) # 潜在変数 z = y.dot(wd) + bd o = activation_2(z) # error(Mean Squared Error) mses.append( cp.sum((o - train) ** 2) / (o.shape[0] * o.shape[1]*2) ) # backward do = o - train dz = do * activation_d_2(z) dwd = y.T.dot(dz) dbd = cp.sum(dz, axis=0) dy = dz.dot(wd.T) dh = dy * activation_d_1(h) dbe = cp.sum(dh, axis=0) dwe = train.T.dot(dh) # update we = we - eta * dwe wd = wd - eta * dwd be = be - eta * dbe bd = bd - eta * dbd if (i+1) % (max_iter//10) == 0: print(mses[-1]) plt.plot(mses)
結果の確認
まずは学習曲線を見ます。多少バリバリしつつもしっかり収束に向かっていることが伺えます。
では結果を見てみましょう。
元データとデコードしたデータを並べてみてみます。
# encode & decode h = x_train.dot(we) + be y = activation_1(h) z = y.dot(wd) + bd o = activation_2(z) # visualize no = cp.asnumpy(o) fig = plt.figure(figsize=(2, 15)) for i in range(10): plt.subplot(10, 2, 2 * i + 1) plt.imshow(mnist.data[i].reshape((28, 28)), cmap='gray') plt.subplot(10, 2, 2 * i + 2) plt.imshow(no[i].reshape((28, 28)), cmap='gray') plt.show()
冒頭5つを抜粋します。
なかなかいい感じにできてますね!!
潜在変数の次元数を2にすると・・・
PCAのときみたいに、グラフ描画に使えるかなーと思って次元数を2にして見た結果・・・
うーん、いまいちピンとこない。
PCAは次元削減するときに、分散の大きい軸から明示的に採用していた = 少ない次元数でも大きな情報量を持つ軸を明示的に選択していたと思いますが、一方のAutoEncoderはそのあたりは明らかではないのでこういった差なのでしょうか?
ちなみに当たり前ですが、こんな状況なのでEncode/Decodeしてもだいたいの結果が同じ感じになってしまいます。
終わりに
今回はAutoEncoderを試しました。
コードを書くとAutoEncoderそのものだけでなく、どういったことが学習に影響しているかなど感覚が掴めてきて面白いですね。
さて、ここまで来たからにはVAEもやりたいのですが、果たして実装できるかどうか・・・
乞うご期待!