TensorflowのTutorial "MNIST For ML Beginners"を試した時のメモ

Tensorflowを勉強するにはTensorflowのTutorialを自分で入力して実行しながら理解するのが一番手っ取り早いです。
特に初めてTensorflowを動かすときには、以下のリンク先のMNIST for ML BeginnersというTutorialがおすすめです。
MNIST For ML Beginners  |  TensorFlow

このチュートリアルは、単純な1層のネットワークで手書き文字を認識するというものです。
1層ですので、重みとバイアスも一つしかありませんが、それでもニューラルネットワークに必要な最小限の要素をすべて含んでいます。

TensorflowのtutorialにはMNISTの手書きデータを使うものはこのbiginner用とCNNを使ったものの2種類があります。
以下に、実際にコードを理解して実行したときの自分用のメモを残しておきます。

                  • -

mnist.train.images は [画像の数, 28x28=784] の shapeで値は0-1の画素の輝度
ラベルは 0~9 の数字でone-hot表現されています。
one-hot表現は例えば、
3 なら [0,0,0,1,0,0,0,0,0,0]
というふうに表現されます。
従ってラベルは
mnist.train.labels [画像の数, 10]というshapeで、値はfloatです。

> x = tf.placeholder(tf.float32, [None, 784])

xは学習データ(手書き数字の画像データを1次元にしたもの), Noneは画像の数は任意の長さを取りうることを意味する。
placeholderはこのように、学習データなどを入れる入れ物。

重みやバイアスは、実行時に随時更新されますので、tfのvariable変数として以下のように定義しています。
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

wは入力が784次元で出力が10次元なので[784, 10]となります。 
784列10行の行列と思えばいい。

ニューラルネットワークの計算のcoreの部分です。
> y = tf.nn.softmax(tf.matmul(x, W) + b)

tf.matmul(x, W)の部分ですが、データ数をNとすると
xは N x 784 の行列で, Wは 784 x 10の行列なので、結果のyはN行10列の行列です。
つまり各行にxの各行にたいする画像データを0から9のどの数値と予測したかの確率がyの各行に入ります。

この予想された出力yと正解とを使って表されたロス関数を最小化することで、適切なW,bを決めます。
従って正解も上記yと同じshapeである必要があるので、
> y_ = tf.placeholder(tf.float32, [None, 10])
というように、N行10列の行列としてplaceholderを用意しておきます。

多値分類問題なのでロス関数としてcross entropyを使用します。
> cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))
tf.reduce_meanは指定されたshapeの成分についての和をとる関数で、今の場合は、reduction_indices=[1]となっているので
1番目の要素についての和をとります。
つまり、0から9の予想された数値の方向に和をとり、N個の要素をもつ1次元配列にします。
この1次元配列は、N個の学習データそれぞれに対するロス関数の値を格納しています。
最後に、tf.reduce_meanで、N個の学習データ(バッチ処理1回分)のそれぞれのロス関数すべての平均をとってこれを最適化関数として使用しています。

ここが少し混乱しやすいのですが、実際のtutorialのコードでは上記の部分が以下のようにsoftmax_cross_entropy_with_logitsという関数で書かれています。
>cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))
解説を読むと、前者の書き方のほうが数値的に不安定するそうです。

実施のコードで試してみると、前者の場合の精度は私の実行環境では、約0.92、後者では、0.91でほぼ同じ結果になりました。
実行時間は前者では約0.77sec, 後者では0.90secでlogits関数の計算の分だけ速度が落ちているようです。
(実際のコードを一番最後に添付しておきます)

logits関数とは、L = log(p/(1-p)) のことで変形するとp= 1/(1+exp(-L))で、p=0.5の時0,P>0.5のときL>0, p<0.5のときL<0となる関数。
確率p [0, 1.0] -> [-inf, inf]にマップする。
softmaxで確率[0, 1.0]の出力を得て、それをlogitsに通すことで正解の確率が大きければ値はより大きくなる。マイナスが掛かっているのでloss関数としては値が小さくなるということです。
確率が0や1に近い時に勾配が数値的に消失したり不安定にあったりするのを防ぐ効果があります。
stackoverflow.com


最適化はGradientDescentで学習率は0.5に設定しています。
>train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

学習結果の評価
>correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
tf.argmaxは引数の配列の中で一番値の高いもののindexを返す。yは[教師データの数, 正解の確率]という配列になっているので、
1番目の要素(数値の0から9のそれぞれの予測確率)から一番高いもの一つが選ばれます。
tf.equalで教師データと一致しているときにTrue, 異なっているときにFalseをセットします。

# 精度の計算
>accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
tf.castはTrue, Falseを以下のように数値に変換
[True, False, True, True] -> [1,0,1,1]
tf.reduce_meanでこの1,0,1,1,・・・の平均をとり結果を全体の精度とする。

今回試してみた実際のコードは以下のようになります。

import tensorflow as tf
import time

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)

start_time = time.time()

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

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]))
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)
# interactiveSessionでtfのsessionを作成
sess = tf.InteractiveSession()
# global変数の初期化
tf.global_variables_initializer().run()

print ("--- start training ---")
for _ in range(1000): # 1000ステップの学習を行う
    # 1 batch = 100個のデータでbatch学習を行う
    batch_xs, batch_ys = mnist.train.next_batch(100)
    sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
print ("--- finish training ---")

# 予測yと正解ラベルy_が同じ値であればTrue、ことなればFalseがセットされる
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
# 精度の計算
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print("精度:")
print( sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

end_time = time.time()
print ("time: " + str(end_time - start_time) + "sec")