Deep Learning

自己符号化器の実験 その1

目的

Deep Learningの層ごとの事前学習(局所収束を避けやすいノード重みの「良い」初期値を得る処理)には自己符号化器や制限ボルツマンマシンを用いるものが存在しています.両者とも隠れ層でデータの「内部表現」(データを簡潔に記述する変数)を獲得するようノード重みを調整します.ここで,「内部表現」とは具体的にはどんな表現を得ているのでしょうか?内部表現が複数存在する場合はどれが選ばれるのでしょうか?今回は簡単な雑音除去自己符号化器を用いて内部表現に何が獲得されるか調べてみます.

理論

理論というほど高級なものではありませんが,何をしようとしているか説明します.

結合

experiment1

今回作った雑音除去自己符号化器の構図です.単純に入力ノードを6つ,隠れ層2つ,出力ノード6つを全結合でつなげます.隠れ層は

{\bf h}=sig(W_1{\bf x}+{\bf b_1})

で表され,出力への活性化関数は恒等写像,すなわち

{\bf \hat{x}}=W_2{\bf h}+{\bf b_2}

としました.ここでW1とW2にW_2=W_{1}^{T}という束縛条件を入れる場合があるそうですが今回は自由に動かせるようにしました.

入力

入力データは6次元ベクトルです.生成手法は次のとおりです.

  1. 3次元の半径1の球面上に一様分布する点を200個生成する
  2. 半径に正規分布するノイズ(平均0,分散1)を加える
  3. 3次元ベクトルに余分な次元を3つ追加する.値は0とする
  4. 6次元空間で回転する.

このようにして生成された6次元の点は実質自由度2の球面上に密集して存在するはずですが,回転がかかっているため6つの次元が全部変化します.

正解データ

入力データの生成手順の2を飛ばして生成します.

誤差関数

6次元ベクトルの差を判定するため平均二乗誤差を使用します.最適化は勾配降下法をつかいました.

内部表現として獲得されるものの予想

データを説明するのに必要な変数は1の段階で用いた3次元球面上の天頂角と方位角のみであるはずです.ただし,曲面上の点の極座標表示はどこを始点に角度を考えても良いという任意性が存在しています.特に天頂角は数学のように北極0南極πとするパターンと緯度のように北極π/2南極-π/2とするパターンが存在しています.内部変数としては2個の角変数が獲得されると考えられますが,どういう角変数が得られるかは予想がつきません.

実験

denoised
正解データの3次元散布図
noised
入力データの3次元散布図

上図のような教師データでネットワークを訓練します.回転行列は次のコードで生成します.

rot=MathFuncs.RotationMatrix([0,0,0,1,0,0],[1,2,3,0,0,0])

RotationMatrixは前回のエントリで実装したN次元回転行列を返す関数です.生成された点はもともと[z2,z3,z4,z,x,y]のフォーマットでz2~z4が0でしたが,回転することでz2~z4も値を持つようになります.このようにして得られた教師データを使って訓練します.訓練後再びノイズありデータを入力し,自己符号化器の出力{\bf \hat{x}}を得たあと,逆回転を行い,z,x,yの値を使って散布図を作成したのが以下になります.

plane
予想出力

また,内部表現を無理やり数学の極座標表示と解釈して半径1でプロットした図が以下です.

 

something
何か

議論

画像ではわかりづらいかもしれませんが,予想出力は平面上に乗っています.出力活性化関数が恒等写像なので平面しか表せないのは当然ですが,円とは似ても似つかない形になってしまいました.また,中間層の値は無理やり極座標表示と解釈するとよくわからない図形を表すという結果になりました(天頂角をいじってみましたがあまり変化はありませんでした).点が少なすぎた,重み行列は共有すべき,最適化手法が悪かった,1層の自己符号化器の限界などの可能性がありますが,次回以降の実験で試してみようと思います.中間層の結果があまりおもしろくないのが残念でした.

ソースコード

MathFuncs.py

import numpy as np
#参照:http://wasan.hatenablog.com/entry/20110321/1300733907
#ユークリッド座標ベクトルは[Z1,Z2,...,Zn-2,X,Y]の形式
#極座標ベクトルは[r,θ1,θ2,...,θn-2,φ]の形式
def PolarToEuclidean(polVec):
ret=np.empty_like(polVec)
ret[0]=polVec[0]*np.cos(polVec[1])
for i in range(2,len(polVec)):#2個目以降に角変数が存在し,最後の角変数だけ特殊
ret[i-1]=polVec[0]*np.sin(polVec[1:i]).prod()*np.cos(polVec[i])
ret[len(polVec)-1]=polVec[0]*np.sin(polVec[1:]).prod()
return ret

