AWS LambdaでNumpy、Scipyを使ってみる!Serverless Frameworkとプラグインを使えばパッケージ管理が簡単!

Numpy、ScipyはLambdaのLayer機能を利用することで今回紹介する方法を使わなくても利用できます。AWSがNumpy、ScipyをホストしたLayerを提供しているので対象のLambdaのレイヤに設定することで利用できます。 詳細はこちらをご確認ください。

どうも!大阪オフィスの西村祐二です。

機械学習やデータ分析でよく目にするライブラリとして、Numpy、Scipyがあると思います。

今回、そのライブラリをAWS Lambda上で動かしてみた、という話になります。

通常、上記を実現するためにいくつか注意点があるのですが、Serverless Frameworkとプラグインのserverless-python-requirementsを使えば、その注意点をほぼ気にすることなく実現できたのでブログにまとめておきます。

AWS Lambdaで外部モジュールを利用する際の注意点

外部モジュールも含めてzipに固めてデプロイする必要がある

Lambdaで外部モジュールを使用する場合は、その外部モジュールをデプロイパッケージに含めてデプロイする必要があり、コードを修正するたびにzipに固めてデプロイパッケージを作成する必要があります。また実行プログラムから外部モジュールをimportする際のパスなど気をつける必要があります。(パッケージ直下に外部モジュールファイルをおくなど)

1つのデプロイパッケージに容量制限がある(2018/10/25現在、50MB)

Lambda 関数デプロイパッケージのサイズ (圧縮 .zip/.jar ファイル)に制限があり、2018/10/25現在、50MBとなっています。また、なるべくデプロイパッケージのサイズは最小限にしておくことが推奨されています。

他のLambdaの制限については下記ドキュメントを参照ください。 https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/limits.html

Pure Pythonなライブラリじゃない場合、Amazon Linux環境でビルドしたファイルをパッケージングする必要がある

Lambdaで利用したい外部モジュールの内部でC言語を使っている場合などPure Pythonじゃないライブラリの場合、Amazon Linux環境でインストール(ビルド)したファイルを使ってパッケージングする必要があります。 Numpy、Scipyはそれに該当します。

MacやWindowsにインストールしたモジュールファイルをデプロイパッケージに固めてデプロイしてもエラーとなります。

Amazon LinuxのDockerイメージを利用する方法やlambci/docker-imageというLambda関数の実行環境とほぼ同等のDockerイメージを使う方法があります。

上記3つのことをLambdaをデプロイする際に、つまりコードを更新する際に気をつける必要があり、少々面倒なところであります。はじめAWS SAMとDockerを使ったオレオレデプロイスクリプトを作っていましたが、そのスクリプトのメンテや案件専用につくったので汎用性がないところなどいろいろとつらくなっていたところ、Serverless Frameworkとプラグインのserverless-python-requirementsを使えば、その注意点をほぼ気にすることなく実現できました。

やってみる

AWSのクレデンシャルの設定は完了している前提で進めていきます。

環境

  • node.js:8.12.0
  • serverless framework:1.32.0
  • serverless-python-requirements:4.2.4

Serverless Frameworkをインストール

$ npm install -g serverless

プロジェクト作成

$ serverless create \
--template aws-python3 \
--name numpy-test \
--path numpy-test

python3環境セットアップ

$ cd numpy-test
$ virtualenv venv --python=python3
Running virtualenv with interpreter /usr/local/bin/python3
Using base prefix '/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6'
New python executable in /Users/username/scratch/numpy-test/venv/bin/python3.6
Also creating executable in /Users/username/scratch/numpy-test/venv/bin/python
Installing setuptools, pip, wheel...done.
$ source venv/bin/activate
(venv) $

Numpy、Scipyインストール

(venv) $ pip install numpy
(venv) $ pip install scipy

requirements.txtに書き出しておきます。プラグインはこのファイルを参照して外部モジュールを含めたデプロイパッケージを作成してデプロイしてくれます。

(venv) $ pip freeze > requirements.txt
(venv) $ cat requirements.txt
numpy==1.15.3
scipy==1.1.0

Lambda関数のプログラムを作成

今回、Numpy、Scipyが動作することだけを確認したかったので、簡単なプログラムにしています。

Numpyでは行列、Scipyでは積分のプログラムを実行しています。

具体的には、Scipyはquad()は与えられた関数を[区間の始まり, 区間の終わり]の区間で定積分し、2つの値を返します。変数1には積分した結果が、変数2には積分計算をした際の誤差が返されます。

最初の4行はserverless-python-requirements でデプロイパッケージ容量を削減する機能をつかうために記載しています。

try:
    import unzip_requirements
except ImportError:
    pass

import numpy as np
from scipy import integrate


def func(x):
    return 2*x + 5


def main(event, context):
    a = np.arange(15).reshape(3, 5)

    print("numpy array:")
    print(a)

    result, err = integrate.quad(func, 0, 5)

    print(f'scipy:積分結果:{result}\n誤差:{err}')


if __name__ == "__main__":
    main('', '')

ローカルで実行してみる

(venv) $ python handler.py
numpy array:
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
scipy:積分結果:50.0
誤差:5.551115123125783e-13

