既存のgithubにある推論コードをABEJA Platformで動かす方法

[ABEJA Platform Advent Calendar](https://qiita.com/advent-calendar/2018/abejaplatform) 2018の3日目です。

さて、今日はABEJA Platformを使って既存のgithubコードをAPI化してみます！

# はじめに

ABEJA Platformは、データの蓄積・アノテーション・学習・デプロイ・再学習と機械学習に必要となる全ての面倒なプロセスを実行する基盤で、これらのインフラを用意する必要がないのが売りとなっています。

しかしながら、多くのケースについて学習するのメンドいよね、ぶっちゃけ人物検出なんてgithubに上がっているんだから、わざわざ学習しなくて良くない？ってケースは非常に多いのではないかと思います。 ~~将来的には、各社パンチの効いた独自モデルをビジネス化しないと、「それって、他社と何が違うの？」と、本部長あたりから何時ものように怒られると思うのですが、世の中がAIだAIだとか騒いでいる間は、ぶっちゃけgithubのモデル使うだけでお茶を濁せますからね！~~

本日の解説を、そのままやるだけで、貴方の好きなgithubのモデルがサービスイン！・・・最高でしょう？というわけで、やっていきましょう。

# ABEJA Platformの推論モデルについて

ABEJA Platformでは、手元にある推論のpythonスクリプトと学習済みモデルをzipで固めてアップロードすると、リクエストに従い所定のエントリポイントが実行されるという仕組みになっています。簡単な例を試してみましょう。以下のURLにあるzipファイルをダウンロードして、ABEJA Platformにアップロードしてみましょう。

```
$ wget https://abeja-platform-config-prod.s3.amazonaws.com/sample_code/keras/resnet50.tar.gz
```

アップロードはログイン後の左のメニューからModelから、さらにModelを選択し、右上の「+Create Model」を選択します。ここで、Modelとは推論モデルを指します。

![Kobito.fIIBOK.png](https://qiita-image-store.s3.amazonaws.com/0/146529/0d5efad4-fa43-aeaf-a5dc-584b63779453.png "Kobito.fIIBOK.png")


すると、以下のようにモデルを設定するためのパラメータ入力画面が表示されます。Model name、Description、Versionを適当に埋め（何でも良いです）、Uploadから先ほどのzipファイルをアップロードしてください。Model Handlerにはエントリポイントを入力しますが、`main:handler`を入力してください。これはリクエストが来るとmain.pyのhandler関数が立ち上がることを意味します。アップロードすると、モデルが登録されます。なお、Versionとは、アップロードした推論モデルのバージョンです。例えば、人物検出と言っても、自身でモデルを作成する場合は、元となる学習データや、パラメータによって様々なバージョンが存在します。Versionはそれを管理するためのタグです。

![Kobito.2pVIAf.png](https://qiita-image-store.s3.amazonaws.com/0/146529/399a574e-e3de-8de1-6cda-e9675e9a698d.png "Kobito.2pVIAf.png")

![Kobito.w8JiOh.png](https://qiita-image-store.s3.amazonaws.com/0/146529/6ac8b083-abd5-e48c-1dd2-eb9e69b1c584.png "Kobito.w8JiOh.png")
![Kobito.AJT82V.png](https://qiita-image-store.s3.amazonaws.com/0/146529/25ca3662-3439-09f6-af3b-9d7a96f90ec9.png "Kobito.AJT82V.png")


次に、アップロードしたモデルをAPIにしてみましょう。左のModelからDeploymentを選択し、「+Create Deployment」を選択しましょう。

![Kobito.ILMduC.png](https://qiita-image-store.s3.amazonaws.com/0/146529/4e20d790-2084-9e93-5034-8cf5d23cc5ea.png "Kobito.ILMduC.png")


ここでもパラメータ入力画面が表れますので、適当なDisplay nameを入力し、Modelとして先ほど作成したModel nameを選択します。Environmentsは、ここでは空にしておいてください。ここは、プログラムに所定のパラメータを設定したい場合に利用しますが、本日の記事では利用しません。全て入力したら、「Create Deployment」を押しましょう。

![Kobito.iusr19.png](https://qiita-image-store.s3.amazonaws.com/0/146529/035522b9-4c58-8624-b889-568cfa6ea3bc.png "Kobito.iusr19.png")


すると、Deploymentsの画面に戻り、先ほど作成したモデルがリストに追加されているのが確認できます。

![Kobito.ID2NUa.png](https://qiita-image-store.s3.amazonaws.com/0/146529/27fb4123-18f9-7727-745b-2a58ad5de52d.png "Kobito.ID2NUa.png")

Display nameをクリックすると、APIを作成する画面に移ります。今回はWebAPIを作成するので、「Create HTTP Service」を選択します。

![Kobito.p4SCjr.png](https://qiita-image-store.s3.amazonaws.com/0/146529/39c92d30-e8bc-c840-20e2-1ac0d3b30fec.png "Kobito.p4SCjr.png")


すると、下記のようにHTTPサービスにデプロイするための詳細（モデルのバージョンや、インスタンスサイズ）を入力する画面が表れます。ここでは、インスタンスタイプをcpu-1、インスタンス数を1で設定し、Create HTTP Serviceをクリックしてください。

![Kobito.gMXD3w.png](https://qiita-image-store.s3.amazonaws.com/0/146529/70d17437-393c-e48d-a598-3f7ec9fc0723.png "Kobito.gMXD3w.png")

しばらく待つと、以下のようにHTTPサービスが立ち上がっていることが確認できます。

![Kobito.fXLQ5K.png](https://qiita-image-store.s3.amazonaws.com/0/146529/6e27e5a3-91b8-d88c-cdb3-83edb097e206.png "Kobito.fXLQ5K.png")

目のマークを押してみると、エンドポイント（アクセスするためのURL）が表示されます。さて、このエンドポイントにアクセスして推論モデルを動かしてみましょう。簡単のため、curlでアクセスしてみます。以下のコマンドを入力してください。

```
$ curl -X POST https://abeja-internal.api.abeja.io/deployments/XXXXXXXXX/services/ser-XXXXXXXX \
	-u user-XXXXXXX:XXXXXXXXXXXXXXXXXXXXXXX \
	-H 'Content-Type:image/jpeg' \
	--data-binary @"./sample.jpg" | jq
```

すると、推論結果が返ってくるかと思います。

```
[
  {
    "label": "ram",
    "probability": 0.624617338180542,
    "id": "n02412080"
  },
  {
    "label": "Arabian_camel",
    "probability": 0.1964111626148224,
    "id": "n02437312"
  },
  {
    "label": "llama",
    "probability": 0.11612541228532791,
    "id": "n02437616"
  },
  {
    "label": "standard_poodle",
    "probability": 0.05013580992817879,
    "id": "n02113799"
  },
  {
    "label": "hog",
    "probability": 0.002520850859582424,
    "id": "n02395406"
  }
]
```

ABEJA Platformを用いると、このように簡単に推論モデルをDeployし、WebAPIにすることができます。

まとめると、以下のフローになります。
- 推論モデルをアップロード
- WebAPIを作るための箱（Deployment）を作成
- HTTP Serviceを作成

簡単でしょ？

# 推論モデルのコーディング方法について

さて、自身で推論モデルを作成する際に、推論モデルを作るためのルールについて解説していきます。ABEJA Platformでは、HTTP Serviceが作成されると、指定したエントリポイントを持つプログラムが先頭から実行されます。この際に、Handlerの外側にある部分が実行されます。通常はここで推論モデル（Deep Learningにおけるネットワーク）を読み込ます。その後、プログラムは待機状態になります。そして、リクエストが来たらHandlerが実行されます。まとめると、以下のようになります。

![Kobito.eXQReH.png](https://qiita-image-store.s3.amazonaws.com/0/146529/95992501-9df7-79f4-e5ca-88cf9fba7bc7.png "Kobito.eXQReH.png")

さて、ここでは最も簡単なプログラムを幾つか作ってみましょう。

## サンプル１：JSONをそのまま返そう

まず初めに、事前の処理は何も無しで、JSONのリクエストが来たら、来たJSONをそのまま返してみましょう。JSONデータがリクエストされると、`_iter`からリクエストされたJSONを得られます。そのJSONを、そのままyeildしてみましょう。

```main.py
def handler(_iter, ctx):
    for d in _iter:
        yield d
```

これをzipで圧縮してアップロードしましょう。圧縮する際は、main.pyを含むディレクトリをzip化するのではなく、main.pyがzipファイルのルートディレクトリに配置されるように気をつけてください。

```
$ zip -r archive.zip *
```

作成したarchive.zipをデプロイ・HTTPサービスを作成してみましょう。作成したらcurlで呼び出してみてください。

```
$ curl -X POST https://XXXXX.api.abeja.io/deployments/XXXXXX/services/ser-XXXXX \
    -u user-XXXXXX:XXXXXXX \
    -H "Content-Type:application/json" \
    -d "{\"key\": \"test\"}"
```

## サンプル２：画像をアップロードし、サイズを返そう

次は、画像ファイルをリクエストし、サイズを返すだけのモデルを作ってみましょう。画像をリクエストした場合、`_iter`からはnumpy.ndarray型のイメージを得られます。そこで、shapeを使い画像の大きさを取得し、それをyieldで返してみましょう。

```main.py
def handler(_iter, ctx):
    for img in _iter:
        h, w, c = img.shape
        yield {'width': w, 'height': h}
```

こちらも先ほどど同様にHTTPサービスを作成し、curlで画像をPOSTしてみましょう。

```
$ curl -X POST https://XXXXX.api.abeja.io/deployments/XXXXXX/services/ser-XXXXX \
    -u user-XXXXXX:XXXXXXX \
    -H "Content-Type:image/jpeg" \
    --data-binary @image.jpg
```

# githubモデルの予備実験

ここまでで、モデルのデプロイ方法と、自分でモデルを作る大まかな方法は分かったかと思います。さて、ここからDeepLearningのモデルのデプロイをしますが、その前に準備をします。先ほどのサンプルより、JSONや画像を入力として、任意の処理をhandlerに書き、その結果をyeildすればAPIができることが確認できたかと思います。ここでは、「任意の処理」を単体で動かしてみましょう。

さて、任意の処理として、Deep Learningのモデルを適当に選んで、画像を入力、JSONを出力として、handlerの中身の部分だけを書いてみましょう。なお、処理を実行する上で様々なモジュールが必要になるかと思います。ABEJA Platformの仕様として、pipインストールで入れられるモジュールであれば、requirments.txtに書いておけば、デプロイ時にインストールしてくれます。

まず、何らかのモデルを手元で動かしてみましょう。pipで入れることができれば、どのようなフレームワーク、どのような処理でも良いのですが、ここでは簡単のために、ChainerのObject Detectionで試してみましょう。ChainerはPreferred Networksさんが開発したイケてるDeep Learningフレームワークです。周辺モジュールとして、Chainercvというコンピュータビジョンのためのライブラリや、化学・生物分野のライブラリがあり、様々なモデルを直ぐに試せます。ここでは、Chainercvのexampleにあるssdを実行してみましょう。

まずは、Chainercvに必要な各種モジュールをインストールしましょう。

```
pip install chainer chainercv
```

続いて、chainercvのgithubにある[ssdサンプル](https://github.com/chainer/chainercv/blob/master/examples/ssd/demo.py)をダウンロードしましょう。

```
$ wget https://raw.githubusercontent.com/chainer/chainercv/master/examples/ssd/demo.py
```

まずは、このデモを動かしてみましょう。なお、入力する画像は何でも良いですが、人や猫が写っている画像が良いです。

```
$ python demo.py cat.jpg
```

実行するために、モデルをダウンロードしたりと時間がかかると思いますが、数分待つと認識結果が画面に出るはずです。

さて、ここからdemo.pyをいじって行きましょう。まずダウンロードしたモデルはホームディレクトリ以下の所定の場所にありますので、これをカレントディレクトリにコピーしてください。もしくは、[ここ](https://github.com/yuyu2172/share-weights/releases/download/0.0.3/ssd300_voc0712_2017_06_06.npz)から直接落として来ても良いです。


```
cp '~/.chainer/dataset/pfnet/chainercv/models/ssd300_voc0712_2017_06_06.npz' .
```

カレントディレクトリにコピーしたモデルを直接読み込んで推論してみましょう。demo.pyを実行する際の`--pretrained-model`オプションにモデルのパスを指定する事で推論が可能なことを確認してみましょう。

```
$ python demo.py --pretrained-model ssd300_voc0712_2017_06_06.npz cat.jpg
```

さて、ここから推論に必要な部分を切り出していきましょう。以下のように改変してみましょう。パラメータとして入力していた`pretrained_model`を直接書き、`args.gpu`のように使わない部分を削ぎ落とします。また、モデルの出力は画像にせず、数値のまま（`bboxes`, `labels`, `scores`）で直接画面に表示してみましょう。なお、numpy形式ですと後々JSONに変換する際にエラーになるので、このタイミングでlist形式にしておきます（L18-L23）。

```demo.py
  1 from PIL import Image
  2 import numpy as np
  3 import chainer
  4 from chainercv.datasets import voc_bbox_label_names
  5 from chainercv.links import SSD300
  6
  7 def main():
  8     pretrained_model = 'ssd300_voc0712_2017_06_06.npz'
  9     model = SSD300(
 10         n_fg_class=len(voc_bbox_label_names),
 11         pretrained_model=pretrained_model)
 12
 13     img = np.array(Image.open('cat.jpg'))
 14     img = img.transpose(2, 0, 1)
 15     bboxes, labels, scores = model.predict([img])
 16     bbox, label, score = bboxes[0], labels[0], scores[0]
 17
 18     result = []
 19     for b, lbl, s in zip(bbox, label, score):
 20         r = {'box': b.tolist(),
 21              'label': voc_bbox_label_names[lbl],
 22              'score': float(s)}
 23         result.append(r)
 24     print(result)
 25
 26 if __name__ == '__main__':
 27     main()
```

これを実行すると、画像を入力として、推論結果がJSONに変換できるlist形式で得られることを確認できると思います。
 
# ABEJA Platform向けの推論モデルを作成

さて、このモデルをABEJA Platformの形式、すなわちhandlerを用いた形式に書き換えてみましょう。ABEJA Platformの推論モデルでは、Deep Learningのモデルはグローバルの部分に置き、画像はnumpy.ndarrayで入力され、出力をJSON形式としますので、先ほどのコードを少し修正して下記のようにします。

```main.py
  1 import numpy as np
  2 import chainer
  3 from chainercv.datasets import voc_bbox_label_names
  4 from chainercv.links import SSD300
  5
  6 pretrained_model = 'ssd300_voc0712_2017_06_06.npz'
  7 model = SSD300(
  8     n_fg_class=len(voc_bbox_label_names),
  9     pretrained_model=pretrained_model)
 10
 11 def handler(_itr, ctx):
 12     for img in _itr:
 13         img = img.transpose(2, 0, 1)
 14         bboxes, labels, scores = model.predict([img])
 15         bbox, label, score = bboxes[0], labels[0], scores[0]
 16
 17         result = []
 18         for b, lbl, s in zip(bbox, label, score):
 19             r = {'box': b.tolist(),
 20                  'label': voc_bbox_label_names[lbl],
 21                  'score': float(s)}
 22             result.append(r)
 23         yield result
```

また、requirements.txtは以下のようにします。

```requirements.txt
  1 pillow==4.2.1
  2 numpy==1.14.2
  3 chainer==4.5.0
  4 chainercv==0.10.0
```

最後に、main.py、requirements.txt、ssd300_voc0712_2017_06_06.npzをzipに纏めてアップロードしましょう。

# 実験

ABEJA Platform上で実験するには、HTTP Serviceの立ち上げ画面にある、Checkから猫の画像を選択してSendをすることによって、推論結果を見ることができます。

![Kobito.DSLUOw.png](https://qiita-image-store.s3.amazonaws.com/0/146529/d568c8e5-00e9-461b-9ace-92418edb27b3.png "Kobito.DSLUOw.png")

また、curlコマンドでも結果を確認できます（以下では見やすくするためにjqを使ってますが、なくても構いません）。

```
$ curl -X POST https://XXXXX.api.abeja.io/deployments/XXXXXX/services/ser-XXXXX \
    -u user-XXXXXX:XXXXXXX \
    -H "Content-Type:image/jpeg" \
    --data-binary @image.jpg | jq
```

レスポンスとして検出結果が返ってくるのを確認できます（ブラウザのものと数値が違うのは画像が違うからです）。

```
[
  {
    "box": [
      18.46795654296875,
      11.873655319213867,
      170.28759765625,
      177.91075134277344
    ],
    "label": "cat",
    "score": 0.9988056421279907
  }
]
```

# 終わりに

本記事では、ABEJA Platformに、既存のgithubにある推論モデルをデプロイし、WebAPIにする手順について解説しました。シンプルな物体検出であれば、このように簡単にサービス化ができるのを体感できたかと思います。また、ABEJA Platformを用いれば、物体検出に限らず、githubに公開されている様々なモデルを、本記事のような流れに従って少しコードを修正するだけで、簡単にサービスに利用することができるようになります。是非とも、他のサービスと組み合わせて、面白いものを作っていただければと思います！