NNCとPython＋Flaskで簡単なディープラーニングWebアプリ

Flaskを少しかじって、ごく簡単なWebアプリを作れるようになったので、さっそくディープラーニングを組み込んで遊んでみました。ディープラーニングの部分はNeural Network Console（NNC）を使用します。

#利用環境
- Windows 10 Home 20H2
- Neural Network Console Windows版 2.0.0
- Neural Network Libraries 1.15.0
- Anaconda3-2020.11-Windows-x86_64
- Flask 1.1.2

#環境構築
環境を構築します。試行錯誤しながらやっているので、いくつか手順が抜けてるかもしれませんが、だいたいこんな感じだったと思います。

##Neural Network Consoleのインストール
こちらからダウンロードしてインストールしてください。
https://dl.sony.com/ja/app/

##Anacondaのインストールと仮想環境構築
Anacondaをインストールし、仮想環境を構築します。具体的な方法は
こちらの記事[【初心者向け】AnacondaでPythonの仮想環境を作成・切り替える(多分)最も簡単な方法](https://qiita.com/ell/items/fb60d2fd765650417c7a)を参照してください。
Neural Network LibrariesはPython 3.6～3.8に対応しているとのことなので、仮想環境構築時にPythonのバージョンを指定するようにします。

##仮想環境にNeural Network Librariesをインストール
Neural Network Librariesのインストール方法は下記ページに記載されています。
https://nnabla.org/ja/install/
といっても難しいものはなく、

```python
> pip install -U nnabla
```

とするだけです。（GPUを利用するときは設定が異なります）

ただし、これ以外にもモジュールが必要なようです。
https://nnabla.readthedocs.io/en/latest/python/installation.html
によるとscipy等も必要らしいので、あわせてインストールしておきます。

```python
> conda install scipy scikit-image ipython
```

##仮想環境にFlaskをインストール
Flaskもインストールしておきます。

```python
> conda install flask
```

#Neural Network Librariesで推論実行できるようにする
今回は下記のチュートリアルの「2. Python APIを用いて推論を実行する方法」をそのまま利用してみました。使用したプロジェクトは`02_binary_cnn`という、手書き数字の4と9を識別するCNN（Convolutional Neural Network，畳み込みニューラルネットワーク）です。
[チュートリアル：Neural Network Consoleによる学習済みニューラルネットワークのNeural Network Librariesを用いた利用方法2種](https://support.dl.sony.com/docs-ja/%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB%EF%BC%9Aneural-network-console%E3%81%AB%E3%82%88%E3%82%8B%E5%AD%A6%E7%BF%92%E6%B8%88%E3%81%BF%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%A9/)

チュートリアル通りに作業して、Anacondaの仮想環境でエラーなく実行できれば準備完了です。

#Webアプリの作成
準備ができたら、Webアプリを作成していきます。

##NNCの推論実行処理
こちらはほぼ上記のチュートリアル通りです。今回画像を扱うので、画像の読み込みと正規化の処理を入れています。
今回は、画像は28×28ピクセルのグレースケールを想定しています。必要に応じて画像のリサイズや減色処理を入れてもいいと思います。

```python:app.py前半
import nnabla as nn
import nnabla.functions as F
import nnabla.parametric_functions as PF

import os
import sys

from PIL import Image
import numpy as np


#----------NNCの処理----------

def network(x, test=False):
    # Input:x -> 1,28,28
    # Convolution -> 16,24,24
    h = PF.convolution(x, 16, (5,5), (0,0), name='Convolution')
    # MaxPooling -> 16,12,12
    h = F.max_pooling(h, (2,2), (2,2))
    # Tanh
    h = F.tanh(h)
    # Convolution_2 -> 8,8,8
    h = PF.convolution(h, 8, (5,5), (0,0), name='Convolution_2')
    # MaxPooling_2 -> 8,4,4
    h = F.max_pooling(h, (2,2), (2,2))
    # Tanh_2
    h = F.tanh(h)
    # Affine -> 10
    h = PF.affine(h, (10,), name='Affine')
    # Tanh_3
    h = F.tanh(h)
    # Affine_2 -> 1
    h = PF.affine(h, (1,), name='Affine_2')
    # Sigmoid
    h = F.sigmoid(h)
    return h

#画像の読み込みと正規化
def normalize_image(save_filepath):
    im_gray = np.array(Image.open(save_filepath)) / 255.0
    return im_gray

def predict(im_gray):
    # load parameters
    nn.load_parameters('./results.nnp')

    # Prepare input variable
    x=nn.Variable((1,1,28,28))

    # Let input data to x.d
    x.d = im_gray
    #x.data.zero()

    # Build network for inference
    y = network(x, test=True)

    # Execute inference
    y.forward()
    print(y.d)
    return y.d

```

##Python+FlaskでWebアプリ作成
FlaskでWebアプリを作ります。`digitsPred.html`に作成したフォーム（後述）から画像を指定してSubmitすると、裏でNNCの推論処理が走って得られた結果を`success.html`で表示します。
今回利用しているNNCのサンプルプロジェクトでは推論結果が0に近ければ"4"、1に近ければ"9"となるように学習されていますので、推論結果が0.5未満であれば"4"、そうでなければ"9"が画像に描かれていたと判定することにします。

```python:app.py後半
from flask import Flask, render_template, request, redirect, url_for
from werkzeug.utils import secure_filename
import os

app = Flask(__name__)

@app.route('/digitsPred', methods=['GET', 'POST'])
def upload():
    # GETの場合
    if request.method == 'GET':
        return render_template('digitsPred.html')
    # POSTの場合
    elif request.method == 'POST':
        #submitされたファイルを取得
        file = request.files['file']
        #安全なファイル名に変換
        save_filename = secure_filename(file.filename)
        #ファイル保存
        save_filepath = os.path.join('./static/image', save_filename)
        file.save(save_filepath)
        #画像ファイルを正規化しndarrayに変換
        im_gray = normalize_image(save_filepath)
        #NNCで推論実行
        pred = predict(im_gray)
        #結果処理
        if pred < 0.5:
            res = 4 #数字の4と判定
        else:
            res = 9 #数字の9と判定
        #結果表示
        return render_template('success.html', save_filename=save_filename, res=res, pred=pred[0][0])

if __name__ == '__main__':
    app.run(debug=True)
```
この2つのコードを、`app.py`という単一のPythonファイルで保存します。
なお、上記の処理で描かれているように、`./static/image`フォルダに画像を保管するようになっているので、フォルダを作成しておきます。


##HTMLファイルの準備
Pythonファイルがある場所に`templates`というフォルダを作成し、その中に以下の3つのHTMLファイルを作成します。

- base.html
- digitsPred.html
- success.html

###base.html
base.htmlで各HTMLファイルで共通となる部分を作っておき、継承するようにします。

```html:base.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>Form Sample</title>
    </head>
    <body>
        {% block content %}
        {% endblock %}
    </body>
</html>
```

###digitsPred.html
このページにフォームを作成し、ファイルをSubmitできるようにします。

```html:digitsPred.html
{% extends "base.html" %}
{% block content %}
<title>深層学習で数字判定</title>
<h1>数字の画像をアップロード</h1>
<p>画像はモノクロで28x28ピクセルにしてください。</p>
<p>現在のところ"4"と"9"の分類のみに対応しています。</p>
<form action="{{url_for('upload')}}" method="POST" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="submit" value="アップロード">
</form>
{% endblock %}
```

###success.html
このページで推論結果を表示します。NNCの推論結果の生値も出しておくことにします。

```html:success.html
{% extends "base.html" %}
{% block content %}
<title>推論結果</title>
<h1>推論が完了しました</h1>
<p>アップロードされたファイルは {{save_filename}} です。</p>
<h2>画像に書かれている数字は{{res}}です。</h2>
<p>NNCの推論結果は{{pred}}でした。</p>
{% endblock %}
```

#実行結果
`app.py`ファイルを実行します。たとえば`http://127.0.0.1:5000/digitsPred`にアクセスすると、フォームが表示されます。

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/346401/155a1f81-7a65-9d42-75a9-4aa8790558b5.png)

画像を指定して［アップロード］ボタンをクリックすると、こんな感じで結果を表示してくれます。
> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/346401/2a93cfdd-3a3c-f6ac-81b1-19e018528f08.png)


※画像ファイルは、NNCのサンプルデータセットに入っているものを使うと簡単です。`neural_network_console_200\neural_network_console\samples\sample_dataset\MNIST\validation`


#参考資料
- Udemyの講座　[Python+FlaskでのWebアプリケーション開発講座！！～0からFlaskをマスターしてSNSを作成する～](https://www.udemy.com/course/flaskpythonweb/)
- [scikit-learnとflaskで簡単な機械学習✕Webアプリ](https://qiita.com/cvusk/items/5506f843bb375fca1f97)