AWS 기술 블로그
IoT 디바이스에서 쉽고 편리하게 기계 학습(ML) 추론하기
기계 학습(Machine Learning) 활용이 일반화 되면서 IoT 디바이스에서도 기계학습을 활용하려는 요구가 증가하고 있습니다. 기계학습 알고리즘을 IoT 디바이스의 동작에 활용하기 위하여 기계학습 서버 API를 이용하면, 1) 디바이스는 항상 네트워크에 접속이 가능하여야 하고, 2) 디바이스의 숫자가 증가하면 서버의 처리 용량이 동일하게 증가되어야 하며, 3) 추론을 위한 API 호출 비용 부담으로 인해서, IoT 디바이스에서 기계학습을 활용할 때 제한 요소가 될 수 있습니다.
“AWS Lambda를 이용한 XGBoost 기계학습(ML) 추론하기”에서는 기계학습 컨테이너를 이용하여 추론하는 서버리스 아키텍처를 보여주고 있습니다. 2020년 12월부터 Lambda가 컨테이너 이미지를 지원함으로써, Lambda의 배포 패키지로 최대 10GB의 컨테이너 이미지를 사용할 수 있게 되었습니다. Lambda는 사용한 만큼만 지불할 수 있고 운용이 편리하므로, 추론 API를 효과적으로 제공할 수 있습니다.
Lambda에서 컨테이너를 이용해 기계학습 추론 API를 제공했던 것처럼, 기계학습 기능을 IoT 디바이스에서 동일하게 사용 할 수 있다면, 1) 네트워크 연결과 관련없이 기계학습을 활용할 수 있고, 2) 디바이스 숫자가 늘더라도 서버에 영향을 주지 않으며, 3) 추론을 위한 API 호출 비용이 발생하지 않습니다. 본 게시글에서는 IoT 디바이스에서 컨테이너를 이용해 기계학습 모델을 쉽게 배포하고, 추론 API를 사용하는 방법에 대해 설명하고자 합니다.
솔루션 개요
AWS Greengrass V2는 오픈소스 edge runtime으로 2020년에 re:Invent에서 소개되었습니다. Java 기반의 Core는 Docker 컨테이너를 사용할 수 있는 컴포넌트를 제공하고, IoT Greengrass Deployments를 통해 편리한 배포 환경을 제공합니다. 본 게시글에서 설명하는 기계학습 API는 Greengrass의 컨테이너 컴포넌트(Container Component)를 이용하여, IoT 디바이스에서 쉽게 기계학습 추론 기능을 사용할 수 있도록 해줍니다. 또한, 기계학습 컨테이너를 사용하게 되면 디바이스 종류와 관계없이 동일한 개발 및 배포 환경을 제공하므로, 다수의 여러 디바이스들에 기계학습 모델을 적용할 때 유용합니다.
솔루션 아키텍처
전체적인 아키텍처는 아래와 같습니다. 서버에서 Lambda로 구현한 Inference API 기능을 IoT Greengrass로 가져와서 컨테이너 컴포넌트를 이용해 구성하고, Local 컴포넌트를 이용하여 추론 API 기능을 이용합니다.
각종 IoT Devices와 연결되어 있는 로컬 컴포넌트(Local Component)는 기계학습을 활용하고자 할 때에 마치 Cloud 사용자가 RESTful API를 이용하여 Lambda의 추론(Inference) API를 호출하듯이, Greengrass의 IPC 통신을 이용하여 컨테이너 컴포넌트의 추론 모듈(Inference Module)을 호출하여 사용할 수 있습니다.
컨테이너 컴포넌트(Container Component)는 Lambda의 Inference API를 가져와서 Greengrass 디바이스에서 실행할 수 있도록 해줍니다. Lambda는 event 방식으로 호출하는 구조이지만, Greengrass의 컴포넌트들은 IPC 통신으로 연결되어야 하므로, 컨테이너 컴포넌트에 있는 인터페이스(Interface.py)에서는 IPC Client V2를 이용해 로컬 컴포넌트와 IPC 세션을 열고, 인입된 요청을 event 형태로 바꾸어서 Inference module을 통해 추론 동작을 수행합니다. 컨테이너 컴포넌트의 추론 모듈은 Lambda와 동일한 추론 API(inference.py)와 기계학습 모델(xgboost machine learning model)을 가지고 있습니다.
AWS CDK로 컨테이너 컴포넌트의 Dockerfile을 이용하여 Inference API를 제공할 수 있는 Docker 컨테이너 이미지를 생성하고, Amazon ECR에 저장한 다음에, AWS IoT Device Management를 이용하여, Greengrass 디바이스에 배포합니다.
IoT 디바이스의 사용환경이 변화하여 새로운 dataset이 주어졌을 때에도 기계학습이 정상적으로 동작하기 위해서는 정기적인 모델 업데이트가 필요합니다. 따라서, 모델을 쉽게 배포하고 운영할 수 있어야 합니다. 아래에서는 초기 모델 및 정기적인 모델 업데이트를 위한 과정을 설명합니다.
- AWS Lambda와 Greengrass디바이스에서 사용할 기계학습 알고리즘은 Jupyter Notebook과 Amazon Sagemaker를 통해 학습되어지고, 이때 만들어진 알고리즘은 Amazon ECR에 컨테이너 이미지로 배포될 수 있습니다. (참고: “AWS Lambda를 이용한 XGBoost 기계학습(ML) 추론하기”)
- 개발된 기계학습 모델은 컨테이너 이미지 형태로 Lambda에 탑재되어 충분히 검증된 이후에, IoT Greengrass 디바이스에 독립되고 안정된 Docker 컨테이너 환경을 활용하여 배포됩니다. 디바이스 종류가 다양해도 Docker 환경을 지원하는 디바이스들은 동일한 방법으로 배포될 수 있습니다. 이와 같이 Greengrass 디바이스는 Lambda에서 사용하였던 기계학습 기능을 IoT Greengrass에서 컨테이너 컴포넌트로 편리하게 사용할 수 있습니다.
- 디바이스에서 기계학습을 실제로 활용하는 Greengrass 컴포넌트들은 서버의 기계학습 API를 호출 하듯이 디바이스 내부의 기계학습 컨테이너 컴포넌트에게 요청을 보내고 추론 응답을 받을 수 있습니다.
이후 기계학습 모델이 업데이트 되면, 마찬가지로 Lambda를 통해 기능을 검증한 다음에, AWS Greeengrass를 통해 다수의 다른 디바이스에 컨테이너 형태로 편리하게 배포할 수 있습니다.
Greengrass V1.x에서는 Docker connector를 이용하였고 V2.0에서는 컴포넌트의 Recipe의 environment variable에 정의된 registry에서 Docker 컨테이너 컴포넌트를 생성하게 됩니다. 여기서는 Greengrass V2에 기반하여 추론용 Docker 이미지를 Greengrass에 컨테이너 컴포넌트로 배포합니다.
사전 준비 사항
이 솔루션을 사용하기 위해서는 사전에 아래와 같은 준비가 되어야 합니다.
단계 요약
이번 게시글은 아래와 같은 단계로 구성됩니다.
- 단계1: Greengrass 설치하기
- 설치 위해 유저 액션 필요
- 단계2: Greengrass에서 추론을 수행하기 위한 Interface 구성하기
- 코드 설명
- 단계3: Local 컴포넌트에서 추론 요청하기
- 코드 설명
- 단계4: AWS CDK로 기계학습 알고리즘 추론을 IoT Greengrass에 배포하기
- 설치 위해 유저 액션 필요
- 단계5: 추론을 요청하는 컴포넌트 생성하기
- 코드 설명
- 단계6: 추론을 위한 컨테이너 컴포넌트의 배포 준비하기
- 코드 설명
- 단계7: 전체 리소스 생성 및 배포하기
- 설치 위해 유저 액션 필요
- 단계8: 배포 후 결과 확인하기
- 결과 확인
단계1: Greengrass 설치하기
Greengrass를 사용하기 위해서는 최소 256MB의 디스크 공간과 96MB의 RAM을 필요로 합니다. 디바이스로 Raspberry Pi를 사용할 수 있으며, 상세한 요구사항은 지원되는 플랫폼 및 요구 사항에서 확인합니다. 타겟 디바이스 종류가 다양하거나, 미정이라면 AWS Cloud9을 활용하여 코드 작성, 실행 및 디버깅을 해볼 수 있습니다.
Greengrass installer를 다운로드하고, Core 디바이스로 등록합니다. 먼저, Docker 이미지를 구동하기 위하여 시스템 권한을 가져야 하므로 아래와 같이 Greengrass User를 docker그룹에 추가합니다.
sudo usermod -aG docker ggc_user
Greengrass V2를 다운로드하고 압축을 풉니다.
curl -s https://d2s8p88vqu9w66.cloudfront.net/releases/greengrass-nucleus-latest.zip > greengrass-nucleus-latest.zip && unzip greengrass-nucleus-latest.zip -d GreengrassInstaller
아래와 같이 Core Device로 등록합니다. 여기에서는 디바이스 이름을 “GreengrassCore-18163f7ac3e”으로 하였습니다.
sudo -E java -Droot="/greengrass/v2" -Dlog.store=FILE -jar ./GreengrassCore/lib/Greengrass.jar \
--aws-region ap-northeast-2 \
--thing-name GreengrassCore-18163f7ac3e \
--thing-group-name GreengrassGroup \
--component-default-user ggc_user:ggc_group \
--provision true \
--setup-system-service true \
--deploy-dev-tools true
Greengrass에서 Docker 컨테이너 컴포넌트를 사용하기 위해서는, ECR과 S3에 대한 사용자 퍼미션을 설정하여야 합니다. Device role을 참조하여, IAM Console에서 GreengrassV2TokenExchangeRole에 아래의 permission을 추가합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::*"
},
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
],
"Resource": "*"
}
]
}
단계2: Greengrass에서 추론을 수행하기 위한 Interface 구성하기
Greengrass에서 사용하려고 하는 기계학습 추론 알고리즘은 기학습된 XGBoost 모델을 사용하여 inference.py로 구현되었습니다. 여기서 Lambda 입력 형태가 event 이므로, interface.py를 이용해 추론을 요청한 컴포넌트의 입력을 event로 변환합니다. 또한, interface.py에서는 IPC Client V2을 활용하여 Greengrass의 컨포넌트들 사이에 IPC 방식으로 통신을 수행합니다. 즉, 먼저, Nucleus와 IPC session을 생성하고, topic이름이 ‘local/inference’인 메시지를 구독(subscribe)합니다. 이후, 다른 컴포넌트가 해당 topic으로 전달한 메시지를 on_stream_event로 받아서, lambda의 event 포맷으로 변경한 후 inference.py의 handler()를 이용하여 추론을 수행합니다. XGBoost 동작에 필요한 라이브러리는 requirements.txt에서 확인할 수 있습니다.
topic = 'local/inference'
from inference import handler
try:
ipc_client = GreengrassCoreIPCClientV2()
_, operation = ipc_client.subscribe_to_topic(topic=topic, on_stream_event=on_stream_event, on_stream_error=on_stream_error, on_stream_closed=on_stream_closed)
def on_stream_event(event: SubscriptionResponseMessage) -> None:
try:
message = str(event.binary_message.message, 'utf-8')
# Inference
json_data = json.loads(message) # json decoding
results = handler(json_data,"")
except:
traceback.print_exc()
단계3: Local 컴포넌트에서 추론 요청하기
요청을 수행하는 Local 컴포넌트(“com.ml.consumer”)는 consumer.py을 실행합니다. 여기서 “message”는 추론 요청에 필요한 json 파일입니다.
topic = 'local/inference'
ipc_client = GreengrassCoreIPCClientV2()
try:
publish_binary_message_to_topic(ipc_client, topic, json.dumps(message))
print('request:', json.dumps(message))
except InterruptedError:
print('Publisher interrupted.')
def publish_binary_message_to_topic(ipc_client, topic, message):
binary_message = BinaryMessage(message=bytes(message, 'utf-8'))
publish_message = PublishMessage(binary_message=binary_message)
ipc_client.publish_to_topic(topic=topic, publish_message=publish_message)
Recipe 파일인 com.ml.consumer-1.0.0.json은 아래와 같이 내부 컴포넌트들이 PUBSUB을 사용할 수 있도록 권한을 설정하고 있습니다.
"ComponentConfiguration": {
"DefaultConfiguration": {
"accessControl": {
"aws.greengrass.ipc.pubsub": {
"com.ml.consumer:pubsub:1": {
"policyDescription": "Allows access to publish to all topics.",
"operations": [
"aws.greengrass#PublishToTopic"
],
"resources": [
"*"
]
}
}
}
}
}
단계4: AWS CDK로 기계학습 알고리즘 추론을 IoT Greengrass에 배포하기
IoT Greengrass에 컴포넌트 배포시 AWS CDK를 활용할 수 있습니다. “cdk-ml-iot” 폴더를 생성하여, 아래와 같이 CDK 초기화를 수행합니다. 여기서는 Typescript를 이용해 인프라를 구성합니다.
mkdir cdk-ml-iot && cd cdk-ml-iot
cdk init app --language typescript
아래와 같이 bootstrap을 수행합니다. 1 회만 수행하면 됩니다.
cdk bootstrap aws://123456789012/ap-northeast-2
여기서, “123456789012”은 AWS account number입니다. AWS Console에서 확인할 수 있고, 아래와 같이 AWS CLI 명령어로 확인할 수도 있습니다.
aws sts get-caller-identity --query Account --output text
여기에서는 CDK V2와 이미지 빌드에 필요한 path 라이브러리를 아래와 같이 설치합니다.
npm install aws-cdk-lib path
Typescript로 작성한 cdk-ml-iot-stack.ts와 같이, Dockerfile과 추론 소스인 inference.py, IPC 통신 및 event로 변경하는 interface.py을 이용하여 추론용 Docker 컨테이너 이미지를 생성하고, Amazon ECR에 Artifact로 저장한 후에, Greengrass에서 컨테이너 컴포넌트를 생성합니다.
export class containerComponent extends cdk.Stack {
constructor(scope: Construct, id: string, version: string, props?: cdk.StackProps) {
super(scope, id, props);
const asset = new DockerImageAsset(this, 'BuildImage', {
directory: path.join(__dirname, '../../src/ml-container'),
})
const imageUri = asset.imageUri
new cdk.CfnOutput(this, 'ImageUri', {
value: imageUri,
description: 'Image Uri',
});
// recipe of component - com.ml.xgboost
const recipe = `{
"RecipeFormatVersion": "2020-01-25",
"ComponentName": "com.ml.xgboost",
"ComponentVersion": "${version}",
"ComponentDescription": "A component that runs a ML docker container from ECR.",
"ComponentPublisher": "Amazon",
"ComponentDependencies": {
"aws.greengrass.DockerApplicationManager": {
"VersionRequirement": "~2.0.0"
},
"aws.greengrass.TokenExchangeService": {
"VersionRequirement": "~2.0.0"
}
},
"ComponentConfiguration": {
"DefaultConfiguration": {
"accessControl": {
"aws.greengrass.ipc.pubsub": {
"com.ml.xgboost:pubsub:1": {
"policyDescription": "Allows access to subscribe to all topics.",
"operations": [
"aws.greengrass#PublishToTopic",
"aws.greengrass#SubscribeToTopic"
],
"resources": [
"local/inference",
"local/result"
]
}
}
}
}
},
"Manifests": [
{
"Platform": {
"os": "all"
},
"Lifecycle": {
"Run":"docker run --rm -v /greengrass/v2/ipc.socket:/greengrass/v2/ipc.socket -e AWS_CONTAINER_AUTHORIZATION_TOKEN=$AWS_CONTAINER_AUTHORIZATION_TOKEN -e SVCUID=$SVCUID -e AWS_GG_NUCLEUS_DOMAIN_SOCKET_FILEPATH_FOR_COMPONENT=/greengrass/v2/ipc.socket -e AWS_CONTAINER_CREDENTIALS_FULL_URI=$AWS_CONTAINER_CREDENTIALS_FULL_URI ${imageUri} --network=host"
},
"Artifacts": [
{
"URI": "docker:${imageUri}"
}
]
}
]
}`
const cfnComponentVersion = new greengrassv2.CfnComponentVersion(this, 'MyCfnComponentVersion_Container', {
inlineRecipe: recipe,
});
}
}
단계5: 추론을 요청하는 컴포넌트 생성하기
추론을 요청하는 컴포넌트인 “com.ml.consumer”는 아래와 같이 로컬 컴포넌트로 생성됩니다. 이 컴포넌트는 동작 테스트를 위해 샘플(sample.json)을 로드하여 consumer.py와 같이 IPC 통신을 사용하여 ‘local/inference’이라는 Topic으로 추론용 컨테이너 컴포넌트인 “com.ml.xgboost”로 요청을 전달합니다.
export class localComponent extends cdk.Stack {
constructor(scope: Construct, id: string, version: string, bucketName: string, props?: cdk.StackProps) {
super(scope, id, props);
// recipe of component - com.ml.consumer
const recipe_consumer = `{
"RecipeFormatVersion": "2020-01-25",
"ComponentName": "com.ml.consumer",
"ComponentVersion": "${version}",
"ComponentDescription": "A component that consumes the API.",
"ComponentPublisher": "Amazon",
"ComponentConfiguration": {
"DefaultConfiguration": {
"accessControl": {
"aws.greengrass.ipc.pubsub": {
"com.ml.consumer:pubsub:1": {
"policyDescription": "Allows access to publish to all topics.",
"operations": [
"aws.greengrass#PublishToTopic",
"aws.greengrass#SubscribeToTopic"
],
"resources": [
"local/inference",
"local/result"
]
}
}
}
}
},
"Manifests": [{
"Platform": {
"os": "linux"
},
"Lifecycle": {
"Install": "pip3 install awsiotsdk pandas",
"Run": "python3 -u {artifacts:path}/consumer.py"
},
"Artifacts": [
{
"URI": "${'s3://'+bucketName}/consumer/artifacts/com.ml.consumer/1.0.0/consumer.py"
},
{
"URI": "${'s3://'+bucketName}/consumer/artifacts/com.ml.consumer/1.0.0/samples.json"
}
]
}]
}`
// recipe of component - com.ml.consumer
new greengrassv2.CfnComponentVersion(this, 'MyCfnComponentVersion-Consumer', {
inlineRecipe: recipe_consumer,
});
}
}
단계6: 추론을 위한 컨테이너 컴포넌트의 배포 준비하기
아래와 같이 Greengrass에 추론용 컴포넌트들을 배포할 수 있습니다.
export class componentDeployment extends cdk.Stack {
constructor(scope: Construct, id: string, version_consumer: string, version_xgboost: string, accountId: string, deviceName: string, props?: cdk.StackProps) {
super(scope, id, props);
// deployments
const cfnDeployment = new greengrassv2.CfnDeployment(this, 'MyCfnDeployment', {
targetArn: `arn:aws:iot:ap-northeast-2:`+accountId+`:thing/`+deviceName,
components: {
"com.ml.consumer": {
componentVersion: version_consumer
},
"com.ml.xgboost": {
componentVersion: version_xgboost
},
"aws.greengrass.Cli": {
componentVersion: "2.9.0",
}
},
deploymentName: 'component-deployment',
deploymentPolicies: {
componentUpdatePolicy: {
action: 'NOTIFY_COMPONENTS',
timeoutInSeconds: 60,
},
failureHandlingPolicy: 'ROLLBACK',
},
});
}
}
단계7: 전체 리소스 생성 및 배포
편의상 컴포넌트들이 여러 개의 Typescript stack들로 구성하였으므로 아래와 같이 전체를 빌드 하여 배포를 수행합니다.
cdk deploy --all
단계8: 배포 후 결과 확인
아래와 같이 Greengrass CLI를 이용하여 배포된 컴포넌트를 확인합니다.
sudo /greengrass/v2/bin/greengrass-cli component list
이때, “com.ml.consumer”와 “com.ml.xgboost”가 배포되었음을 확인할 수 있습니다.
아래와 같이 “docker ps” 명령어로 정상적으로 추론용 Docker 컨테이너가 배포되었는지 확인할 수 있습니다.
AWS Console의 Component에서 아래처럼 생성된 컴포넌트에 대한 정보를 확인할 수 있습니다.
AWS Console의 Deployment에서 아래와 같이 배포 상태를 확인할 수 있습니다.
추론을 요청했던 “com.ml.consumer”의 로그를 보면 아래와 같은 요청에 대해 정상적으로 추론 동작을 수행하였음을 알 수 있습니다.
리소스 정리하기
AWS CDK로 생성된 인프라는 아래와 같이 삭제할 수 있습니다.
cdk destroy --all
배포에 사용했던 S3 및 ECR의 Recipe, Artifact는 상기 명령어를 이용해 삭제합니다. 하지만 디바이스에 배포된 컴포넌트들은 Deployment에서 삭제할 컴포넌트를 리스트에서 제외하고 재배포하여야 삭제가 가능합니다.
결론
다양한 IoT 디바이스에 기계학습을 본격적으로 활용하기 위해서는 기계학습 모델을 쉽게 배포하고 운영할 수 있어야 합니다. 지금까지 알아본 바와 같이 IoT Greengrass의 컨테이너 컴포넌트를 활용하여 IoT 디바이스에 기계학습 모델을 쉽게 배포하고 추론을 수행할 수 있습니다. 기계학습 알고리즘을 IoT 디바이스에 활용함으로써 디바이스의 활용성을 높이고 더 많은 비지니스에 효과적으로 이용될 수 있을 것으로 보여집니다.
참고
Cloud9과 같은 디바이스에서 실습 할 수 있는 소스파일을 아래에서 확인할 수 있습니다.