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

tensorflow

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

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になるのを避けるために重みの初期化時にノイズを付ける。 tf.truncated_normal正規分布で、μ±2σ範囲内のランダムな値を返す。 以下の例だと、meanのデフォルトが0.0なので、正規分布 N(0, 0.01)の、-0.2<=x<=0.2な値がランダムに返ることになる。

また、ReLU(Rectified Linear Unit, 正規化線形関数)ニューロンを使うので、“死んだニューロン"を避けるために、バイアスは小さな正の値で初期化する。

ニューラルネットワークと活性化関数 - sambaiz-net

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ということになる。 それぞれのチャンネルに対してのバイアスがb_conv1。

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