TensorFlowには、最初にとっかかると良いとされる「MNIST For ML Beginners」というチュートリアルがあります。
日本語訳をしてくれているのはこちら。
アルゴリズム的なところではなくコードの解説寄りなので、softmaxや判別手法などについては公式ページを見てもらったりしたほうがよいと思います。どうしてこうなるのかまだよくわかってないし…
プログラム全体の構成
このプログラムの目的は、「手書き文字を判別する」というものになります。
MNISTというデータを使用した、機械学習のベンチマーク的なものでもあるよう。
まず、上記チュートリアルのコードをまとめたものがこれになります。(コメントの挿入は筆者)
※公式のほうのコードと日本語訳サイトのコードに差異があるので、公式のほうを採用します。
# coding: utf-8 # MNISTの学習データをダウンロード from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) ## データの学習準備 # tensorflowをimport import tensorflow as tf # 28x28の行列を格納する変数 x = tf.placeholder(tf.float32, [None, 784]) # 重み (W)とバイアス (b) W = tf.Variable(tf.zeros([784, 10])) b = tf.Variable(tf.zeros([10])) # 評価式 y = tf.nn.softmax(tf.matmul(x, W) + b) y_ = tf.placeholder(tf.float32, [None, 10]) cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1])) train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy) ## 訓練 # TensorFlow内の変数の初期化 # placeholderを使うときには必要 init = tf.initialize_all_variables() # Sessionの作成 # Session内で実行したものが一貫して適用される sess = tf.Session() sess.run(init) # 訓練データによる訓練の実行 for i in range(1000): batch_xs, batch_ys = mnist.train.next_batch(100) sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys}) ## モデルの評価 correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))
これを実行すると、MNISTデータをダウンロード、展開し、学習してテストを行います。
最後に出力される正答率は91%(0.911)くらいになるよう。
コードはおおまかに「学習の準備」「訓練」「評価」の3段階になります。
データの学習準備
xやWなどの値を定義しているところになります。placeholderで作っているものが、コードから入力する値。Valiableで定義しているものが、TensorFlowが自動調整してくれる値のよう。
これは機械学習でも「教師あり学習」に分類され、要はパターンと正解のセットを用意して学習させた後、未知のデータがどれに近いものかを判別する、というものになります。
手書き文字をこれでどのように判別するのか…というところについては、まずMNISTのデータは28x28ピクセルのモノクロ画像の手書き数字となっており、そのピクセル(1byte)の値をひとつづつ評価することで、結果として近いものを判別するというようになっています。
x = tf.placeholder(tf.float32, [None, 784])
で784のサイズを確保しているのは、28(縦)x28(横)x1(モノクロ)ということですね。
それで、実際に判別する式は
y = tf.nn.softmax(tf.matmul(x, W) + b)
となるのですが、Wとbを調整するために訓練する式も必要になります。
その訓練ための式が、train_stapに集約されています。
訓練の実行
次に訓練については、MNISTのデータを100づつ読み出してtrain_stepに入力して、ということを1000回繰り返しています。
TensorFlowではrunしたときに処理が走るので、ここまでtrain_stepに組み込んできた式(Tensor)を一気に解決しているイメージですね。
feed_dict={x: batch_xs, y_: batch_ys}
の部分で、Tensorで組んできたxおよびy_にbatch_xs, batch_ysを代入して、Wとbのパラメータを調整していきます。
…これなー、Pythonの変数xとy_をそのまま連想配列のハッシュに使って代入していると気づくまで、ほんと時間かかってなー…
「なんでxとy_、どこかで(TensorFlow的に)宣言した!?」ってずっと悩んでおったわ。placeholderで返ってきた値がそのままキーになって、feed_dictに渡っているだけという何のことはない処理だったんですけどね。
理解ポイントとしては、
- placeholderの戻り値をキーにして値を放り込む
- Valiableが自動調整される
- runで実行されるまでは、式を構築しているだけ(処理は走っていない)
というあたりでした。
余談ですがTensorFlowの訓練はパラメータの調整なので、同じデータでも何度か繰り返し入力することで変化していくので、無駄ではないみたいです。ただし過学習という、「その結果に最適化された、ゆえに未知のデータに対応できない」パラメータになってしまう可能性も高まります。
モデルの評価
そうやって鍛えた評価式がちゃんと判別してくれるか、テストデータを流してチェックします。
それが
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))
の部分。
結局、xに入力された画像がどの数字に該当するか、というのはyに構築したsoftmaxの式で判別できていて、それが「正解」であるlabelsのインデックスと一致したかの平均を出しています。
tf.argmaxがその配列の中で一番大きな値のインデックスを返す、というメソッドになっており、yには各数字のスコアが、y_には正解のインデックスの値にだけ「1」があるため、それが一致したら成功ということになっている。
感覚としては、構築された式が関数ポインタのような感じで使用されているのかなというところです。
訓練の結果、Wとbはこの時点の最適な値に確定しているので、それを用いて評価し、一番「それっぽい」数字のインデックスを返しているのがy。
このテストコードの評価をもう少し見ていきます。
次のように
print(sess.run(y, feed_dict={x: [mnist.test.images[0]]})) print(sess.run(tf.argmax(y,1), feed_dict={x: [mnist.test.images[0]]})) print(mnist.test.labels[0])
として部分的に実行してやると、
[[ 4.14293536e-05 6.96104241e-09 1.26298241e-04 2.24499172e-03 2.10823237e-06 2.78009356e-05 1.44323247e-08 9.97211039e-01 2.87266303e-05 3.17556784e-04]] [7] [ 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
という結果になります。
最初の結果がそれぞれの数値(0-9)のスコア、それをargmaxで一番大きいものを取り出したのが[7]、testの正解として用意されているラベルのインデックスで、1(正解)となっているものも7。ということで一致しましたね。
個人的にTensorFlowで混乱したのって、Pythonのコードの流れが掴めなかったところが大きい気がしているので、もしそのあたりが引っかかっている方のヒントになれば幸いです。