Theano で Deep Learning <2> : 多層パーセプトロン
Python Theano
を使って Deep Learning の理論とアルゴリズムを学ぶ会、第二回。
目次
DeepLearning 0.1 より、
第一回 MNIST データをロジスティック回帰で判別する | 英 |
第二回 多層パーセプトロン (今回) | 英 |
第三回 畳み込みニューラルネットワーク | 英 |
第四回 Denoising オートエンコーダ | 英 |
第五回 多層 Denoising オートエンコーダ | 英 |
第六回の準備1 networkx でマルコフ確率場 / 確率伝搬法を実装する | - |
第六回の準備2 ホップフィールドネットワーク | - |
第六回 制約付きボルツマンマシン | 英 |
Deep Belief Networks | 英 |
Hybrid Monte-Carlo Sampling | 英 |
Recurrent Neural Networks with Word Embeddings | 英 |
LSTM Networks for Sentiment Analysis | 英 |
Modeling and generating sequences of polyphonic music with the RNN-RBM | 英 |
多層パーセプトロンとは
ロジスティック回帰では判別関数が直線のため、入力データが線形分離できない場合は判別率が落ちていた。
多層パーセプトロンでは、入力を非線形変換/次元変換することによってデータを分離しやすくし、そのような場合の判別率を上げようとするもの。
モデル
多層パーセプトロンを DeepLearning 0.1 Multilayer Perceptron の式をベースに図示するとこんな感じになる。
- : 入力層 - 隠れ層の間で適用される係数行列。次元は 入力データの説明変数の数 x 隠れ層のユニット数
- : 入力層 - 隠れ層の間で適用される重みベクトル。次元は 隠れ層のユニット数
- : 隠れ層の活性化関数。シグモイド関数もしくは (ハイパボリックタンジェント) をよく使う。
- : 隠れ層 - 出力層の間で適用される係数行列 = ロジスティック回帰の係数行列。次元は 隠れ層のユニット数 x 出力データのクラス数
- : 隠れ層 - 出力層の間で適用される重みベクトル = ロジスティック回帰の重みベクトル。次元は 出力データのクラス数
- : 出力層の活性化関数。2クラスの場合は シグモイド、多クラスの場合はソフトマックス関数 (ロジスティック回帰と同じ)。
つまり 3層パーセプトロンでは、
また、多層パーセプトロンの各層のユニット数 = その層に渡ってくるデータの次元 と考えればよい。
補足 PRML ではバイアス項をダミーのユニットを作って扱っているため、ユニット数 / 係数行列の次元など定義が異なる。
Pattern Recognition and Machine Learning (Information Science and Statistics)
- 作者: Christopher Bishop
- 出版社/メーカー: Springer
- 発売日: 2010/02/15
- メディア: ハードカバー
- 購入: 5人 クリック: 67回
- この商品を含むブログ (29件) を見る
ロジスティック回帰から多層パーセプトロンへ
まず、隠れ層をあらわす HiddenLayer
クラスを定義する。
係数行列の初期値の決め方
前回の LogisticRegression
クラスと同じく、HiddenLayer
クラスは 係数行列 W
( 上式 ) , バイアス b
( 上式 ) を Theano
の共有変数として宣言する。
係数行列 W
の初期値は以下の範囲の一様乱数からとると (特にネットワークの層が深い場合に) 学習の進みがよいらしい 。
活性化関数が
tanh
:活性化関数が
sigmoid
:
※ , は、それぞれ係数行列の前層 / 後続層のユニット数
そのため、HiddenLayer
クラスでは 指定範囲の一様乱数を生成する numpy.random.uniform
を使って係数行列 W
の初期値を設定している。宣言中で使われている以下の変数は HiddenLayer.__init__
の引数として渡ってきているもの。
rng
: 乱数のシードn_in
: 入力層のユニット数n_out
: 隠れ層のユニット数
W_values = numpy.asarray( rng.uniform( low=-numpy.sqrt(6. / (n_in + n_out)), high=numpy.sqrt(6. / (n_in + n_out)), size=(n_in, n_out) ), dtype=theano.config.floatX ) if activation == theano.tensor.nnet.sigmoid: W_values *= 4 W = theano.shared(value=W_values, name='W', borrow=True)
例えば、前層のユニット数が 3, 後続層のユニット数が 4 で 活性化関数が tanh
の場合、係数行列の初期値は以下のようになる。
import numpy n_in = 3 n_out = 4 numpy.random.uniform( low=-numpy.sqrt(6. / (n_in + n_out)), high=numpy.sqrt(6. / (n_in + n_out)), size=(n_in, n_out) ) # [[ 0.01572058 -0.46004291 -0.23671422 -0.54857948] # [ 0.081257 0.87090393 -0.72051252 -0.38176917] # [-0.09591302 0.04587085 -0.42426934 -0.35021306]]
また、バイアス b
の初期値は 0ベクトルでよいようだ。
b_values = numpy.zeros((n_out,), dtype=theano.config.floatX) b = theano.shared(value=b_values, name='b', borrow=True)
続けて、隠れ層からの出力を計算する式を HiddenLayer.output
プロパティとして定義している。宣言中で使われている以下の変数は HiddenLayer.__init__
の引数として渡ってきているもの。
input
: 入力データをあらわすシンボル (TensorVariable
型)。activation
: 活性化関数。デフォルトはtheano.tensor.tanh
(np.tanh
のテンソル版 )。
また、HiddenLayer
はこの時点では実際のデータを受け取っておらず、Theano
のシンボルから式を作っているだけ。ここも考え方は 前回同様。
lin_output = T.dot(input, self.W) + self.b self.output = ( lin_output if activation is None else activation(lin_output) )
MLP
クラスの定義
続けて 多層パーセプトロン自体をあらわすクラス MLP
を定義している。このクラスは 以下二つのインスタンスをプロパティとして持つ。
正則化 (Regularization)
MLP
クラスでは 学習の際に 負の対数尤度だけでなく、以下 二つのノルムを正則化項として最小化するように設定している。一般には、これらを罰則 ( Penalty ) として最小化することで過学習をさけ、学習器の汎化性能 (未知のデータへのあてはまりの度合い) を上げることができる。
- L1 ノルム : 係数行列 , の各要素の絶対値の和
- L2 ノルム : 係数行列 , の各要素の二乗和
# L1 ノルム self.L1 = ( abs(self.hiddenLayer.W).sum() + abs(self.logRegressionLayer.W).sum() ) # L2 ノルム self.L2_sqr = ( (self.hiddenLayer.W ** 2).sum() + (self.logRegressionLayer.W ** 2).sum() )
負の対数尤度と判別誤差
他、モデル自体の負の対数尤度 ( negative_log_likelihood
) / 判別誤差 ( errors
) はモデルの出力に対して計算すればよいため、出力層である LogisticRegression
での計算結果をそのまま使っている。
self.negative_log_likelihood = ( self.logRegressionLayer.negative_log_likelihood ) self.errors = self.logRegressionLayer.errors
損失関数の定義
これら 負の対数尤度と正則化項を足し合わせて損失関数を計算する式 cost
を作っている。L1_reg
, L2_reg
はそれぞれ L1ノルム, L2ノルムの正則化項の重み。既定値は L1_reg=0.00
, L2_reg=0.0001
のため、サンプルをそのまま実行した場合は L2 ノルムのみが正則化項として考慮されることになる。
cost = ( classifier.negative_log_likelihood(y) + L1_reg * classifier.L1 + L2_reg * classifier.L2_sqr )
勾配の計算
ここが今回のポイント。多層パーセプトロンの学習は 誤差逆伝播法 ( Back propagation) というアルゴリズムで行われ、一般には以下のような処理 (順逆モデリング) が必要になる。
- 学習器の出力値と、真の値との差異 = 出力誤差を計算
- 1 で求めた出力誤差を小さくする , の勾配を計算し、 , を更新
- 1 で求めた出力誤差に対して "隠れ層 - 出力層間の処理の逆演算"を行い、"出力誤差が隠れ層からでたと仮定した場合の出力値 = 隠れ層誤差" を求める
- 3 で求めた隠れ層誤差を小さくする , の勾配を計算し、 , を更新
、、、めんどくさい、、、。
が、Theano
では式表現に対して偏微分を行い偏導関数を求めることができる。そのため、上で定義した損失関数 cost
について、最適化したいパラメータ HiddenLayer.W
, HiddenLayer.b
, LogisticRegression.W
, LogisticRegression.b
それぞれで偏微分した偏導関数を求めておけば、学習の際はそれらを使ってパラメータごとに勾配計算 / 更新をかけていくことができる。
つまり、今後 パーセプトロンの層 / パラメータが増えた場合でも、モデル全体の損失関数を偏微分することによって各パラメータを個別に学習できることになる。これは強力だな、、、。
classifier.params
:HiddenLayer.W
,HiddenLayer.b
,LogisticRegression.W
,LogisticRegression.b
からなるリストlearning_rate
: 学習率。デフォルトは 0.01
gparams = [T.grad(cost, param) for param in classifier.params] updates = [ (param, param - learning_rate * gparam) for param, gparam in zip(classifier.params, gparams) ]
続けて定義されている theano.function
の読み方は 前回と同じ。以下 一連の処理を行う関数を作っている。
index
を引数として受け取り、givens
の指定によりindex
の位置のデータを切り出して シンボルx
,y
に入れ、x
,y
をoutputs
の式 (負の対数尤度 + 正則化項) に与えて計算結果を得て、updates
で指定された更新式 ( 損失関数の偏導関数からの勾配計算 ) によって共有変数 ( 各係数行列、バイアス ) を更新する
全部まとめて
プログラムの量としては Theano
のおかげで、え?こんだけでいいの?という感じだ。最後に置いてあるスクリプトを前回と同じディレクトリに入れて実行すればよい。
パラメータ調整のコツ
また、元文書では補足的に以下のパラメータ調整についてカッコ内のようなことが記載されている。自分でパラメータ調整したい方は読むといいですね。
- 隠れ層の活性化関数 (
tanh
がいい感じ ) - 係数行列の初期値 (上の内容と同じ)
- 学習率 ( で試せ、時間で減衰させてみろ )
- 隠れ層のユニット数 ( データによる。モデルが複雑な場合は増やせ )
隠れ層からの出力の可視化
最後。元文書にはない内容だが、今回のサンプルについて 入力が隠れ層においてどういった感じで分離されるのか、以下の方法で可視化してみた。
- 学習データ (
train_set_x
,train_set_y
) を対象 - MNIST のラベル 0 〜 9 について、隣り合う 9組 ( 0と1, 1と2 ... ) を比較
- 隠れ層からの出力データ 500 次元に主成分分析をかけて 2次元に写像
- ランダムサンプリングした 300 レコードを描画
入力 - 隠れ層 間の変換によって各クラスが 主成分 ( = 分散大 ) の方向に離れていけば、分離超平面がみえるはずだ。
学習開始前 (隠れ層出力)
50 エポック学習後 (隠れ層出力)
曇りのない目で見てみると、なんか少し分離しやすそうになったかも?といえなくもない感じですね!
プログラム
プロット部分のみ gist に置いた ( 他は元文書のスクリプトと同じなので )。
Theano
に関してのポイントは 1 点だけ。隠れ層からの出力を計算する HiddenLayer.output
は TensorVariable
型のため、実際に計算するには一度 theano.function
を通すこと。
# 隠れ層の出力 z_data を計算 apply_hidden = theano.function(inputs=[x_symbol], outputs=classifier.hiddenLayer.output) z_data = apply_hidden(x_data.get_value()) labels = y_data.eval()
Visualize Multilayer Perceptron Example in deeplearning.net · GitHub
11/30追記: 隠れ層での変換についてもう少しわかりやすい例をつくった。
12/07追記 続きはこちら。
- 作者: 岡谷貴之
- 出版社/メーカー: 講談社
- 発売日: 2015/04/08
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (4件) を見る