def EuclideanToPolar(eVec):
ret=np.empty_like(eVec)
ret[0]=np.linalg.norm(eVec)
ret[1]=np.arccos(eVec[0]/ret[0])
for i in range(2,len(eVec)-1):#最後2つの座標は各変数の定義域が0<=theta<=2Piなので特別扱いする ret[i]=np.arccos(eVec[i-1]/ret[0]/np.sin(ret[1:i]).prod()) x=eVec[len(eVec)-2]/ret[0]/np.sin(ret[1:len(ret)-1]).prod() y=eVec[len(eVec)-1]/ret[0]/np.sin(ret[1:len(ret)-1]).prod() theta=np.arccos(np.absolute(x)) #print("x:%f y:%f theta:%f"%(x,y,theta)) if x>0 and y>=0:
ret[len(ret)-1]=theta
elif x<=0 and y>0:
ret[len(ret)-1]=np.pi-theta
elif x<0 and y<=0:
ret[len(ret)-1]=np.pi+theta
else:
ret[len(ret)-1]=np.pi*2-theta
return ret

#fromVecをrotatedVecに重ねる回転行列を返す
def RotationMatrix(fromVec,rotatedVec):
if len(fromVec)!=len(rotatedVec):
raise ArithmeticError('Incompatible vector length')
eu=fromVec/np.linalg.norm(fromVec)
ev=rotatedVec-np.dot(rotatedVec,eu)*eu
ev/=np.linalg.norm(ev)
theta=np.arccos(np.dot(eu,rotatedVec)/np.linalg.norm(rotatedVec))
ret=np.zeros([len(fromVec),len(fromVec)])
for i in range(len(fromVec)):
for j in range(len(rotatedVec)):
ret[i,j]=eu[i]*((np.cos(theta)-1)*eu[j]-np.sin(theta)*ev[j])+ev[i]*(np.sin(theta)*eu[j]+(np.cos(theta)-1)*ev[j])
ret[i,i]+=1
return ret

ManifoldLearningExperiment.py

#初期化
#numpyなどのモジュールはJupyterの設定でインポート済み
import tensorflow as tf
#高負荷な処理は行わないので手軽にInteractiveSession
sess=tf.InteractiveSession()

#便利関数のインポート
import MathFuncs

#分布データを作る
points=np.zeros((200,6)) #6次元ベクトルを200個
for i in range(points.shape[0]):
    while points[i,0]<=0:
        points[i,0]=1+np.random.randn()*0.1 #半径に雑音をのせる
    points[i,1]=points[i,2]=points[i,3]=np.pi/2
    points[i,4]=np.random.random()*np.pi
    points[i,5]=np.random.random()*np.pi*2
    #他の要素は0
denoised=np.copy(points)
for point in denoised:
    point[0]=1
#今は3個の軸が死んでるけど適当に回すことで3次元球上の点を6次元に埋め込む
rot=MathFuncs.RotationMatrix([0,0,0,1,0,0],[1,2,3,0,0,0])
rotated=np.empty_like(points)
denoisedRotated=np.empty_like(denoised)
for i in range(points.shape[0]):
    rotated[i]=np.dot(rot,MathFuncs.PolarToEuclidean(points[i]))
for i in range(denoised.shape[0]):
    denoisedRotated[i]=np.dot(rot,MathFuncs.PolarToEuclidean(denoised[i]))

#ノードの宣言
x=tf.placeholder(tf.float32,shape=[None,6])
y_=tf.placeholder(tf.float32,shape=[None,6])

def weight_variable(shape):
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)

def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)

#全結合重みの宣言
W1=weight_variable([6,2])
b1=bias_variable([2])
W2=weight_variable([2,6]) #W1の転置になるらしいけどとりあえずこう宣言しておく
b2=bias_variable([6])
sess.run(tf.initialize_all_variables())
#結合則の定義
hidden_layer=tf.sigmoid(tf.matmul(x,W1)+b1)
y=tf.matmul(hidden_layer,W2)+b2
#損失関数の定義
#ベクトルの差を見るのでmse
mse=tf.reduce_mean(tf.square(y_-y))

#損失関数を最小化する
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(mse)
for i in range(10000):
    train_step.run(feed_dict={x:rotated,y_:denoisedRotated})

#復号化された点を逆回転し極座標表示
decoded_pol=np.empty_like(decoded)
decodedUnrotated=np.dot(rot.T,decoded.T).T
for i,point in enumerate(decodedUnrotated):
    decoded_pol[i]=MathFuncs.EuclideanToPolar(point)
広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中