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

Итак, вы обучили свою потрясающую модель машинного обучения с помощью блокнота Jupyter, достигли тех смехотворно высоких показателей точности, которыми вы гордитесь, доказали, что скептики в вашей компании ошибались, и хотите, чтобы ваша модель была развернута в рабочей среде, когда вы столкнетесь с большим препятствие — никто из команды инженеров не знает, как правильно развернуть эту модель. Эта статья (и сопутствующий код) поможет вам и вашей команде инженеров поместить вашу модель в контейнер и подготовить ее к развертыванию!! 🎉

Обновление: декларативные MLOps — оптимизация обслуживания моделей в Kubernetes. Вот запись моего выступления, в которой кратко изложена эта статья, а также приведены другие рекомендации по созданию собственных контейнеров и использованию GitOps для их развертывания с помощью CI. /CD.

Если вы хотите читать больше такого премиум-контента от меня и других авторов на Medium, рассмотрите возможность стать платным подписчиком по этой ссылке — https://medium.com/@rparundekar/membership.

Если вам понравилась статья, подпишитесь на me на Medium, чьи изменения в партнерской программе затрагивают таких авторов, как я.

Почему очень мало моделей доходят до серийного производства?

Начнем сначала с проблемы. В серийное производство попадают очень немногие модели. В статье VentureBeat еще в 2019 году сообщалось, что этот показатель составляет всего 13 %. Вызывает тревогу тот факт, что с 2019 года мало что изменилось: опрос 114 человек, проведенный KDNuggets в ноябре 2021 года, показал, что подавляющее число респондентов сообщили, что только 0–20% моделей развернуты в рабочей среде. Около 35% людей сообщили, что технические проблемы были основным препятствием. Другие причины также были освещены в другом месте, которые указывают на отсутствие поддержки инструментов и фреймворка.

Основываясь на беседах, которые у меня были с учеными по данным, я бы сказал, что процессы развертывания моделей машинного обучения не были четко определены в большинстве организаций. В то время как инженерные группы имеют процессы CI/CD, настроенные в песочнице, интеграции, промежуточной и производственной средах, специалисты по обработке и анализу данных работают в блокноте Jupyter на своих рабочих станциях/близко к данным и на самом деле не установили аналогичные строгие процессы для своих моделей. развернуты (хотя у них есть несколько хороших процессов, настроенных для обучения модели — разделение наборов данных, отслеживание экспериментов и т. д.). В Рисунке восемь еще в 2018 году мы поняли, что лучший способ преодолеть этот разрыв — следовать примеру инженерной организации и использовать те же методы, которые они используют для развертывания программного обеспечения для развертывания модели. Итак, тесно сотрудничая с замечательной командой DevOps и инженеров, мы контейнеризировали наши модели и использовали установленные конвейеры CI/CD для проверки кода, автоматических тестов при фиксации, отправки образов контейнеров в репозитории и продвижения изображений через различные среды.

Почему именно контейнеры?

Контейнеры делают код воспроизводимым, переносимым и масштабируемым. Подумайте о контейнере как об образе системы, содержащем ваш код, вашу модель и все установленные зависимости. Создав образ контейнера, вы можете запустить столько экземпляров, сколько вам нужно, используя Amazon ECS, Kubernetes или любые другие подобные платформы. Эти фреймворки следят за тем, чтобы было запущено достаточное количество экземпляров, старые зацикливались, а в случае сбоя они перезапускались. Современные команды DevOps уже перешли на использование контейнеров для всего инженерного кода. Мы можем сделать то же самое для наших моделей. Однако контейнеры предлагают гораздо больше, чем просто образ системы, и если вы новичок в контейнерах и таких технологиях, как Docker, я настоятельно рекомендую вам посмотреть TechWorld с каналом Наны на YouTube.

Архитектура: 3 способа развертывания вашей модели

Я уверен, что, планируя свой проект, вы думали о предполагаемом использовании вашей модели. Вероятно, это один из трех способов: (1) модель прогнозирует данные, сгенерированные действием пользователя, и пользователь/пользовательский интерфейс ожидает ответа; (2) системное событие/действие пользователя запускает прогнозирование модели, а отдельный процесс асинхронно воздействует на прогнозирование модели; или (3) вы периодически получаете пакет данных для прогнозирования.

Мы можем думать о модели, обслуживающей архитектуру, как об отражении этих трех способов.

Способ 1: обслуживание модели с помощью конечной точки HTTP.

