TensorFlow チュートリアル2(Deep MNIST for Experts)

(2016-07-12)

前回に引き続き、まとめながら進めていく。

Deep MNIST for Experts

Start TensorFlow InteractiveSession

今回は、前回のようにグラフを作成してからSessionを開始する代わりに InteractiveSessionを使う。 グラフを作成し実行するのをインタラクティブに行うことができ、IPythonのような環境で便利だ。

import tensorflow as tf
sess = tf.InteractiveSession()

Build a Multilayer Convolutional Network

前回のシンプルなモデルでは、あまり良い結果が出なかった。 そこで、今回はもう少し洗練されたモデル、小さな畳み込みニューラルネットワークを作成する。

Weight Initialization

このモデルを作成するためには、たくさんの重みとバイアスを作成する必要がある。

重みは、対称性を破り、勾配0を避けるために、少しのノイズで初期化すべきだ。

また、ReLU(Rectified Linear Unit, 正規化線形関数)ニューロンを使うので、”死んだニューロン”を避けるためにわずかな正の値のバイアスで初期化すると良い。

tf.truncated_normal正規分布で、μ±2σ範囲内のランダムな値を返す。 以下の例だと、meanのデフォルトが0.0なので、正規分布 N(0, 0.01)の、-0.2<=x<=0.2な値がランダムに返ることになる。

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)

Convolution and Pooling

TensorFlowは柔軟な畳み込みとプーリングの手続きを提供している。

畳み込みというのは、画像に対してフィルターを少しずつ動かしながら掛けていく処理のことだ。このページが分かりやすい。 例えば、ソーベルフィルタで輪郭になっているところを抽出するように、 フィルターの値によって、その区域における、ある特徴を際立たせたりすることができる。 今回はこのフィルターが重みとなり、際立たせたいのはその数字を識別するための特徴ということになる。 前回は画像を一次元の配列として扱い重みを学習していたので、縦の情報が失われていたが、この方法ではそれがない。

プーリングというのは画像から区域ごとにサンプリングする処理だ。最大プーリングや、平均プーリングなどの手法がある。 畳み込みのように順番に区域を見ていって、最大プーリングならそのうちの最大のものを採用し、他のものは無視する。 サイズが小さくなるだけではなく、ちょっとした位置のずれを吸収することができる。

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

tf.nn.conv2d畳み込みを行う。 主な入力は[画像の数, 縦サイズ, 横サイズ, チャンネル数(色とか)]の画像と、 [縦サイズ, 横サイズ, 入力チャンネル数, 出力チャンネル数]のフィルターだ。 stridesは一度にどれくらいフィルターを動かしていくかで、strides[1]が縦、strides[2]が横に動かす量だ。 strides[0]strides[3]は1でなくてはならない。 paddingは”SAME”か”VALID”から選択できるパディングについての設定だ。詳細は ここ に書いてある。

tf.nn.max_pool最大プーリングを行う。 ksizeは入力のそれぞれの次元に対応した見ていく区域のサイズだ。[1, 2, 2, 1]ならそれぞれの画像を2*2ずつ見ていくことになる。

First Convolutional Layer

最初のレイヤーは、畳み込みと最大プーリングで構成される。 畳み込みはそれぞれ5*5の32チャンネル出力のフィルターでされる。 つまり、重みであるフィルターは[5, 5, 1, 32]のtensorということになる。 そして、それぞれのチャンネルに対してバイアスが存在する。

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

画像に対して、畳み込みを適用するするためにtf.reshape変形する 必要がある。-1というのは特別な値で、他の次元との積が合計が元のものと変わらないように決定される。

x = tf.placeholder(tf.float32, [None, 784])
x_image = tf.reshape(x, [-1,28,28,1])

これで畳み込めるようになったので、画像と重みを畳み込み、バイアスを足したものにReLUを適用した後、最大プーリングする。

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

Second Convolutional Layer

deepネットワークにするために最初のレイヤーのようなものをいくつか重ねる。 2番目のレイヤーでは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)

Densely Connected Layer

2つのレイヤーを経て元々28*28だった画像は7*7にまで削減された。 2つめのレイヤーの出力は64チャンネルだったので、7*7*64次元のデータになっている。 このレイヤーでは、これらにそれぞれ1024のニューロンを結びつける。

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)

Dropout

過学習を防ぐために ドロップアウト を行う。 ドロップアウトというのは訓練データごとにニューロンを何割か無視することだ。

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

Readout Layer

最後にsoftmaxでそれぞれの数字である確率を求める。

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)

Train and Evaluate the Model

今回は前回使ったGradientDescentOptimizerよりも洗練されているAdamOptimizerというのが使われている。

y_ = tf.placeholder(tf.float32, [None, 10])
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y_conv), reduction_indices=[1]))
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}))

これを実行するとこんな出力が得られた。 かなり時間がかかったのでここで打ち切ったが、およそ99.2%の正解率になるらしい。 前回が92%だったのに比べてもすごく良い値に見える。

step 0, training accuracy 0.12
step 100, training accuracy 0.8
step 200, training accuracy 0.9
step 300, training accuracy 0.9
step 400, training accuracy 0.98
step 500, training accuracy 0.88
step 600, training accuracy 0.98
step 700, training accuracy 0.98
step 800, training accuracy 0.9
step 900, training accuracy 1
step 1000, training accuracy 0.98
step 1100, training accuracy 0.98
step 1200, training accuracy 1
step 1300, training accuracy 0.98
step 1400, training accuracy 0.94
step 1500, training accuracy 0.98