После разработки и изучения модели машинного обучения самое сложное - это запустить и поддерживать ее в производственной среде. AWS предлагает размещать и развертывать модели через хостинг машинного обучения по требованию на различных типах и размерах инстансов, упакованных в виде службы SageMaker. Они начинаются с экземпляров ml.t2.medium и доходят до экземпляров ml.p3.16xlarge с ускорением на GPU.

Чтобы оставаться бессерверным в уже бессерверном развертывании, а также для снижения затрат и обеспечения гибкого масштабирования, желателен вывод модели машинного обучения на чистой AWS Lambda. Такие подходы существуют и уже были описаны в другом месте, но имеют некоторые оговорки, например, запуск через интерфейс REST и конечную точку шлюза API. В более крупном производственном развертывании лямбда-функций и пошаговых функций можно просто вызвать поток, инициируемый чистым лямбда-событием, без обхода шлюза REST и API. Поскольку многие инструменты AI и ML основаны на Python и используют инструменты на основе Python, такие как scikit-learn, выбрана среда выполнения Lambda на основе Python.

Ближе к концу этого поста представлено сравнение стоимости такого бессерверного подхода и традиционного хостинга.

На этой диаграмме показан конкретный поток машинного обучения в более крупном конвейере обработки данных. Данные и документы декомпозируются и записываются в таблицу DynamoDB, а изменения помещаются в поток данных DynamoDB для нескольких нижестоящих потребителей. На самом деле это немного сложнее из-за общих ограничений обработки и использования шаблона разветвления. Более общие детали описаны в этом сообщении блога AWS Как выполнить упорядоченную репликацию данных между приложениями с помощью Amazon DynamoDB. Потоки ».

Оглавление

Сгенерируйте и сохраните модель машинного обучения

Модель была создана с помощью блокнотов Jupyter и использует scikit. Поскольку входные данные основаны на тексте, использовались методы NLP (обработка естественного языка). Чтобы сохранить и сохранить обученную модель, scikit предлагает (де) сериализацию моделей, содержащих большие массивы данных, с помощью функции Joblib.

from sklearn.externals import joblib 
# Save to file in the current working directory 
joblib_file = “MyModel20190104v1.joblib” 
joblib.dump(model, joblib_file) 
# Load from file
joblib_model = joblib.load(joblib_file)

Использование сохраненных моделей таким образом требует внимания к следующим моментам:

  • Базовая версия Python. Для десериализации модели следует использовать ту же основную версию Python, что и для ее сохранения / сериализации.
  • Версии библиотеки. Версии всех основных библиотек, используемых во время создания модели, должны быть одинаковыми при десериализации сохраненной модели. В основном это применимо к версии NumPy и версии scikit-learn. Кроме того, если для построения модели используется другая ОС, важно, чтобы скомпилированные в собственном коде программы на C подходили для среды выполнения AWS Lambda.

Создайте лямбда-функцию вывода машинного обучения

Принимая во внимание вышеуказанные требования, можно создать и развернуть функцию ML Lambda. Чтобы упростить управление жизненным циклом базовой модели и упростить обновление, когда модель оптимизируется или адаптируется к новым наборам данных, будут использоваться слои AWS Lambda Layers, представленные на re: Invent 2018.

Слои AWS Lambda

С помощью AWS Lambda Layers можно разделить различные части и зависимости кода, поэтому нет необходимости загружать все артефакты и зависимости при каждом изменении.

Теперь также возможно создать полностью настраиваемую среду выполнения, поддерживающую любой язык программирования, что может стать следующим шагом для оптимизации времени выполнения лямбда-вывода ML (см. Следующий раздел о времени вызова).

Уровень 1. Создайте и опубликуйте уровень зависимостей с помощью Docker.

Предполагается, что виртуальная среда уже создана (в данном случае с помощью pipenv) с правильной версией Python (среда, в которой выполнялась настройка и проектирование модели). Сопровождающий файл требований может быть создан с помощью:

pipenv lock -r > requirements.txt

Чтобы иметь определенную версию Python, а также соответствующую среду выполнения лямбда-выражения AWS, зависимости строятся через докер-контейнер из проекта LambCI.

Следующий скрипт get-layer-package.sh может быть выполнен для генерации зависимостей:

#!/bin/bash

export PKG_DIR="python"

rm -rf ${PKG_DIR} && mkdir -p ${PKG_DIR}

docker run --rm -v $(pwd):/foo -w /foo lambci/lambda:build-python3.6 \
    pip install -r requirements.txt --no-deps -t ${PKG_DIR}

Все зависимости будут экспортированы в папку Python в виртуальной среде. Структура каталогов и файлы должны быть заархивированы, чтобы быть готовыми к загрузке:

