KivyでMNIST 学習結果を確認するお絵描きツールを作る．

本日は

Chainerによる実践深層学習でCNNの勉強をしていて学習済みモデルの推論（ここでは手書き文字が何の数かを当てること）
がうまくいっているかを確かめるツールを作りました．

# ツールの概要

ここでのツールとはペイントアプリのことで，お絵描きした文字のピクセルを認識してそれをモデルに入力する機能を持つものです．

![eight.png](https://qiita-image-store.s3.amazonaws.com/0/135978/6edeca99-466b-a684-c2f4-06273c459c63.png)

左図がKivyで作ったアプリで入力されたデータを右図のMatplotlibで表示しています．

# コードたち

## DeepLeaning用

Chainerによる実践深層学習のコードを参考にGPUで動くものを作りました．

```py3:mnist_cnn.py
import os 
import numpy as np
import chainer
from chainer import datasets
from chainer import Chain, Variable
from chainer import iterators, optimizers, training
from chainer.training import extensions

import chainer.functions as F 
import chainer.links as L 

class CnnModel(Chain):
    def __init__(self):
        super(CnnModel,self).__init__(
            cn1=L.Convolution2D(1,20,5),
            cn2=L.Convolution2D(20,50,5),
            fc1=L.Linear(800,500),
            fc2=L.Linear(500,10),
        )

    def __call__(self,x,t):
        return F.softmax_cross_entropy(self.fwd(x),t)

    def fwd(self,x):
        h1=F.max_pooling_2d(F.relu(self.cn1(x)),2)
        h2=F.max_pooling_2d(F.relu(self.cn2(h1)),2)
        h3=F.dropout(F.relu(self.fc1(h2)))
        return self.fc2(h3)

def train(num_loop):
    chainer.cuda.get_device_from_id(0).use()
    model=CnnModel()
    model.to_gpu()

    optimizer=optimizers.Adam()
    optimizer.setup(model)
    minibatch_size=1000
    train, test = datasets.get_mnist(ndim=3)
    iterator = iterators.SerialIterator(train, minibatch_size)
    updater=training.StandardUpdater(iterator,optimizer,device=0)
    loops=(num_loop,'epoch')
    if not os.path.exists('result'):
        os.mkdir('result')
    trainer = training.Trainer(updater,loops,out='result')
    trainer.extend(extensions.ProgressBar())
    trainer.extend(extensions.snapshot_object(
        model, 'cnn_{.updater.epoch}.npz'), trigger=(1,'epoch'))
    print('start to train')
    trainer.run()
    print('finish to train')


def check_accuracy(num_loop):
    print("lets check_accuracy")
    train, test = datasets.get_mnist(ndim=3)
    model=CnnModel()
    chainer.serializers.load_npz(os.path.join('result','cnn_{}.npz'.format(num_loop)),model)
    model.to_cpu()
    counter=0
    for t in test:
        img,label=t
        x=Variable(np.array([img]))
        predict = np.argmax(model.fwd(x).data)
        if predict==label:
            counter+=1
    print("done")
    print(counter/len(test))


def main():
    num_loop=10
    train(num_loop)
    check_accuracy(num_loop)
if __name__ == '__main__':
    main()
```

## お絵描き部分

```py3:main.py
from predictor import predict
from kivy.config import Config
Config.set('graphics', 'width', 560)
Config.set('graphics', 'height', 560)
Config.set('graphics', 'resizable', 0)
from kivy.core.window import Window
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.utils import get_color_from_hex
from kivy.graphics import Color, Line, BindTexture
from matplotlib import pyplot as plt
import numpy as np
from scipy import interpolate
from scipy.misc import imresize


class CanvasWidget(Widget):

    def __init__(self):
        super(CanvasWidget, self).__init__()
        self.locked = False

    def on_touch_down(self, touch):
        # avoid painting line on button
        if Widget.on_touch_down(self, touch):
            return
        if self.locked:
            return

        with self.canvas:
            # add line primitive
            Color(*get_color_from_hex("#0080FF80"))
            touch.ud['current_line'] = Line(
                points=(touch.x, touch.y), width=10)

    def on_touch_move(self, touch):
        if Widget.on_touch_down(self, touch):
            return
        if self.locked:
            return
        if 'current_line' in touch.ud:
            touch.ud['current_line'].points += (touch.x, touch.y)

    def clear_canvas(self):
        # copy children by writing [:]
        saved = self.children[:]

        self.clear_widgets()
        self.canvas.clear()
        for widget in saved:
            self.add_widget(widget)

    def predict_number(self):
        all_pts = []
        for w in self.canvas.children:
            if isinstance(w, Line):
                wpts = np.array(w.points)
                xs = wpts[::2]
                ys = wpts[1::2]
                try:
                    tck, u = interpolate.splprep([xs, ys], s=0)
                except Exception as e:
                    print('warn', e)
                    continue
                u_new = np.arange(np.min(u), np.max(u), 0.01)
                out = interpolate.splev(u_new, tck)
                all_pts += out
        
        img = np.zeros((560+10, 560+10)).astype(np.uint8)
        polyx = np.array(all_pts[::2]).astype(np.int)
        polyy = np.array(all_pts[1::2]).astype(np.int)
        R = 10
        from itertools import product
        for xs, ys in zip(polyx, polyy):
            for x, y in zip(xs, ys):
                circle = [(x+i, y+j) for (i, j) in product(range(-R,
                                                                 R+1), repeat=2) if i**2+j**2 < R**2]
                for c in circle:
                    img[c[0], c[1]] = 128
        img = imresize(np.rot90(img), (28, 28),
                       interp='bilinear').astype('f')/np.max(img)
        plt.imshow(img, cmap='gray')
        plt.show()
        predict(img)


class PaintApp(App):

    def build(self):
        return CanvasWidget()


def main():
    Config.set("input", "mouse", "mouse,disable_multitouch")
    Window.clearcolor = get_color_from_hex("#FFFFFF")
    PaintApp().run()

if __name__ == '__main__':
    main()
```

## kv file

```py3:paint.kv
<CanvasWidget>:
    BoxLayout:
        orientation: 'vertical'
        padding: 2
        spacing: 2
        x:0
        top: root.top
        Button:
            text: 'Delete'
            on_release:root.clear_canvas()
        Button:
            text: 'Predict'
            on_release:root.predict_number()

```

## predictor

```py3:predictor.py
import os 
import numpy as np
import chainer
from chainer import Chain, Variable
from mnist_cnn import CnnModel

def predict(img):
    print("lets predict")
    model=CnnModel()
    chainer.serializers.load_npz(os.path.join('result','cnn_10.npz'),model)
    model.to_cpu()
    x=Variable(np.array([[img]]))
    result = np.argmax(model.fwd(x).data)
    print('result',result)
```

# 実行方法

## 起動
```shell-session
python main.py
```
画面が立ち上がります．

## お絵描き

お絵かきします．気分で数字を書きましょう．

![three.png](https://qiita-image-store.s3.amazonaws.com/0/135978/72e4a641-255e-8f8f-33ae-b9ce30aff014.png)

次にPredict ボタンを押します．そうすると入力結果の確認のためにMatplotlibで描画したデータが現れます．![three.png](https://qiita-image-store.s3.amazonaws.com/0/135978/63aa2068-adce-11db-332f-047e20018178.png)

その窓を閉じると コンソール上にモデルが推論した値が返ってきます．この場合は３が返ってきます．

Kivyがインストール出来たらぜひ出来たら遊んでみてくださいまし．