上記が、Lambdaを実行したときの出力結果となれば成功です。

プラグインをインストール

(venv) $ npm install --save serverless-python-requirements

serverless.ymlを編集する

デプロイファイルを設定していきます。

今回は下記のように設定しました。

service: numpy-test

provider:
  name: aws
  runtime: python3.6

plugins:
  - serverless-python-requirements

custom:
  pythonRequirements:
    dockerizePip: non-linux
    slim: true
    zip: true

package:
  include:
    - handler.py
  exclude:
    - '**'

functions:
  numpy:
    handler: handler.main

特徴のある設定項目を見ていきます。

  • 7,8行目
  • 利用するプラグインを指定します。

  • 12行目(dockerizePip: non-linux)

  • Dockerイメージlambci/lambda:build-python3.6を使って外部モジュールをパッケージングしてくれます。設定はtrueでもいいもみたいです。

  • 13行目(slim: true)

  • 外部モジュールからstrip.soファイル、__pycache__ディレクトリとdist-infoディレクトリなど削除してくれます。

  • 14行目(zip: true)

  • numpy、scipy、scikit-learnなどのような大きなライブラリを圧縮してくれる機能のようです。

  • 17行目(include)

  • パッケージに含めたいファイルを指定します。
  • 19行目(exclude)
  • パッケージに含めたくないファイルを指定します。今回includeで指定したファイル以外はLambda関数として使わないので'**'としてすべて除外しておきます。(外部モジュールは含まれます。

デプロイしてみる

dockerを使いますのでdockerデーモンを起動しておてください。

未インストールの場合は下記リンクなどからインストールし、起動しておいてください。

https://docs.docker.com/docker-for-mac/install/

(venv) $ sls deploy
Serverless: Adding Python requirements helper...
Serverless: Generated requirements from /Users/nishimura.yuji/study/sls/numpy-test/requirements.txt in /Users/nishimura.yuji/study/sls/numpy-test/.serverless/requirements.txt...
Serverless: Installing requirements from /Users/nishimura.yuji/study/sls/numpy-test/.serverless/requirements/requirements.txt ...
Serverless: Docker Image: lambci/lambda:build-python3.6
Serverless: Zipping required Python packages...
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Removing Python requirements helper...
Serverless: Injecting required Python packages to package...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (42.02 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.........
Serverless: Stack update finished...
Service Information
service: numpy-test
stage: dev
region: us-east-1
stack: numpy-test-dev
api keys:
None
endpoints:
None
functions:
numpy: numpy-test-dev-numpy

パッケージサイズは42.02 MBとなり50M制限にひっかかることはなさそうです。

試してみる

下記コマンドでターミナルからLambdaを実行できます。

(venv) $ sls invoke -f numpy --log

null
--------------------------------------------------------------------
START RequestId:  Version: $LATEST
numpy array:
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]]
scipy:積分結果:50.0
誤差:5.551115123125783e-13
END RequestId: 
REPORT RequestId: 	Duration: 0.77 ms	Billed Duration: 100 ms 	Memory Size: 1024 MB	Max Memory Used: 289 MB

やりましたね。ローカルで実行した値と同じであり、AWS Lambda上でNumpy、Scipyがうまく動作していることがわかります。

さいごに

いかがだったでしょうか。

AWS LambdaでNumpy、Scipyを使ってみました。デプロイする際に気をつける必要がある項目については、Serverless Frameworkとプラグインのserverless-python-requirementsを使うことでほぼ気にすることなく、デプロイすることができます。これでLambda関数のロジック部分の開発に注力できますね。

誰かの参考になれば幸いです。

追記

lambda上でnp.show_config()の出力結果を知りたいとコメントいただきましたので、試してみました。

np.show_config()を追記して実行した結果が下記になります。

ここらへんの知識がなく詳しいことはわかりませんが、OpenBLASなどは使える?みたいです。

null
--------------------------------------------------------------------
START RequestId:  Version: $LATEST
numpy array:
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]]
scipy:積分結果:50.0
誤差:5.551115123125783e-13
blas_mkl_info:
  NOT AVAILABLE
blis_info:
  NOT AVAILABLE
openblas_info:
    libraries = ['openblas', 'openblas']
    library_dirs = ['/usr/local/lib']
    language = c
    define_macros = [('HAVE_CBLAS', None)]
blas_opt_info:
    libraries = ['openblas', 'openblas']
    library_dirs = ['/usr/local/lib']
    language = c
    define_macros = [('HAVE_CBLAS', None)]
lapack_mkl_info:
  NOT AVAILABLE
openblas_lapack_info:
    libraries = ['openblas', 'openblas']
    library_dirs = ['/usr/local/lib']
    language = c
    define_macros = [('HAVE_CBLAS', None)]
lapack_opt_info:
    libraries = ['openblas', 'openblas']
    library_dirs = ['/usr/local/lib']
    language = c
    define_macros = [('HAVE_CBLAS', None)]
END RequestId: 
REPORT RequestId: 	Duration: 1.08 ms	Billed Duration: 100 ms 	Memory Size: 1024 MB	Max Memory Used: 291 MB