См. Также: https://medium.com/@sunilmallya/serverless-inference-for-keras-with-aws-lambda-79f51a91c497

Обновление: с новыми слоями лямбда это стало намного проще. Посетите https://github.com/antonpaquin/Tensorflow-Lambda-Layer, чтобы узнать, как это сделать.

В прошлом году я обучил небольшой двоичный классификатор (находится на https://isitanime.website), чтобы научиться основам машинного обучения. Но когда дело дошло до размещения его в сети, Raspberry Pi, который был моим сервером последние несколько лет, не собирался его сокращать. Мне нужен был более мощный сервер для быстрого выполнения выводов для нескольких коротких пакетов, но я не хотел раскошелиться на более крупный экземпляр EC2, который большую часть времени простаивал бы. Разве не для этого нужна лямбда?

Однако есть проблема: Amazon устанавливает ограничение в 250 МБ для пакетов развертывания, а Tensorflow сам по себе составляет чуть менее 300 МБ. К счастью, другие уже проходили этот путь раньше. Мне не удалось запустить ни один их код из коробки, но я смог использовать те же идеи для создания своего собственного пакета развертывания.

Теперь моя модель (файл .h5 размером 23 МБ) работает как лямбда с максимальным объемом выделенной памяти. На самом деле он использует не так много памяти, но дополнительные вычисления делают его немного быстрее - примерно 0,7 секунды на запрос. У меня производительность примерно такая же, как у t2.medium, но я значительно ниже пределов бесплатного уровня.

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

Код ниже

Вот как это работает.

Весь задействованный код, а также tar-архив пакета развертывания находятся на моем github по адресу https://github.com/antonpaquin/Tensorflow-Lambda

Сначала я создаю экземпляр t2.small с базовым AMI amazon-linux.

aws ec2 run-instances \
 --image-id "ami-f973ab84" \
 --key-name "my_ssh_key" \
 --security-groups "GlobalSSH" \
 --instance-type "t2.small" \
 --placement "AvailabilityZone=us-east-1b" \
 --count 1

Затем я отправляю свой код и модель (src.zip) и SSH. Сначала я устанавливаю некоторые базовые библиотеки, openssl и python 3.6.

mkdir build
mv src.zip build
pushd build
unzip src.zip
popd
sudo yum groupinstall -y \
 development
sudo yum install -y \
 zlib-devel \
 openssl-devel 
 
wget https://github.com/openssl/openssl/archive/OpenSSL_1_0_2l.tar.gz
tar -zxvf OpenSSL_1_0_2l.tar.gz
pushd openssl-OpenSSL_1_0_2l/
./config shared
make
sudo make install
export LD_LIBRARY_PATH=/usr/local/ssl/lib/
popd
rm -rf OpenSSL_1_0_2l.tar.gz openssl-OpenSSL_1_0_2l/
 
wget https://www.python.org/ftp/python/3.6.0/Python-3.6.0.tar.xz
tar xJf Python-3.6.0.tar.xz
pushd Python-3.6.0
./configure
make
sudo make install
popd
sudo rm -rf Python-3.6.0.tar.xz Python-3.6.0

Я запускаю пакет развертывания virtualenv и устанавливаю нужные пакеты python (keras, tensorflow и подушка).

sudo env PATH=$PATH pip3 install --upgrade virtualenv
virtualenv -p python3 env
source env/bin/activate
 
pip3 install \
  keras \
  tensorflow \
  pillow

А вот и самое интересное. Я использую «inotifytools» для создания списка всех файлов, к которым обращается Python при запуске моей модели. Все файлы, к которым осуществляется доступ, являются истинными зависимостями и будут скопированы. Остальные можно проигнорировать.

wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
sudo yum install -y \
  epel-release-latest-7.noarch.rpm
rm epel-release-latest-7.noarch.rpm
sudo yum install -y \
  inotify-tools
inotifywait \
  -m \
  -e access \
  -o inotifywait.list \
  --format "%w%f" \
  -r \
  $VIRTUAL_ENV/lib/python3.6/site-packages/ &
# Make sure to save the PID so it can be killed later
INOTIFY="$!"

На этом этапе я запускаю тест своей модели, который запускает все обращения к файлам. (Код был разархивирован в ~ / build ранее, не показан)

pushd build
python3 test.py
kill $INOTIFY
popd

Теперь я могу скопировать все затронутые файлы, а также исходные файлы .py.

pushd build
for f in $(cat /home/ec2-user/inotifywait.list); do
 if [ -f $f ]; then
  REL=$(dirname $f | cut -c 48-)
  mkdir -p $REL
  cp $f $REL
 fi
done
 
pushd $VIRTUAL_ENV/lib/python3.6/site-packages/
find . -name "*.py" | cut -c 3- > $HOME/pydep.list
popd
for f in $(cat $HOME/pydep.list); do
 cp "$VIRTUAL_ENV/lib/python3.6/site-packages/$f" "/home/ec2-user/build/$f" 2>/dev/null
done
popd

На этом этапе в Tensorflow есть строка, которая прерывает сборку. Кажется, что что-то неправильно передается в строку и ломается только при развертывании лямбда. Это поправимо:

cat <<EOF | patch build/tensorflow/python/util/all_util.py
55c55
<                     for m in _reference_pattern.finditer(doc_module.__doc__)
---
>                     for m in _reference_pattern.finditer(str(doc_module.__doc__))
EOF

Теперь я могу уменьшить общие объекты, что является самой большой операцией по экономии места:

pushd build
find . -name "*.so" | xargs strip
popd

Однако есть один .so, который жалуется на это удаление, так что давайте исправим это, просто используя оригинал.

cp \
    $VIRTUAL_ENV/lib/python3.6/site-packages/PIL/_imaging.cpython-36m-x86_64-linux-gnu.so \
    $HOME/build/PIL/_imaging.cpython-36m-x86_64-linux-gnu.so

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

pushd build
python3 test.py
rm test.py
popd

А потом застегиваю и отправляю.

pushd build
zip -r9 lambda.zip *
mv lambda.zip ..
popd
aws s3 cp lambda.zip s3://my-s3-bucket/lambda.zip

Вот и все! Окончательный размер: 220 МБ. Этот zip-файл можно загрузить в lambda.