В Wehkamp мы уже давно используем машинное обучение. Мы обучаем моделей в Databricks (Spark) и Keras. Это создает файл Keras, который мы используем для реальных прогнозов. Обучение - это одно, а доведение до производства - совсем другое!

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

Настраивать

Наша служба управления цифровыми активами (DAM) отправляет активы в корзину S3. После загрузки мы хотели бы классифицировать изображения. При загрузке автоматически запускается лямбда с моделями машинного обучения. Мы отправим изображения через классификаторы и сохраним результаты как метаданные S3 по объектам.

Поскольку мы используем Databricks, мы знакомы с Python, поэтому мы решили создать лямбду и на Python.

Лямбда: нет предела?

Ну нет. В AWS Lambda есть несколько серьезных ограничений по размеру:

  • Размер пакета развертывания имеет жесткое ограничение в
    262144000 байт, то есть 262 МБ. Таким образом, разархивированный размер вашего пакета, включая слои, не может быть больше этого числа.
  • Предел временного хранилища составляет 512 МБ.
  • Предел памяти составляет 3008 МБ.

Наша программа имеет следующие зависимости:

tensorflow==1.8.0
Keras==2.2.4
pillow==5.4.1
numpy==1.16.3
colorgram.py==1.2.0
webcolors==1.8.1
boto3==1.9.137

Эти пакеты Python должны поставляться с нашим Lambda. Когда мы устанавливаем их в один каталог, мы получаем 415 МБ. Мы используем модели - это модели Keras H5, которые обе по 159 МБ. Когда мы округляем размер нашего фактического кода до одного МБ, мы приходим к следующему выводу:

программа + пакеты + модели =
1 МБ + 415 МБ + 318 МБ = 734 МБ =
слишком много для AWS Lambda !

Уровень AWS для TF + Keras + PIL

Мы не первые люди, у которых возникают проблемы с ограничениями размера AWS Lambda. Антон Пакуин экспериментировал с Lambda Layer, который содержит TensorFlow, Keras и PIL и имеет меньше лимита в 250 МБ!

Мы будем использовать слой arn:aws:lambda:eu-west-1:347034527139:layer:tf_keras_pillow:1 и размером всего 230 МБ. Он использует TensorFlow 1.8.0, потому что в настоящее время это последняя версия, достаточно маленькая для Lambda (версия 1.12 составляет 282 МБ).

Это означает, что нам нужно отправлять меньше посылок. Слой также включает Boto3 для связи S3, поэтому нам не нужно его загружать.

Пакеты с боковой загрузкой

Нам все еще нужно отправить следующие пакеты:

numpy==1.16.3
colorgram.py==1.2.0
webcolors==1.8.1

Один из пакетов также зависит от Pillow, но из-за слоя нам не нужно его отправлять. Если мы посчитаем размер пакетов, то увидим, что нам нужно всего лишь 81 МБ! Но как мы собираемся это сделать?

Упаковать их

Мы - за неимением лучшего термина - собираемся загружать дополнительные наши пакеты. Мы собираемся заархивировать их (чтобы сэкономить место) и развернуть при запуске лямбда.

Сначала нам нужно упаковать зависимости в новый zip-файл. Давайте создадим новый requirements-lambda.txt с пакетами, которые нам нужно отправить, и запустим этот сценарий:

#!/bin/bash
name=$(basename -s .git `git config --get remote.origin.url`)

if [ -d "deploy" ]; then rm -Rf deploy; fi
mkdir deploy

pip install -r requirements-lambda.txt -t deploy/requirements-lambda/

cd deploy/requirements-lambda
rm -r PIL
rm -r Pillow*

zip -9 -r ../$name-requirements.zip .

cd ..
rm -r requirements-lambda

Размер zip-архива составляет всего 15,7 МБ, что означает, что он подходит для нашей лямбда-выражения. Так что мы действительно можем отправить его с нашей лямбдой. (У вас молния больше? Не беспокойтесь, просто читайте дальше).

Un(z/sh)ip it

Мы отправим его с пакетом Lambda в корне. Когда Lambda запустится, нам нужно будет его распаковать. Давайте создадим новый setup.py, который распакует зависимости и добавит их в программу:

import os
import sys
import zipfile

pkgdir = '/tmp/requirements'
zip_requirements = 'lambda-requirements.zip'

if os.environ.get("AWS_EXECUTION_ENV") is not None:
    if not os.path.exists(pkgdir):

        root = os.environ.get('LAMBDA_TASK_ROOT', os.getcwd())
        zip_requirements = os.path.join(root, zip_requirements)
        zipfile.ZipFile(zip_requirements, 'r').extractall(pkgdir)

        sys.path.append(pkgdir)

В вашем обработчике просто используйте import setup в качестве первой строки, и будут использоваться разархивированные пакеты.

Слишком велик для отправки с посылкой?

Что делать, если размер пакета требований превышает 20 МБ? Или что, если вы хотите иметь возможность редактировать пакет в онлайн-редакторе? (Тогда загруженный пакет (без слоев) должен быть меньше 3 МБ).

Мы могли бы использовать S3 для отправки наших зависимостей! Используйте ту же процедуру пакета, но в вашем setup.py используйте:

import boto3
import os
import sys
import zipfile

REQUIREMENTS_BUCKET_NAME = ''
REQUIREMENTS_KEY = ''

pkgdir = '/tmp/requirements'
zip_requirements = '/tmp/lambda-requirements.zip'

sys.path.append(pkgdir)

if os.environ.get("AWS_EXECUTION_ENV") is not None:
    if not os.path.exists(pkgdir):

        s3 = boto3.resource('s3')
        bucket = s3.Bucket(REQUIREMENTS_BUCKET_NAME)
        bucket.download_file(REQUIREMENTS_KEY, zip_requirements)

        zipfile.ZipFile(zip_requirements, 'r').extractall(pkgdir)
        os.remove('zip_requirements')

        sys.path.append(pkgdir)

Ленивая загрузка моделей из S3

Наши модели уже «жили» в S3. Последний шаг нашего обучающего сценария Databricks - отправить модели Keras в корзину S3. Мы все равно не можем отправить модели с посылкой, так как они слишком велики. И имеет смысл рассматривать модели как данные.

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

import boto3
import keras
import os

MODEL_BUCKET = ''
cache = {}

def get_model(key):

    if key in cache:
        return cache[key]

    local_path = os.path.join('/tmp', key)

    # download from S3
    if not os.path.isfile(local_path):
        bucket=s3.Bucket(MODEL_BUCKET)
        bucket.download_file(key, local_path)
        
    cache[key] = keras.models.load_model(local_path)
    return cache[key]

В реальном решении мы используем файл конфигурации JSON для загрузки моделей, но идея та же.

So…

Размер лямбды ограничен, но мы можем его обойти. Наш макет лямбда выглядит примерно так:

Для другой модели места не так много. Лучшей идеей может быть использование одной модели для лямбда. В этом случае мы должны настроить наш CI / CD для повторного развертывания лямбда для каждой модели - но это в другой раз.

Жалко, что нам приходится вскакивать на обручи, чтобы загрузить более крупную функцию. Я очень надеюсь, что AWS позволит нам использовать хотя бы более крупные пакеты.

Первоначально опубликовано на https://keestalkstech.com 28 апреля 2019 г.