ONNX RuntimeをAzure Functionsで動かしてみる

> Azure AI に関わることであれば「初めて触ってみた」「こんなもの作ってみた」「Deep Dive した」「エイサイハラマスコ～イ」などなんでもありです。お気軽にご参加ください ٩(ˊᗜˋ*)و

よっしゃ！！！　エイサ～イ ハラマスコ～イ₍₍ (ง ˙ω˙)ว ⁾⁾₍₍ (ง ˙ω˙)ว ⁾⁾₍₍ (ง ˙ω˙)ว ⁾⁾

# 概要

DNNは学習させて終わりではなく、デプロイ/デリバーの事も考えなくてはいけません。

すこし前だと、私自身もVMを借りFlask+WSGI+Apacheで、推論サーバを作っていました。
しかしこの形式だと、考えるべき事が多いです。

- 環境構築の難しさ
- サーバ代
- サーバが落ちたときにどうすれば良いのか（運用）
- etc

DNNの推論も、サーバレスで行いたいものです。上記懸念が圧倒的に少なくなるからです。

**ONNX RuntimeとAzure Functionsを使えば、ミニマムな構成で推論の機能を実装できます。**
この記事ではサンプルコードやメリットをご紹介します。

## ONNXとは

ONNXは、様々なディープラーニング・ライブラリ/フレームワークで、共通して動くモデルフォーマットをつくろうというプロジェクトです。
PyTorchやChainerをはじめ、すでに様々なライブラリが対応しています。

### 推論に、ディープラーニング・ライブラリはもういらない

