Edited at

ChainerでDeep Autoencoderを作ってみる

More than 1 year has passed since last update.


Chainer

Pythonなどで使える、DeepLearningの為のパッケージです。今回はPython2.7環境で使いました。Chainerについての解説は公式のドキュメントか他にも解説してくださってるかたがいるので省こうと思います。


今回作りたいもの

Chainerを使ったAutoencoderです。


Autoencoderとは

入力信号$x$を受け取った時にそれと同じ入力を返すネットワーク(関数)のことです。今回は簡単のために入力は実数だとして$x\in\mathbb{R}^n$と考えます。

https://en.wikipedia.org/wiki/Autoencoder


(絵がわかりやすいと思ったので英語版を乗せます)

当たり前ですが、$f(x)=x$にしたら達成出来るじゃん!!ということになるので、関数$f$に制約を付けます。

具体的には中間層$h=f_1(x)$を作る関数$f_1$とそこから出力$t=f_2(h)$を作る$f_2$によって出力が決定するします。

それを踏まえれば全体では

$$

t=f_2(h)=f_2(f_1(x))

$$

となります。


何が嬉しいか

関数$f_1,f_2$をちょっと単純化して線形変換+定数を返すようなアフィン写像であるとします。

要するに入力 $x$ を受け取って、それに重み $W_1,W_2$ をかけたあと、バイアス $b_1,b_2$ を付ける、という関数です。

わかりやすいよう、関数 $f_1$ の出力を便宜的に $h$ とおくと

$$

\begin{eqnarray}

h&=&f_1(x)=W_1x+b_1\\

t&=&f_2(h)=W_2h+b_2

\end{eqnarray}

$$

と表せます。ここで話を単純化して、バイアスを無視して$W_2=W_1^T$だとすれば

$$

t=W_1W_1^Tx

$$

になり、誤差関数$\phi$として二乗誤差$\phi(x,t)=||x-t||^2$使ってあげると

$$

\min_{W_1}{\mathbb{E}[\phi(x,t)]}=\mathbb{E}[||x-W_1W_1^Tx||^2]

$$

になって、これは主成分分析PCAと同じ結果となります。

実際にはAutoendoderではバイアス項もありますし、$f_1,f_2$もアフィン写像のあとに活性化関数を通すので全く一致するわけではありません。

しかしイメージとしては主成分分析と似た発想に従っていて、それをより複雑な関数で行っている、という見方ができると思います。与えられたDataが持っている共通の動きを学習して、Dataの特徴を捉えるということに関して両者が行っていることに違いはそんなにないように感じています。(この辺は多様体とかそういう学問なのかな?と漠然と思っていますがよくわかりません…)


作ったもの

AutoencoderとAutoencoderをtrainingするクラス、それにそれを積層させた隠れ層3のDeepAutoencoder(DAE)とそれをtrainingするクラスです。ついでなのでDropoutとnoize付加も出来るようにしてみました。

コードは https://github.com/Nyker510/Chainer においてあります。


動かしてみる


使うデータセット

MNISTを使います。sklearn.fetch_mldataで引っ張ってきます(最初時間が掛るので注意してください)


AutoEncoder


autoencoder.py

if __name__ == '__main__':

mnist = fetch_mldata('MNIST original')
mnist.data = mnist.data.astype(np.float32)
mnist.data /= 255
mnist.target = mnist.target.astype(np.int32)

data_train,\
data_test,\
target_train,\
target_test = train_test_split(mnist.data, mnist.target)

data = [data_train, data_test]
target = [target_train, target_test]
ae = AutoEncoder(
n_inputs=784,
n_hidden=100,
dropout_ratio=.10,
activate=F.relu
)

train_ae = Train_AutoEncoder(
tt_data=data,
model=ae,
optimizer=optimizers.AdaDelta,
corruption_ratio=.3)
train_ae.start(epoch=20)

filename = train_ae.condition_to_string()
#save weight figure
save_images(ae.encoder.W.data,filename=filename+"enc.png")
save_images(ae.decoder.W.data.T,filename=filename+"dec.png")


dropout、勾配法のalgorithmを指定して、最後重みを保存しています.


活性化関数による比較

epoch=20,corruption_ratio=0.30(ノイズの割合です。この確率で画像の値が0に欠落します)、Dropout=0.10で固定して、活性化関数だけ変化させた時の図が以下になります。

${\rm relu}(x) = \max(0,x)$