zip -r MyMLfunction_layer1.zip python

Сгенерированный zip-пакет можно загрузить напрямую, если его размер не превышает 50 МБ, что наиболее вероятно в данном случае. Кроме того, существует ограничение на общий размер слоя, который составляет около 250 МБ. Поэтому сначала мы выполняем загрузку в S3:

aws s3 cp MyMLfunction_layer1.zip s3://mybucket/layers/

А потом сразу опубликуйте слой:

aws lambda publish-layer-version --layer-name MyMLfunction_layer1 --content S3Bucket=mybucket,S3Key=layers/MyMLfunction_layer1.zip --compatible-runtimes python3.6

Уровень 2: публикация модели и уровня препроцессора.

Для нашей конкретной модели у нас есть два препроцессора и модель, сохраненная в виртуальной среде, все с расширением файла .joblib.

$ ls  *.joblib
PreProc1_MyModel.joblib
PreProc2_MyModel.joblib
MyModel.joblib

Эти файлы необходимо заархивировать, а затем опубликовать как дополнительный слой. Здесь мы выполняем прямую загрузку, поскольку размер слоя составляет всего несколько МБ:

zip MyMLfunction_layer2_model20190104v1.zip *.joblib
aws lambda publish-layer-version --layer-name MyMLfunction_layer2 --zip-file fileb://MyMLfunction_layer2_model20190104v1.zip --compatible-runtimes python3.6

Для последующей загрузки во время выполнения Lambda очень важно, чтобы эти файлы были доступны в смонтированном каталоге / opt в контейнере Lambda.

Наличие этого дополнительного слоя, содержащего только модель, позволяет нам позже легко опубликовать новую версию модели.

Функциональный уровень: создание и публикация функции

Лямбда-функция должна считывать данные из потока Kinesis, а затем выполнять прогноз, используя загруженную модель. Он запускается через события Kinesis и определенный размер пакета (задается в конфигурации лямбда для события Kinesis). Нижеследующий lambda_function.py считывает все соответствующие текстовые данные и генерирует список, который затем отправляется функции прогнозирования за один раз.

import base64
import json
from sklearn.externals import joblib
PREP1_FILE_NAME = '/opt/PreProc1_MyModel.joblib'
PREP2_FILE_NAME = '/opt/PreProc2_MyModel.joblib'
MODEL_FILE_NAME = '/opt/MyModel.joblib'
def predict(data):
    # Load the model pre-processors
    pre1 = joblib.load(PREP1_FILE_NAME)
    pre2 = joblib.load(PREP2_FILE_NAME)
    clf = joblib.load(MODEL_FILE_NAME)
    print("Loaded Model and pre-processors")
    # perform the prediction
    X1 = pre1.transform(data)
    X2 = pre2.transform(X1)
    output = dict(zip(data, clf.predict(X2)[:]))
    return output
def lambda_handler(event, context):
    predictList = []
    for record in event['Records']:
       # decode the base64 Kinesis data
       decoded_record_data = base64.b64decode(record['kinesis']['data'])
       # load the dynamo DB record
       deserialized_data = json.loads(decoded_record_data)
       predictList.append(json.loads(deserialized_data['textblob']))
       result = predict(predictList)
    return result

И снова функция будет заархивирована, а затем опубликована. Предполагается, что основная роль уже создана:

zip MyModel20190104v1_lambda_function.zip lambda_function.py
aws lambda create-function --function-name MyModelInference --runtime python3.6 --handler lambda_function.lambda_handler --role arn:aws:iam::xxxxxxxxxxx:role/MyModelInference_role --zip-file fileb://MyModel20190104v1_lambda_function.zip

Свяжите слои

Теперь слои необходимо связать с ранее созданной функцией с помощью ARN:

aws lambda update-function-configuration --function-name MyModelInference --layers arn:aws:lambda:eu-west-1:xxxxxxxxxxx:layer:MyMLfunction_layer1:1 arn:aws:lambda:eu-west-1:xxxxxxxxxxx:layer:MyMLfunction_layer2:1

Здесь используется начальная «версия 1» слоя, которая отображается по номеру после двоеточия.

Обновить (только) слои модели

Если создается новая улучшенная версия модели, ее необходимо загрузить еще раз:

zip MyMLfunction_layer2_model20190105v1.zip *.joblib
aws lambda publish-layer-version --layer-name MyMLfunction_layer2 --zip-file fileb://MyMLfunction_layer2_model20190105v1.zip --compatible-runtimes python3.6

и конфигурация слоя функции должна быть обновлена:

