見出し画像

AWS Lambda+API Gatewayでディープラーニングモデルを簡易API化する方法

はじめに

ディープラーニングモデルを作成した後、できるだけ簡単にAPIとして利用できる形にしたいというモチベーションの元、サーバーなしにコードを実行することができるLambdaと簡単にAPIを作成できるAPI Gatewayの構成でそれを実現しました。

今回は入力を画像、出力は予測結果としてjsonを返すとします。つまり、できるものは以下のようなものを期待します。(デモは物体認識)

curlで画像をPOSTすると、

$ curl -H "Content-Type: image/png" --data-binary "@input/test_small.png" -X POST https://○○○.execute-api.ap-northeast-1.amazonaws.com/default/lambda-test

以下のようにjsonが返ってきます。

"[{\"box\": {\"x\": 249, \"y\": 100, \"w\": 104, \"h\": 222}, \"class\": 0, \"label\": \"person\", \"confidence\": 0.9975835680961609}, {\"box\": {\"x\": 3, \"y\": 115, \"w\": 106, \"h\": 213}, \"class\": 0, \"label\": \"person\", \"confidence\": 0.9960794448852539}, {\"box\": {\"x\": 170, \"y\": 113, \"w\": 92, \"h\": 216}, \"class\": 0, \"label\": \"person\", \"confidence\": 0.9951063990592957}, {\"box\": {\"x\": 338, \"y\": 109, \"w\": 104, \"h\": 217}, \"class\": 0, \"label\": \"person\", \"confidence\": 0.9948359131813049}, {\"box\": {\"x\": 92, \"y\": 115, \"w\": 104, \"h\": 212}, \"class\": 0, \"label\": \"person\", \"confidence\": 0.980797290802002}, {\"box\": {\"x\": 414, \"y\": 120, \"w\": 84, \"h\": 209}, \"class\": 0, \"label\": \"person\", \"confidence\": 0.9711345434188843}]"

この構成のメリットとしては
・サーバーを用意しなくても簡単に用意できること
・サーバーのように常時お金がかかることがないのでコスト削減になること
・アクセス管理などAPIとしての構成がすぐに用意できること
などがあります。
一方で、アクセスごとにモデルをロードするのでレスポンス速度は遅いところに欠点があります。そのため、本番運用には向かないですが、簡単なデモとしてなら機能すると思います。

それでは、具体的に説明していきます。

functionの作成

まず、ローカルでfunctionを実装します。このfunctionがAPI化する関数になります。ここではファイル名lambda_function.pyを用意して、その中でlambda_handler関数を作成します。(このファイル名と関数名は変更して対応も可能ですが、はじめはこの名前で進めるのが無難です。)
以下が、作成したサンプルです。以下のObjectDetectionクラスは独自で作成したモデルになります。ここは、自分で作成したモデルに変更します。
画像の受け取りは、AWS上ではevent['body-json']でbase64形式で受け取るように設定しますが、ローカルでは自分でローカルの画像を使ってencodeしてテストします。

import os
import numpy as np
from models.ObjectDetection import ObjectDetection
import json
import base64
from PIL import Image
from io import BytesIO

# ローカルでのテスト時はTrue
TEST = os.environ['TEST']

def lambda_handler(event, context):
   object_detection = ObjectDetection(
       "", 0.5, 0.3, None
   )

   if TEST:
       # テストのときはローカルの画像で試す
       image_path = os.path.join("", "input/test.png")
       event['body-json'] = base64.b64encode(open(image_path, 'rb').read())
   # base64でencodeしたものを受け取る
   rgb_image = Image.open(BytesIO(base64.b64decode(event['body-json'])))
   rgb_image = np.array(rgb_image.convert('RGB'))
   annotations = object_detection.detect(rgb_image)

   return json.dumps(annotations, cls = MyEncoder)


# numpyの型をdumpできるように変換するため
class MyEncoder(json.JSONEncoder):
   def default(self, obj):
       if isinstance(obj, np.integer):
           return int(obj)
       elif isinstance(obj, np.floating):
           return float(obj)
       elif isinstance(obj, np.ndarray):
           return obj.tolist()
       else:
           return super(MyEncoder, self).default(obj)

Lambda上で環境変数の設定も可能なので、「TEST」は環境変数で設定します。

モデルの重みのロード

作成したモデルがロードする重みはディレクトリの中にいれずに、外部からロードするようにします。lambdaではファイルをzipにまとめてアップロードしますが、最大250MBしかアップロードできないので、重みを入れてしまうとその制限に引っかかる可能性が高いからです。以下のように、/tmp/ の中に重みをダウンロードします。このようにすればLambda上でもダウンロードしてくれます。また、一度ダウンロードすれば保存されるので、アクセスの度にアクセスする必要もありません。

filename = (ファイル名)
file_path = os.path.join("/tmp/", filename)
weight_url = (ダウンロードする重みのURL)
if not os.path.exists(file_path):
    def _progress(count, block_size, total_size):
        sys.stdout.write('\r>> Downloading %s %.1f%%' % (
            filename, float(count * block_size) / float(total_size) * 100.0))
        sys.stdout.flush()
    file_path, _ = urllib.request.urlretrieve(weight_url, file_path, _progress)

functionのローカル実行

docker-lambdaを使ってローカルで実行のテストをします。まず、下記のようなDockerfileを作成します。これによって、AWSのLambda環境を擬似的にローカル環境でも適用できます。

FROM lambci/lambda:build-python3.6
ADD . .
CMD pip3 install -r requirements.txt -t /var/task && \
 zip -9 deploy_package.zip lambda_function.py && \
 zip -r9 deploy_package.zip *

