方針と目標
今回はtensorflowの用いて手書き文字を認識することであるMNISTを取り扱って行く。
MNISTの方針として深層学習である畳み込みニューラルネットワークを用いる
流れとして、入力画像に対して畳み込みニューラルネットワークを用いて画像を処理しやすいように加工し
その画像に対して関数を与え数字が何か識別して行く。
MNIST
MNISTは,0~9までの手書き文字画像のデータセットとなる。
このデータセットには、画像サイズ $28 \times 28$ のデータがトレーニングデータとして60000件、テストデータとして10000件含まれている。
コード
# coding: utf-8
# tensorflowの読み込み
import tensorflow as tf
# データの読み込み
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data',one_hot=True)
# interactiveセッションを起動
sess = tf.InteractiveSession()
# x, y_のplaceholderを定義
x = tf.placeholder(tf.float32,shape=[None,784])
y_ = tf.placeholder(tf.float32,shape=[None,10])
# 関数を定義
## 重みの初期化処理関数
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)
## 2次元の畳見込み処理関数(ストライド1, ゼロパディング)
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')
# 1回目
## 1回目の畳み込みで用いるW(重み付け値)
## ([width, height, input, filters] 28×28の画像1(input)枚に対して5(width)×5(height)の32種類(filters)の畳み込みフィルターでそれぞれ畳み込み)
W_conv1 = weight_variable([5,5,1,32])
## 1回目の畳み込みで用いるb(バイアス)
b_conv1 = bias_variable([32])
# 入力の画像。畳み込みを行うためにテンソルに戻している。
# -1はをreshapeに設定することで実行時に動的に値が決まる
x_image = tf.reshape(x, [-1,28,28,1])
# 1回目の畳み込み
h_conv1 = tf.nn.relu(conv2d(x_image,W_conv1) + b_conv1)
# 1回目のプーリング
h_pool1 = max_pool_2x2(h_conv1)
# 2回目
## 2回目の畳み込みで用いるW(重み付け値)
## ([width, height, input, filters] 14×14の画像32枚に対して5×5の64種類の畳み込みフィルターでそれぞれ畳み込み)
W_conv2 = weight_variable([5,5,32,64])
## 2回目の畳み込みで用いるb(バイアス)
b_conv2 = bias_variable([64])
## 1回目のプーリングで出力されたテンソルを使って2回目の畳み込み
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
## 2回目のプーリング
h_pool2 = max_pool_2x2(h_conv2)
# 全結合層(全ての入力を1つにまとめてsoftmax関数に入力)
## 全結合層で用いるW(重み付け値)
W_fc1 = weight_variable([7*7*64,1024])
## 全結合層で用いるb(バイアス)
b_fc1 = bias_variable([1024])
## 2回目の畳み込みで出力されたh_pool2を2次元のテンソルに変換
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)
## 全結合層へのDropOutで用いるW(重み付け値)
W_fc2 = weight_variable([1024,10])
## 全結合層へのDropOutで用いるb(バイアス)
b_fc2 = bias_variable([10])
# 最終的な予測を出力
y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2 //回帰モデル
# 最小化したいcross_entropyを定義
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(y_conv,y_))
# AdamOptimizerを用いた最小化のステップを定義
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
# このあたりはBegginerと同じ
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())
# ミニバッチで学習を実行(50をランダムに取って最適化を50回繰り返す)
for i in range(20000):
batch = mnist.train.next_batch(50)
# 100回ごとに経過を出力
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}))
tensorflow独特のもの
* プレースホルダー
x = tf.placeholder("float", shape=[None, 784])
y_ = tf.placeholder("float", shape=[None, 10])
プレースホルダとは実際のデータを後から挿入するために、仮に確保した領域のこと→データは未定のままグラフを構築し、具体的な値は実行するときに与える。
プレースホルダーを利用するケースとして、ファイルから読み込んだデータをグラフに与えて処理する場合が考えられる。
xは入力される画像のことで、ここではMNISTの出力次元が28 x 28の計784次元から構成されている
y_は出力次元のことで数字の0~9までの値なので計10次元から構成されるため、この次元分の領域を確保しておく。
Noneについては本来ここにバッチサイズが入るが、この時点では未定のためNoneと指定ある。
* 重みとバイアス
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の切断正規分布
→重みは畳み込みの処理を行う際のフィルタとして扱われる -
バイアス:0.1の定数
入力の変形
x_image = tf.reshape(x, [-1, 28, 28, 1])
畳み込み、プーリングの処理を行うために、入力データの次元を定義した関数が扱えるように変形する必要がある。
今回の場合第二引数の配列である4次元に変形している。
第一引数は入力データを表している。
配列の第0要素に-1を指定することで、多次元配列の要素数のつじつまがあるような要素数をまとめることができる、
第2要素、第3要素でネットワークに入力するために必要な高さ、幅に変換している、
第4要素でチャンネルを指定しており、MNISTの画像はグレースケースのため、すなわち1チャンネルであると考えることができるため1と指定してある。仮にRGBで構成された画像を入力する場合は3チャンネルと指定する。
畳み込み層
def conv2d(x, W)
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
-
第一引数:入力データ
→[データ数、入力データの高さ、入力データの幅、チャンネル数] -
第二引数:フィルタ
-
第3引数:ストライド
→フィルタを適用する位置の関係[1,縦ストライド,横ストライド,1]
ここにおける0と3の要素は1で固定する -
第4引数:paddingのこと
→畳み込み層の処理を行う前に入力データの周囲に固定データを埋める処理
だいたい0で行う
プーリング層
def max_pool_2x2(x, W)
return tf.nn.max_pool(x, kside=[1, 2, 2, 1], strides=[1, 2, 2,
1], padding='SAME')
プーリング層とは縦・横方向の空間を小さくする演算
maxプーリングとは最大値をとる演算である。
一般的にプーリングのウィンドウサイズとストライドは同じ値に設定する
-
第一引数:入力データ
→畳み込み層からの出力データ -
第二引数:プーリングサイズ[1,縦プーリング,横プーリング,1]
-
第三引数以降は畳み込みと同じになる
第一畳み込み層とプーリング層について
- 第一畳み込み
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) ##フィルタとバイアスを画像につけて畳み込みを行う
W_conv1にて5x5のフィルター設定をしている。入力は白黒画像なので1チャンネルで、出力は32チャンネル(32チャンネルの特徴をコンピュータに抽出)にしている。
こでReLU関数を適用することで、コンピュータに特徴として抽出されない部分、すなわち0及び負の値を全て0にして出力する。
- プーリング
h_pool1 = max_pool_2x2(h_conv1) ## プーリング処理
第1プーリング層では、第1畳み込み層から得られた出力に対し、上で定義した関数を用いてプーリングを行う。 ここでReLU関数によって0となった特徴として抽出されない部分が削減され、第1プーリングを行った時点では、$14\times 14$サイズ、32チャンネルのデータとなっている。
第2畳み込み層とプーリング層について
W_conv2 = weight_variable([5, 5, 32, 64]) b_conv2 = bias_variable([64]) h_conv2 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) h_pool1 = max_pool_2x2(h_conv1)
第1畳み込み層と同じ処理を行っており、異なる点は $14 \times 14$サイズの32チャンネルのデータとなっている。これより64チャンネルの特徴を抽出している。
第2プリーング層においても同様の処理を行っており、第2プーリングを行った時点で $7 \times 7$サイズの64チャンネルのデータとなっている。
第一全結合層
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)
第1全結合層では、畳み込み層とプーリング層においては、データを4次元で処理していたが、実際はどれに分類される画像であるか判断するために、画像全体をまとめて処理する必要があるため、1次元のベクトルに直す処理を行っている。
ドロップアウト
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
過学習を抑制するためドロップアウトを用いる。
ドロップアウトとはニューロンをランダムに消去しながら学習する手法である。
消去されたニューロンは、信号の伝達が行われなくなる。なお、訓練時には、データが流れるたびに、消去するニューロンをランダムに選択する。 そして、テスト時には全てのニューロンの信号を伝達するが、各ニューロンの出力に対して、訓練時に消去した割合を乗算して出力する
第2全結合層(読み出し層)
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)
第2全結合層では、第1全結合層で得られたベクトルにsoftmax関数を適用している。 これにより、得られた訓練データが、MNISTの例においては、どの数字に当てはまるか、すなわちどの正解ラベルに分類される確率が高いかを判断する。
モデルの訓練と評価
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})
交差エントロピー誤差(cross entropy error)が損失関数として用いられている。
最適化手法としてはAdamと呼ばれるものが用いられ、この場合の学習係数は0.0001である。最適化を行う際には、微分計算が関わることがあるが、tf.Optimizerクラスでは、微分計算式をユーザーが記述することなく行うことができる。
この場合の学習回数は、20,000回である。 また、ニューラルネットワークにはバッチ処理というテクニックがあり、バッチには、「束」という意味があり、即ち、データをいくつかの束に分けて処理することで、処理時間を短縮することができる。ここでは、バッチサイズを50として処理を行っている。 尚、この条件での学習の正確性は、約99%程度になることが示されている。
ニューラルネットワークの終わった後
参考にさせてもらったサイト
日本語コメントつきTensorFlow MNIST Begginer/Expertのコード
MNISTを用いた畳み込みニューラルネットワーク