Deep Learning

TensorFlow Tutorial for Expertsを解読する(後半)

前回に続いて後半のCNNを使ったMNIST画像認識のコードを解読しています.前回の変数を一部再利用するのでノートブックのセルを前回分まで評価しておいてください.

TensorFlowチュートリアル作成者の言

Getting 91% accuracy on MNIST is bad. It’s almost embarrassingly bad.

単純なソフトマックス回帰の結果MNISTで91%の正答率が得られましたが「不愉快なくらい悪い」正答率だそうです.これをディープラーニングで改善しようというのが今回の趣旨です.

CNN

コードに入る前にCNNについてさらっとまとめておきます.CNNは畳み込みニューラルネットワーク(Convolutional Neural Network)の頭文字で,「畳み込み層」「プーリング層」「全結合層」「読み出し層」からなります.

畳み込み層

画像の部分部分に畳み込み演算を行います.具体的には部分をベクトルと考えて重みベクトル(フィルタ)との内積をとります.畳込みに使う領域は「ストライド」分だけずらしながら変更していきますが,重みは変更しません.したがって例えば28×28の画像の5×5の領域をストライド1で25次元の重みベクトルと畳み込んでいくと24×24個の内積結果を得ることができます.更に次の層に渡す前に活性化関数に通します.重みの種類を何セットか(例えば32セット)用意しておいてそれぞれについて24×24個の内積結果を得て活性化関数に通すのが畳み込み層の行う計算です.それぞれの重みが別々の特徴を計算しているという解釈をします.

プーリング層

畳み込み層から得られた結果を「誇張」(強調?圧縮?)する働きをします.先ほどの例で言うとひとつの重みベクトルから計算される結果は24×24個存在しました.この24×24の値を縦横に並べて再び画像とみなします.畳み込み層同様に部分領域を取り出すのですが,この中の最大の値をその領域からの出力として出力するのがプーリング(最大プーリング)です.例えば領域の大きさを2×2としてプーリングを行うと12×12の出力が得られます.これをすべての24×24画像に対して行うのがプーリング層の計算です.(プーリングの仕方は最大プーリングの他にも平均プーリングなどがあります.)

全結合層

畳込み層とプーリング層は何層も(本記事では交互に)重ねられます.何層か後に最後に置く層が全結合層です.最後のプーリング層から6×6の画像がn個得られたとします.全結合層では36n次元のベクトルとしてすべての画像を入力し,重み行列をかけ,バイアスを加え,活性化関数に通します.例えば重みベクトルが100個存在したとしたらそれらをまとめた36n×100の重み行列をかけ100次元のベクトルを得ます.それに100次元のバイアスベクトルを足したあと,ReLU活性化関数に入力します.ReLUに関しては以前深層学習の本の勉強ログを書いたのでそれを参考にするか,他のサイトの記事に当たると良いかと思います.

読み出し層

全結合層の出力をソフトマックス回帰し,分類結果とします.例えば100次元ベクトルに100×10の重み行列をかけ,バイアスを加え,ソフトマックスに通します.

ここまで書いてきましたが,他のサイトにも画像を使った優良な説明があるのでそちらをあたってもいいかもしれません.例えば下のページは画像を使ってイメージしやすく説明しています.

Convolutional Neural Networkとは何なのか – Qiita

解読

ちょっとしたユーティリティ関数

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)

def conv2d(x, W):
  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x):
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
                        strides=[1, 2, 2, 1], padding='SAME')

あとで説明しますが畳み込み層とプーリング層は2個ずつ用意します.同じ作業を繰り返すことになるのでユーティリティ関数を作っておきます.weight_variableではノイズの乗った重み行列,bias_variableではバイアスベクトルを作っています.weight_variablesにノイズを乗せておく理由は重み行列が均等に同じ値だと勾配消失問題が起こるからだそうです.

/*勾配消失問題は深層にした時に上の層で勾配が数学的には存在するが小さすぎて計算機上から扱えなくなる問題です.重みの対称性とどう関係があるんだろう…*/

conv2dでは畳み込み層の計算方法を定義しています.ここでtf.nn.conv2dで渡しているstridesがストライドの値になりますが,4次元のベクトルになっています.stridesは[入力高さ,入力幅,入力チャンネル数,出力チャンネル数]のフォーマットです.チャンネルは入力層ではRGBAみたいなもの,以降の層では各重みベクトルと畳み込まれた画像データの集合だと思っています/*違っていたら教えてください*/.これを1にするということは色データが同じサイズの別の画像で与えられたとしても別々に畳み込むことを意味します(もっともMNISTでは濃淡が一枚の画像データに記録されているだけなので今回は関係ありません).paddingは畳み込みの領域が画像をはみ出すときはどうしたら良いのかを表す引数で,’SAME’を指定すると畳み込み結果が元画像と同じサイズになるような幅だけ画像の周囲を0で埋めることになります.MNISTで5×5のフィルタを用いているのでパディング幅は4になります.max_pool_2x2は2×2の領域で最大プーリングを行う層を定義する関数です.ksizeがプーリング領域の指定,stridesでその領域の動かし方を指定します.paddingはなんのためにある引数なのか正直良くわかりませんでした.