Если пользователь выполняет действие и ожидает прогнозного ответа, я бы предпочел использовать модель в качестве конечной точки HTTP. Здесь важно отметить одну важную часть: время прогнозирования модели должно быть менее 300 мс — своего рода стандартное время отклика, ожидаемое для любого HTTP-сервиса. Службы HTTP, которые занимают больше времени, рискуют отстать от объема запросов на прогнозирование до такой степени, что они начнут истекать по тайм-ауту. На приведенной ниже диаграмме архитектуры в качестве примера пользователь использует приложение, и оно отправляет запрос на предсказание того, что он (например) ввел. Убедившись, что запрос аутентифицирован, сервер модели может ответить прогнозом. Вы можете настроить свойство автоматического масштабирования в вашей системе оркестрации контейнеров для масштабирования в зависимости от загрузки ЦП (например, автоматически масштабировать службу модели до 10 модулей, если средняя загрузка ЦП превышает 40%).

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

Способ 2: Сервис, который обрабатывает запросы в очереди сообщений

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

Совет. Эта архитектура отлично подходит для мини-пакетных прогнозов, так как вы можете получить несколько запросов из очереди за раз и сделать прогноз.

Способ 3: длительная задача, выполняющая пакетную обработку

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

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

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

Код в этом руководстве поможет вам поместить модель в контейнер и подготовить ее к использованию одним из трех способов.

Код

Чтобы объяснить, как вы можете контейнеризовать свою модель, я буду использовать вариант использования текстовой категоризации в качестве примера (в качестве замены вашего типичного Hello World). Сопутствующий код для этого учебника используется совместно с лицензией MIT здесь. Мы будем контейнеризовать этот конвейер HuggingFace классификации Zero-Shot 🤗 из-за его простоты.

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

Модель, которую мы будем контейнеризовать

Давайте сначала посмотрим на пример кода здесь, который мы попробуем контейнеризовать. В примере мы создаем конвейер классификатора. Для HuggingFace фактическая модель загружается при первом запуске кода. У вас есть предложение, которое вы хотите классифицировать, и метки-кандидаты классов, по которым вы хотите классифицировать это предложение. Вы можете использовать конвейер классификатора, чтобы получить прогноз. Объект предсказания получает оценки (или вероятности) для меток-кандидатов. Очень просто.

from transformers import pipeline
# Create the pipeline
classifier = pipeline("zero-shot-classification",
                      model="facebook/bart-large-mnli")
# Get a prediction
sequence_to_classify = "one day I will see the world"
candidate_labels = ['travel', 'cooking', 'dancing']
prediction = classifier(sequence_to_classify, candidate_labels)
# Returned prediction
#{'labels': ['travel', 'dancing', 'cooking'],
# 'scores': [0.9938651323318481, 0.0032737774308770895, 0.002861034357920289],
# 'sequence': 'one day I will see the world'}

При разработке модели вы, вероятно, сохранили код в блокноте Jupyter и веса модели. Хотя ваша записная книжка может включать исследовательский анализ данных, предварительную обработку, обучение модели, эксперименты и результаты, нам нужно будет извлечь код, связанный с прогнозированием модели, и поместить его в контейнер аналогичным образом. Это может включать (а) требования к установке (б) код предварительной обработки (например, очистка текста, изменение размера изображения и т. д.), (в) код для прогнозирования и (г) файл весов модели. Я объясню, как вы можете интегрировать их по ходу дела.

Требования к установке

Чтобы запустить пример категоризации текста, нам явно нужны как минимум библиотека transformers и pytorch. Ваша модель может иметь аналогичную зависимость от pillow, tensorflow и т. д. (или вы даже можете устанавливать библиотеки непосредственно для своей системы). Мы установим системные зависимости в нашем Dockerfile и зависимости pip с помощью файла requirements.txt.

Нам также потребуется установить другие библиотеки и зависимости для обслуживания нашей модели тремя описанными выше способами. Мы будем использовать nginx, flask, gunicorn и gevent для конечной точки HTTP, redis для очереди сообщений и pandas. для

(a) Установка системы: мы поместим требования к установке системы в Dockerfile.

Начнем с тонкого базового образа Python 3.9, основанного на debian. Если вы достаточно смелы (и хорошо финансируетесь), чтобы обслуживать свои модели с использованием графического процессора для больших нагрузок или моделей обработки видео, вы также можете использовать pytorch или tensorflow, предоставленные базовые изображения графического процессора. Однако они громоздки. Я хочу развернуть свою модель с использованием ЦП и не люблю лишнего объема. Поэтому я выбрал эту тонкую базу в форме яблочка.