AE_alg=Adam_epoch=20_corp=30drop=10activate=relu_nhidden=100.png

${\rm sigmoid}(x) = \frac{1}{1+\exp(-x)}$AE_alg=Adam_epoch=20_corp=30drop=10activate=sigmoid_nhidden=100.png

$\tanh(x)=\frac{\exp(x)-\exp(-x)}{\exp(x)+\exp(-x)}$

AE_alg=Adam_epoch=20_corp=30drop=10activate=tanh_nhidden=100.png

reluが1番先鋭で、勾配消失しにくい→学習の速度が早いということで過学習気味のようです。このなかだと1番シグモイド関数の時がよく画像のエッジを抽出出来ている用に見えます。

乗せては居ませんが、ロス関数の値と人間が見た時に優位かどうか(一般的な数字の特徴を表せているかどうか)はあまり関係がないようでした。(すごくロスの値が小さい時もノイジーな絵しか得られない)

条件に関しては、他にもcorruption ratio,dropout,epochなどを振っていろいろ試したのですが、1番良さそうだったのはcorrpution,dropoutともに0.30でepoch=10のシグモイド関数の時でした。(もっとも何を持ってして良いというのか微妙なところが有りますが……)

dropout,corruption=0.30,epoch=10,activate=sigmoidの時

AE_alg=Adam_epoch=10_corp=30drop=30activate=sigmoid_nhidden=100.png


epoch数による比較

ちなみにepochを増やすとこんな感じです。過学習な臭いがします。

epoch=20

AE_alg=Adam_epoch=20_corp=30drop=30activate=sigmoid_nhidden=100.png

epoch=30

AE_alg=Adam_epoch=30_corp=30drop=30activate=sigmoid_nhidden=100.png


Deep Autoencoder

Autoencoderのときシグモイド関数が良さそうだったので、ひとまずすべての層において活性化関数をシグモイド関数にセットしました。

事前学習として各層でAutoencoderを学習させて、最後にfinetuningを行っています。


deep_autoencoder.py

    dae = DAE(n_hidden_layers=n_hidden_layers)

train_dae = Train_DAE(tt_data=data,model=dae)
train_dae.pre_training(epoch=20)
train_dae.fine_tuning(epoch=10)
condition = train_dae.condition_to_string()
save_images(train_dae.model.l2.W.data,filename='DAE_'+condition+'layer2.png')
save_images(train_dae.model.l1.W.data,filename='DAE_'+condition+'layer1.png')


中間層[100,25]の時

先ほどのAutoencoderの時はすべて隠れ層の数を100にしていたので、それより更に低次元に落とし込んだ時どうなるかを確認したいので100,25のセットを選びました。

結果 layer1

DAE_alg=Adam_epoch=10_corp=30nhidden1=100nhidden2=25corp=0activate=sigmoidlayer1.png

layer2

DAE_alg=Adam_epoch=10_corp=30nhidden1=100nhidden2=25corp=0activate=sigmoidlayer2.png

なんだかよくわからないですね……


中間層[28*28,10*10]の時

結果 layer1

DAE_alg=Adam_epoch=10_corp=30nhidden1=784nhidden2=100corp=0activate=sigmoidlayer1.png

結果 layer2

DAE_alg=Adam_epoch=10_corp=30nhidden1=784nhidden2=100corp=0activate=sigmoidlayer2.png

テストデータに対するロス関数の値を見ている限りでは、思ったより良くはならないんだなーというのが率直な感想です。

ただ、一層のAutoencoderよりもロス関数がかなり小さい値を取る時もあるようなので、表現能力の高さから何かしらの特徴を学習しているのは間違いなさそうです。

しかし相変わらず、重みベクトルの可視化をしても画像の特徴が得られているわけでもないので、さらなる汎化能力の獲得には、畳み込みネットのような他の手法を使う必要があるようです。


まとめ


  • AutoencoderとDeepAutoencoderを作りました

  • 可視化しても意味がわかる時とわからない時がある


    • もしかしたら過学習している?(でもpredictive_lossの大きさもそんなに変わっていない時もあるのでよくわからない)



  • Deepにしても基本的に得られる重みの特徴は変化しない


    • 人間が見ているような特徴を判定するような高度な般化性を得るためには画像に適した手法(畳込み)をする必要があるみたい

    • ロス関数の値はAutoencoderよりも低くなることが多い(表現能力高さ故?)



  • 活性化関数や、その他パラメータによって結果がかなり異なる