TensorFlowで手書き数字画像を多クラス分類


TensorFlowのチュートリアルを見ていきます。

*MNIST For ML Beginners*
<https://www.tensorflow.org/versions/r0.7/tutorials/mnist/beginners/index.html>

このチュートリアルではソフトマックス回帰を行うことで、手書き数字を画像認識します。

## MNIST

MNISTは[MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges](http://yann.lecun.com/exdb/mnist/)で提供されている手書き数字の画像データで、トレーニング用の画像60000個とテスト用の画像10000個が利用できます。
画像は縦横28pxの256段階（白0～黒255）のグレースケールで、上から1行ずつ左から右へと並んでいます。

ためしにトレーニングデータの10番目の数字は以下のように取り出せます。

```python
import gzip, struct
from PIL import Image

with gzip.open('train-images-idx3-ubyte.gz', 'rb') as f:
    # header
    magic, items, width, height = struct.unpack('>4i', f.read(16))
    size = width * height

    idx = 9 # 0-origin
    f.seek(idx * size, 1)

    data = bytes(map(lambda x: 255 - x, f.read(size)))
    image = Image.frombytes('L', (width, height), data)
    image.save("sample.png")
```

![sample.png](https://qiita-image-store.s3.amazonaws.com/0/121144/c67102f3-cbc5-7c1d-537a-aaf25fe41232.png)

TensorFlowではチュートリアル用にMNISTのファイルを取得し、データを抽出する関数が用意されています。

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

トレーニングデータのうち55000個をトレーニング用に、5000個を検証用に使います。
画像はサイズ 784 (=28*28) のベクトルとして扱います。画像データのテンソルのシェイプは`[55000, 784]`になります。
ラベルはone-hotベクトルとして扱います。サイズ10のベクトルでそれぞれの要素が0から9に対応し、一致している要素のみ1に、その他の要素は0になります。
例えばラベルが3であれば`[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]`と表せます。
ラベルデータのテンソルのシェイプは`[55000, 10]`になります。

## ソフトマックス回帰

ソフトマックス回帰は、異なる複数のクラスのうちのどれに所属するか確率を出したい、といったときに用いられます。

ソフトマックス回帰は2ステップで行われます。
最初のステップは入力が各クラスの徴候を示すかを計算します。このチュートリアルでは各ピクセル毎に重みを設定して、そのピクセルにおける黒の濃度をかけて足したものとします。（正確には数字毎に設定されたバイアスと呼ばれる値を足します）
重みはその数字だった場合に他の数字とくらべて黒く塗られやすいかどうかになります。黒く塗られることが多いところはプラスの値に、少ないところはマイナスの値になります。下の画像は学習の結果、数字毎に設定された重みを絵にしたものです。青い部分がプラスで、赤い部分がマイナスになります。

![](https://www.tensorflow.org/versions/r0.7/images/softmax-weights.png)

iが数字（0～9）、jがピクセル、Wが重み、bがバイアス、xが画像データとして次の式で計算します。

```math
\mathrm{evidence}_i = \sum_j W_{i,j} x_j + b_i
```

次のステップは、徴候から確率を求めます。これは[ソフトマックス関数](https://ja.wikipedia.org/wiki/%E6%B4%BB%E6%80%A7%E5%8C%96%E9%96%A2%E6%95%B0#.E5.A4.9A.E3.82.AF.E3.83.A9.E3.82.B9.E5.88.86.E9.A1.9E)で求めます。ソフトマックス関数についてはここでは説明~~できません~~しません。
yが求める確率として次の式で書けます。

```math
y = \mathrm{softmax}(\mathrm{evidence})
```

2つの式から以下が導き出せます。

```math
y_i = \mathrm{softmax}(\sum_j W_{i,j} x_j + b_i)
```

ラベルデータy'を正解値として、[交差エントロピー](https://ja.wikipedia.org/wiki/%E4%BA%A4%E5%B7%AE%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AD%E3%83%94%E3%83%BC) $- \sum y' \log(y) $ が最小となるようにトレーニングします。

## コード

```python:mnist_beginners.py
#Import Data
from tensorflow.examples.tutorials.mnist import input_data

import tensorflow as tf

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

# Create the model
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)

# Define loss and optimizer
y_ = tf.placeholder(tf.float32, [None, 10])
cross_entropy = -tf.reduce_sum(y_ * tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

# Train
init = tf.initialize_all_variables()
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})

# Test trained model
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}))
```

## コード内容

```python:mnist_beginners.py#L8-L12
# Create the model
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)
```

モデルを作成しています。xは外部データなのでプレースホルダーとして定義します。`None`はサイズが不定なのを意味します。

```python:mnist_beginners.py#L14-L17
# Define loss and optimizer
y_ = tf.placeholder(tf.float32, [None, 10])
cross_entropy = -tf.reduce_sum(y_ * tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
```

交差エントロピーを再急降下法で最少になるように最適化します。
y'も外部データなのでプレースホルダーとして定義しています。

```python:mnist_beginners.py#L19-L25
# Train
init = tf.initialize_all_variables()
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})
```

トレーニング用データを100個ずつ、1000回トレーニングさせています。

```python:mnist_beginners.py#L27-L30
# Test trained model
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}))
```

トレーニングされたモデルに対して、テスト用データを与えて正解できるかを確認します。
確率がもっとも高い数字を機械学習の回答にしています。
correct_prediction で正解かどうかの真偽値のリストを作成して、accuracy では真なら1.0、偽なら0.0に変換して平均をとることで正答率を求めています。
最後に正解率が表示します。

## 実行結果

```bash
$ python mnist_beginners.py
Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
0.9197
```

正答率は92%くらいになります。

## おまけ

自分で書いた数字を写真にとって、実際に正しく認識されるかどうか試してみます。

※[別記事](http://qiita.com/jkkch/items/d4009fff1722a52be8dc)にあげました(4/15)