FROM python:3.9-slim-bullseye

Совет: у вас может возникнуть соблазн использовать базовое изображение альпийского питона. "Не". Я пробовал недавно, и в итоге вам нужно установить всякие зависимости для самых простых библиотек. В других случаях альпийские версии общих библиотек ужасно устарели.

Затем мы включаем команды установки для системных зависимостей — nginx, ca-сертификаты нужны для flask, а pm2 будет использоваться для обслуживания очередь сообщений.

RUN apt-get -y update && \
   apt-get install -y --no-install-recommends build-essential  \
   curl wget nginx ca-certificates npm \
   && npm install pm2 -g \
   && pip install --upgrade pip setuptools \
   && rm -rf /var/lib/apt/lists/*

(b) Требования к Pip: мы добавляем наши зависимости установки pip в файл requirements.txt. и установите их, прежде чем мы скопируем наш код в контейнер. (Примечание. Возможно, вы использовали conda во время разработки, и у вас может возникнуть соблазн использовать его выше. Он включает в себя множество пакетов, которые вы, возможно, не используете, поэтому я рекомендую вам устанавливать только те пакеты, которые тебе нужно.)

Это те, которые нам нужны для нашего примера (для всех трех способов)

  • Связанные модели: факел, трансформаторы
  • Рекомендации, связанные с: pylint, pytest
  • Связано с конечной точкой HTTP: flask, flask-cors, flask-expects-json, event, gunicorn
  • Связано с очередью сообщений: redis
  • Пакетная обработка файлов: pandas, tqdm

И мы устанавливаем pip в Dockerfile следующим образом:

COPY app/requirements.txt .
RUN pip install -r requirements.txt

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

Совет. Рекомендуется закреплять версии в файле requirements.txt. Это отличная практика (особенно когда эти библиотеки обновляются в одночасье и ломают ваши сборки). Но если вы создаете образы контейнеров и продвигаете их в своих средах, проблема становится менее серьезной. Мне нравится держать их незакрепленными, если это возможно, поскольку ночные обновления этих библиотек также часто устраняют уязвимости контейнеров.

Код предсказания

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

Поскольку мы должны создавать экземпляр модели только один раз, несмотря на множество запросов, которые мы можем получить через flask и нашу очередь сообщений, мы будем использовать переменные класса для хранения загруженной модели и методы класса для прогнозирования. У нас также есть метод load, который обеспечивает загрузку модели до вызова predict. Пока я показываю суть класса ниже, обратитесь к коду со всеми наворотами здесь.

class ZeroShotTextClassifier:
  # Class variable for the model
  classifier = None
  @classmethod
  def load(cls):
    if cls.classifier is None:
      # Load the model only once
      cls.classifier = pipeline("zero-shot-classification", 
                         model="facebook/bart-large-mnli")
  @classmethod
  def predict(cls, text, candidate_labels):
    # Ensure the model is loaded
    cls.load()
    # Predict
    huggingface_predictions = cls.classifier(text, candidate_labels)
    # Create our own prediction object with the best label
    max_index = np.argmax(huggingface_predictions["scores"])
    label = huggingface_predictions["labels"][max_index]
    score = huggingface_predictions["scores"][max_index]
    return {"label": label, "score": score}

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

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

Совет. Не включайте в этот класс логику преобразования (например, синтаксический анализ JSON), внешнюю логику (например, выборку изображений по URL-адресам) или другую бизнес-логику (например, проверки базы данных). Будь проще. Вместо этого они могут находиться в файлах, связанных с Flask/Redis, или в других вспомогательных классах.

Совет. При этом иногда один запрос на прогнозирование может включать несколько моделей (например, двухэтапное обнаружение объектов) или поиск в базе данных (например, проверка лица в базе данных встраивания). Можно включить такую ​​логику в этот единственный класс, если вы считаете, что наличие одного класса для каждой модели будет излишним.

Подготовка модели. Большинству фреймворков требуется некоторое время для загрузки модели в память из локального файла и даже для первого прогноза. Это время «разогрева модели». Например, загрузка модели facebook/bart-large-mnli размером 1,5 ГБ на моем ноутбуке занимает около 9 секунд. Имея отдельную функцию загрузки, мы также позволяем загружать модель сразу при запуске службы, чтобы при поступлении запроса на прогноз мы могли прогнозировать напрямую, вместо того, чтобы заставлять запрашивающую сторону ждать первого прогноза.

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

Совет. Я предпочитаю обслуживать один поток в одном контейнере для одной модели. Поскольку мы будем использовать некоторую систему оркестрации контейнеров, я вместо этого предпочитаю делегировать автомасштабирование этой системе: (а) мне не нужно беспокоиться о безопасности потоков, (б) некоторые особенности библиотек, такие как tensorflow вам нужно сохранить график в переменной класса, а затем указать, что вы используете этот график, если у вас есть несколько потоков, (c) если вы обслуживаете свою модель с помощью графического процессора, каждый контейнер может работать на своем собственном узле, чтобы избежать ошибок графического процессора OOM (Важно отметить: масштабирование контейнера пока плохо работает с графическими процессорами).

Вес модели

Наряду с кодом прогнозирования нам также необходимо включить веса модели. Если вы обучили модель, вы, вероятно, сохранили веса модели в виде файла pickle, h5, protobuf или подобного файла.

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

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

  1. Загрузите модель в Dockerfile: мы делаем это для модели обнимающего лица. Модель загружается при сборке контейнера и включается в образ, помещенный в репозиторий. Когда экземпляр контейнера создан, модель находится прямо там, и мы можем загрузить ее быстрее.
  2. Включить модель в репозиторий кода. Еще один отличный вариант, который включает в себя дополнительную функцию управления версиями модели, — сохранить модель в репозитории git. Чтобы преодолеть ограничение размера файла, которое есть в большинстве репозиториев, используйте Git LFS. Управление версиями модели теперь также является частью вашего цикла проверки кода. Когда вы копируете код в контейнер, веса модели также копируются.
  3. Хранится в томе: Наконец, возможно, на полпути между загрузкой на лету и включением в аргумент репозитория, есть возможность сохранить вашу модель в блочном хранилище, которое можно смонтировать как том на ваш компьютер. контейнер. Вам по-прежнему потребуется выделить память контейнера (поскольку модель будет загружаться из файловой системы в память), помимо дополнительных усилий, связанных с тем, чтобы сделать объемное хранилище частью операций управления версиями вашей модели. Управление разными путями для разных сред усложняет ситуацию.

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

RUN python -c "from transformers import pipeline; classifier = pipeline('zero-shot-classification', model='facebook/bart-large-mnli')"

Совет. Убедитесь, что модель загружена в нужное место, особенно если вы используете пользователя без полномочий root в Dockerfile. Вы можете видеть, что я переключаю пользователя с root на user, чтобы загрузить модель, а затем снова переключаю пользователя на root, чтобы выполнить оставшиеся шаги.

3 способа развертывания модели

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

Способ 1: Конечная точка HTTP

В то время как другие варианты, такие как tf-serving, seldon и fast-ai, могут иметь очень простой способ обслуживания модели с использованием конечной точки HTTP, создание нашего контейнера таким образом обеспечивает большую гибкость и контроль над тем, как вы хотите обслуживать модель. . Вы также можете включить бизнес-логику (например, авторизацию, понижение и повышение уровня API и т. д.) и улучшить процессы контроля версий кода, а продвижение образа контейнера станет намного проще.

Мы используем Flask для обслуживания модели. В файле app.py есть код конечной точки, который вызывает класс модели для прогнозирования.

@app.route("/predict", methods=["POST"])
@expects_json(SCHEMA)
def predict():
  request_obj = g.data
  text = request_obj["text"]
  candidate_labels = request_obj["candidate_labels"]
  # predict
  prediction = ZeroShotTextClassifier.predict(
    text=text, 
    candidate_labels=candidate_labels)
return jsonify(prediction)

Одна проблема с обслуживанием контейнеров заключается в том, что системы оркестрации могут выполнять проверку работоспособности службы. По сути, они периодически обращаются к конечной точке /ping или /health_check, чтобы проверить, правильно ли запущен контейнер. В обычном коде это будет, например, проверять, активно ли соединение с базой данных. Только после успешной проверки работоспособности балансировщик нагрузки начинает отправлять контейнеру HTTP-трафик. Для модели мы используем эту возможность, чтобы разогреть ее.

@app.route("/health_check", methods=["GET"])
def health_check():
  ZeroShotTextClassifier.load()
  return jsonify({"success": True}), 200

Совет. Обязательно сообщите команде DevOps, сколько времени потребуется для прогрева модели, чтобы они могли соответствующим образом настроить проверку работоспособности. Кроме того, быстро посчитайте в уме, сколько времени заняла бы проверка, если бы модель загружалась. Нет загрузки лучше.

При обслуживании flask в производственной среде используются gunicorn и gevent за сервером nginx. Эти настройки устанавливаются в файле serve. (Примечание: если вы хотите изменить некоторые тайм-ауты и количество рабочих процессов, можно использовать переменные окружения SERVEER_TIMEOUT и SERVER_WORKERS.)

Способ 2: очередь сообщений

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

Файл serve_mq запускает основную функцию mq.py, которая подключается к Redis и прослушивает запросы прогнозов в теме запросов и отправляет прогноз в тему ответов. Очевидно, вам понадобится потребитель в теме ответа, чтобы выполнить какое-то действие с прогнозом.

if __name__ == "__main__":
  redis_url = os.environ.get("REDIS_URL")
  redis_connection = redis.Redis.from_url(redis_url)
  predict_request_topic = os.environ.get("PREDICT_REQUEST_TOPIC") 
  predict_response_topic = os.environ.get("PREDICT_RESPONSE_TOPIC")
  # Warm-up
  ZeroShotTextClassifier.load()
  while True:
    _, msg = redis_connection.blpop(predict_request_topic)
    request_obj = json.loads(msg.decode("utf-8"))
    # Predict, and use the id to track the request.
    _id = request_obj.get("id", str(uuid4()))
    prediction = predict(request_obj)
    prediction["id"] = _id
    # Put the response on the response topic.
    redis_connection.lpush(predict_response_topic,
       json.dumps(prediction))

Примечание. Поскольку создание очереди сообщений является асинхронным, вам необходимо отслеживать запросы и ответы с помощью идентификатора.

В обоих вышеприведенных случаях мы также проверяем схему JSON запроса и сообщения с помощью проверки JSON-схемы. Прибамбасы в коде.

Способ 3: Пакетная обработка

Часто вы хотите запустить свою модель на данных, собранных за определенный период времени. Например, прогнозы по ежедневным данным или пакет .csv, созданный за несколько дней. Мы можем запустить контейнер как длительную задачу для прогнозирования пакетных данных. Пакетные данные можно смонтировать с помощью тома (или загрузить во время прогнозирования).

batch_process запускает файл batch.py, который загружает .csv с помощью pandas и прогнозирует данные для каждого из ряды.

df = pd.read_csv(input_file, encoding="utf-8")
with open(output_file, "w", encoding="utf-8") as f:
  # Create the output csv file
  csv_writer = DictWriter(f, fieldnames=["text", "label", "score"])
  csv_writer.writeheader()
  # Iterate trough the rows.
  for index, row in df.iterrows():
    prediction = ZeroShotTextClassifier.predict(
              tegt=row["text"], 
              candidate_labels=candidate_labels)
    prediction["text"] = row["text"]
    csv_writer.writerow(prediction)

Другие лучшие практики

Я изо всех сил старался включить в код как можно больше лучших практик. Хотя некоторые важные передовые практики находятся за пределами кода.

Обзор кода

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

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

Линтинг

Хорошее программирование также означает хороший код. Линтинг помогает убедиться, что код чистый и соответствует стандартам. Добавление linting в Dockerfile также помогает избежать многих ошибок во время выполнения.

# Pylint to make sure our code is not too bad.
RUN pylint --disable=R,C ./**/*.py

