読者です 読者をやめる 読者になる 読者になる

TensorFlow "高度な" MNISTチュートリアル2

前半はこちら。

pmonty.hatenablog.com

※前半(上記リンク先)のコードが入力されていないと、以下のコードは実行できません。ご注意ください。

※最下部に、対話型環境で実行可能なコードをまとめています。コピペにて走ります。 

 

 

原文はこちら

Deep MNIST for Experts  |  TensorFlow

 

TensorFlowの学習のため、チュートリアルの翻訳を行いました。

誤っている点があれば教えてください。

 

 

ディープニューラルネットワークの作成 *1 

 

重みの初期化

 

多層のニューラルネットワークを実装します。それぞれの層が重み W と バイアス b を持っています。

0勾配を予防するために(to prevent 0 gradient)、少量のノイズで重みを初期化する必要があります*2

今回、活性化関数に ReLU*3を使っているので、小さな正の値で初期化するのが有効とのこと。

 

ニューラルネットの構築に便利な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)

 

 

畳み込みとプーリング

 

TensorFlow では、関数を用いて柔軟に畳み込み(convolution)とプーリング(pooling)を行うことができます。 今回は、非常にシンプルな方法(vanilla version バニラ (ソフトウェア) - Wikipedia )を使っているとのことです。

ストライドは 1 で、 0 をパディングを行います (zero padded) *4。アウトプットはインプットと同じサイズです。

プーリングも、昔ながらの(plain old) 2x2 マックスプーリングです。

 

関数として定義します。

 

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

 

 

第一の畳み込み層(First Convolutional Layer)

 

第一層を実装します。畳み込み層とマックスプーリング層からなります。

畳み込みでは 5x5 の大きさのパッチを使い、32 の 特徴マップ(feature map)を計算します。

テンソルの形は、 [5, 5, 1, 32] となります。最初の二つの次元がパッチサイズ、次に入力するチャンネル数、最後に出力のチャンネル数です。

 

同様に、バイアスも作成します。

 

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

 

レイヤー(畳み込み+プーリング)を適用するために、MNIST の画像を変形します。幅と高さに対応する 2,3 番目の次元、カラーチャネルの数に対応する最後の次元を設定し、4次元のテンソルに変形します。

 

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

 

x_image に対して 重みテンソル(W_conv1)で畳み込み、バイアス(b_conv1)を加え、活性化関数(ReLU)を適用。

マックスプーリングを行います。

 

画像の画素数は 14x14 に縮小されます。

 

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

h_pool1 = max_pool_2x2(h_conv1)

 

 

第2の畳み込み層

 

この層でも、5x5 のサイズのパッチを使ます。

前の層で計算した32 の特徴マップを使い、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)

 

7×7に縮小された画像から、画像全体を処理できる、1024個のニューロンを備えた完全連結レイヤーを追加します。

プール層のテンソルを、ベクトルのバッチに再構成し、活性化関数に ReLU を適用します。

 

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)

 

過学習(overfit)を防ぐために、ドロップアウトを使います*5

ドロップアウトを適用する時、ニューロンからのアウトプットが保たれる確率を新たに設定した placeholder に保持します。これにより、学習中にはドロップアウトをオンに、テスト中にはオフにすることができます。

TensorFlow  の tf.nn.dropout 関数は、マスキングに加えてニューロンのスケーリングを自動的に処理するため、ドロップアウトは追加のスケーリングを行わずに動作します*6

 

keep_prob = tf.placeholder(tf.float32)

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

 

 

読み出し層(readout layer)

 

W_fc2 = weight_variable([1024, 10])

b_fc2 = bias_variable([10])

 

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

 

 

モデルのトレーニングと評価

 

モデルをトレーニングして評価するために、上記の単純な一層の Softmax ネットワークとほぼ同じコードを使います。

 

違いは

 Optimizer として、SGD の代わりに、より洗練された Adam を使います。

 ドロップアウトを制御するするパラメータ、Keep_prob を feed_dict 内に設定します。

 100回のトレーニング毎にログを表示します。

 

次のコードを実行してみてください。ただし、20000 回のトレーニングを行うと、(コンピュータによりますが)恐らく最大で30分程度の時間がかかります*7

 

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

 

このコードを実行した後、精度は最終的に約 99.2% となります。

 

 

 

*******************************************************

コードのまとめ

import tensorflow as tf

 

from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

 

## 時間経過の測定
import time

start = time.time()

time.ctime(start) 

 

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

 


x = tf.placeholder(tf.float32, shape=[None, 784])
x_image = tf.reshape(x, [-1,28,28,1])
y_ = tf.placeholder(tf.float32, shape=[None, 10])
W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))
y = tf.matmul(x,W) + b

W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
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(labels=y_, logits=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 = tf.InteractiveSession()
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}))

 


## 時間経過の表示

print("start time = " + (time.ctime(start)))
print("fin time = " + (time.ctime(time.time())))
elapsed_time = time.time() - start
print ("elapsed_time:{0} sec".format(elapsed_time))

 

 

 

 

 

*1:Build a Multilayer Convolutional Network

*2:初期値が 0 など偏った値だと、学習が上手く進みにくいらしい

*3:Rectified Linear Unit: ゼロ以下は0を返し、0を超える数値はそのままの値を返す。ニューラルネットワークの層が深くなっても学習がうまくいきやすい。以前使われていた "Sigmoid" 関数では、層が深くなると学習が上手くいかなくなる問題があった

*4:ストライド・パディングについては別途ご確認ください。たとえばこの本に、大変詳しく書いてあります

*5:ドロップアウトについては別途ご確認ください。たとえばこの本に、詳しく書いてあります

*6:すみません、直訳です。私は意味が分かりませんでした。原文:TensorFlow's tf.nn.dropout op automatically handles scaling neuron outputs in addition to masking them, so dropout just works without any additional scaling.

なお、更にコメントとして「この小さな畳み込みネットワークでは、精度はドロップアウトの有無にかかわらず同程度です。ドロップアウトは非常に大きなネットワークを訓練する場合に有用です。と記載されていました

*7:私のノートパソコンでは、1時間15分程度かかりました。CPU: 6 Gen Core i7, 20GB RAM, SSD, accuracy は 0.9916 でした