TensorFlowのDeep MNISTを読み解いてみる

この間は MNIST For ML Beginners を振り返ったので、今回は Deep MNIST for Experts を読んでいきます。

Softmaxによる回帰モデル

この項は For ML Beginners とほぼ同じで、少し説明が詳しいだけなので気になったところだけ。

Placeholder

TensorFlow を見たときから自分の中で謎が深かった placeholder から回帰モデルの説明は始まります。

特定の値が決まっておらず、実行時に代入してプログラムを動かすための変数みたいなもの、という理解は変わらず。

前回よくわからなかった None については、バッチサイズに対応する第一の次元は任意のサイズになりうることを示していると書かれていますが、やはりピンときません。

placeholder を使うとき shape の指定は任意ですが、よくバグの温床になるらしく、結局指定しなければならないようです。

Multilayer Convolutional Network

softmax による回帰では 92% の精度しか得られず、10 文字書いたら 1 文字くらい認識できません。複雑な画像認識の精度を考えると、それでも高い気はしますが…

ここでようやく畳み込みニューラルネットワーク (Convolutional Neural Network) の出番です。認識の精度は 99% を上回ります。

重みとバイアス

重み W とバイアス b を初期化します。重みは要素の標準偏差 0.1 に、バイアスはすべての要素を 0.1 にします。

活性化関数に ReLU を用いる場合、バイアスは softmax のときのようにゼロベクトルにするよりも、僅かに正の値をとっているほうがいいようです。

畳み込みとプーリング処理

畳み込みとプーリングの処理を記述します。ここら辺も関数が用意されてるので細かいことを考える必要はありません。それでブラックボックス化に繋がってるんですが。

ストライドは 1 、つまり一画素ずつずらして処理していきます。また、ゼロパディングすることで画像サイズが小さくならないようにしています。

畳み込み層 第一層

パッチは 5×5 の正方形として、畳み込みと最大値プーリング処理をします。

重みの shape は [PATCH_SIZE, PATCH_SIZE, INPUT_CHANNEL, OUTPUT_CHANNEL] となります。OUTPUT_CHANNEL が 32 である理由は明示されていませんが、バイアスもそれに合わせます。

畳み込みするために、x も reshape します。第二、第三要素は幅と高さ、第四要素はカラーチャンネルです。第一要素のマイナスって何…

畳み込み層 第二層

第一層と似ていますが、入出力のチャンネル数が異なります。第一層の出力を第二層の入力にしていることを考えれば当然です。

入出力層の間に何層か重ねて学習させることでよりよいモデルを作るディープラーニングの特徴ともいえそう。

全結合層

画像全体を処理するために、1024 個のニューロンを持つ全結合層に繋ぎます。

ここに到達したとき、画像サイズは畳み込み層を経るごとに 1/2 されていくので、最初の 28×28 の入力は 7×7 まで小さくなっています。

入力は第二層のプーリング層の出力で、これは 2 次元 Tensor なので、それを 1 次元ベクトルに変換します。そうしないと W との内積が 2 次元 Tensor になり、1 次元ベクトルであるバイアスと足し合わせることができません。

ここで活性化関数 ReLU をかませています。For ML Beginners ではシグモイド関数の多変量版として softmax 関数を用いてましたが、ReLU は多変量に対応してるという解釈でいいんでしょうか?

ドロップアウト

モデルがデータに従いすぎるのを防ぐために、結果の一部は敢えて学習しないという手法を用います。

チュートリアルのソースコードに従うと、学習に用いる確率は placeholder にしておいて、実行時に値を決めます。

出力層

ドロップアウトの出力と重みの内積にバイアスを足して 1 次元ベクトルを返します。ベクトルの大きさは 1 なので、確率のような感覚です。

訓練と評価

最適化関数には Adam を使います。前回は SGD を用いていましたが、Adam のほうがより早く収束することや学習率の逓減が必要ないことからこっちを使っているのかなと思います。

訓練回数は 2 万回、バッチサイズは 50 になりました。CPU で計算すると、結構時間がかかります。数時間で終わればいいですが。

ソースコード

# 重みとバイアス
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')

# 畳み込み層 第一層
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)

# 全結合層
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)

# ドロップアウト
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

# 出力層
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2

# 訓練と実行
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(y_conv, y_))
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.global_variables_initializer())
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}))

最後に

for Experts を読むと、それなりに TensorFlow の記述がわかってくるような気がしないでもないですね。

TensorFlow Mechanics 101 で MNIST 以外のモデルでの構築などに触れているようなので、得るものがあればまた書き残しておきたいと思います。