Тесты

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

from unittest import TestCase
from helpers.zero_shot_text_classifier import ZeroShotTextClassifier
class ZeroShotTestCase(TestCase):
  def setUp(self):
    ZeroShotTextClassifier.load()
  def test_default_widget_size(self):
    prediction = ZeroShotTextClassifier.predict(
         text="This is great!", 
         candidate_labels=["sad", "happy"])
    self.assertEqual(prediction["label"], "happy")
    self.assertGreaterEqual(prediction["score"], 0.9)

Дополнительные сведения см. в папке tests/ .

CI/CD

Непрерывная интеграция и непрерывная поставка — это современная практика DevOps для раннего обнаружения дефектов, повышения производительности и ускорения циклов выпуска. Когда вы фиксируете свой код в функциональной ветке и открываете запрос на вытягивание, хорошо построенная часть CI конвейера запускает команды make build && make test, чтобы убедиться, зафиксированная ветка хорошо строится и проходит модульные тесты проверки работоспособности.

В то же время CD-часть конвейера создает образ контейнера и отправляет его в репозиторий контейнеров. Откуда система, подобная Jenkins, может использоваться специалистами по данным в режиме «самообслуживания» для развертывания образа контейнера в песочнице, интеграции, промежуточной или производственной среде, когда это необходимо (в идеале, с более автоматизированным тестированием на этом пути). .

