初めてのTensorflowコーディング(MNISTを用いた手書き文字の認識)

さて、本日より実際にtensorflowによる本格的なコーディングに入っていきたいと思います。

今日は、チュートリアル的な意味合いということで、公式の「MNIST For ML Beginners」を少し読み解いていこうと思います。

とてもわかってそうなことを書いていますが、機械学習の勉強を始めてまだ2週間、Tensorflowを弄り始めてまだ1週間の超初心者です。

間違いがあれば、ご指摘などいただけると私自身の成長にも繋がるため非常に助かります。よろしくお願いいたします。

MNIST For ML Beginners

さて、今日やるMNIST For ML Beginnersですが、これはTensorflowの公式サイトに、チュートリアルとして掲載されているコードとなります。

以下のページに英語ですが説明があるので、詳しいことを知りたい方は一度読んでみると良いかと思います。

参考:MNIST For ML Beginners

今回、コードを読み解くにあたり、例えば重みやバイアスであるとか、ソフトマックス関数や交差エントロピー等について、一つずつ説明して行っても良かったのですが、記事が長くなってしまうため、今回は詳しいことについてはあまり書きません(私自身が学習途上ということもあります)。

いずれは学習の意味も込めて、書ける機会があれば良いと思いますが、今回はTensorflowの文法や、コーディングの仕方について書いていきたいと思っています。

ちょっと機械学習知っていて、Tensorflowやろうと思ったけど、今一つコードの書き方がわからないよ……って人向けです。

ちょっと知っていてのレベルは、私が本当に機械学習の勉強を始めて1か月も経ってないレベルなので、ほんのちょっとです。ご安心ください。

MNISTとは?

MNIST は手書き数字のデータセットです。

以下のような数字が記されたデータセットで、Deep Learningでは超有名なデータセットです。

今回、MNIST For ML Beginnersでは、このデータセットを使って、手書き文字の認識について取り組みます。

これは、例えば「5」と書かれた画像を入力すると、「5」と認識してくれるようなモデルを作成する問題です。

MNIST For ML Beginners

わたしがTensorflowのコードを初めて見て思ったことは、どこで何をしているのかがよくわからないということでした。

Tensorflowのコードは、先に学習モデルを作成し、モデルに実際のデータを入力し、学習を実行するのは後から行います。

まずはそのことを頭に留めながら、上から順番にコードを辿っていきましょう。

今回参照するコードは以下となります(ほとんど公式と同じです)。

ちなみに、出力結果は以下となりました(最後の確率値は、実行する度に毎回変動します)。大体、91~92%程度の値が出るのではないでしょうか。

Extracting /tmp/tensorflow/mnist/input_data\train-images-idx3-ubyte.gz
Extracting /tmp/tensorflow/mnist/input_data\train-labels-idx1-ubyte.gz
Extracting /tmp/tensorflow/mnist/input_data\t10k-images-idx3-ubyte.gz
Extracting /tmp/tensorflow/mnist/input_data\t10k-labels-idx1-ubyte.gz
0.9168

それでは、コードの各行を少し詳しく見ていきましょう。

① データセットのインポート

最初に見ると、非常にわかりにくい1行かと思います。

# Mnistに使うデータセットをインポートする
mnist = input_data.read_data_sets('/tmp/tensorflow/mnist/input_data',
  one_hot=True)

ここでは、先ほど説明したMNISTのデータセット(画像や輝度値の数字列)を「mnist」に代入していると考えてください。

MNISTのデータセットは55000枚のトレーニング画像と、10000枚のテスト画像と、検証用の5000枚の計70000枚から成ります。それらが全て「mnist」の中に入っているとお考え下さい。

何故、これが1行でできてしまうのか? と不思議に思うかもしれませんが、カラクリは簡単で、MNISTのデータセットを代入する「read_data_sets」という関数が用意されていて、その関数の中で全て処理をしているからです。

なので、実際に自分で用意した大量の画像とかを、このようにたった1行のコードでインポートできてしまうというわけではありませんので、そこは注意してください。

気になる人はread_data_setsの中身も読んでみると面白いと思いますが、ここでは深追いはしないでおきましょう。

