tensorflowで手書き数字を認識させてみた

## これは何？
* tensorflowというGoogle製のディープラーニングライブラリを利用して、0〜9までの数字認識を行うもの
    * 学習データとして、mnistという手書き数字の画像データセット(単純なコンピュータビジョンのデータセット)を利用
    * 基本的にはtensorflowの公式チュートリアル通り進めてできました。備忘録としてまとめておきます。
* 今回は、mnist 上級者用 チュートリアル の モデル (認識率が約99%) をやってみます

## tensorflowとは？
* ニューラルネットワークの構築が可能
    * 私たち人間の脳内にある神経細胞（ニューロン）のつながりを数式的なモデルで表現したもの
    * このニューラルネットワークを使用することによって、機械が私たち人間と同じように論理的思考や学習を行うことができるようになる
    * ニューラルネットワークは、入力層、出力層、隠れ層から構成され、層と層の間には、ニューロン同士のつながりの強さを示す重み「W」がある        
        * ディープラーニング(深層学習)とは、「隠れ層が多数存在する多層構造のニューラルネットワーク」のことを指す。言葉で特徴を定義するのが難しい場合に高い効果を発揮
    * 次のレイヤーに渡す際の数式
        * f(x * w + b)
            * x : 入力値
            * w : 重み
            * b : バイアス
    * ニューラルネットワークがどのように学習するのか
        * 教師あり学習 : mnistなどのトレーニングデータセットが必要。正解がある
        * 教師なし学習 : 学習データにラベルを付けないで学習する方法。正解がない。不正検知などの異常検知に使われている
    * 利点
        * あらゆる問題にある程度確からしい解を答えられる
    * 欠点
        * 大量の処理が必要。CPUのみだと明らかに速度不足なのでGPUが必須か
        * 多次元行列(テンソル)の表現と計算についての深い知識が必要

* データの読み込み、前処理、計算、状態、出力といった処理に対してテンソルを扱っている
* 対応環境はMacOS、Ubuntu/Linux、Windows。CPUは64bitのみ。本格的に利用したい場合はGPU必須
* Android、iOS、Raspberry Piといったモバイル機器でも利用可能
* PythonやC,C++から利用可能。tensorflow自体はC++で書かれている
* 画像認識、音声認識、翻訳などができる。

## 環境構築手順(AWS Deep Learning AMI - ubuntu p3 instance)
* AWS Deep Learning AMI の用意
    * GPUを使いたいので、p3.2xlarge インスタンスを生成
    * メモリ 8GB, SSD 32GB
    * SSH接続設定
    * セキュリティグループ設定
        * 80,443,22,8888(jupyterで利用)ポートをマイIPのみ開放
* Anaconda(Python本体と、Pythonでよく利用されるライブラリをセットにしたPythonパッケージ)のインストール
    * SSHでAWSにログイン
    * 以下コマンドを実行

            $ wget https://repo.anaconda.com/archive/Anaconda3-5.2.0-Linux-x86_64.sh
            $ bash Anaconda3-5.2.0-Linux-x86_64.sh
            $ conda -V (インストール確認)
            $ conda info --envs (仮想環境一覧を表示)

* tensorflowのインストール
    
        $ conda c install -c conda-forge tensorflow

* jupyter notebook を起動＆ログイン
    * 起動

            $ jupyter notebook

    * ローカル設定 : ssh portfoward (ローカルの12411ポート -> AWSの8888ポート)
        * portfoward方法はここでは割愛
    * 以下に接続できることを確認
        * http://localhost:12411
        * jupyter notebook コマンド時のtokenをログイン画面に貼り付け