Наконец, процесс CI/CD также позволяет нам улучшить нашу способность быстро поставлять модели. Слаженная команда способна выпускать улучшения каждый день (кроме пятницы 😎 ). Итак, когда у вас есть новая модель «Чемпион» 🏆, вы можете PR (т. е. запрос на включение) вашей функциональной ветки, а когда она будет принята, объединиться с основной и использовать инструмент CI/CD для развертывания вашей модели в рабочей среде на любой день недели, оканчивающийся на y (кроме пятницы, субботы и воскресенья).

Оркестрация контейнеров

Хотя у меня лично есть некоторый опыт работы с Amazon ECS, любая система оркестрации контейнеров, используемая вашей командой DevOps, должна иметь возможность развертывать модели при поставке с контейнерами. Kubernetes, и так далее, и тому подобное.

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

Инструменты и фреймворки «инфраструктура как код» должны упростить развертывание. Спросите своего дружелюбного соседа DevOps для получения дополнительной информации о том, как они развертывают контейнеры с инфраструктурой как кодом.

Запуск контейнеров в средах

Я предоставляю Makefile и docker-compose для простых способов локального запуска контейнеров для разработки. Команды make (make build && make test) также можно использовать для конвейеров CI/CD. Но для фактического развертывания с предпочитаемой вами системой оркестрации контейнеров вам может потребоваться запуск с командами, которые использует docker-compose. Например, вы можете использовать реальную очередь сообщений, а не небезопасный контейнер Redis, запущенный с помощью docker-compose. В этом случае запускайте контейнер только командой make start_mq с правильными переменными среды.

