ブラウザ上で手書きの数字を判別するAIを、コピー＆ペーストで作ってみよう

#はじめに
この記事は[NTTテクノクロス Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ntt-tx)の10日目の記事です。

こんにちは、NTTテクノクロスの江島と申します。
普段は、研究開発向けの試作、[アジャイル開発の普及活動](https://www.ntt-tx.co.jp/products/soft_dojyo/?utm_source=qiita&utm_medium=ac2018&utm_campaign=10)、AIや機械学習の社内発表などをしています。

休日は子供と一緒にScratch（教育用のプログラム環境）でプログラミングしたりしています。作ったプログラムを他のユーザと共有したり、OSSのように他の人のプログラムを閲覧・改善できるなどなかなか高度で面白いです。

さて、今回は、「AIや機械学習って難しそうだけどちょっとやってみたい」というエンジニアのかた向けに、簡単・わかりやすいAIを作ってみます。



# この記事で作るもの
以下のようなことができるプログラムを、ブラウザ上の実行環境にコピー＆ペーストで作ります。

1. ブラウザ上の描画スペース上に、マウスで０から９のいずれかの文字を書きます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/319751/2678cd14-c7f1-a8dd-8ef9-a69074e73dcd.png)

1. どの数字を書いたのか、AIが判定し、結果を表示します。0から9の数字のうち、可能性が高いと判定されたものほど1に近い値が表示されます。
例えば下記の例だと、最も可能性が高いと判定されたのが3になります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/319751/f1d7c675-06e4-5caa-79d6-18862f2b1018.png)



### 動作確認環境

* OS:windows10 64bit（注意：tensorflowが32bitOSに対応していないため、 <font color="Red">32bitOSでは動作しません</font>）
* ブラウザ：chrome

環境に依存する処理はなるべく使わないようにしたので、Pythonのインストーラをmac版にしてターミナルから各種インストールを実行すれば基本的にmacでも動作すると思いますが、うまく動かない等あればコメントいただければ幸いです。

### システム全体イメージ
全体像はこのようなイメージです。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/319751/389f6162-8062-a74c-ee1b-2c153193513e.png)

今回主に直接的に操作するJupyter notebookとは、ブラウザで実行するプログラムの対話型実行環境です。
下記はhello worldの実行例です。
このように、スクリプトを書いてすぐ実行して結果を確認することができるので、対話的・効率的にデータ分析などを行なえます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/319751/4499d9d3-f837-2318-0a45-7f397cd4472d.png)



# 手順その１：動作環境の準備

まずはライブラリのインストール等を行い、動作環境を準備しましょう。

####（１）pythonのインストール

* インストーラに、pythonのパスを通すかどうか聞くチェックボックスがあるので手動で設定しない場合はチェックして下さい。
* pythonは12/10時点で3.7系が最新ですが、この現時点ではTensorflowが3.7系に未対応なのでpython3.6をインストールして下さい。 
https://www.python.org/downloads/release/python-366/

####（２）windows power shellを起動します
Windowsキーを押して、```power```と入力すると見つけやすいかもしれません。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/319751/18720b9a-296b-5a01-c7de-f7fad05e5e9f.png)


####（３）Jupyter Notebookのインストール
windows power shell上で下記コマンドを実行すると、インストールが実行されます。（要NW接続）

```
> pip install jupyter
```

####（４）jupyterの起動

```
> jupyter notebook
```

を実行すると、jupyter notebookのサーバが立ち上がり、ブラウザにjupyterの画面が表示されます。
ブラウザが自動で起動しない場合は、URL
http://localhost:8888/tree
をブラウザで開きます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/319751/cbc4fb26-0bad-9249-9779-8d4ed74cb9c3.png)


####（５）動作確認してみます。
右上のnewから、python3を選択します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/319751/39ef0f78-b596-7377-37eb-c315ae0ef384.png)

jupyter notebookでは、セルと呼ばれる枠内にpythonのコードを記入して使用します。
セルを作成するために、「＋」と表示されているボタンを押します。

```print("hello world")```
をコピー＆ペーストして、画面上部の"Run"と書かれたボタンを押すと、処理が実行されます。下記のように表示されればOKです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/319751/7ec69009-f447-5255-8d59-7b7e6cb3858e.png)




####（６）その他のライブラリのインストールします。
下記コマンドをwindows power shell上で実行して、各種ライブラリをインストールします。