さて、この「mnist」には何が入っているのでしょうか。

先ほども少し触れましたが、このmnistの中に「train」「validation」「test」という画像のセットが入っていて、例えば「mnist.test.images」と書くことで、test用のデータセットの画像にアクセスすることが可能です。

② モデルの作成

# モデルの作成
x = tf.placeholder(tf.float32, [None, 784]) # 入力するPlaceholder
W = tf.Variable(tf.zeros([784, 10])) # 重み
b = tf.Variable(tf.zeros([10])) # バイアス
y = tf.matmul(x, W) + b #内積計算とバイアスの加算

それでは、モデルの作成部分に入ります。

Tensorflowでは、まずモデルを設定してから、そのモデルを使って学習やテストを行います。

特に、この中でまずTensorflowのコーディングという意味で是非覚えたいのが、最初の1行に登場する「placeholder」です。

placeholderとは何かと言いますと、関数で言うところの引数のようなものです。もう少し平たく言えば、データが格納される予定の箱です。

Tensorflowでは、まずモデルを設定してから、そのモデルを使って学習やテストを行うと書きました。なので、学習やテストを行うときの実行文は、当然これよりも後に出てきます。

そこで学習を実行するときに、学習に用いる画像のデータをモデルに渡さなければなりません。それを受け取る部分を、今は箱にしておこうというわけです。

この部分を、もう少し細かく見ていきましょう。

「tf.float32」は想像がつくかもしれませんが、入ってくる値のデータ型です。今回はfloat型が入ります。

では、[None, 784]はなんでしょうか?

MNISTのデータセットでは、1枚の画像サイズが28×28のサイズを持っています。28×28 = 784なので、後半の784は1枚の画像のサイズを意味します。

そして、Noneが一番わかりにくいのですが、Noneとは「任意の大きさを持つ」という意味で使われています。

すなわち、(1,784)の行列かもしれないし、(2,784)の行列かもしれないし、(100,784)の行列かもしれないし……ということです。

実行時に箱に入れられるものによって変わってくるため、定義の時点では柔軟性を持たせているような形になっています。

Deep Learningの学習では、1枚ずつ学習を行うのではなく、50枚や100枚などのまとまった画像を一度に入れて学習を行う、バッチ学習が行われます。

このバッチのサイズを簡単に変化させられるように、入り口は「None」で未確定になっているというイメージです。

注意したいのは、以下の2行はあくまでも変数を定義している部分であるということです。

W = tf.Variable(tf.zeros([784, 10])) # 重み
b = tf.Variable(tf.zeros([10])) # バイアス

C言語で言えば「int a=0」と一緒です。

よって、

W = tf.Variable(tf.zeros([784, 10])) # 重み
b = tf.Variable(tf.zeros([10])) # バイアス
x = tf.placeholder(tf.float32, [None, 784]) # 入力するPlaceholder
y = tf.matmul(x, W) + b #内積計算とバイアスの加算

別にこう書いても動きますし

b = tf.Variable(tf.zeros([10])) # バイアス
W = tf.Variable(tf.zeros([784, 10])) # 重み
x = tf.placeholder(tf.float32, [None, 784]) # 入力するPlaceholder
y = tf.matmul(x, W) + b #内積計算とバイアスの加算

こう書いても動きます。

a+bをするまでにaとbは定義してないといけないけれども、aやbを定義するのはどこでも良いのと同じですね。

③ 正解を入力するPlaceholderを作成する

y_ = tf.placeholder(tf.float32, [None, 10])

この部分では再度placeholderが登場しますが、「y_」は正解ラベルを表しています。

具体的には、正解のみが1になっていて、他は0になっている配列とお考え下さい。

例えば、5が正解の画像なら[0,0,0,0,0,1,0,0,0,0]のようなものを渡すイメージです。0から始まっているので、5は6番目に登場することに注意です。

計算を行い、認識結果が「y」に入るのに対し、「y_」はその認識に関する正解を与えています。正解は、実際に実行するときにこちらから与えてやらないといけないため、placeholder化されています。

④ 損失の計算と、オプティマイザーを定義