## 実行手順
* 認識させたい数字の画像ファイルをSCPなどでAWSに送信
    * photoshopやpaintなどのソフトで、手書きペンで書いたものを利用
    * ホワイトボードや紙に書いた数字を写真で撮ったファイルは、認識率が悪かった
        * ノイズを加えた学習データでモデルを作成していないため？
    * 今回使用した数字は以下
   ![image.png](https://qiita-image-store.s3.amazonaws.com/0/85701/908b1ba7-2468-febe-bb66-089b62becc92.png)

* jupyter notebook を実行

## 実行結果
10個中9個が認識された

## ソースコード

```python
# coding:utf-8
#TensorFlowをインポート
#TensorFlowのバックエンドではC++の高速なライブラリを使用しています
import tensorflow as tf
#MNISTデータのロード
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
import time
import os,sys
import numpy as np
from PIL import Image
from matplotlib import pylab as plt
start = time.time()
tf.reset_default_graph()
sess = tf.InteractiveSession()
#プレースホルダーにてTFに計算を依頼
x = tf.placeholder("float", shape=[None, 784])
y_ = tf.placeholder("float", shape=[None, 10])
###############多層畳み込みネットワークの構築####################
#重みの初期化
def weight_variable(shape):
  initial = tf.truncated_normal(shape, stddev=0.1)
  return tf.Variable(initial)
def bias_variable(shape):
  initial = tf.constant(0.1, shape=shape)
  return tf.Variable(initial)
##畳み込み
def conv2d(x, W):
  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
##プーリング(次元削減)
def max_pool_2x2(x):
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
                        strides=[1, 2, 2, 1], padding='SAME')
#######畳み込み層第1層############################
W_conv1 = weight_variable( [5, 5, 1, 32] )
b_conv1 = bias_variable([32])
x_image = tf.reshape(x, [-1, 28, 28, 1])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
#######畳み込み層第2層############################
W_conv2 = weight_variable( [5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
########密集接続層######################
W_fc1 = weight_variable( [7*7*64, 1024] )
b_fc1 = bias_variable( [1024] )
h_pool2_flat = tf.reshape(h_pool2, [-1,7*7*64] )
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
keep_prob = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
#########読みだし層#################
W_fc2 = weight_variable( [1024, 10] )
b_fc2 = bias_variable( [10] )
y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
#####訓練######
cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_predition = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_predition, "float"))
#初期化
sess.run(tf.initialize_all_variables())
#学習データ保存用
saver = tf.train.Saver()
#保存用ディレクトリの作成
os.makedirs("./mnist_model_2", exist_ok=True)

ckpt = tf.train.get_checkpoint_state('./mnist_model_2')
if ckpt: # 学習データがある場合
    print("学習データを読み込みます")
    print(ckpt.model_checkpoint_path)
    saver.restore(sess, ckpt.model_checkpoint_path) # データの読み込み
else: #学習データがない場合→学習を開始
    print("学習を開始します")
    for i in range(20000):
        batch = mnist.train.next_batch(50)
        if i%100 ==0:
            train_accuracy = accuracy.eval(feed_dict={x:batch[0], y_:batch[1], keep_prob: 1.0})
            print("学習回数： %d 回, training accuracy %g"%(i, train_accuracy))
        train_step.run(feed_dict={x:batch[0], y_:batch[1], keep_prob:0.5})
    ##学習パラメータの保存
    saver.save(sess, "./mnist_model_2/CNN.ckpt")
print("この学習の正解率は、")
#学習結果の表示
print("この学習の正解率は %g です"%accuracy.eval(feed_dict={ x: mnist.test.images, y_:mnist.test.labels, keep_prob: 1.0}))
#ここから手書きの文字を認識させる
#ファイルを開く
data_dir_path = u"./numbers/"
file_list = os.listdir(r'./numbers/')
for file_name in file_list:
    root, ext = os.path.splitext(file_name)
    if ext == u'.png' or u'.jpeg' or u'.jpg':
        abs_name = data_dir_path + '/' + file_name
        #グレースケールとして画像の読み込み
        img = Image.open(abs_name).convert('L')
        # バックを白に
        img = Image.frombytes('L', img.size,
                                bytes(map(lambda x: 255 if (x > 160) else x,
                                          img.getdata())))
        print (abs_name+"の画像")
        plt.imshow(img)
        plt.pause(1)
        img.thumbnail((28, 28))
        # input_data の形に揃える
        img = map(lambda x: 255 - x, img.getdata())
        img = np.fromiter(img, dtype=np.uint8)
        img = img.reshape(1, 784)
        img = img.astype(np.float32)
        img = np.multiply(img, 1.0 / 255.0)
        #学習データと読み込んだ数値との比較を行う
        p = sess.run(y_conv, feed_dict={x:img, y_: [[0.0] * 10], keep_prob: 0.5})[0]
        #最も可能性のある数字を表示
        print (abs_name+"の認識結果は.....................................")
        print ("*******************************")
        print (np.argmax(p))
        print ("*******************************")
sess.close()
timer = time.time() - start
print("time:{0}".format(timer) + "[sec]")
```

## その他
* Pandasについて
    * 機械学習を行うまでの前処理(データの読込、クリーニング、欠損値の補完、正規化など)が、全ての作業の8〜9割を占める
    * Pandasを使うとそのような処理が効率的に行えるようになるため、Pythonで機械学習を行うには、Pandasは必須のライブラリとなっている
    * Pandas便利


## 参考
* 公式チュートリアル
    * https://www.tensorflow.org/tutorials/layers
* http://fuchami.hatenadiary.jp/entry/2017/02/14/025446
* https://udemy.benesse.co.jp/ai/tensorflow.html
* https://qiita.com/jkkch/items/d4009fff1722a52be8dc
* https://qiita.com/jintaka1989/items/3b70b5c5541620536fa2
* https://qiita.com/jintaka1989/items/3b70b5c5541620536fa2