```
> pip install matplotlib
```
```
> pip install scipy
```
```
> pip install pandas
```
```
> pip install scikit-learn
```
```
> pip install tensorflow
```
```
> pip install Pillow
```




# 手順その２：ここからいよいよ機械学習
####（１）学習データを読み込んで、機械学習させる。
先ほどのhello worldと同様に、セルに下記のコードをコピー＆ペーストして、Runボタンを押します。機械学習の処理が始まるので、しばらく待ちます。

```
import tensorflow as tf
import numpy as np

from tensorflow.contrib.learn.python.learn.datasets import mnist as mnist_loader

# （１）手書き数字の学習データを読み込みます。
mnist = mnist_loader.read_data_sets("MNIST_data/", one_hot=True)

# （２）ニューラルネットワークサイズ等設定値を指定します。
x = tf.placeholder("float",[None,784])
Weight = tf.Variable(tf.zeros([784,10])) # 縦28セル*横28セルなので、ニューラルネットワークの第1層は。784
b = tf.Variable(tf.zeros([10])) # 第2層は10個のニューロンを設定。10個の数字の対応する箇所の値が大きくなるよう学習させる
y = tf.nn.softmax(tf.matmul(x,Weight)+b)
y_ = tf.placeholder("float",[None,10])
cross_entropy = -tf.reduce_sum(y_*tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

# （３）学習の実行。ニューラルネットワークに学習させる回数は1200回。
init = tf.global_variables_initializer() 
ss = tf.InteractiveSession()
ss.run(init)

for times in range(1200):
    xb, yb = mnist.train.next_batch(100)
    ss.run(train_step, feed_dict={x: xb, y_: yb})
```

このように、学習データを取得・解凍した旨のメッセージが表示され、エラーが表示されなければ成功です。
エラーが発生した場合は、インストールが正常にできていない可能性があるので、環境を確認してみて下さい。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/319751/ec548b34-06a9-06fb-2869-c7dfd91b23e5.png)

####（２）上の（１）で読み込んだ学習データがどのようなものか、簡単に可視化してみましょう
下記をコピー＆ペーストして、Runボタンを押します。

```
# 学習データの可視化
index_test_image = 12 #何番目のデータを可視化するかここで指定します。この例では12番目に格納されている手書き数字（9です）が表示されます

fig = plt.figure(figsize=(8, 6))
ax1 = fig.add_subplot(2, 1, 1)
ax1.imshow(mnist.test.images[index_test_image].reshape([28, 28]), cmap="Greys")
```

下記のように表示されれば成功です。学習データの12番目は9ですね。他の値に変更していくつか見てみると、比較的バリエーション豊富な手書きデータがあることがわかります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/319751/bb7ebdff-56a8-eb3a-b1f3-56aa1e1dd4d2.png)

####（３）ニューラルネットワークの重み付けを可視化してみる
今度は、（１）で学習した結果、ニューラルネットワークの重み付けがどの様になっているか見てみましょう。下記をコピー＆ペーストして、Runボタンを押します。

```
# 重みの可視化

weight = Weight.eval().T
fig = plt.figure(figsize=(15, 10))

# 0可視化する数字を指定。ここでは0を指定。他の数字問参照してみて下さい。
# 一見では数字ごとの特徴がわかりにくいですが、比較してみると少しわかりやすいかもしれません（例：0と8など）
targetNum = 0; 

ax = fig.add_subplot(2, 1, 1)
ax.imshow(weight[targetNum].reshape([28, 28]), cmap="Greys")
```

