インフィニットループ 技術ブログ

2018年01月23日 (火)

著者 : nob

入門 Keras (5) 学習済みモデルと Flask で API サービスを作る

連載一覧

第5回は毛色を変えて、学習済みのモデルと Python の Flask フレームワークを使って、簡易的な API サービスを構築します。
まだまだディープラーニングの入り口をちょっと覗いただけですが、せっかくニューラルネットに学習させることが出来るようになりましたので、自分の PC 上の Jupyter Notebook で正解/不正解を確認するだけではなく、おもちゃではありますが実際に動くサービスを作ってみましょう!

仮称 whichiris サービス

第4回に作成した Iris データセットを3種に分類するのと同じものを、今度はAPI サービス whichiris として作ってみましょう。
まずは Anaconda Prompt を起動し、Keras が動く環境に移ったあと whichiris ディレクトリとその下に modeldocroot の2つのディレクトリを作成します。
また conda-forge チャネルからの flask のインストールもここで行います。

activate mykeras
conda install -c conda-forge flask
cd whichiris
(mykeras) C:\Users\n-hatano\whichiris>tree /f
フォルダー パスの一覧
ボリューム シリアル番号は 000000FE C099:5357 です
C:.
├─docroot
│      app.py
│
└─model
        training.py

training.py

model ディレクトリに training.py というファイルで学習させるプログラムを作成します。
第4回で使ったコードと基本的には同じですが、checkpointer コールバックを定義して、学習中に損失関数がより小さくなるかモニターしモデルが向上した場合にのみ (save_best_only=True) ファイル名 “iris.h5” でその都度保存するように model.fit に追加しました。
また 学習させる際にはモデルが良くなったかどうか判定するためのテストデータとその正解を vlidation_data というパラメータで渡してあります。

from keras.callbacks import ModelCheckpoint
# 損失関数がより小さくなったときだけモデルをファイル保存するコールバック
checkpointer = ModelCheckpoint(filepath = "iris.h5", save_best_only=True)
# 学習成果をモニターしながら fit させるために、テストデータとその正解を validation_data として付加
model.fit(train_x, train_t, epochs=50, batch_size=10, validation_data=(test_x, test_t), callbacks=[checkpointer])

そして最後のテストにはファイル保存しておいたモデルを model.load_model で読み込み、 ファイルから作成したモデルの方で分類を実行するようにしています。
訓練部分のコード全体は以下になります。

from keras.models import Sequential, load_model
from keras.layers import Dense, Activation
from keras.optimizers import SGD
from keras.utils import np_utils
from keras.callbacks import ModelCheckpoint
from sklearn import datasets
from sklearn.model_selection import train_test_split
import numpy as np
'''
データ準備
'''
np.random.seed(0) # 乱数を固定値で初期化し再現性を持たせる
iris = datasets.load_iris()
X = iris.data
T = iris.target
T = np_utils.to_categorical(T) # 数値を、位置に変換 [0,1,2] ==> [ [1,0,0],[0,1,0],[0,0,1] ]
train_x, test_x, train_t, test_t = train_test_split(X, T, train_size=0.8, test_size=0.2) # 訓練とテストで分割
'''
モデル作成
'''
model = Sequential()
model.add(Dense(input_dim=4, units=3))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.1))
'''
トレーニング
'''
# 損失関数がより小さくなったときだけモデルをファイル保存するコールバック
checkpointer = ModelCheckpoint(filepath = "iris.h5", save_best_only=True)
# 学習成果をモニターしながら fit させるためにテストデータとその正解を validation_data として付加
model.fit(train_x, train_t, epochs=40, batch_size=10, validation_data=(test_x, test_t), callbacks=[checkpointer])
'''
新たに作ったモデルにファイルから読み込む
'''
app = load_model("iris.h5")
'''
ファイルから作ったモデルで分類する
'''
Y = app.predict_classes(test_x, batch_size=10)
'''
結果検証
'''
_, T_index = np.where(test_t > 0) # to_categorical の逆変換
print()
print('RESULT')
print(Y == T_index)

model ディレクトリで training.py を Anaconda Prompt 上の python で直接動かして動作を確認します。
第4回と同じく最後の RESULT が正しく表示され、iris.h5 ファイルが生成されていれば OK です。

python training.py
: : : :
: 省略 :
: : : :
Epoch 38/40
120/120 [==============================] - 0s - loss: 0.1839 - val_loss: 0.1319
Epoch 39/40
120/120 [==============================] - 0s - loss: 0.1820 - val_loss: 0.1361
Epoch 40/40
120/120 [==============================] - 0s - loss: 0.1961 - val_loss: 0.1286
10/30 [=========>....................] - ETA: 0s
RESULT
[ True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True]

app.py

