はじめに
ご了承
- この記事はわかりにくい(何をしたいか分からない,日本語がバグってる)かもしれません.
- この記事のプログラムは,僕が書いたスクリプトから一部抜き取ったものなので,動くかどうかはわかりません.
- Flaskを使った機械学習APIの記事 (たとえば これとか,これとか,これ) はAPIのFlaskでの実装を主としていますが,この記事はクライアント側もPython実装する場合を考えるため,Webサービスを作りたい方には参考にならないかもしれません.
シチュエーション
機械学習モデルにデータをたくさん予測させるようなシチュエーションを想定しています.この記事の内容はすべてCLIで実行することを前提として書いてあります.どちらかと言うと研究用.
APIも,client側のプログラムも自分で実装するものとします.
_________ ___________
| client | ---- x: object ---> | api server|
| data:x | | y = f(x) |
|_________| <--- y: object ---- |___________|
メリット
たとえば,機械学習のモデルをサーバ側にAPI実装して,クライアントから,予測したいデータをPython Objectで投げて,それをそのままサーバ側で使えたらデータの形に依存しない通信が可能となる.データ通信の部分を考えずに,スクリプトを実装できるようになる.
Client側の実装
requestsライブラリを使います.requests.post("URL")
でURLにPOSTリクエストを送れます.
こんなクラスを実装します.Model
インスタンスを通して,APIにQueryを投げます.
import requests
import pickle
class Model(object):
def __init__(self, base_url:str):
"""base_url is api url
for example: base_url=http://abc.xyz/
following must be accessable:
- http://abc.xyz/evaluate
response must include 'evaluation' and 'class' fields.
"""
self.base_url = base_url
def evaluate(self, x:object) -> dict:
""" :X -> dict
return the api result
x must be able to convert to a pickle object
"""
# make payload
payload = {"instance": x}
resp = requests.post(self.base_url+"evaluate",
data=pickle.dumps(payload),
headers={"Content-Type": "application/octet-stream"})
resp.raise_for_status()
# if status is 200, raise_for_status returns None
return pickle.loads(resp.content)
from model import Model
if __name__ == "__main__":
model = Model("your_api_url")
#
# Scratch here :)
#
ポイント
- Objectを
pickle.dumps(object)
でPickle化 (わざわざファイルに保存する必要はない) して通信することで,引数x
の型に依存しなくなる.これで,使うモデル,データが変わっても,このクラスを使えるようになる.クライアント側はscript.py
だけの変更で良い. - レスポンスもPickle化されているため,
pickle.loads(resp)
でAPI出力を受け取る.Pickle化の恩恵で,APIの出力に依存しない.結果の解析はscript.py
でやればいい. -
headers={"Content-Type": "application/octet-stream"}
が一番大事.これがないと,flaskで読み取れない.一般にdata
がjsonの場合はapplication/json
とか書くでしょ?知らんけど. -
raise_for_status()
で例外処理.400,500系のエラーが発生したらプログラムを終了する. - モデルはサーバ側にあるためクライアント側では気にしなくていい.とりあえず,
model=Model("your_api_url")
ってやっておいて,実験スクリプトをscript.py
に書けばいい. - データセットはクライアント側にあっても,なくてもよい.
- あるとき:
x = cv2.imread(img_path)
とかで,データを読み込んで,model.evaluate(x)
でおk. - ないとき:大規模なデータセットがサーバ上にあって,そのテストデータのいくつかを予測をしたい場合,
model.evaluate(x=("test", 5)
とかにしておいて,API側で,データをロードすればいい.この場合,テストデータの5番目のデータがロードされるようにAPIを実装する.
- あるとき:
Server側の実装
サーバ側はFlaskをつかって実装します.Flaskを使ったAPI実装方法は上で示した記事などにも書いてあるため,詳細は割愛します.
以下の例では,学習済みのscikit-learnのmlpを使う例を示します.
このAPIの出力はx
の予測されたクラスと,その確率の辞書,すなわち{"prob": prob, "class", cls}
となります.
import pickle
import flask
app = flask.Flask(__name__)
model = None
class Model(object):
def __init__(self):
# load model
with open("pre-trained-mlp.pkl", "rb") as f:
self.model = pickle.load(f)
def evaluate(self, x, cls):
return self.model.predict_proba(x)[0][cls]
def predict(self, x):
return self.model.predict(x)[0]
@app.route("/evaluate", methods=["POST"])
def evaluate():
if flask.request.method == "POST":
data = pickle.loads(flask.request.data)
x = data["instance"]
cls = model.predict(x)
prob = model.evaluate(x, cls)
return pickle.dumps({
"prob": prob,
"class": cls
})
if __name__ == "__main__":
print(" * Starting server ...")
print(" * initializing ...")
model = Model()
print(" * done")
app.run()
ポイント
- サーバ側にもモデルを制御するためのクラス (この場合は
Model
) を作る.ここは要実装.わざわざクラスにしなくても,グローバル変数で十分な場合もあるかも. データの前処理が多い場合などはクラスにしたほうがいいかも. -
flask.request.method=="POST"
でリクエストの種類の判定 -
flask.request.data
にPickle化されたx
の生データが入っている.requests.post
でheaders={"Content-Type": "application/octet-stream"}
を指定することで,これで受け取れる. -
pickle.loads(flask.request.data)
でPython Objectに戻す.あとは,こいつをモデルに入力して結果を得る. - 結果はPickle化して
return
する.これで,どんな結果でも返信できる.
おわりに
とりあえず,意図するものができました.分かっていれば多分簡単かもしれませんが,Flaskを触ったのが初めてだったので色々戸惑いました.
機械学習の実験のために書いたものですが,他のタスクにも応用できそうですね.
改善点や変なところがあったらコメントいただけると嬉しいです.