機械学習モデルをGPUデバイスや組み込み機器などに実装することは、とても良い方法の一つです。
しかし、その一方で、非常にコストがかかります。
そこで、NVIDIA Jetson Nano Developer Kit B01を使って、物体検出モデル(Pytorch)と認識モデル(Tensorflow-Keras)を実行し、コスト削減を図りました。
しかし、Jetson nanoに展開するために最適化されたモデルを使用しても、メモリを多く消費するようで、同じデバイス上で両方のモデルを実行することは非常に困難でした。そこで、1つのモデルをAWSのクラウドシステムを利用して展開することにしました。
Jetson Nano上の検出モデルとAWS Cloud Services(AWS)上の認識モデルにより、システム全体の性能と速度を向上させることができました。
次の図は、組み込み機器とAWSへのモデル展開について説明しています:
今回は、AWS Lambda関数、docker、Cloudformationを利用しました。Lambda関数とdockerイメージは、AWS Cloudformation pipelineを使って自動的に作成されます。Cloudformation pipelineはyamlファイルを使って作成します。JSONフォーマットを利用することにもできますが、Cloudformationを扱う上でYAMLはJSONよりも多くの利点があります。
##Dockerfileを作成する。
こちらがdockerfileの例です。
# ベースイメージにpython 3.8のLambdaランタイムを使用
FROM public.ecr.aws/lambda/python:3.8
# 変数の格納にはArgumentを使用
ARG AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
ARG AWS_DEFAULT_REGION
# このapp.pyが機械学習の推論スクリプトになります。
COPY app.py ./
# requirements.txtからpythonの要件をインストールします。
RUN python3.8 -m pip install -r requirements.txt
# CMDをラムダ関数ハンドラに設定する
CMD ["app.handler"]
このDockerfileは、次のセクションで説明するCloudformationパイプラインを実行すると、イメージが作成されます。
Dockerについてもっと知りたい方は、こちらをご覧ください。
##Cloudformationを使ったパイプラインテンプレートの作成
AWSでは、各サービスについて非常に優れたドキュメントを用意しています。そこで、Lambda関数の作成にはAWS::Lambda::Functionのドキュメントを使用します。
パラメータテンプレートスニペットの設定は以下のように行います:
Note:この方法では、各オプションのデフォルト名を設定することができます。例えば、アプリケーション名、S3バケット名、dockerイメージ名、ラムダ関数名などです。パラメーターセクションで名前を設定しないと、パイプラインを実行すると、長くて気持ち悪い名前
が作成されてしまいます。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
ApplicationName:
Default: project_name_abc
Type: String
Description: Enter the name of your application
CodeBuildImage:
Default: 'aws/codebuild/standard:2.0'
Type: String
Description: Name of CodeBuild image.
SourceBranch:
Default: 'master'
Type: String
Description: Name of the branch.
Environment:
Default: 'development'(or 'production') # We can change depending on the environment
Type: String
Description: The environment to build.
LambdaFunction:
Default: 'lambda-function-name'
Type: String
Description: The predict lambda name.
基本的なスニペットを作成したら、今度はパイプライン用のResources
を作成します。Resources
を書く前に注意すべき点があります:
1. リポジトリ名は、命名規則に従ってください。
2. 名前はアルファベットで始まり、アルファベットの小文字、数字、ハイフン、アンダースコア、スラッシュのみを含むことができます。
3. すべてのリソースにAWS::IAM:Roleを与えることが推奨されます。
4. Amazon CloudWatchは、AWS、ハイブリッド、オンプレミスのアプリケーションやインフラリソースのデータや実用的なインサイトを提供する監視・管理サービスです。CloudWatchは、アプリケーション、インフラ、サービスといったスタック全体を監視し、アラーム、ログ、イベントデータを活用して自動化されたアクションを取ることができます。
5. AWS::CodeBuild::Projectリソースは、AWS CodeBuildがどのようにソースコードをビルドするかを設定します。
6. ルートレベルで buildspec.yamlを用意します。
7. AWS::CodePipeline::Pipelineリソースは、ソフトウェアの一部が変更されたときに、リリースプロセスがどのように機能するかを説明するパイプラインを作成します。
8. AWS::Lambda::Functionでは、Lambda関数を作成しています。実行時にLambda関数が使用できる最大メモリは10GBです。
Resources:
SourceRepository:
Type: 'AWS::CodeCommit::Repository'
Properties:
RepositoryName: !Sub '${ApplicationName}-${Environment}'
RepositoryDescription: !Sub 'Source code for ${ApplicationName}'
MyRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Sub '${ApplicationName}-${Environment}/repository_name'
CodeBuildRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service:
- codebuild.amazonaws.com
CodeBuildPolicy:
Type: 'AWS::IAM::Policy'
Properties:
PolicyName: CodeBuildPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: '*'
Effect: Allow
Roles:
- !Ref CodeBuildRole
MyContainerBuild:
Type: 'AWS::CodeBuild::Project'
Properties:
Artifacts:
Type: CODEPIPELINE
Environment:
ComputeType: BUILD_GENERAL1_SMALL # ビルドには最大で3GBのメモリと2つのvCPUを使用します。
Image: !Ref CodeBuildImage
Type: LINUX_CONTAINER
PrivilegedMode: True
EnvironmentVariables:
- Name: REPOSITORY_URI # 環境変数で設定できます(本記事では説明していません)。
Value: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${MyRepository}'
- Name: ENVIRONMENT # Not covered in this article
Value: !Sub '${Environment}'
Name: !Sub '${ApplicationName}-${Environment}-MyContainer-Build'
ServiceRole: !GetAtt
- CodeBuildRole
- Arn
Source:
Type: CODEPIPELINE
BuildSpec: 'buildspec.yaml'
AppPipeline:
Type: 'AWS::CodePipeline::Pipeline'
Properties:
Name: !Sub '${ApplicationName}-${Environment}-Pipeline'
ArtifactStore:
Type: S3
Location: !Ref ArtifactBucketStore
RoleArn: !GetAtt
- CodePipelineRole
- Arn
Stages:
- Name: Source
Actions:
- ActionTypeId:
Category: Source
Owner: AWS
Version: '1'
Provider: CodeCommit
Configuration:
BranchName: !Ref SourceBranch
RepositoryName: !GetAtt
- SourceRepository
- Name
OutputArtifacts:
- Name: SourceRepo
RunOrder: 1
Name: Source
- Name: Build-Containers
Actions:
- InputArtifacts:
- Name: SourceRepo
Name: Build-My-Container
ActionTypeId:
Category: Build
Owner: AWS
Version: '1'
Provider: CodeBuild
Configuration:
ProjectName: !Ref MyContainerBuild
RunOrder: 1
CodePipelineRole:
Type: 'AWS::IAM::Role'
Properties:
Policies:
- PolicyName: DefaultPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'codecommit:CancelUploadArchive'
- 'codecommit:GetBranch'
- 'codecommit:GetCommit'
Resource: '*'
Effect: Allow
- Action:
- 'cloudwatch:*'
- 'iam:PassRole'
Resource: '*'
Effect: Allow
- Action:
- 'lambda:InvokeFunction'
- 'lambda:ListFunctions'
Resource: '*'
Effect: Allow
- Action:
- 'codebuild:BatchGetBuilds'
- 'codebuild:StartBuild'
Resource: '*'
Effect: Allow
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service:
- codepipeline.amazonaws.com
LambdaFunctionRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: LambdaFunctionPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
Effect: Allow
- Action:
- 'lambda:InvokeFunction'
- 'lambda:InvokeAsync'
Resource: '*'
Effect: Allow
LambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
FunctionName: !Sub '${ApplicationName}-${MyLambda}-${Environment}'
MemorySize: 4096
Timeout: 500
Role: !GetAtt LambdaFunctionRole.Arn
Code:
ImageUri: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${MyRepository}:latest'
PackageType: Image
Environment:
Variables:
S3BUCKETNAME: !Sub ${BucketName}
S3BUCKETREGION: !Sub ${AWS::Region}
さて、CloudFormationのテンプレートを用意したら、いよいよスタックの作成です。
まず、「Upload a template file」オプションを選択し、pipeline.yml
ファイルを選択します。
スタック名の入力: (スタック名には、アルファベット(A-Z、a-z)、数字(0-9)、ダッシュ(-)が使用できます。)
また、パイプラインの作成時に設定したすべてのパラメータを確認することができます。
そして最後に、スタックを作成しましょう。
問題やエラーがなければ、dockerイメージが作成され、Elastic Container Registry にアクセスできるようになります。
Lambda関数が作成できたら、次にAWSコンソールから最新版のdockerイメージを手動でデプロイする必要があります。latest
のDockerイメージを選択します。現在のところ、Cloudformation pipelineのビルドから自動的にイメージをデプロイすることはできません。
以上、ご紹介しました。