Докеризованные лямбды
Цель этого поста - настроить бессерверную инфраструктуру, управляемую в коде, для обслуживания прогнозов контейнерной модели машинного обучения через Rest API так просто, как:
$ curl \
$ -X POST \
$ --header "Content-Type: application/json" \
$ --data '{"sepal_length": 5.9, "sepal_width": 3, "petal_length": 5.1, "petal_width": 1.8}' \
$ https://my-api.execute-api.eu-central-1.amazonaws.com/predict/
{"prediction": {"label": "virginica", "probability": 0.9997}}
Мы будем использовать Terraform для управления нашей инфраструктурой, включая AWS ECR, S3, Lambda и API Gateway. Мы будем использовать AWS Lambda для запуска кода модели фактически в контейнере, что является очень недавней функцией. Мы будем использовать AWS API Gateway для обслуживания модели через Rest API. Сам артефакт модели будет жить в S3. Вы можете найти полный код здесь.
Предпосылки
Мы используем Terraform v0.14.0
и aws-cli/1.18.206 Python/3.7.9 Darwin/19.6.0 botocore/1.19.46
.
Нам необходимо пройти аутентификацию в AWS, чтобы:
- настроить инфраструктуру с помощью Terraform.
- обучить модель и сохранить результирующий артефакт модели в S3
- протестировать инфраструктуру с помощью AWS CLI (здесь:)
Учетные данные AWS можно настроить в файле учетных данных, то есть ~/.aws/credentials
, используя профиль с именем lambda-model
:
[lambda-model] aws_access_key_id=... aws_secret_access_key=... region=eu-central-1
Это позволит нам указать Terraform, а также интерфейс командной строки AWS, какие учетные данные использовать. Сама функция Lambda будет аутентифицироваться с использованием роли, поэтому явные учетные данные не требуются.
Кроме того, нам нужно определить регион, имя корзины и некоторые другие переменные, они также определены в переменных Terraform, как мы увидим позже:
export AWS_REGION=$(aws --profile lambda-model configure get region) export BUCKET_NAME="my-lambda-model-bucket" export LAMBDA_FUNCTION_NAME="my-lambda-model-function" export API_NAME="my-lambda-model-api" export IMAGE_NAME="my-lambda-model" export IMAGE_TAG="latest"
Создание контейнерной модели
Давайте построим очень простую контейнерную модель на iris
наборе данных. Мы определим:
model.py
: фактический код моделиutils.py
: служебные функцииtrain.py
: сценарий для запуска обучения моделиtest.py
: сценарий для генерации прогнозов (в целях тестирования)app.py
: обработчик Lambda
Чтобы сохранить артефакт модели и загрузить данные для обучения модели, мы определим несколько вспомогательных функций для связи с S3 и загрузки файлов с обучающими данными из общедоступных конечных точек в utils.py
:
Кроме того, нам нужен класс-оболочка для нашей модели:
- обучить его на внешних данных
- сохранить состояние и сохранить и загрузить артефакт модели
- передавать полезные данные для вывода
Это будет определено в model.py
:
Для обучения и прогнозирования без реальной инфраструктуры лямбда мы также настроим два скрипта, train.py
и predict.py
. Сценарий обучения может быть очень простым, мы могли бы также передать методу train
другие источники данных.
from model import ModelWrapper
model_wrapper = ModelWrapper() model_wrapper.train()
И простой predict.py
, который выводит прогнозы на консоль:
import json import sys from model import ModelWrapper model_wrapper = ModelWrapper() model_wrapper.load_model() data = json.loads(sys.argv[1]) print(f"Data: {data}") prediction = model_wrapper.predict(data=data) print(f"Prediction: {prediction}")
Наконец, нам нужен обработчик для передачи данных в оболочку модели. Это то, что будет вызываться лямбда-функцией. Мы будем максимально минималистичны, обработчик просто передаст запрос в оболочку и преобразует возвращаемые прогнозы в выходной формат, ожидаемый API Gateway:
Мы поместим все это в Docker (точнее Dockerfile
) и воспользуемся одним из базовых образов AWS Lambda:
FROM public.ecr.aws/lambda/python:3.8 COPY requirements.txt . RUN pip install -r requirements.txt COPY app.py utils.py model.py train.py predict.py ./ CMD ["app.handler"]
Создание ресурсов ECR и S3
Давайте теперь определим репозиторий ECR и корзину S3 через Terraform. Правильно организованный код Terraform можно найти в репозитории GitHub.
Мы определяем некоторые конфигурации (variables
и locals
) и AWS как provider
. В качестве альтернативы переменные также могут быть загружены из среды.
Кроме того, репозиторий S3 и ECR:
Давайте создадим нашу корзину S3 и репозиторий ECR:
(cd terraform && \ terraform apply \ -target=aws_ecr_repository.lambda_model_repository \ -target=aws_s3_bucket.lambda_model_bucket)
Сборка и отправка образа докера
Теперь мы можем создать наш образ докера и отправить его в репозиторий (в качестве альтернативы это можно сделать в null_resource
provisioner
в Terraform). Мы экспортируем идентификатор реестра, чтобы создать URI изображения, куда мы хотим отправить изображение:
export REGISTRY_ID=$(aws ecr \ --profile lambda-model \ describe-repositories \ --query 'repositories[?repositoryName == `'$IMAGE_NAME'`].registryId' \ --output text) export IMAGE_URI=${REGISTRY_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${IMAGE_NAME} # ecr login $(aws --profile lambda-model \ ecr get-login \ --region $AWS_REGION \ --registry-ids $REGISTRY_ID \ --no-include-email)
Теперь строить и продвигать так же просто, как:
(cd app && \ docker build -t $IMAGE_URI . && \ docker push $IMAGE_URI:$IMAGE_TAG)
Обучение модели
Давайте теперь обучим нашу модель, используя train.py
точку входа нашего недавно созданного контейнера докеров:
docker run \ -v ~/.aws:/root/.aws \ -e AWS_PROFILE=lambda-model \ -e BUCKET_NAME=$BUCKET_NAME \ --entrypoint=python \ $IMAGE_URI:$IMAGE_TAG \ train.py # Loading data. # Creating model. # Fitting model with 150 datapoints. # Saving model.
Тестирование модели
Используя точку входа predict.py
, мы также можем протестировать его с некоторыми данными:
docker run \ -v ~/.aws:/root/.aws \ -e AWS_PROFILE=lambda-model \ -e BUCKET_NAME=$BUCKET_NAME \ --entrypoint=python \ $IMAGE_URI:$IMAGE_TAG \ predict.py \ '{"sepal_length": 5.1, "sepal_width": 3.5, "petal_length": 1.4, "petal_width": 0.2}' # Loading model. # Data: {'sepal_length': 5.1, 'sepal_width': 3.5, 'petal_length': 1.4, 'petal_width': 0.2} # Prediction: ('setosa', 0.9999555689374946)
Планирование нашей основной инфраструктуры с Terraform
Теперь мы можем спланировать логическую часть инфраструктуры, настройку Lambda & API Gateway:
- функция Lambda, включая роль и политику для доступа к S3 и создания журналов,
- API Gateway, включая необходимые разрешения и настройки.
Теперь мы можем применить это снова, используя Terraform CLI, что займет около минуты.
(cd terraform && terraform apply)
Тестирование инфраструктуры
Чтобы проверить функцию Lambda, мы можем вызвать ее с помощью интерфейса командной строки AWS и сохранить ответ на response.json
:
aws --profile lambda-model \ lambda \ invoke \ --function-name $LAMBDA_FUNCTION_NAME \ --payload '{"body": {"sepal_length": 5.9, "sepal_width": 3, "petal_length": 5.1, "petal_width": 1.8}}' \ response.json # { # "StatusCode": 200, # "ExecutedVersion": "$LATEST" # }
response.json
будет выглядеть так:
{ "statusCode": 200, "body": "{\"prediction\": {\"label\": \"virginica\", \"probability\": 0.9997}}", "isBase64Encoded": false }
И мы также можем протестировать наш API, используя curl
или python. Сначала нам нужно узнать URL-адрес нашей конечной точки, например, снова с помощью интерфейса командной строки AWS или, в качестве альтернативы, распечатки вывода Terraform.
export ENDPOINT_ID=$(aws \ --profile lambda-model \ apigateway \ get-rest-apis \ --query 'items[?name == `'$API_NAME'`].id' \ --output text) export ENDPOINT_URL=https://${ENDPOINT_ID}.execute-api.${AWS_REGION}.amazonaws.com/predict curl \ -X POST \ --header "Content-Type: application/json" \ --data '{"sepal_length": 5.9, "sepal_width": 3, "petal_length": 5.1, "petal_width": 1.8}' \ $ENDPOINT_URL # {"prediction": {"label": "virginica", "probability": 0.9997}}
В качестве альтернативы мы можем отправлять запросы POST с помощью python:
import requests import os endpoint_url = os.environ['ENDPOINT_URL'] data = {"sepal_length": 5.9, "sepal_width": 3, "petal_length": 5.1, "petal_width": 1.8} req = requests.post(endpoint_url, json=data) req.json()
Больше замечаний
Чтобы обновить образ контейнера, мы можем снова использовать CLI:
aws --profile lambda-model \ lambda \ update-function-code \ --function-name $LAMBDA_FUNCTION_NAME \ --image-uri $IMAGE_URI:$IMAGE_TAG
Если мы хотим удалить нашу инфраструктуру, мы должны сначала очистить нашу корзину, после чего мы можем уничтожить наши ресурсы:
aws s3 --profile lambda-model rm s3://${BUCKET_NAME}/model.pkl (cd terraform && terraform destroy)
Заключение
Благодаря новой функции контейнерных Lambdas стало еще проще развертывать модели машинного обучения в бессерверной среде AWS. Есть много альтернатив AWS этому (ECS, Fargate, Sagemaker), но Lambda поставляется с множеством готовых инструментов, например, с ведением журнала и мониторингом на основе запросов, и с легкостью позволяет быстро создавать прототипы. Тем не менее, у него также есть некоторые недостатки, например, накладные расходы, связанные с задержкой запроса и использование некоторой проприетарной облачной службы, которую нельзя полностью настроить.
Еще одно преимущество состоит в том, что контейнеризация позволяет нам изолировать код машинного обучения и правильно поддерживать зависимости пакетов. Если мы сведем код обработчика к минимуму, мы сможем тщательно протестировать образ и убедиться, что наша среда разработки очень близка к производственной инфраструктуре. В то же время мы не привязаны к технологии AWS - мы можем очень легко заменить обработчик на наш собственный веб-фреймворк и развернуть его в Kubernetes.
Наконец, мы потенциально можем улучшить инфраструктуру нашей модели, запустив обучение удаленно (например, с помощью ECS), добавив управление версиями и предупреждения CloudWatch. Если нужно, мы могли бы добавить процесс, чтобы Лямбда оставалась теплой, поскольку холодный старт занимает несколько секунд. Мы также должны добавить аутентификацию в конечную точку.
Первоначально опубликовано на https://blog.telsemeyer.com.