次に docroot ディレクトリで以下の app.py ファイルを作成します。
Flask フレームワークを使う Web アプリケーションですが、ポイントは

  • グローバルのオブジェクトで model を作成し、load_model でファイルから読み込む
    global model, graph
    model = keras.models.load_model("../model/iris.h5")
    
  • 次にグローバル・オブジェクトの graph を作成し、get_default_graph でバックエンドの TensorFlow のグラフへの参照を設定しておきます
    graph = tf.get_default_graph()
    
  • Flask のルーティングの root() がアプリの本体になりますが
    1. with graph.as_default() でグローバルで設定した TensorFlow のグラフにこのスレッドでのコンテキストを固定します
    2. requets.args.get で GET パラメータを入手した後、モデルへの入力を作成します
    3. 与えるパラメータは4要素の1行のみですが、model.predict_classes は n 行 m 列のデータを想定していますので np.array( [ [ p1, p2, p3, p4 ] ] ) の2次元配列の形式で作成します

詳細は以下のコードの方でご確認ください。

from flask import Flask, request
import numpy as np
import keras.models
import tensorflow as tf
app = Flask(__name__)
# model and backend graph must be created on global
global model, graph
model = keras.models.load_model("../model/iris.h5")
graph = tf.get_default_graph()
@app.route('/', methods=['GET'])
def root():
    names = [
    'Iris-Setosa セトナ ヒオウギアヤメ',
    'Iris-Versicolour バーシクル ブルーフラッグ',
    'Iris-Virginica バージニカ'
    ]
    sl = sw = pl = pw = 0.0
    sl = request.args.get('sepal_l')
    sw = request.args.get('sepal_w')
    pl = request.args.get('petal_l')
    pw = request.args.get('petal_w')
    parm = np.array([[sl, sw, pl, pw]])
    with graph.as_default(): # use the global graph
        predicted = model.predict_classes(parm)
        return names[predicted[0]]
if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000)

ビルトイン Web サーバー起動

python のビルトインの Web サーバーの機能を使って Flask アプリケーションを動かします。 docroot ディレクトリで python app.py コマンドで動きます。終了させるときは CTRL-C を使います。
起動した直後にモデルを読み込んで TensorFlow を使うためのコンパイルが行われますが、GPU 環境でない場合は以下のように SSE や AVX 命令が使える CPU だが使っていない旨のワーニングが出る場合がありますが、そのままで問題ありません。

(mykeras) C:\Users\n-hatano\whichiris\docroot>python app.py
Using TensorFlow backend.
2017-12-08 16:29:43.302843: W d:\nwani\l\tensorflow_1497951990344\work\tensorflow-1.1.0\tensorflow\core\platform\cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE instructions, but these are available on your machine and could speed up CPU computations.
: : :
2017-12-08 16:29:43.303878: W d:\nwani\l\tensorflow_1497951990344\work\tensorflow-1.1.0\tensorflow\core\platform\cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX instructions, but these are available on your machine and could speed up CPU computations.
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

テストしてみる

ブラウザから sepal_w など4つのパラメータを付けながら

  • http://localhost:5000/?sepal_l=5.5&sepal_w=4.2&petal_l=1.4&petal_w=0.2
  • http://localhost:5000/?sepal_l=6&sepal_w=2.2&petal_l=4&petal_w=1
  • http://localhost:5000/?sepal_l=5.8&sepal_w=2.8&petal_l=5.1&petal_w=2.4

でアクセスしてみましょう。アイリスの名前が出てきました!学習済みのモデルを静的ファイルとしてアプリケーションサーバーに持ち込んで評価に使うことが出来ました!

次に外のマシンから Apache Bench で少し負荷をかけてみます。これは性能を測るためというより、ある程度の同時処理を実行させることで Flask + Tensorflow + Keras で モデルからのpredictが動作しない問題 を正しく回避出来ているかどうか確認するためです。

$ ab -c 16 -n 2000 'http://x.x.x.x:5000/?sepal_l=6&sepal_w=2.2&petal_l=4&petal_w=1'
**省略**
Document Path:          /?sepal_l=6&sepal_w=2.2&petal_l=4&petal_w=1
Document Length:        54 bytes
Concurrency Level:      16
Time taken for tests:   0.715 seconds
Complete requests:      200
Failed requests:        0
Write errors:           0
Total transferred:      41600 bytes
HTML transferred:       10800 bytes
Requests per second:    279.88 [#/sec] (mean)
Time per request:       57.168 [ms] (mean)
Time per request:       3.573 [ms] (mean, across all concurrent requests)
Transfer rate:          56.85 [Kbytes/sec] received
**省略**

16並列 200 リクエストで 279 リクエスト/秒 で動いていますので、動作的に問題はなさそうです。

まとめ

学習済みの Keras と TensorFlow によるモデルを Flask から使うことに焦点をあてるため、Python のビルトインサーバーを使ったり、Flask のアプリの内容は GET を使ったりなどかなり原始的な内容でしたが、Keras で訓練したモデルをアプリから使う場合の本質を味わって頂けましたら幸いです。
次回 2月6日(火)はまたディープラーニングに話を戻しまして、学習過程の可視化とパラメーターチューニングについてお届けする予定です。ご期待ください!!

ブログ記事検索

このブログについて

このブログは、札幌市・仙台市の「株式会社インフィニットループ」が運営する技術ブログです。 お仕事で使えるITネタを社員たちが発信します!