また、requirements.txtに必要パッケージを記述します。

以上で、lambda_function.pyと自分のモデルファイル、Dockerfile、requirements.txtが用意できました。

ここまでできたらdockerでbuildします。(名前はご自由に。ここではlambda-test。)

$ docker build -t lambda-test .

次に、パッケージをzip化します。これでpipのパッケージも含めてLambdaで使うことができるようになります。250MBの制限があるので、必要最低限だけrequirements.txtには含めて下さい。実行すると、deploy_package.zipが生成されます。これをAWSにアップロードすることになります。

$ docker run -v "$PWD":/var/task lambda-test

以下がLambdaのテスト実行のコマンドです。実行して期待する値が返ってくれば成功です。

$ docker run -v "$PWD":/var/task lambci/lambda:python3.6 lambda_function.lambda_handler

LambdaをAWSにアップロード

作成されたdeploy_package.zipをアップロードし、AWS上にLambdaを作成します。AWSのコンソールからも設定できますが、ここでは以下のようにawsのコマンドで実行します。AWS CLI のインストールと設定でAWSのキーの設定は行って下さい。

$ aws lambda create-function \
--role arn:aws:iam::[アカウントID]:role/lambda_basic_execution \
--runtime python3.6 \
--handler lambda_function.lambda_handler \
--zip-file fileb://./deploy_package.zip \
--region ap-northeast-1 \
--function-name lambda-test

deploy_package.zipの内容をアップデートするときは以下のコマンドで可能です。

$ aws lambda update-function-code \
--zip-file fileb://./deploy_package.zip \
--publish \
--function-name lambda-test

通常のzipファイルのアップロードは50MBまでなので、それを超える場合は、S3上にアップロードとして設定を行い、対応します(それでも250MBまでなので注意が必要です)。

Lambdaの確認

まず、作成したlambdaをAWSのコンソールで確認してみましょう。サービスからLambdaを探します。作成されているのが確認できると思います。

重みのダウンロードは時間がかかるので先にダウンロードだけしてしまいます。ページ下部のタイムアウトの項目を大きくします。(例えば3分)

その後ページ右上の「テスト」から適当にテストを作成して実行します。

メモリのエラーが出る可能性が高いので、もしメモリが足りていなければタイムアウトの上のメモリを大きくします。

うまくダウンロードができれば、event['body-json']のkey errorが出るはずですが、問題ありません。次のAPI Gatewayの設定でこのキーの設定も行います。

API Gatewayの作成

まず、トリガーの追加からAPI Gatewayを追加します。

下記のようにトリガーを設定します。セキュリティはここではオープンにしています。

「API Gateway」に進むと、以下のような画面になります。


アクションを追加します。「メソッドの作成」をクリックし、画像をPOSTするのでPOSTで設定します。

そして、内容は以下のように設定します。Lambda関数のところは各自設定したLambda関数の名前を入力します。

保存すると以下のように設定ができているはずです。これでAPI GatewayとLambdaをつなげることができました。

次に、受け取る情報を整理するために、マッピングテンプレートを設定します。右上の「統合リクエスト」をクリックして、ページ下部のマッピングテンプレートに以下のような設定をします。

また、バイナリを扱うために、左のカラムから「設定」の項目をクリックし、バイナリメディアタイプを設定します。

ここまでできたら以下のようにアクションからAPIのデプロイを選択して、API Gatewayを適用させれば準備完了です。

curlで実行確認

URLは作成したAPI Gatewayのものに変えて、以下を実行する。適切な値が返ってくれば成功です。

$ curl -H "Content-Type: image/png" --data-binary "@input/test_small.png" -X POST https://○○○.execute-api.ap-northeast-1.amazonaws.com/default/lambda-test

エラーが出た場合は、メモリ不足、タイムアウト時間を確認してみて下さい。また、Lambdaのログも見れるのでそちらも確認してみて下さい。

おわりに

以上で説明は終わりです。サーバーの構築なしにAPI化することができました。アップロード制限やタイムアウト制限などの制限も多いですが、簡易的なものであれば対応できます。もちろん、レスポンスはどうしても遅くなってしまうので、本番運用であればサーバーをちゃんと立てて運用したいところですが、簡易的なものでよい場合は選択のひとつにはなるのではないでしょうか。

参考文献

docker-lambdaで簡単なLambda開発環境を構築
  - dockerを使ったLamda環境の構築で参考になりました。
API Gatewayがバイナリデータをサポートしたので試してみました
  - LambdaとAPI Gatewayで画像をPOSTする部分で参考にしました。
AWS Lambda および Tensorflow を使用してディープラーニングモデルをデプロイする方法
  - サーバーレス環境xディープラーニングで参考になる記事でした。

最後に

私が所属している株式会社ACESでは、Deep Learningを用いた画像認識技術を中心に、APIによるアルゴリズムパッケージの提供や、共同研究開発を行なっています。特に、ヒトの認識・解析に強みを持って研究開発を行っておりますので、ご興味のある方は、ぜひお問い合わせください!
【詳細・お問い合わせはこちら】 acesinc.co.jp sharon.jp

◆画像認識アルゴリズム「SHARON」について
ヒトの行動や感情の認識、モノの検知などを実現する画像認識アルゴリズムを開発しています。スポーツにおけるパフォーマンス分析やマーケティングにおけるヒトの心の動きの可視化、ストレスなどの可視化による健康状態の管理を始めとするAIアルゴリズムを提供しています。


この記事が気に入ったらサポートをしてみませんか?