畳み込み層とプーリング層を重ねる

W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])

x_image = tf.reshape(x, [-1,28,28,1])

h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])

h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)

1,2行目で畳み込み層のフィルタとバイアスの宣言,4行目で入力層の宣言をしています.フィルタは5×5で32個用意しています.入力層は28×28の画像1チャンネル分なので[-1,28,28,1]という指定になります.次に実際の計算の仕方を定義しています.単純なソフトマックス回帰と異なっているのは活性化関数指定がtf.nn.reluになっていること,matmulがconv2dに置き換わっていることです.h_pool1で最大プーリングし,フィルタに畳み込まれた28×28画像を14×14に縮小します.こうして14×14画像32枚を得たあと再び畳み込み層に入力します.今回のフィルタの宣言は[5,5,32,64]となっています.これは5×5のフィルタを32枚の画像をチャンネルとみなして(色情報が32次元になったイメージ.もちろん本当の色情報ではありませんが)適用し,64個のフィルタに対して14×14画像64枚を得ます.さらにh_pool2で一枚あたり7×7に縮小して最終的に7×7画像64枚を得ます.

全結合層を定義する

W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])

h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

全結合層のユニット数は適当に1024に決めます.W_fc1で7×7×64次元の重みベクトルを1024個生成します.同様にバイアス項も1024個作ります.h_pool2_flatで7×7画像64個をひとつの7×7×64次元ベクトルに変形し,h_fc1でrelu活性化関数に通して1024個のノードの値を計算します.ここの表記は前回のソフトマックス回帰と同じです.

Dropout

keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

過学習を防ぐ方法としてランダムに選んだノードを訓練事例ごとに無効化するDropoutという手法が用いられます(詳細は過去エントリや他サイトを参照してください).これを利用するためにkeep_probというノードを一個追加します.Dropoutを使うときは結果をα倍しないといけないといった留意点が存在したと思いますが,tf.nn.dropoutを使うだけでノードの遮断からα倍までやってくれます.手軽ですね.

読み出し層の定義

W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])

y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

全結合層の出力をソフトマックス回帰します.これに関しては前回のエントリで説明したとおりです.

トレーニングと性能評価

cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
sess.run(tf.initialize_all_variables())
for i in range(20000):
  batch = mnist.train.next_batch(50)
  if i%100 == 0:
    train_accuracy = accuracy.eval(feed_dict={
        x:batch[0], y_: batch[1], keep_prob: 1.0})
    print("step %d, training accuracy %g"%(i, train_accuracy))
  train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})

print("test accuracy %g"%accuracy.eval(feed_dict={
    x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))

今回のトレーニングはAdamを使って学習率を制御しながら確率的勾配降下を用いてクロスエントロピーを最小化します.forループの実行に非常に時間がかかるのでiが100で割り切れるごとに訓練集合に対する適合度を表示して暇を潰します.train_step.runではkeep_probに値を指定してやる必要があるようです.ここに指定する値は経験とか勘になるんでしょうか.最終結果を最終行で表示します.

実行結果

Screenshot from 2016-02-21 15-54-54

99.2%の正答率が得られました.チュートリアル作成者いわく「最新技術程ではないけど認めてやっても良い」水準だそうです.

雑感

ディープというほど深くはない(計6層)ですがそれなりの結果をこれだけ手軽に(一切数学的な操作をせずパラメータ調整も特になしに)得られるのはなかなか興味深いですね.ただし,今回自分のノートPC(CPU:Intel(R) Core(TM) i7-3630QM CPU @ 2.40GHz,RAM:8GB)で動かしてみた結果ソフトマックスは一瞬,CNNは50分かかりました.InteractiveSessionをやめて予めグラフを構築する方法を取れば少しは早くなるのかと思いますが,高負荷な画像処理はやはり強いGPU載せたマシンでないと実用には堪えないだろうと思います.次回はチュートリアルをすすめるか自己符号化器の実験をしようと思います.

広告

コメントを残す

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

WordPress.com ロゴ

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

Twitter 画像

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

Facebook の写真

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

Google+ フォト

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

%s と連携中