aws lambda update-function-configuration --function-name MyModelInference --layers arn:aws:lambda:eu-west-1:xxxxxxxxxxx:layer:MyMLfunction_layer1:1 arn:aws:lambda:eu-west-1:xxxxxxxxxxx:layer:MyMLfunction_layer2:2

После этого шага развертывается новая модель с нашей лямбда-функцией вывода машинного обучения.

Наблюдения за выполнением и стоимостью

Как упоминалось во введении к этой статье, мы провели некоторое исследование, касающееся продолжительности и стоимости выполнения. Такой анализ необходим в большинстве проектов и является обязательным, если вы следуете AWS Well-Architected Framework.

В нашем случае получение данных основано на пакетном процессе и происходит всего несколько раз в день. При каждом приеме будет сгенерировано 50 000 элементов данных (события Kinesis), поэтому в течение дня необходимо выполнить несколько коротких пиков по 50 000 прогнозов каждый.

Время исполнения

Функция запускалась с разными размерами пакетов: 1, 10 и 100, чтобы увидеть общую разницу во времени выполнения.

  • 1 прогноз: среднее время работы 3803 мс, оплачиваемая продолжительность: 3900 мс, максимальный объем используемой памяти: 151 МБ.
  • 10 прогнозов: среднее время выполнения 3838 мс, оплачиваемая продолжительность: 3900 мс, максимальный объем используемой памяти: 151 МБ.
  • 100 прогнозов: среднее время выполнения 3912 мс, оплачиваемая продолжительность: 4000 мс, максимальный объем используемой памяти: 151 МБ.

Время загрузки и инициализации, конечно, является ограничивающим фактором, так как общее время выполнения не сильно меняется при увеличении количества прогнозов. В этом случае мы бы выбрали размер пакета 100. Для будущих исследований мы также рассмотрим, как мы можем запустить функцию более оптимизированным образом, возможно, с настраиваемой средой выполнения, чтобы сократить продолжительность инициализации, а также сделать более мелкие пакеты большего размера. эффективный.

Стоимость исполнения

Как было сказано в начале, основным вариантом развертывания машинного обучения в AWS является размещение модели через Sagemaker. Если выбран наименьший размер экземпляра ml.t2.medium, будет выставлена ​​почасовая оплата в размере 0,07 доллара США, что составит ежемесячную плату в размере 50,40 доллара США за время работы в режиме 24/7, так что это наш нижний предел для сравнения, поскольку для более высоких нагрузок более мощный и приходится использовать дорогостоящие экземпляры.

В AWS Lambda доступен уровень бесплатного пользования, и при выбранном нами размере времени выполнения 192 МБ это составит 2133 333 секунды. После этого уровня бесплатного пользования применяется цена за 100 миллисекунд 0,000000313 долларов США.

Предполагая, что выполнение функции составляет 4 секунды с любым размером пакета от 1 до 100, мы приходим к стоимости 0,0000124 доллара за выполнение.

Однократный прием 50000 элементов будет означать, что такая партия стоит:

  • Размер пакета 1: 50000 x 0,0000124 USD / 1 = 0,62 USD за прием
  • Размер пакета 10: 50000 x 0,0000124 USD / 10 = 0,062 USD за прием
  • Размер пакета 100: 50000 x 0,0000124 USD / 100 = 0,0062 USD за прием

Давайте сравним, сколько захватов с размером пакета 100 мы можем сделать в месяц, пока не достигнем стоимости самого маленького размещенного экземпляра машинного обучения в 50,40 доллара:

50,40 USD / 0,0062 USD = 8129 приемов

Это будет 271 прием в день или 11 приемов в час, прежде чем наша лямбда-функция станет дороже.

Заключение

Выполнение логического вывода машинного обучения с использованием полностью бессерверного подхода может быть очень эффективным. Если время выполнения может быть ограничено и используется правильный размер пакета, это более доступно, чем модель и конечная точка с постоянным размещением. Кроме того, отказ от сервера даже позволяет автоматически обрабатывать пики нагрузки без настраиваемой логики / конфигурации масштабирования или адаптации размера размещенного экземпляра.

Использование многоуровневого подхода позволяет нам независимо и легко развертывать новые модели, не затрагивая и не развертывая весь код. Конечно, также легко вернуться к предыдущей версии.

Сборка с помощью контейнеров докеров позволяет нам иметь согласованную среду выполнения для функций. Как показал Келси Хайтауэр во время своего выступления на KubeCon 2018: Kubernetes и путь к бессерверному режиму (Рекомендуется посмотреть!), можно извлечь слой образа докера и загрузить это для создания настраиваемой среды выполнения. Думаю, скоро мы увидим больше в этом пространстве!