Edited at

MNIST for ML Beginner (TensorFlow)

More than 3 years have passed since last update.

TensorFlow tutorialsについてまとめていく。


MNIST for ML Beginner

mnistは手書きの数字の画像のことで、手書き数字の画像を見て、それが何か予測するプログラムを作る


扱うデータ


概要

Yann LeCunのサイトのMNIST(手書きの画像)データを用いる。↓の様なもの


詳細

ダウンロードしたデータは60,000の点でできた、トレーニング用データ(mnist.train)と、10,000の点でできたテストデータ(mnist.test)に分けられる(学習に用いるデータとそうでないデータを分けることが機械学習では重要)

画像をxそれに対応するラベルをyと呼ぶこととする

トレーニング用データの画像はmnist.train.imagesラベルはmnist.train.labels

それぞれの画像は28x28pxなので、28x28=784の数字の配列で表せる↓



この配列を28x28=784次元のベクトルに格納する

つまり、MNISTは784次元ベクトル空間の点の集まりである

2次元構造は失われる(softmax回帰では有効活用しないが、有効活用するものもある)

結果として、mnist.train.imagesは[60000, 784]の形をしたテンソル(n次元の配列)となる

それに対応するmnist.train.labelは0から9の数字で、与えられた画像がどの数字であるかを示す。

ラベルは"one-hot vectors"(一つの次元以外は0であるベクトル)を目指す

ex)3 = [0,0,1,0,0,0,0,0,0,0]

結果mnist.train.labelsは[60000, 10]の浮動小数点の配列となる


扱う手法

softmax回帰と呼ばれる手法を用いる。2ステップあって、初めに、入力が、あるクラスにあるというevidenceを足し上げる。その後、softmax関数を用いて、そのevidenceを確率yに変換する。


Softmax回帰

与えられた画像がそのクラスにあるevidenceは、pixelの密度の加重された和として計算する。

その加重は、pixelの密度が濃い部分が、そのクラスにあることに反する証拠であればマイナス、肯定する証拠であればプラス

さらにバイアスと呼ばれる証拠も加える。(imputと関係ないことを言うためのもの)

evidence_i = \sum_{j}^{} (W_{ij} \times x_j) + b_i

wiは比重で、biがiクラスへのバイアス

これが、入力xがiクラスにあるevidenceであり

その後、softmax関数で、evidenceをprobabilityであるyに変換する。(softmax関数を用いることで、出力y_iが入力xがiクラスである確率として扱えるようになる)

y_i = softmax(evidence_i)

softmax関数は

softmax(x_i) = \frac{exp(x_i)}{\sum_{j}^{} exp(x_j)}

である。

入力が3次元のベクトルで、ラベルも3次元のベクトルである例を見てみると



式で書くと



積を用いて書くと



シンプルに書いてしまえば、

y = softmax(Wx + b)

となる


cross-entropy

- \sum_{i}{} y'_i log(y_i)

で足しあわせられるそれぞれのy'_i log(y_i)についてみてみると、正しくないクラスの場合はy'_iが0であるので、y'_i log(y_i)は0となる。y'_iは正しいクラスの場合のみ1となり、その場合はlog(y_i)y_i(0から1までの確率)の対数として、最大値0(y_iが1のとき)で、y_iが0のとき-∞に発散する値をとる。よって、正しいクラスの場合の予測した確率y_iのみが影響し、そのy_iが1のときに誤差0, y_iが0のとき誤差∞となる誤差関数となる。


ソースコードの解説


流れ

大きな流れはデータの準備→モデル作成→学習→モデルの評価

もう少し細かいステップとしては


  1. 学習する画像をダウンロードしインポート

  2. TensorFlowをインポート

  3. 入力のためのplaceholderを用意

  4. weightとbiasをVariableとして用意

  5. placeholderと, weight, biasに対してsoftmax回帰を適用して確率yを定義する

  6. cross-entropyと呼ばれる誤差関数を用いてモデルの評価を行うので、正解を入力するplaceholderとしてy_を作成し、yについてのcross-entropyを定義

  7. gradient descentアルゴリズムを用いて、0.01のlearning rateでcross-entropyを最小化させる命令(train_step)を作成

  8. 作成した変数を初期化する命令(init)とSessionを用意

  9. Sessionでinitを実行した後、そのSessionで、train_stepのplaceholderに、用意した訓練データセットから100個のランダムなデータのバッチを与えて実行、という操作を1000回繰り返す

  10. tf.argmaxという関数を用いて、出力されたyのうち、最も確率の高いものが正解y_と一致しているかを判定したbooleanリストを作成

  11. booleanリストから正答率をfloatで求める


詳細


データの準備

[Google Git]https://tensorflow.googlesource.com/tensorflow/+/master/tensorflow/g3doc/tutorials/mnist/input_data.py

からダウンロードし

import input_data

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

を最初に記入しておく。


回帰の実装法

