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

В этом посте я хочу поделиться своими знаниями о производстве моделей рекомендаций. Я пройдусь по этапам и жизненному циклу создания проекта машинного обучения в продакшене в сочетании с материалами из курса Coursera Machine Learning Engineering for Production (MLOPs).

Нулевой этап: определение архитектуры и SLA

Tools: meetings with PMs, cross team collaboration

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

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

В большинстве случаев вы можете воспользоваться платформой развертывания модели, которая сделает всю тяжелую работу за вас. В нашем случае, поскольку модель разработана на Python, мы выбрали BentoML в качестве среды развертывания, поддерживающей библиотеку Tensorflow keras. BentoML обеспечивает некоторую магию повышения производительности при обслуживании конечной точки, поэтому нам не нужно слишком беспокоиться об этом. Подробнее об этом мы расскажем в разделе Третий этап: обучение и обслуживание моделейниже.

Этап 1: данные

Tools: Kafka, Databricks, Spark

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

При сборе данных нам также необходимо тестировать данные, чтобы гарантировать качество данных и соответствовать контракту данных для моделирования.

Второй этап: особенности модели

Tools: Databricks, Spark

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

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

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

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

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

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

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

Этап третий: архитектура модели

Tools: Databricks, Tensorflow

Именно на этом этапе сосредоточено большинство исследований ML. Поэтому я опущу здесь подробности и просто отмечу, что мы используем библиотеку Tensorflow keras для обучения встраиваниям, выполняя задачу контролируемой классификации с моделью на основе преобразователя. Поскольку данных о продукте очень мало, мы используем SparseCategoricalCrossEntropy в качестве функции потерь. Метрикой оценки модели является NDCG, что довольно часто встречается в рекомендательных системах.

Этап четвертый: модельное обучение и обслуживание​

Tools: BentoML, Databricks GPU instance, feature store

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

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

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

  • Локальная отладка обучающего кода и использование dbx cli для запуска удаленного обучения на графическом процессоре. Использование библиотеки BentoML для сохранения модели (bentoml.tensorflow.keras.save).
  • Используйте модель фиксации Github на блоке графического процессора, чтобы запустить сборку на Jenkins​
  • Подавайте модель с библиотекой BentoML (bentoml.tensorflow.keras.load)
  • В конечной точке проанализируйте входные данные, найдите функции в хранилище функций (обычно это база данных NoSQL), выберите обслуживаемую модель и сгенерируйте выходные данные для возврата клиенту.
  • Раскрутите конечную точку с помощью команды BentoML, и теперь ваш сервис готов!

Этап пятый: профилирование​

Tools: CProfile, snakeviz

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

Поскольку мы используем Python, мы используем cProfile для создания файла профилирования и используем SnakeViz для визуализации результатов. Один запрос занимает ~ 70 мс, в то время как бегун BentoML Keras занимает больше всего времени (50 мс), а это означает, что если мы хотим еще больше уменьшить задержку, нам нужно оптимизировать слои модели.

Runner позволяет BentoML лучше использовать несколько потоков или процессов для более высокой загрузки оборудования (ЦП, ГП, памяти) и более высокой параллелизма: распараллелить извлечение данных, преобразование данных или выполнение нескольких исполнителей.

Этап шестой: конвейер CI/CD и развертывание

Tools: Jenkins, Docker, Kubernetes

Теперь все выглядит хорошо локально, пришло время заставить все работать в облаке. Чтобы перенести библиотеки Python в облако через конвейер CI/CD (например, Jenkins), мы используем поэзию для блокировки пакетов Python на локальном компьютере с файлом поэзии.toml и многоэтапной сборкой в ​​файле Dockerfile. Первый этап заключается в использовании поэзии для установки библиотек Python; второй этап — копирование модели и предоставление файлов, а также библиотек Python в virtualenv с первого этапа.

В конвейере CI/CD мы используем Docker для создания, тегирования и отправки образа, а также развертывания образа в Kubernetes для обслуживания конечной точки вывода. ​

​Этап седьмой: тест производительности Blazemeter​

Tools: Blazemeter

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

При тестировании мы начинаем с медленной загрузки, чтобы измерить задержку одного запроса на модуль Kubernetes. Эта конфигурация тестирования может дать нам представление о том, сколько времени занимает один запрос. Например, если обработка одного запроса занимает 70 мс, за 1000 мс можно обработать 14 запросов. Поэтому, пока нагрузка ниже 14, не должно быть резервных копий запросов.

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

  • HPA настроен на максимальное количество модулей 10 и минимум на 3 модуля, чтобы избежать проблем с занятыми соседями.
  • Параллелизм = 30, HPA будет запускать 3 модуля (10 запросов в секунду на модуль), пропускная способность/количество запросов в секунду установлено на 30, что означает, что запросы не должны накапливаться.
  • Параллелизм = 30, hpa будет запускать 3 модуля (10 запросов в секунду на модуль), пропускная способность / количество запросов в секунду установлены на 60, что означает, что запросы будут накапливаться, что приведет к удвоению задержки.

Решите, сколько ЦП и памяти мы должны запросить для каждого модуля, чтобы выполнить нашу стратегию масштабирования. Перед тестированием мы сталкиваемся с ситуацией, когда запросы резервируются, что приводит к очень большой задержке. Но сервис не масштабировался, потому что не было добавлено новых модулей. Причина этого, как мы позже выяснили, заключается в том, что загрузка ЦП низкая, а HPA в Kubernetes запускает новые только тогда, когда загрузка ЦП достигает 60%. Поскольку служба прогнозирования является задачей, привязанной к ЦП, с блокировкой ввода-вывода, поэтому загрузка ЦП не достигнет 60%, даже если есть резервные копии запросов, поэтому она не будет автоматически масштабироваться.

Работники BentoML

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

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

  • Сценарий 1: 1 bentoml asyncio worker (уже встроенная поддержка в оболочке BentoML)
  • Сценарий 2: 2 асинхронных рабочих процесса bentoml (обрабатывают как ввод-вывод, так и вычисления) -> 2 рабочих процесса в каждом модуле, процесс использует 2 ЦП для распараллеливания вычислений (1 для вычислений и 1 для ввода-вывода, уровень вычислительных ресурсов потребляет ЦП)
  • Сценарий 3: оптимальное решение — пул процессов: только 1 передний веб-сервер (ввод-вывод) + 3 рабочих процесса (только вычисления, интенсивное использование ЦП), использование пула процессов для обработки запросов (веб-сервер отправляет запрос рабочему ПРОЦЕССОР)

Помните о параллельной природе Python, Python может использовать только одно ядро, поэтому задача с интенсивным вводом-выводом и ЦП будет выполняться в одном потоке, что блокирует поток на 70 мс, когда модель выполняет прогноз. Сравните с режимом асинхронной блокировки Java, каждый поток может занимать 1 ядро ​​​​процессора, если 32 ядра, то Java может иметь 32 потока для параллельной обработки.

​Этап восьмой: Мониторинг развертывания и онлайн-показателей

Tools: Grafana, Tensorflow dashboards

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

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

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

Обнаружение дрейфа входных данных: дрейф функций или концепций

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

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

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

Этап девятый: конвейер данных и переобучение

Tools: online metrics, Databricks

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

Когда модель работает, мы можем собирать онлайн-показатели взаимодействия пользователей с моделью. Мы можем запланировать задания Spark (расписание на Airflow или Databricks) для запуска конвейера данных (скажем, еженедельно или ежемесячно), чтобы генерировать более актуальные обучающие данные из источника и повторно обучать их.