TensorFlow MNIST For ML Beginnersの補足っぽいメモ

　[TensorFlow MNIST For ML Beginners](https://www.tensorflow.org/get_started/mnist/beginners)は、TensorFlowを使って手書き数字の画像認識を行うチュートリアルで、[TensorFlowチュートリアル - ML初心者のためのMNIST（翻訳） - Qiita](http://qiita.com/KojiOhki/items/ff6ae04d6cf02f1b6edf)のサイトで日本語訳が公開されています。

　考え方については、 [TensorFlowコトハジメ 手書き文字認識(MNIST)による多クラス識別問題](http://yaju3d.hatenablog.jp/entry/2016/04/22/073249)の解説が分かり易いと思います。


# MNISTの画像データ
　MNISTの画像データは、28ピクセル x 28ピクセルの手書き数字0～9の画像と、それぞれに対応したラベルのデータセットです。
　例えば、下記の画像の場合、画像ファイルと、「7」というラベル情報がセットになっています。

![mnist_train_image7.png](https://qiita-image-store.s3.amazonaws.com/0/117636/9caefea2-f6a1-35ef-b4ba-128ee2e883bb.png)


## プログラムが読み込んだ画像データを表示する

　とりあえず、下記のコードを実行すると、MNISTの訓練用画像の先頭画像を、上記の例のように出力してくれます。いろいろと前置きが長いですが、最後の5行が画像の表示に関するコードです。直接input_dataでダウンロードしたアーカイブファイルを展開しても画像を確認するだけならできますが．．．

```py3:mnist_picture_sample.py
"""This is a test program."""
# -*- coding: utf-8 -*-

import time
import numpy as np
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.cm as cm
from matplotlib import pylab as plt

# 開始時刻
START_TIME = time.time()
print("開始時刻: " + str(START_TIME))

# MNISTデータの読み込み
# 訓練用画像 55000件 (60000件という説もあるらしい)
# 検証用画像 10000件
# 訓練データとテストデータは、それぞれ0～9の画像とそれぞれに対応するラベル（0～9）が設定されている
# 画像サイズは28px X 28px(=784)
# mnist.train.imagesは[55000, 784]の配列
# mnist.train.lablesは、read_data_setsメソッドのone_hotのT/Fにより下記の通り
#   one_hot = Trueの場合 : [55000, 10]の配列で、対応するimagesの画像が「3」の時、[0,0,0,1,0,0,0,0,0,0]
#                         np.argmax([0,0,0,1,0,0,0,0,0,0]) ⇒ 3に変換できる
#   one_hot = Falseの場合: [55000, 1]の配列で、対応するimagesの画像が「3」の時、3
# mnist.test.imagesは[10000, 784]の配列、mnist.test.lablesは[10000, 10]の配列で、内容はmnist.trainと同様

print("--- MNISTデータの読み込み開始 ---")
is_one_hot = True
mnist = input_data.read_data_sets("MNIST_data/", one_hot=is_one_hot)
print("--- MNISTデータの読み込み完了 ---")

### 先頭画像がどの数字の画像なのか確認する ###
# one_hot=Trueの場合
if is_one_hot == True:
    label = np.argmax(mnist.train.labels[0])
else:
    label = mnist.train.labels[0]

### 訓練画像の数の確認 ###
print('mnist.train.images = ' + str(len(mnist.train.images)))
### 検証画像の数の確認 ###
print('mnist.test.images = ' + str(len(mnist.test.images)))

# 配列に格納されている画像データの表示
# 28x28の画像情報（配列）を数値で見てもよくわからないのでコメントにしておく
#print(mnist.train.images[0].reshape(28, 28))

# 終了時刻
END_TIME = time.time()
print("終了時刻: " + str(END_TIME))
print("所要時間: " + str(END_TIME - START_TIME))

# 学習用画像の先頭の絵をgrayscaleの画像として表示
plt.imshow(mnist.train.images[0].reshape(28, 28), cmap = cm.Greys_r)
plt.title(str(label))
plt.axis('off')
plt.show()
plt.close()
```


　「# 学習用画像の先頭の絵をgrayscaleの画像として表示」からの処理を下記のように変更すると、訓練用画像の先頭から10枚を出力してくれるようになります。

```py3
# 学習用画像の先頭10枚の絵をgrayscaleの画像として表示
# 2行 x 5列の画像出力領域を確保する
fig, axarr = plt.subplots(2, 5)

# 各出力領域に絵をセットする
for idx in range(10):
    ax = axarr[int(idx / 5)][idx % 5]
    ax.imshow(mnist.train.images[idx].reshape(28, 28), cmap = cm.Greys_r)

    label = ''
    if IS_ONE_HOT == True:
        label = np.argmax(mnist.train.labels[idx])
    else:
        label = mnist.train.labels[idx]
    ax.set_title(str(label))
    ax.axes.get_xaxis().set_visible(False)
    ax.axes.get_yaxis().set_visible(False)
# 絵を出力する
plt.show()
plt.close()
```

実行結果は、こんな感じ。

![mnist_train_image_2x5.png](https://qiita-image-store.s3.amazonaws.com/0/117636/c6c3d413-99e9-a9cb-7231-65488a68ec0b.png)


　なんというか、先頭の「7」の絵は「ヲ」にしか見えないような．．．むしろ「4」の方が「7」に見える感じが．．．

# TensorFlow MNIST For ML Beginnersの動くコード
　[TensorFlowのチュートリアルをやってみる（1） | mwSoft](http://www.mwsoft.jp/programming/tensor/tutorial_beginners.html)で公開されているコードをまるパクリさせて頂き、実行してみます。

```py3:mnist_beginner.py
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# データ読み込み
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

# placeholder用意
x = tf.placeholder(tf.float32, [None, 784])
y_ = tf.placeholder(tf.float32, [None, 10])

# weightとbias
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

# Softmax Regressionを使う
y = tf.nn.softmax(tf.matmul(x, W) + b)

# 交差エントロピー
cross_entropy = -tf.reduce_sum(y_ * tf.log(y))

# 先ほど使ったGradientDescentOptimizerで、今回はcross_entropyを利用
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

# 初期化
# initialize_all_variablesメソッドは2017/3/2以降削除されるらしいので
# global_variables_initializerに変更。
# (2017/6現在削除されてないけど）
#init = tf.initialize_all_variables()
init = tf.global_variables_initializer()
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, tf.float32))
# 結果を表示できるよう変更
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))
```


　繰り返して実行すると結果は多少変わりますが、だいたい90%くらいの確率で認識できている（らしい）ことが確認できると思います。

　で、[TensorFlowのチュートリアルをやってみる（1） | mwSoft](http://www.mwsoft.jp/programming/tensor/tutorial_beginners.html)のメインコードの続きの解説を「# 結果を表示できるよう変更」の下に追加すると、バイアスの値とかWeightがどうなっているかとかも表示できるようになります。


```py3
・・・
# 結果を表示できるよう変更
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

# biasの値の表示
print(sess.run(b))

# 画像描画に必要なライブラリのimport
import matplotlib.cm as cm
from matplotlib import pylab as plt

# weightsの中身
weights = sess.run(W)
f, axarr = plt.subplots(2, 5)
for idx in range(10):
    ax = axarr[int(idx / 5)][idx % 5]
    ax.imshow(weights[:, idx].reshape(28, 28), cmap = cm.Greys_r)
    ax.set_title(str(idx))
    ax.axes.get_xaxis().set_visible(False)
    ax.axes.get_yaxis().set_visible(False)
plt.show()
plt.close()

# 各数値の正解率の表示
corrects = sess.run(correct_prediction, feed_dict={x: mnist.test.images, y_: mnist.test.labels})
for i in range(10):
    positive = sum(mnist.test.labels[corrects][:, i] == 1)
    all = sum(mnist.test.labels[:, i] == 1)
    print(i, positive / all)

# 正解画像の表示
f, axarr = plt.subplots(5, 8)
for idx, img in enumerate(mnist.test.images[corrects][0:40]):
        ax= axarr[int(idx / 8)][idx % 8]
        ax.imshow(img.reshape(28, 28), cmap = cm.Greys_r)
        ax.axes.get_xaxis().set_visible(False)
        ax.axes.get_yaxis().set_visible(False)
plt.show()
plt.close()

# 不正解画像の表示
f, axarr = plt.subplots(5, 8)
for idx, img in enumerate(mnist.test.images[~ corrects][0:40]):
        ax = axarr[int(idx / 8)][idx % 8]
        ax.imshow(img.reshape(28, 28), cmap = cm.Greys_r)
        ax.axes.get_xaxis().set_visible(False)
        ax.axes.get_yaxis().set_visible(False)
plt.show()
plt.close()
```


## 自分が手書きした画像を認識させる
　上記のコードを実行すると、Tensorflowを使って手書き文字画像を90%くらいで認識できていそう、なことはなんとなく分かるのですが、

自分：![3.png](https://qiita-image-store.s3.amazonaws.com/0/117636/2f91cb5e-1fcb-a117-edea-d617c8e449ec.png)の数字は何？
PC：「3」です。

みたいなことができません。ということで、学習した結果を利用して、自分が用意した画像の認識をするコードを「# 結果を表示できるよう変更」の下あたりに追加します。

```py3
・・・
# 結果を表示できるよう変更
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

# 画像読み込み
import cv2
import numpy as np

img = input("画像ファイルのパスを入力してください >")
img = cv2.imread(img, cv2.IMREAD_GRAYSCALE)
img = cv2.resize(img, (28, 28))
ximage = img.flatten().astype(np.float32)/255.0     # 形式を変更
ximage = np.expand_dims(ximage, axis=0)             # 行と列の入れ替え (784, 1) ⇒ (1, 784)に変換

# 画像認識の実行
predict = sess.run(y, feed_dict={x: ximage})
print('画像認識結果は「' + str(sess.run(tf.argmax(predict[0]))) + '」です')
```

　コード追加後のスクリプトを実行すると、ユーザが指定した画像の画像認識結果を返してくれるはずです。


## 学習と画像認識のスクリプトを分離する
　一枚、画像認識させる度に、毎回一から学習し直すのは効率が悪いので、学習用スクリプトで学習結果をファイルに保存し、画像認識用スクリプトでは、学習結果を利用して画像認識を実行させるよう改造します。


## 学習用スクリプト
　「######## ここから ########」～「######## ここまで ########」で囲んだ2か所のコードを追加すると、学習結果をファイルに保存します。

```py3:mnist_beginner_learn.py
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# データ読み込み
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

# placeholder用意
x = tf.placeholder(tf.float32, [None, 784])
y_ = tf.placeholder(tf.float32, [None, 10])

# weightとbias
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

# Softmax Regressionを使う
y = tf.nn.softmax(tf.matmul(x, W) + b)

# 交差エントロピー
cross_entropy = -tf.reduce_sum(y_ * tf.log(y))

# 先ほど使ったGradientDescentOptimizerで、今回はcross_entropyを利用
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

# 初期化
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

######## ここから ########
# tf.train.Saverメソッドを引数無しで実行すると、
# すべてのtf.Variableが保存対象
saver = tf.train.Saver()
######## ここまで ########

# 学習
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, tf.float32))
# 結果を表示できるよう変更
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

######## ここから ########
# tf.Variableの保存
# とりあえず、カレントディレクトリにckptディレクトリを作成して、
# その下にファイルを出力します。
import os
if os.path.exists('ckpt') == False:
    os.mkdir('ckpt')
saver.save(sess, 'ckpt' + os.sep + 'model.ckpt')
######## ここまで ########
```


## 画像認識用スクリプト
　主要なコードは学習用スクリプトと共通です。
　パラメータ初期化後、学習するコードを、ファイルからロードするコードに書き換えています。

```py3:mnist_predict.py
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# データ読み込み
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

# placeholder用意
x = tf.placeholder(tf.float32, [None, 784])
y_ = tf.placeholder(tf.float32, [None, 10])

# weightとbias
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

# Softmax Regressionを使う
y = tf.nn.softmax(tf.matmul(x, W) + b)

# 交差エントロピー
cross_entropy = -tf.reduce_sum(y_ * tf.log(y))

# 先ほど使ったGradientDescentOptimizerで、今回はcross_entropyを利用
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

# 初期化
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

######## ここまでは学習用スクリプトと同じ ########

# 学習結果のロード
saver = tf.train.Saver()
sess.run(tf.global_variables_initializer())
ckpt = tf.train.get_checkpoint_state('./ckpt')
saver.restore(sess, ckpt.model_checkpoint_path) # 変数データの読み込み

######## ここから下は「自分が手書きした画像を認識させる」と同じ ########
# 画像読み込み
import cv2
import numpy as np

img = input("画像ファイルのパスを入力してください >")
img = cv2.imread(img, cv2.IMREAD_GRAYSCALE)
img = cv2.resize(img, (28, 28))
ximage = img.flatten().astype(np.float32)/255.0     # 形式を変更
ximage = np.expand_dims(ximage, axis=0)             # 行と列の入れ替え (784, 1) ⇒ (1, 784)に変換

# 画像認識の実行
predict = sess.run(y, feed_dict={x: ximage})
print('画像認識結果は「' + str(sess.run(tf.argmax(predict[0]))) + '」です')
```