Переменные среды

НИКОГДА не фиксируйте какой-либо секрет в коммите git (даже в ветке). Злоумышленник, получивший доступ к вашему коду, может проанализировать историю, чтобы извлечь использованные вами секретные ключи. Поэтому лучше всего передавать реальные ключи в ваш контейнер в переменных окружения. В системе оркестровки контейнеров есть способы запустить контейнер с переменными среды. Таким образом, вы передаете, например, пароль, содержащий строку подключения Redis, в контейнер с помощью переменных среды. Еще одно преимущество использования env vars заключается в том, что вы можете указывать модели на разные очереди сообщений в разных переменных среды.

Мониторинг

Вы можете добавить свой любимый фреймворк (например, Arize, Fiddler, New Relic и т. д.) для отслеживания времени прогнозирования, затрачиваемого службой. Обычно DevOps также настраивает оповещения типа «Пейджер», чтобы разбудить вас посреди ночи, если сервисы начнут выдавать 500 ошибок (т. проблемы и ловить исключения изящно. Вот для чего нужны ваши модульные тесты.

Совет: если вы записываете прогнозы модели в консоль. Прекрати это, прямо сейчас. Логи имеют необоснованную привычку храниться где-то на платформе очень долго, и если в этих данных есть информация, позволяющая установить вашу личность, вы настраиваете себя на длительные проблемы с законом/безопасностью.

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

Заключение

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

Совет. Если вы попали сюда, подпишитесь на меня на Medium, чьи изменения в партнерской программе затрагивают таких писателей, как я. Нравится и подписывайтесь ✌️ 😀.

Мне всегда интересно узнать, как специалисты по обработке и анализу данных создают модели и внедряют их. Если вам интересно поделиться своими мыслями и точками зрения, я буду рада поговорить!

Фу! Это было долгое чтение. Надеюсь, вы решили использовать контейнеры для развертывания своих моделей, а этот блог и код помогут вам и вашей команде инженеров Rockstar быстрее развернуть ваши модели в рабочей среде. Если вы все еще сомневаетесь, вот еще один важный рисунок.

Эта статья написана Рахулом Парундекаром для AI Hero.

Рахул — эксперт по искусственному интеллекту с более чем 13-летним опытом проектирования и создания продуктов искусственного интеллекта, проектирования, исследований и руководства. Он увлечен улучшением человеческого опыта с помощью искусственного интеллекта.

AI Hero — это платформа без кода для автоматизации визуальных задач.