Edited at

MNIST for Experts (TensorFlow)

More than 3 years have passed since last update.

TensorFlow tutorialsについてまとめていく


扱うデータ

MNIST for ML beginnersと同じものを扱うのでそちらを参照

[MNIST for ML Beginner]http://qiita.com/tuno-tky/items/cf7068f14ceea08802e8


扱う手法

for beginnersの手法に加えて

畳み込み

プーリング

ドロップアウト

を用いる。


畳み込み

W×W画素の画像があった時に、それぞれの画素をx_{ij}(i,jは0からW-1までの整数, x_{ij} ∈ R)と表す。

それより小さなH×H画素の画像(フィルタと呼ぶ)を考え、それぞれの画素をH_pq(p,qは0からH-1までの整数, H_{pq} ∈ R)とした

u_{ij} = \sum_{p=0}^{H-1}\sum_{q=0}^{H-1} x_{i+p,j+q} h_{pq}

を計算したものであり、この畳み込みはフィルタの濃淡パターンと類似した濃淡パターンが画像上どこにあるかを検出する働きがある。

このu_ijに活性化関数を適用したものが畳み込み層の出力となる。


パディング

畳み込み行うと、本来、もとの画像からフィルタがはみ出す場合は計算しないので、画素数はW×Wから(W - 2floor(H/2))×(W - 2floor(H/2))となる(floorは小数点以下を切り捨て、整数化する関数)

もとの画像と畳み込みを行った後の画素数を同じにしたい場合のためにもとの画像の外側にfloor(H/2)の画素の"ふち"を何らかの値で付け足したものをパディングと呼ぶ。


ストライド

フィルタを1画素ずつずらして畳み込みを計算するのではなく、数画素ずつずらして計算する場合もあるので、フィルタをずらす画素の間隔をストライドと呼ぶ。


チャネル

一つの画素はグレースケールの場合は1つの値のみであるが、カラーのRBGでは3つの値をもち、一般に書く画素は複数の値を持つ。その値の数をチャネルと呼ぶ。チャネル数がKのとき画像の大きさをW×W×Kなどと表す。

入力画像にK個のチャネルがある場合にフィルタをM個用意すると、1つのフィルタによる畳込みをチャネル毎に行った後、画素ごとに全チャネルについてその値を加算し、活性化関数を適用し、そのフィルタの出力とする。



活性化関数適用前のu_{ijm}は一層前の出力z^{l-1}とフィルタの画素hとバイアスbを用いて以下の様に書ける。

u_{ijm} = \sum_{k=0}^{K-1}\sum_{p=0}^{H-1}\sum_{q=0}^{H-1} z_{i+p,j+q,k}^{l-1}h_{pqkm} + b_{ijm}


プーリング

プーリング層は通常畳み込み層の直後に設置される。畳み込み層で抽出された特徴の位置感度若干低下させ、対象とする特徴量の画像内での位置が若干変化した場合でもプーリング層の出力を不変にする役割を持つ。

入力画像上で画素(i,j)を中心とするH×Hの正方領域をとり、その中の画素の最大値を出力するのが最大プーリング、平均値を出力するのが平均プーリングである。

通常はこれには活性化関数は適用しない。


ドロップアウト

学習時に、多層ネットワークのユニットをランダムに選別して学習させることで、学習時のネットワークの自由度を小さくし、過適合(誤差関数の極小値のうち、比較的値の大きな極小値にトラップされている状態)を避ける。


ソースコードの解説


流れ

1.Weightの初期化

2.畳み込みとプーリングの設定

3.第一畳み込み層の設定

4.第二畳み込み層の設定

5.全結合層の設定

6.読みだし層の設定

7.モデルの学習と評価


詳細


重みの初期化

モデルの作成の為に多くのweightとbiasを作成する必要がある。一般に少量のノイズでweightの初期値を決定することで、勾配消失問題(シグモイド関数を用いている場合などは一層目のweightには何度もシグモイド関数がかかることで、勾配が消失してしまう)や対称性の破れ(無限小に小さなゆらぎが系の分岐の方向を決定してしまうこと)を防ぐ。今回はReLUを用いるので、わずかに正のbiasを初期値として与えるのが役に立たない回路をなくす為には、同様に良い方法であろう。



モデルの作成中に何度もこの初期化をする代わりに便利な関数を作成する

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)


畳み込みとプーリングの設定

一般的な設定とする

畳み込みではゼロパディング(縁にすべて0の画素を追加し、畳み込み後の出力と入力のサイズが同じになるようにする)を施し、ストライド1でフィルターを動かす。

プーリングは2×2画素の最大プーリングとする。

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')


第一畳み込み層の設定

5×5のフィルター32種を用いて1チャンネルの画像(1画素につき1つの値(おそらくグレースケール))を処理するので、重みのテンソルは[5, 5, 1, 32]と書かれる

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

b_conv1 = bias_variable([32])

この層に適応させるために、入力画像のxを4dのテンソルに変形する。

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

2d,3dは画像のpx数で、4dは色のチャンネル数(グレースケールなので1)

1dについての記述なし(バッチサイズっぽいのだが、そうだとすると-1がどういう意味か不明)

その後、x_imageを畳み込み、ReLU関数を作用させ、最後に最大プーリングする

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

h_pool1 = max_pool_2x2(h_conv1)


第二畳み込み層の設定

第二層では64の5×5フィルタを用い、64の特徴を計算する

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)


全結合層の設定

ここまでに画像は7×7(それぞれの画素のチャンネルは64)まで減らされています(二度の最大プーリングで28÷2÷2)。1024個の出力を行う全結合層(全ての入力と出力がニューロンで結びついている層)を作成する。

プーリング層からの出力テンソルをベクトルのバッチh_pool2_glatに変換し、weightとの積をとって、ReLU関数を適用し、biasを足したものを出力とする

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)


ドロップアウトの設定

過学習を防ぐために、読みだし層の前にドロップアウトを適用する。

ニューロンからの出力がドロップアウトの間保持される、確率を入れるためのplaceholderを用意する。これにより学習中にドロップアウトを行い、テスト中には行わないということが可能になる。

TensorFlowのtf.nn.dropout関数はニューロンを隠すだけではなく、出力の補正(隠すことでニューロンがp倍になった場合はそれぞれの出力をp倍することで、推論時のニューロン数が1/p倍に増えていることを補填する。)も行ってくれる(要は勝手にドロップアウトを実現してくれるということ)

keep_prob = tf.placeholder("float")

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


読み出し層の設定

ソフトマックス関数を適用する。

```pyhton

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)

```


モデルの学習と評価

for beginersとの違いはgradient descentでの最適化より洗練されたADAMでの最適化を行うことと、keep_prob(ドロップアウトのためのplaceholder)があることと、100回ごとにログを出すことと、2万回ループを回すことである。

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, "float"))
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})

結果は99.2%程度になるはず。