https://kindpark.jpn.org/dogemotion/
こんなウェブアプリを作りました。犬の写真から表情を見て、感情を推測するAI(convolutional neural network)です。データサイエンスの観点からは、簡単に機械学習モデルを使えるインターフェイスを作ることはとても意義のあるものだと思います。獣医学的には、あまり意義…。とはいえ、できるまでとても大変だったので、その過程をメモしておきます。
概念図
デプロイする前
デプロイした後
FastAPI
pythonでサーバーを扱うフレームワークには、django、flask、fastAPIがあります。flaskも扱ったことありますが、fastAPIはデバッグがしやすいですね。uvicorn server立ち上げたあとに、サーバーのアドレスの後ろにdocsを付けてアクセスすると簡易的なインターフェイスが立ち上がります。curl使ってpostとかやらなくても、簡易的なインターフェイスでget、postを試すことができるのでとてもありがたいです。
Heroku
無料でAPIを公開させてくれるサーバーです。もちろん制限はありますが、無料というのがありがたいです。pythonは、基本のサーバー用のソフトapacheやnginxに加えてuvicornやgunicornといったソフトが必要になります。uvicornやgunicornを常に起動させておく必要があるのですが、それを許してくれるレンタルサーバーにはお金を積まないといけないようです。最低価格帯の共同レンタルサーバーでもできることはできるそうなのですが、かなり大変みたいです。
機械学習モデルをherokuにデプロイするに当たって大変なことは、容量制限です。tensorflowでneural networkのモデルを作ってherokuにアップロードすると工夫しない限り Compiled slug size: 700.5M is too large (max is 500M) とか言われてエラーになっていまいます。そこを乗り越えた方法もメモっておきます。
私のモデルはAutokerasで作成したモデルで、autokerasとtensorflowを両方読み込ませないとh5モデルを読み込めませんでした。なぜかtensorflow-lite用のモデルに変換すると、tensorflow単独で読み込めるようになるのでそれを利用してautokerasをそぎ落としました。
レンタルサーバー
Herokuにデプロイして使えるようになったAPI。しかしAPIを叩くインターフェイス(ウェブページ)を用意しないと、プログラマー以外はAPIにアクセスできないでしょう。そのため、レンタルサーバーでホームページを作り、そこからjavascriptを使ってAPIに対してpostします。
デプロイ前の壁(主にfastAPIの使い方)
- HTMLからfastAPIへの画像をPOSTするやり方は?
- fastAPIで画像をどうやって受け取ってtensorflowに読ませる?
- fastAPIで画像解析してその結果をどうやってHTMLが受け取る?
デプロイ時の壁(Herokuの使い方)
- Compiled slug size: 577.2M is too large (max is 500M)
デプロイ後の壁(FastAPIの設定の仕方)
- No ‘Access-Control-Allow-Origin’ header is present on the requested resource
- 30分APIを叩かないとAPIが寝てしまい、次にPOSTした際の初回は反応がない。
基本的なFastAPIの使い方
仮想環境に入り、以下のコマンド。
pip install fastapi
pip install uvicorn[standard]
pythonファイル内に、以下のテンプレートを使ってかく。まずFastAPI()でアプリオブジェクトを作成。その後、アプリオブジェクトに関数を追加。@app.get(“/”) ルートディレクトリにGETのリクエストが来た場合の処理を@の下に関数として書き込む。
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
コマンドラインから仮想環境内に入り、main.pyがあるフォルダ内で以下のコマンドを実行することでuvicornサーバーを立てることができる。mainは、pythonのファイル名。–reloadは、ファイルに変更があるときに、変更がリアルタイムでされる。これがないと、ファイル変更するたびにサーバーを再起動しないといけないのでめんどくさいです。
uvicorn main:app --reload
以下のアドレスでサーバーが立ち上がります。
http://127.0.0.1:8000
たぶんエラー的なものがでていると思いますが、気にしないでください。getしてないので当然です。
以下のアドレスで、fastAPIが自動的に作成するインターフェイス(swagger UI)がでてきます。ここでget・postを試したりできるのでデバッグできます。特に、pythonファイル内のアルゴリズムがおかしいのか、それとも通信系がおかしいのかの切り分けに役に立ちます。ここがfastAPIの良い機能です!
http://127.0.0.1:8000/docs
HTMLからfastAPIへの画像をPOSTするやり方は?
いくつかあるみたいですが、jQueryを使いました。まず、index.htmlのインターフェイスのtagたちです。ファイルのアップロードを可能にします。
<form method="post" action="" enctype="multipart/form-data" class="mycss">
<input type="file" id="file" name="file" accept="image/*" />
<input type="button" class="button" value="Upload" id="but_upload">
</form>
上記のHTMLが操作されたときにJavascriptです。アップロードされた画像を宛先のアドレスにpostします。
$(document).ready(function () {
$("#but_upload").click(function () {
var fd = new FormData();
var files = $('#file')[0].files;
// Check file selected or not
if (files.length > 0) {
fd.append('file', files[0]);
$.ajax({
url: 'https://~~宛先のアドレス',
type: 'post',
data: fd,
contentType: false,
processData: false,
success: function (response) {
if (response != 0) {
$("#prediction").text(response)
}
},
});
}
});
});
上記のファイルを作っていざ送信だと思っても、以下のようなエラーが起こりました。
Access to XMLHttpRequest at ‘http://127.0.0.1:8000/’ from origin ‘null’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
これは、htmlとjavascriptのサーバーを立ててから、uvicorn(fastAPI)に送らないといけません。つまり、uvicornでサーバー立てて、htmlとjavascriptのサーバー立ててと、2つのサーバーを立てて通信させることになります。
htmlとjavascriptのサーバーを立てるのは、visual studio codeならlive serverというエクステンションで一発です。vs codeでフォルダ開いて、F1押してstart live serverです。
fastAPIで画像を受け取ってどうやってtensorflowに読ませる?
画像を読み込ませるためには、追加のパッケージが必要です。
pip install python-multipart
pythonファイルに書き込みます。ファイルを受け取るときの関数の書き方は、async def create_upload_file(file: UploadFile = File(…)): です。
ファイルを受け取ったら、contents = await file.read() でファイルを読み込みます。
BytesIOというので受け取り、それをpillowで読み込むとうまくいきます。
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
from io import BytesIO
from PIL import Image
from tensorflow.keras.preprocessing.image import img_to_array
import numpy as np
from tensorflow.keras.models import load_model
@app.post("/")
async def create_upload_file(file: UploadFile = File(...)):
contents = await file.read()
im = Image.open(BytesIO(contents))
img = im.resize((224,224))
img = img_to_array(img)
img = img.reshape(224, 224, 3)
img = img.astype('float32')/255
X_trial = []
X_trial.append(img)
X_trial = np.array(X_trial, dtype='float')
model = load_model("model.h5")
pred = model.predict(X_trial)
classlist = ["Happy","Angry","Neutral","No energy"]
result = classlist[np.argmax(pred)]
return result
fastAPIで画像解析してその結果をどうやってHTMLが受け取る?
さきほどのpostリクエストの下のほうに、successという項目があります。ここにresponseが来た時の処理を書き込みます。
$(document).ready(function () {
$("#but_upload").click(function () {
var fd = new FormData();
var files = $('#file')[0].files;
// Check file selected or not
if (files.length > 0) {
fd.append('file', files[0]);
$.ajax({
url: 'https://~~宛先のアドレス',
type: 'post',
data: fd,
contentType: false,
processData: false,
/* ここにresponseが来た時の処理を記載する */
success: function (response) {
if (response != 0) {
$("#prediction").text(response)
}
},
});
}
});
});
Herokuのデプロイの仕方
gitを使えるようにしておきます。
フォルダ内に、main.pyとtensorflowのモデルh5ファイルの2つしかない状態にします。
仮想環境にいる状態で以下のコマンドを実行してください。3ファイル作ります。計5ファイルできます。
pip freeze > requirements.txt
echo web: uvicorn main:app --host=0.0.0.0 --port=${PORT:-5000} > Procfile
echo python-3.8.11 > runtime.txt
1つめは、仮想環境で使っているライブラリを記録します。このファイルをつかってherokuがpipしてパッケージをインストールするそうです。
2つめは、サーバーの設定です。
3つめは、pythonのバージョンを記載します。2021年7月現在以下のランタイムが選べるようです。
python-3.9.6
python-3.8.11
python-3.7.11
python-3.6.14
herokuにアカウントを作成して、CLIツールをダウンロードします。さきほどの5ファイルあるフォルダ内に移動して、コマンドラインから以下のコマンドを実行します。
git init
git add .
git commit -m "first"
heroku login
heroku create
heroku apps:rename myappname
git push --set-upstream https://git.heroku.com/〇〇.git master
heroku git:remote -a myappname
heroku open
デプロイ時の壁(Herokuの使い方)
Compiled slug size: 577.2M is too large (max is 500M)
上記のようなエラーがでませんでしたか?パッケージがでかすぎると。私の場合は、autokerasのモデルを使っていたのでautokerasとtensorflowの両方入りのどでかいパッケージになっていました。
Tensorflow-cpu & Tensorflow-Lite
tensorflowを普通にpipするとCPUだけでなくGPUを使うライブラリもついてきます。herokuではGPUを使わないので、CPUのライブラリだけでいいです。pip install tensorflow-cpuとするとGPUライブラリがついてこないのでかなりサイズが小さくなります。
それでもサイズがでかい場合には、tensorflow-Liteをお勧めします。これは、モバイル端末等で使うことを想定されているものです。ただ、pip install “URL”でインストールするのでherokuで使えるか分かりません。その上、h5ファイルをtflite方式にすればモデルのサイズが少し小さくなります。(200Mから190Mくらい)
autokerasのモデルは通常、読み込む際にautokerasのロードを必要としますがtensorflow-liteのモデルに変換しておけばtensorflow単独で読み込めるようになりました。
tensorflow-liteの使い方は、通常版と異なります。以下の記事の最後でh5ファイルからtfliteモデルに変更する方法、それからtfliteでpredictする方法を記載しています。
分かりにくかったかもしれないのでまとめると、基本的にはpip install tensorflowのかわりにpip install tensorflow-cpuにするだけでいいです。それでもだめなら、h5モデルを小さくします。
h5モデルをtflite形式で小さくした場合でも、pip install tensorflow-cpuだけで使えます。tensorflow-cpuの中にtensorflow-liteが入っています。
tensorflow-liteのみでデプロイできるか確認してません。
デプロイ後の壁(FastAPIの設定の仕方)
No ‘Access-Control-Allow-Origin’ header is present on the requested resource
このエラーはあるあるらしくて、色々なところに色々な回答がありますが、なかなかうまくいきません。これはfastAPIのmain.pyファイルに問題があるようです。どうやらpostする側の問題ではないようです。私の場合は以下のようにしたらうまくいきました。
最初は、allow_originsに許可する受信先を限定するように書き方をしていたのですが、どうにもうまくいかずに全許可(アスタリスク)に切り替えました。どこからでもAPIを叩けるようになるのでsecurity上良くないそうです。ただ、いろんな人に使ってほしい場合はこうしないとダメでしょう。allow_originsをアスタリスクにした場合、allow_credentialsをfalseにすることを忘れないでください。公式チュートリアルではtrueになっていてハマりポイントです。
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
from fastapi.middleware.cors import CORSMiddleware
# origins = [
# "https://〇〇〇〇/"
# ]
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
30分APIを叩かないとAPIが寝てしまい、次にPOSTした際の初回は反応がない
30分毎に叩き起こすしかないです。そんなことをやってくれるサイトがあります。
https://kaffeine.herokuapp.com/
自分のAPIを記入するだけです。やめたいときは、右下のremove your upp?のところから再度APIを記入すればやめれるようです。
無料版だと、APIの継続時間・起動時間に制限があるので気を付けて下さい!