はじめに
皆さんこんにちは、新卒2年目エンジニアの細川です。
今回はAzure Functionsについての記事を書いていこうと思います。
今年オープンソースカンファレンスのオフライン開催が復活し、広島、京都に参加するための展示物として、きのこの山、たけのこの里の表情分析による投票システムを作成しました。作成物については、同期の龍ちゃんの記事や、後輩の山崎くんの記事にまとまっているのでぜひそちらをご覧ください。
この投票システムを作る際に、Azure Functionsのみでディープラーニングの学習済みモデルの推論サーバーは作れるか検証してみて、実際に作成することができたのでその手順について書いていきます。
今回はPy-Featというライブラリを使う際の手順ですが、他の学習済みモデルライブラリでも、同じような状況で詰まることもあると思うので参考になれば幸いです。
ローカルではモデルを使って推論できる状態になっていると仮定します。
結論が先に知りたいって方はまとめをご覧ください。
Azureでディープラーニングのモデルを利用する方法
今回は利用しませんでしたが、基本的にAzureでディープラーニングのモデルを学習させたり推論したりする場合には、Azure Machine Learningというサービスを使うことが多いそうです。このサービスはデータの成形などから学習、推論までを実現できるそうです。推論サーバーまでAzure Machine Learningのみで立てられるかは不明ですが、Azure Functionsなどとも連携ができそうなので、基本的にはこちらを利用する方が良さそうです。
今回はすでにローカルにPy-Featの推論環境が出来上がっていたこと、Azure Machine Learningの料金体制が良く分からなかったことなどの理由からAzure Machine Learningは利用せず、Azure Functionsのみで推論サーバーを立てることにしました。すでにモデルがあり、ちょっとした推論サーバーを立てたいくらいであれば、Azure Functionsは無料枠も大きいため、おすすめです。
Azure Functions作成のための前準備
実際にAzure FunctionsでPy-Featの推論サーバーを作っていきたいと思います。
Py-Featは人物が写っている写真を入力すると、その人の顔の領域を抽出して表情をスコア付けしてくれるライブラリです。
python3.7, 3,8, 3.9で動くようですが、ローカルで検証した際に3.7で上手く動いたため、今回は3.7の環境を構築しました。
ちなみにローカルで動かす際にはこちらの記事を参考にさせていただきました。本記事ではローカルでは推論できている前提で書いていきます。
さっそくAzure Functionsを作成しようと思ったのですが、Azure Functionsではすでにpython3.7のサポートが終了しており、ポータル上からではうまく作れません。
そのため、CLIツールのCore Toolsを使ってリソースの作成を行います。
Core Toolsの導入についてはこちらが参考になります。
Azure Functionsの作成
作成したいディレクトリに移動し、以下のコマンドを実行します。
func init --worker-runtime python
このコマンドを実行すると、以下のようなファイルが生成されると思います。
続いて、以下のコマンドをたたき、関数の作成を行います。
func new --name 関数名 --template "HTTP trigger"
すると、指定した名前のディレクトリが作成され、__init__.pyとfunction.jsonが作成されます。
これでひな型の作成としては完了になります。
PyFeatを使う
__init__.pyを以下のように編集します
import logging
import azure.functions as func
import json
import os
from feat import Detector
from azure.functions import HttpRequest, HttpResponse
detector = Detector(
face_model="retinaface",
landmark_model="mobilefacenet",
au_model='jaanet', # ['svm', 'logistic', 'jaanet']
emotion_model="resmasknet",
)
def main(req: HttpRequest) -> HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
dirname = os.path.dirname(__file__)
image_url = req.params.get('img')
if image_url:
logging.info("Image URL received: " + image_url)
results = detector.detect_image(dirname + "/" + image_url)
headers = {"Content-type": "application/json", "Access-Control-Allow-Origin": "*"}
return func.HttpResponse(results.to_string(index=True), headers=headers)
とりあえず、input.jpgとinput2.jpgというファイルを一緒にデプロイして、imgというクエリパラメータで受け取り、予測を行うという実装にしています。(実際に使う際にはBLOB Strageなどと繋いだりするかと思います。)
このように作成された関数名のディレクトリ直下に人物の顔が写っている適当な画像を配置しています。
pyfeatはライブラリをインポートさえしてしまえば、
results = detector.detect_image(image_path)
という非常に短いコードで予測を行うことができます。
PyFeatのインポート
Azure Functionsでライブラリをインストールするにはrequirements.txtに必要なライブラリとバージョンを記述すると自動でインポートしてくれます。そのため、通常通りpyFeatをrequirements.txtに以下のように追記してデプロイし、テストしてみましたが、うまくいきませんでした。
#requirements.txt
# Do not include azure-functions-worker in this file
# The Python Worker is managed by the Azure Functions platform
# Manually managing azure-functions-worker may cause unexpected issues
azure-functions
py-feat==0.3.7 # 追記
pyFeatは最初に実行する際に学習済みモデルをネット上から引っ張ってきてローカルに保存します。Azure Functionsではセキュリティを高めるためにデプロイ後のファイルを変更できないようになっているため、学習済みモデルを保存するタイミングでエラーとなってしまいうまく動きません。
そこで、requirements.txtに追記してAzure側でimportしてもらうのではなく、重みファイル込みのライブラリのソースコードを直接ソースと同じ個所において、デプロイします。pyFeatに限らず公開していない自作ライブラリのコードを使いたい場合なども同様にFunctionsのルートに置くことでデプロイできるので参考にしてみて下さい。
こちらの記事などを参考に、ローカルで一度動かして、学習済みモデルのダウンロードを済ませておいてください。
実際のライブラリのコードがどこにあるかは環境によるのですが、anacondaの場合は以下の場所にあるかと思います。
C:\Users\ユーザー名\anaconda3\envs\仮想環境名\Lib\site-packages
この下にある、featというディレクトリをまるっと先ほどの作業ディレクトリのルートに配置します。
関数名のディレクトリではなく、ルートの点に注意してください
一度でも推論させていれば、feat/resources
の下に以下のように重みファイルが追加されているはずです。
pyFeat以外の必要なパッケージをpip listなどで出力して、requirements.txtに追記します。
pyfeatだけは一緒にデプロイするため、requirements.txtから削除するのを忘れないでください。
一応、以下に僕の環境で動いたrequirements.txtを載せておきます。
余分なものが入っていたらすみません。
# requirements.txt
# Do not include azure-functions-worker in this file
# The Python Worker is managed by the Azure Functions platform
# Manually managing azure-functions-worker may cause unexpected issues
azure-functions
backcall==0.2.0
certifi==2022.12.7
charset-normalizer==3.3.0
colorama==0.4.6
cycler==0.11.0
debugpy==1.7.0
decorator==5.1.1
deepdish==0.3.7
entrypoints==0.4
fonttools==4.38.0
h5py==3.8.0
idna==3.4
imageio==2.31.2
ipykernel==6.16.2
ipython==7.34.0
jedi==0.19.1
joblib==1.3.2
jupyter-client==7.4.9
jupyter-core==4.12.0
kiwisolver==1.4.5
lxml==4.9.3
matplotlib==3.5.3
matplotlib-inline==0.1.6
nest-asyncio==1.5.8
networkx==2.6.3
nibabel==4.0.2
nilearn==0.10.2
nltools==0.4.7
numexpr==2.8.6
numpy==1.21.6
opencv-contrib-python==4.8.1.78
packaging==23.2
pandas==1.3.5
parso==0.8.3
pickleshare==0.7.5
Pillow==9.5.0
pip==22.3.1
prompt-toolkit==3.0.39
psutil==5.9.6
Pygments==2.16.1
pynv==0.3
pyparsing==3.1.1
python-dateutil==2.8.2
pytz==2023.3.post1
PyWavelets==1.3.0
pyzmq==25.1.1
requests==2.31.0
scikit-image==0.19.3
scikit-learn==1.0.2
scipy==1.7.3
seaborn==0.12.2
setuptools==65.6.3
six==1.16.0
tables==3.7.0
threadpoolctl==3.1.0
tifffile==2021.11.2
-f https://download.pytorch.org/whl/torch_stable.html
torch==1.13.0+cpu
torchvision==0.14.0+cpu
tornado==6.2
traitlets==5.9.0
typing-extensions==4.7.1
urllib3==2.0.7
wcwidth==0.2.8
wheel==0.38.4
wincertstore==0.2
PyFeatのコードの修正
先ほども書いた通り、PyFeatは最初にモデルをダウンロードしに行くので、その部分をコメントアウトして消しておかないとエラーで落ちてしまいます。
具体的にはfeat/detector.pyの115行目から132行目あたりをコメントアウトしてください。
with open(os.path.join(get_resource_path(), "model_list.json"), "r") as f:
model_urls = json.load(f)
# -------↓以下のような記述があればコメントアウト------------------
# if face_model:
# for url in model_urls["face_detectors"][face_model.lower()]["urls"]:
# download_url(url, get_resource_path())
# if landmark_model:
# for url in model_urls["landmark_detectors"][landmark_model.lower()]["urls"]:
# download_url(url, get_resource_path())
# if au_model:
# for url in model_urls["au_detectors"][au_model.lower()]["urls"]:
# download_url(url, get_resource_path())
# if ".zip" in url:
# import zipfile
# with zipfile.ZipFile(os.path.join(get_resource_path(), "JAANetparams.zip"), 'r') as zip_ref:
# zip_ref.extractall(os.path.join(get_resource_path()))
# if au_model.lower() in ['logistic', 'svm', 'rf']:
# download_url(
# model_urls["au_detectors"]['hog-pca']['urls'][0], get_resource_path())
# download_url(
# model_urls["au_detectors"]['au_scalar']['urls'][0], get_resource_path())
# if emotion_model:
# for url in model_urls["emotion_detectors"][emotion_model.lower()]["urls"]:
# download_url(url, get_resource_path())
# if emotion_model.lower() in ['svm', 'rf']:
# download_url(
# model_urls["emotion_detectors"]['emo_pca']['urls'][0], get_resource_path())
# download_url(
# model_urls["emotion_detectors"]['emo_scalar']['urls'][0], get_resource_path())
デプロイ
デプロイにはVSCodeのAzure拡張機能が便利です。
Azure Toolsと検索して拡張機能を導入してみてください。
Azure Toolsは直感的に操作できると思いますので、詳細な説明は省きますが、有効なサブスクリプションにログインし、Azure Functionsを作成して、そこにデプロイします。
動作確認
デプロイ後はAzure portalで確認を行います。
Azure Functionsのページに行き、作成した関数のページに飛びます。
関数のURLの取得をクリックし、関数のURLをコピーします。
今回はクエリパラメータで画像のパスを指定するようにしたので、コピーしたURLに以下の文字列を追加してブラウザでアクセスします。
&img=input.jpg
ブラウザでアクセスしてみて、以下のようにjsonが返ってくることが確認できれば、推論サーバーの完成です。
今回の例のようにinput.jpgとinput2.jpgを一緒にデプロイしている場合には、クエリパラメータをinput2.jpgとすることで、結果が変わることも確認できるかと思います。
まとめ
- Azure Functionsではデプロイ後のファイルを編集することができないため、重みファイルを自動でダウンロードできない
- 初回起動時に重みファイルをダウンロードするようなライブラリを利用する際は、ローカルに重みファイルをダウンロードしておき、一緒にデプロイする
- 公開されているライブラリに変更を加えた場合や自作ライブラリをAzure Functionsにデプロイしたい場合には、requirements.txtに記述せずに、関数プロジェクトのルートディレクトリにライブラリのソースコードを配置
- pyFeatの場合、modelをダウンロードする部分をコメントアウトする必要あり
おわりに
今回はAzure FunctionsのみでpyFeatの推論サーバーを立てる際の注意点を紹介しました。
最初にも書いた通り、基本的にはあまり使わなそうですが、ローカルに推論環境が完成しており、パッとクラウド上で推論サーバーを立てたい場合などには参考にしていただけますと幸いです。