GoogleAppEngine(GAE)フレキシブル環境でディープラーニングの予測タスクを動かしてみた(CNN,画像分類,keras/TensorFlow)

#はじめに
GoogleAppEngine(FlexibleEnv)でKeras/TensorFlowを利用し画像分類(CNN)の予測タスクを実行してみました。

本当はStandard環境で動かしたかったのですが、TensorFlowが、Python3.7非対応なので、Python3.6のFlexible環境で動作させています。
そもそもCloud ML Engineを使えば、という声が聞こえますが、GAEで完結できれば、よりお手軽なのではと思い、やってみました。

色々勉強をしながらなので、何かあればご指摘いただけると嬉しいです。

こんなユーザが対象です。

* GoogleAppEngineでKeras/TensorFlowを動かしてみたい
* MLEを利用することなくGAEで予測タスクも完結させたい（お手軽さや、コスト低減も期待

##環境

* GoogleAppEngine(FlexibleEnbironment,Python3.6)
* モデルはGCSに保存

##簡易構成図と簡単な流れは、以下です。
1. 事前準備(予測するためのモデルを作成して、GCSへ保存しておく)
2. GAEに予測画像のアップロード用のindex.htmlを作る
3. アップロード画像を予測できるデータに変換
4. GCSからモデルをロード
5. 予測実行


![構成図](https://storage.googleapis.com/permanent_koizumi/20181115_predict_cnn.png "構成図")


## 1. 事前準備(予測するためのモデルを作成して、GCSへ保存しておく)
事前にCNNで画像分類モデルを作って、GCSに置いてください。色々サイトで手順やコードがあるので、ここで詳細は触れません。
今回私は、CNNで二つの種類の画像分類モデル(蒙古タンメン中本と、ラーメン二郎w)を作っておきました。
[CNNで2つの画像分類モデルを作ったコードはgithubに公開しました。](https://github.com/ko-ism/datalab_keras_cnn)
上記のコードでは、Cloud Datalab上で、Keras/TensorFlowを利用して、モデル(HDF5ファイル)を生成、GCSへアップロードしています。

### GoogleAppEngineの環境の準備もしておく
app.yamlとrequirements.txtを以下のようにしておきます。
TensorFlowはPython3.7非対応なので、Flexible環境で構築。

```Python:app.yaml
runtime: python
env: flex
entrypoint: gunicorn -b :$PORT main:app

runtime_config:
  python_version: 3
```

```Python:requirements.txt
Flask==1.0.2
google-cloud-storage==1.7.0
gunicorn==19.8.1
WTForms-Appengine==0.1
numpy==1.15.2
keras==2.2.4
Pillow==5.3.0
tensorflow==1.12.0
```


## 2. GAEに予測画像のアップロード用のindex.htmlを作る
アップロード用と、結果レスポンス用のhtmlの準備をしておきます。


```html:index.html(抜粋)
<form method="post" action="post_image" enctype="multipart/form-data">
    <input type="file" name="file_predict">
    <p><input type="submit" value="送信する"></p>
</form>
```

```html:predict_kekka.html(抜粋)
<p><h3>予測結果</h3></p>
予測値:{{ predict_kekka }}
```

## 3. アップロード画像を予測できるデータに変換
ここから、コードに入ります。
まずは、予測したい画像がPOSTされたら、予測できる形式にデータを変換します。
Pillowを利用して、ファイルをopenし、RGB変換、リサイズをしたうえで、numpyでarray変換しています。

```Python:main.py
        DOWNLOAD_FOLDER = '/tmp/'
        file = request.files['file_predict']
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            
        file.save(os.path.join(DOWNLOAD_FOLDER , filename))
        filepath = os.path.join(DOWNLOAD_FOLDER , filename)

        image = Image.open(filepath)
        image = image.convert('RGB')
        image = image.resize((image_size, image_size))
        data = np.asarray(image)
        X = []
        X.append(data)
        X = np.array(X)
```

## 4. GCSからモデルをロード
予測をするために事前に作っておいたモデル(HDF5ファイル=nakamoto_jiro_cnn.h5)をGCSから取得し、loadします。

```Python:main.py
        model_name = 'nakamoto_jiro_cnn.h5'
        modelpath = os.path.join(DOWNLOAD_FOLDER , model_name)

        storage_client = storage.Client()
        bucket = storage_client.get_bucket(CLOUD_STORAGE_BUCKET)
        blob = bucket.get_blob(model_name)
        blob.download_to_filename(modelpath)
        model = load_model(modelpath)
```

## 5. 予測実行
最後に、予測実行し、結果を取得します。

```Python:main.py
        result = model.predict([X])[0]
        predicted = result.argmax()
        percentage = int(result[predicted] * 100)

        predict_kekka = "ラベル： " + classes[predicted] + ", 確率："+ str(percentage) + " %"
```

コードはここまでが主要なところで、あとは、GAEをデプロイして、トップページにアクセスし、2で作成したhtml(FORM)から画像をアップロードすることで、予測結果が返ってきます。


## おわりに
GoogleAppEngineで画像分類の予測タスクを完結できますし、GCSからモデルをロードするだけで簡単に使えるので、MLEにモデルをホストして使うよりお手軽ではと思いました。
[今回作成したGAEのコードは、githubにアップロードしています。](https://github.com/ko-ism/gae_keras_predict)