# 損失の計算方法と、オプティマイザーを定義
cross_entropy = tf.reduce_mean(
 tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

この部分では、まず認識結果「y」と正解「y_」を基に交差エントロピーが計算されます。

そして、この交差エントロピーを小さくしていくことで学習を行っていくわけですが、どうやってモデルを正解に近づけていくかというオプティマイザーを最後の行で設定しています。

minimize(cross_entropy)は交差エントロピーを小さくする方向に最適化していくことを示し、GradientDescentOptimizerは「勾配降下」というDeepLeaningでは基礎的な学習方法にて、学習を行わせることを意味します。

ここは様々なオプティマイザーが存在するため、使いたいオプティマイザーに応じて変更することになります。

また、与えている「0.5」は学習率を表しており、一度にどれくらい重みやバイアスなどの値を変化させるかというパラメータになっています。

⑤ Sessionのスタート

さて、モデルや損失関数、オプティマイザーは何を使うかなどの定義が終了したため、ここからは実際に学習を行っていくコードとなります。

# セッションの作成と初期化
sess = tf.InteractiveSession()
tf.global_variables_initializer().run()

Tensorflowで、作成したグラフを用いて計算を実行するには、必ずSessionオブジェクトを作成する必要があります。

上記のコードでは、1行目でSessionを作成し、2行目で変数の初期化を行います。ここは大体定型文なので、覚えてしまっても良さそうです。

⑥ 学習を行う

# 学習部(1000回学習)
for _ in range(1000):
 batch_xs, batch_ys = mnist.train.next_batch(100)
 sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

上記のコードで、学習を実行します。

まず、mnist.train.next_batch(100)でバッチを作成します。これは、100枚をセットにして1回の学習を行うということになります。詳しくは「バッチ学習」で調べると情報が見つかるでしょう。

「sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})」が学習実行部です。

feed_dictは、placeholderへ渡すものを示しています。

すなわち、xというplaceholderにbatch_xsという入力画像を束ねたものを渡し、y_というplaceholderにbatch_ysという正解ラベルを束ねたものを渡すイメージです。

そして、それを渡した上でsess.run()で1度計算を実行します。

range(1000)なので、1000回実行され、その度に学習が進むことになります。

1000回の学習を終えて、重みやバイアスが変更された結果、学習されたモデルが完成します。

⑦ テストする

最後に、テスト部です。ここまでで学習したモデルを使って、実際にテストをしてみます。

# テスト部
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(sess.run(accuracy, feed_dict={x: mnist.test.images,
 y_: mnist.test.labels}))

ここは、最後の1行から見ていきましょう。

feed_dictは前述した通り、グラフのplaceholderに渡すものとなります。今回はテストなので、テストデータの固まりをplaceholderに渡して、実行しています。

今回のsess.runでは、feed_dictの前に「accuracy」が入っていますが、これは何を表しているのでしょうか。

これは、今回の計算で得たいもの(返り値)を表していると思ってください。

つまり、テストデータの固まりを渡して、実行した結果得られる「accuracy」を教えてください、ということになります。

さて、accuracyはなんだったかというと、その上の2行ですね。

correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))

で、yとy_の最大確率のものが一致しているか、すなわちテストデータの認識が成功したのかどうかという情報を得ます。

tf.equalの戻り値はbool型ですが、認識が上手く行ったら「1」が、失敗したら「0」が返却されるイメージです。

accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

そして、得られたcorrect_predictionを全てのテストデータについて足し合わせて、平均を取るのが「tf.reduce_mean」です。

「tf.cast」は型変換をしていて、accuracyを出す際には値が小数になるので、float型で扱うために変換しています。

まとめ

以上が、MNIST For ML Beginnersの内容となります。

ネットワーク自体は、非常に簡単なものを使っていますが、ネットワークが複雑化しても基本的にはコードが増えるだけで、同じような流れでコードが書けると思います。

次回は、もう少し複雑なネットワークを扱うか、MNISTではなく、自分で用意した画像から実行する場合にどうするかとか、そのあたりを少し書いてみたいです。

スポンサーリンク

シェアする

フォローする