pythonで効率的な計算を行うためには、通常、行列の掛け算のような重い計算をpythonの外で行う、Numpyなどのライブラリを用いるが、pythonに戻ってくる際に多くのオーバーヘッドが生まれてしまう。

このオーバーヘッドはGPUや異なる並列計算を動かしたい場合に非常に不利である。

TensorFlowでは、1つの重たい計算だけをpythonから独立して行うのではなく、完全にpythonの外で動いていて、相互に関連する計算のグラフを描くことを可能にしている。これにより先述のオーバーヘッドは回避できる。


モデルの作成

TensorFlowを使うにはまず

import tensorflow as tf

としてインポート

相互に関連する計算をシンボリック変数を操作することで記述するので、まず一つ作る。

x = tf.placeholder("float", [None, 784])

xは特定の値ではなく、TensorFlowに計算をしてもらう時に入力する値、placeholderである

784次元のベクトルに変換されたMNISTの画像をどれでも入力出来るようにしたいので、[None, 784]という形の浮動小数点の2階のテンソルで表す。(Noneは何次元にもなれるということを表す)

また、先述のモデルにはweightやbiasも必要なので、Variableでそれらを扱う。

Variableは変更可能なテンソルでTensorFlowの相互に関連した計算のグラフ内で用いられる。計算により使ったり、変更したりもできる。

機械学習のアプリケーションは、普通Variableと呼ばれるそのモデルのパラメータを持っている。

w = tf.Variable(tf.zeros([784, 10]))

b = tf.Variable(tf.zeros([10]))

tf.Variableに、全て0のテンソルを初期値として与え、wとbをというVariableを作成した(wとbを学習していくので、初期値が何であるかは大きな影響を与えない)

ここで、1行でモデルを作成できる。

y = tf.nn.softmax(tf.matmul(x,W) + b)

tf.matmul(x,W)でxとWの積を計算し、bを足して、tf.nn.softmaxを適用する。xを2階のテンソルとしていたのはWとの積をとれるようにする工夫。

((1,1)型の2階混合テンソルの積はベクトルの外積と同様にみなすことができる)


学習

何が良いモデルかを定量的に評価するために、今回はcross-entropyと呼ばれる誤差関数を用いる。

H_y'(y) = - \sum_{i}{} y'_i log(y_i)

yが予測した確率分布で、y'が真の分布である(真の分布にはone-hotベクトルを入力する)

参考)[cross-entropy(英語)]http://colah.github.io/posts/2015-09-Visual-Information/

cross-entropyを実装するにはまず、正解を入力するplaceholderが必要であるので準備する。

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

そして、cross-entropy- \sum_{i}{} y'_i log(y_i)を実装する。

cross_entropy = -tf.reduce_sum(y_*tf.log(y))

まず、tf.logはyの要素それぞれの対数を計算する。その後、y_の要素それぞれについて対応するtf.log(y)の要素との積をとる。最後にtf.reduce_sumでテンソルの全ての要素の和をとる。

TesnorFlowは計算の全てのグラフを知っているので、自動的にbackpropagation algorithmを使うことができる。

※backpropagation algorithmは最小化したいコストに変数がどれほど影響しているかを効率的に決定できる[参考]http://colah.github.io/posts/2015-08-Backprop/

train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

この場合TensorFlowにgradient descentアルゴリズムを用いて、0.01のlearning rateでcross_entropyを最小化するように命令している。

gradient descentにより、TensorFlowはそれぞれの変数を、誤差が小さくなる方向に少しだけ動かしてくれる。

TensorFlowはその他の最適化アルゴリズムも多く用いていて、それを用いるのはコードを1行変える程度で行える。

これでモデルを訓練する準備が出来たが、実行する前に、作成した変数を初期化する命令を加えなければならない

init = tf.initialize_all_variables()

こうしてSession内でモデルを実行することが出来るので、変数を初期化する命令を実行。

sess = tf.Session()

sess.run(init)

そして1000回train_step実行。

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}

ループの各ステップで、用意した訓練データセットから100個のランダムなデータ点の"バッチ"を取得する。そしてtrain_stepを実行し、各バッチデータをplaceholderに与えていく。

ランダムなデータからなる小さなバッチを用いる手法はstochastic trainingと呼ばれていて、毎回全てのデータを使うのが理想的ではあるが、コストが掛かり過ぎるので、低コストでほぼ同等の効果が得られるこの手法を用いる。


モデルの評価

tf.argmaxはいくつかの軸にそって、tensor内の最も高い入力のインデックスを返す便利な関数である。

例えば、tf.argmax(y,1)は各インプットに対して最も確率の高いラベルを返し、tf.argmax(y_,1)は正解のラベルを返します。そしてtf.equalで予測が当たっていたかを判定出来る。

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

結果として、ブール値のリストが返される。扱いやすいようにFloat値の数字に変換して、平均をとる。例えば、[True, False, True, True]を[1,0,1,1]とし、0.75とする。

accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

最後に、正答率を尋ねる。

print sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})

91%程度になるはず。