下記のように表示されれば成功です。一見したところだと数字ごとの特徴がわかりにくいかもしれませんが、比較してみると少しわかりやすいかもしれません（例：0と8など）
![image.png](https://qiita-image-store.s3.amazonaws.com/0/319751/569ba3ca-e9fe-cd4e-a04e-08f3ccd56d37.png)


####（４）手書きする描画スペースを作成
手書きする描画スペースを作成します。下記をコピー＆ペーストして、Runボタンを押します。

```
from IPython.core.display import HTML
import base64
import numpy as np
from io import BytesIO
from PIL import Image

numcanvas = '''
    使い方：<BR>
    ①黄色の範囲内にマウスでに数字を書きます<BR>
    <canvas id="numArea" height="28px" width="28px" 
    style="border: 3px double #999999; background-color:yellow;"></canvas>
    <BR>

    <button id="save">②このボタンを押すと数値（配列）に変換されます</button>
    <BR>
    （書き直す場合はセルを再実行して下さい）
    <script>
        var kernel = IPython.notebook.kernel;
        
        var currentX ;
        var currentY ;
        var isMoving = 0;

        var imgDecoded 
            = 'imgArray = np.asarray(Image.open(BytesIO(base64.b64decode(imgDataSp))))[:,:,3]'
        var save = document.getElementById("save");
        var numArea = document.getElementById("numArea");
        var context;
        if (numArea.getContext("2d")){
            context = numArea.getContext("2d");
        }

        save.addEventListener("click", function(){
            kernel.execute("imgData = '" + numArea.toDataURL() + "'");
            kernel.execute('imgDataSp = imgData.split(",")[-1]');
            kernel.execute('imgArray = '+imgDecoded);
        });

        numArea.addEventListener("mouseup", end, false);
        numArea.addEventListener("mousemove", move, false);
        numArea.addEventListener("mousedown", start, false);

        function end(evt){
            if(isMoving === 0){
               context.lineTo(currentX-1, currentY-1);
               context.lineWidth = 2;
               context.stroke();

            }
            isMoving = 0;
        }

        function move(evt){
          if(evt.buttons === 1 || evt.witch === 1 ){
            currentX = evt.layerX;
            currentY = evt.layerY;
            isMoving = 1;

            context.lineTo(currentX, currentY);
            context.lineWidth = 2;
            context.stroke();
          }
        }

        function start(evt){
          evt.preventDefault();
          context.beginPath();

          currentX = evt.layerX;
          currentY = evt.layerY;

          context.moveTo(currentX, currentY);
        }

    </script>
'''

HTML(numcanvas)

```

下記のような入力欄が表示されて、

![image.png](https://qiita-image-store.s3.amazonaws.com/0/319751/7f07dae8-ae9c-4368-3963-21192c393508.png)

下記のように、マウスで手書き数字を書ければ成功です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/319751/2e8dbc22-c0fd-133f-c9be-33e3a36af02b.png)


####（５）手書きする描画スペースを作成
手書き数字を数値に変換します。下記をコピー＆ペーストして、Runボタンを押します。

```
import matplotlib.pyplot as plt

print("取り込んだ文字（拡大）")
plt.imshow(imgArray, cmap="Greys")
plt.show()

print("文字を①次元の配列に変換した数値を参照すると、0から255の値であることがわかります")
imgArray_flatten = imgArray.flatten()#1次元のベクトルに変換
print(imgArray_flatten)
```

取り込みに成功すると、下記のように手書き文字が表示されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/319751/6c0d86a4-7a70-1d33-c7bc-ed1a126d45c4.png)


####（６）手書き文字の分類
いよいよ手書き文字の分類です。

```
import matplotlib.pyplot as plt

# 分類する
result = y.eval(feed_dict={x: [imgArray_flatten]})

print(result) # 分類結果の表示。配列の先頭が0で末尾が9に相当し、可能性が高いと判定されたものほど1に近い値が表示されます。

plt.bar(np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), result[0])
```
判定結果が表示されれば成功です。お疲れさまでした！

![image.png](https://qiita-image-store.s3.amazonaws.com/0/319751/806c0446-8727-de6f-b726-b4a58eeff7c1.png)


#####補足：完成イメージはこのような形になります。うまく動かない！というかたはご参照下さい。
<img src="https://qiita-image-store.s3.amazonaws.com/0/319751/53039f39-12ff-553d-7fea-31b867979f0f.png" width=10%>


# まとめ
以上で、ほとんどコピー＆ペーストで手書き文字を判別する環境が作れました。

今回は、わかりやすさのために２層のニューラルネットワークとしたため、認識精度は限定的でした。
例えば、数字の２は比較的よく判別しますが、他の数字はあまり正確に判別できないなど。

jupyter notebookではパラメータを簡単に変更できるので、学習係数（今回は0.01）を変えてみたり、学習の回数（今回は1200）を増減させるなどいろいろ試してみて、処理結果がどのように変わるか観察してはいかがでしょうか。

さらに精度を上げるには、ニューラルネットワークを３層に増やしたり、学習データを増やすなど、様々な方法があるので、調査してチャレンジするとより理解が深まると思います。