Microsoftから[ONNX Runtime](https://microsoft.github.io/onnxruntime/)という、ONNXの推論用ライブラリが発表されています。

これまでであれば、推論のみでも[ディープラーニング・ライブラリを使用するのが常だった](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-machine-learning-tensorflow)と思いますが、もうその必要はありません。**ONNX形式のモデルファイルとONNX Runtimeがあれば、ミニマムにDNNの推論が可能になります。**
（進化の速い世界ですから、ディープラーニング・ライブラリのアップデートによって、モデル形式や記述方法の互換性が捨てられるといった可能性もあります。そうした懸念からも比較的開放されます）

また、ONNX Runtimeは、C, C++, C#, Pythonで使用でき、クロスプラットフォーム(Windows, Mac, Linux)に対応しています。


# Azure FunctionsでONNX Runtimeを動かそう

サンプルプロジェクトをGitHubにもアップしました。合わせてご覧ください。(2019/12/21追記)
https://github.com/okajax/onnx-runtime-azure-functions-example

## 構成

今回の主な構成は下記の通りです。

- Python 3.6
- Azure Functions
- Azure Blob Storage（モデルとラベル辞書の置き場）
  - ONNXモデル（今回は[学習済MobileNet](https://github.com/onnx/models/tree/master/vision/classification/mobilenet)を使用）
  - [ImageNetラベル辞書](https://gist.github.com/PonDad/4dcb4b242b9358e524b4ddecbee385e9) ※


※ ラベルインデックスとラベルテキストとの紐付けは、推論とは別で行わなければいけませんので、今回はPonDadさんが作成した和訳付きのラベル辞書を使用しています。大感謝です。


### 今回実装するものの概要

1. HTTPTriggerのFuntionsに、画像をPOSTをします。
1. Azure Blob StorageにはONNXモデルが配置されていて、Funcitonsはそれを読み込み推論を実行。
1. 推論結果をJSON形式で返します。


## ライブラリをAzure Functionsにあげる

Azure Functionsでライブラリをアップするには、requirements.txtを用意してデプロイするだけです。シンプルですね。
今回私の実装では、下記の通りになっています。

```requirements.txt
azure-core==1.0.0
azure-functions==1.0.4
azure-storage-blob==12.0.0
certifi==2019.9.11
cffi==1.13.2
chardet==3.0.4
cryptography==2.8
idna==2.8
isodate==0.6.0
msrest==0.6.10
numpy==1.17.4
oauthlib==3.1.0
onnxruntime==1.0.0
Pillow==6.2.1
pycparser==2.19
requests==2.22.0
requests-oauthlib==1.3.0
six==1.13.0
urllib3==1.25.7
```

従来（ディープラーニング・ライブラリを使用）よりは、コンパクトになっているのではないでしょうか。


## ソースコード

それではソースコードです。要所にコメントを入れていますので、参考にしてみてください。

```python

import os
import io
import json
import logging

import azure.functions as func
from azure.storage.blob import BlobServiceClient
import numpy as np
from PIL import Image
import onnxruntime

def main(req: func.HttpRequest) -> func.HttpResponse:

    if req.method != 'POST':
        return func.HttpResponse("Bad request", status_code=400)

    # 画像の取得
    post_img = req.get_body()
    in_memory = io.BytesIO(post_img)

    # リサイズと正規化
    ximg = Image.open(in_memory)
    ximg = ximg.resize((224, 224))
    ximg = np.array(ximg)
    ximg = ximg / 255

    # MobileNetに渡す次元は (N x 3 x H x W) である必要があるので、転置します （参考: https://github.com/onnx/models/tree/master/vision/classification/mobilenet）
    x = ximg.transpose(2, 0, 1)
    x = x[np.newaxis, :, :, :]  # (1, 3, 224, 224) になります
    x = x.astype(np.float32)


    # モデル、ラベル辞書のダウンロード
    try:
        blob_service_client = BlobServiceClient.from_connection_string(os.environ['BLOB_CONNECTION_STRING']) # Blobサービスとの接続。connectionStringは環境変数（ローカルの場合はlocal.setting.json）に格納しています
        container_client = blob_service_client.get_container_client('my_blob_container') # コンテナーの指定
        model_blob = container_client.get_blob_client('mobilenetv2-1.0.onnx') # ファイル名の指定
        json_blob = container_client.get_blob_client('imagenet_class_index.json')

        print("Downloading model and JSON...")
        download_stream_model = model_blob.download_blob()
        download_stream_json = json_blob.download_blob()

    except Exception as e:
        logging.error(e)
        return func.HttpResponse("Download failed.", status_code=503)

    # モデルをONNX Runtimeで読み込み
    model = download_stream_model.readall()
    session = onnxruntime.InferenceSession(model)

    session.get_modelmeta()
    input_name = session.get_inputs()[0].name
    output_name = session.get_outputs()[0].name

    # 推論
    probs = session.run([output_name], {input_name: x})
    results = np.argsort(-(probs[0][0])) # 推論結果の確率を降順でソート、"高い順のindex"がリストで返ります。

    # インデックスとラベルテキストの対応付け
    labels_dict = download_stream_json.readall() # JSONの読み込み
    labels_dict = json.loads(labels_dict.decode('utf-8'))
    display_results = [labels_dict[i]['ja'] for i in results[:10]] # 上位10件のラベルを抽出

    return func.HttpResponse(json.dumps(display_results, ensure_ascii=False))

```

## 結果

今回使用した画像は[こちら](https://www.pakutaso.com/20120535129post-1450.html)。レトリバーの画像です。

結果は、上位10件のラベルが返ってきます。

![ss_doggo_result.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124249/731b866c-4718-1186-1439-2bb0d4714c2d.jpeg)


結果は概ね合っていますね。素晴らしい。:clap:


# サーバレス、Azure Functionsにするメリット

- Pythonサーバをつくらなくていい
- 運用の懸念を少なくできる
- 従量課金である
- タイマートリガーを利用すればバッチとして利用できる
- ローカル開発もできるので、開発スピード低下もさほど心配ない

という点でしょう。

**推論処理は、ONNX Runtime＋サーバレスによって完全に独立させられるようになりました。**
既存のサービスをAIで拡張したい場合にも、既存Webサーバに手を入れる必要はありません。
リソースごと切り離したほうが、考えることも減って楽ですよね。

バッチ処理化など、サクッとFunctionsにあるトリガーのメリットを教授できるのも良い点です。
複雑なタスクは数秒以内で結果を返すのが難しいでしょうから、バッチスタイルは非常にフィットしているでしょう。
（個人的には、Blob Storageに貯めたファイルを、日次でラベリングするプログラムを作る予定です）


# さいごに：なぜ「AzureでAI」を選ぶのか

（※こちらは~~完全にポエム~~個人的意見です）
MicrosoftがONNXの発足メンバーであり、AzureにもONNXについての日本語ドキュメントが充実してきているからです。
つまり、**ONNX（とそれで実現しようとしている世界観）に対する本気度**が推し理由です。

現状、AWSやGCPのほうが、魅力的な機能があるかも（そう見えるかも）しれません（認知も強いですしね）。
とはいえ、細かな機能としては各社、利便性の足並みは自然と揃ってきます。
（AWSにあった機能がAzureにも実装される... その逆も「あるある」です）

では、何を基準にして選ぶのが良いのか。その答えは、目指している世界観ではないでしょうか。
ONNX Runtimeのマルチプラットフォーム対応から見える、AI everywhereな世界観。そこに対するコミットの本気度は、推しポイントです。
[ONNX Runtimeのドキュメント](https://docs.microsoft.com/ja-jp/azure/machine-learning/service/concept-onnx)も翻訳されており、すでに試験的な物ではないという気概を感じます。

より多様なデバイス（HoloLensもWindowsですし、IoT用OSではWindows 10 IoTもあります）でDNNを動かしたいときにも、ONNXの形で資産を貯めておけば直ぐに役立つはずです（ONNX.jsもあります）。

ONNXには大きな可能性があります。
AI everywhere, AI for everyoneな世界になりますように。

それでは楽しいAI開発/デリバーライフを。:+1: