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

Введение

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

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

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

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

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

Проблема

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

Чтобы смоделировать данные, мы будем использовать набор данных Список подержанных автомобилей Ford от Kaggle, набор данных, содержащий цену продажи более 15 000 автомобилей и их соответствующие атрибуты (тип топлива, пробег, модель и т. д.). , и т. д).

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

Предлагаемая архитектура

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

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

CDC с Дебезиумом и Кафкой

Сбор данных об изменениях или просто CDC — это процесс мониторинга и отслеживания изменений в базе данных. Вы можете думать о CDC как о сплетнях данных: каждый раз, когда что-то происходит внутри базы данных, инструмент CDC слушает и делится сообщением со своими «друзьями».

Например, если запись (Жоао, 21) добавлена ​​в таблицу neighbours, инструмент прошепчет что-то вроде: {'добавлено':{'имя': 'Жоао', 'возраст': 21, «идентификатор»: 214}}.

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

Debezium — это инструмент с открытым исходным кодом, специализирующийся на CDC. Он работает, читая журналы базы данных (в данном случае называемой источником) и преобразовывая обнаруженные изменения в стандартизированные структурированные сообщения, отформатированные в AVRO или JSON, поэтому другое приложение может использовать их, не беспокоясь о том, кто является источником.

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

Debezium построен на базе Apache Kafka, известного открытого инструмента распределенной потоковой передачи событий, который многие крупные компании, такие как Uber и Netflix, ежедневно перемещают гигабайтами данных. Из-за этой огромной масштабируемости, когда дело доходит до перемещения данных, Kafka обладает огромным потенциалом для помощи моделям машинного обучения в производстве.

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

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

Вот как перемещаются наши данные: когда Debezium настроен на просмотр какой-либо таблицы в нашей базе данных, он преобразует обнаруженные изменения в стандартизированные сообщения, сериализует их в байты и отправляет в топик Kafka.

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

БентоМЛ

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

Это отличный инструмент, особенно если вы из мира науки о данных и никогда не переносили модель из «счастливых полей» Jupyter Notebook в «производственный» мир.

В известных библиотеках Python для машинного обучения либо нет способа обслуживать модели, потому что они считают его выходящим за рамки, либо, когда он у них есть, его не так просто использовать. Из-за этого многие проекты полагаются на доставку своих моделей через API, созданные с помощью FastAPI или Flask, что хорошо, но не оптимально.

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

Мы узнаем больше об этом в следующих разделах.

Соединяем все вместе

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

У нас будет экземпляр Debezium, наблюдающий за нашей базой данных и передающий каждое обнаруженное изменение в тему Kafka. С другой стороны, приложение Python принимает сообщения и перенаправляет их в службу BentoML, которая возвращает прогнозируемую цену. Затем приложение Python объединяет записи с их прогнозируемыми ценами и записывает их обратно в другую тему Kafka. Наконец, экземпляр Debezium, который также следит за этой темой, читает сообщения и сохраняет их обратно в базу данных.

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

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

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

Реализация

Постараюсь быть кратким, полный подробный код будет на GitHub.

Окружающая среда

Первое, что нужно сделать, это настроить среду, все, что вам нужно, это:

  1. Среда Python со следующими пакетами:
numpy
pandas
scikit-learn==1.1.2
xgboost==1.6.1
bentoml
pydantic

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

2. Docker и docker-compose.

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

И это все 👍

Настройка Постгреса

Конфигурация Postgres очень проста, нам нужно только создать таблицу для хранения данных автомобиля и установить конфигурацию wal_level=logical.

Итак, файл Postgres Dockerfile выглядит следующим образом:

wal_level=logical — это конфигурация, необходимая для правильной работы Postgres с Debezium.

Настройка коннекторов Debezium и Kafka

Начать работу с Kafka и Debezium (с Docker) просто, нужно лишь правильно настроить образы и коннекторы. Docker-compose и Dockerfile, используемые в этом проекте, были основаны на одном из примеров Debezium в официальном репозитории.

Примечание. Я скрыл некоторые строки, чтобы сделать этот блок кода короче, проверьте полный код на GitHub.

Файл Debezium Dockerfile настроен с драйверами для Kafka Connect и Postgres.

С помощью этого файла docker-compose мы готовы настроить Debezium. Чтобы запустить контейнеры, введите в терминале:

docker-compose up --build

После некоторых начальных настроек (и большого количества журналов) контейнеры должны запускаться правильно. Теперь вы можете открыть браузер на localhost:8083.

Это базовая конечная точка API Debezium, где происходят все настройки. Например, если мы перейдем к localhost:8083/connector-plugins/, можно увидеть все доступные плагины для создания коннектора.

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

Давайте создадим исходный коннектор для нашей базы данных Postgres и посмотрим, как это работает.

Нам просто нужно передать адрес базы данных и учетные данные, класс соединителя (один из доступных в конечной точке /connector-plugins/) и таблицу, которую мы хотим захватить.

Подробнее об этих коннекторах и конфигурациях вы можете узнать в этом посте.

Теперь Debezium создаст тему Kafka с именем car_database.public.car_data и начнет потоковую передачу в нее изменений.

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

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

Вот почему нам не нужно создавать вторую таблицу в Postgres: она будет автоматически сгенерирована Debezium.

Развертывание нашей модели с помощью BentoML

Следующее, что нужно сделать, — развернуть нашу модель машинного обучения с помощью BentoML. Это достигается за три шага: сохранение нашей модели, сборка Bento и преобразование ее в контейнер Docker.

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

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

Сохранив модель, мы можем создать службу для ее развертывания.

Обученная модель загружается как раннер — особый тип объекта, используемый BentoML для представления моделей. Раннер используется для создания объекта service и с его помощью мы определяем конечную точку /predict, отвечающую за получение записи и возвращая свою прогнозируемую цену.

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

Затем, выполнив приведенные ниже команды, создаем Docker-образ с нашим сервисом с именем ford_price_predictor:1.0.0.

bentoml build --version 1.0.0
bentoml containerize ford_price_predictor:1.0.0

Наконец, мы можем запустить экземпляр контейнера нашего сервиса.

docker run -p 3000:3000 ford_price_predictor:1.0.0

И взаимодействуйте с ним, зайдя на localhost:3000.

Подключение потока и модели

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

С одной стороны, Debezium передает данные в тему Kafka car_database.public.car_data и ожидает сообщений в car_data_predicted. С другой стороны, служба BentoML развернута и ожидает прогнозов на конечной точке /predictions.

Для подключения к Kafka мы будем использовать пакет confluent_kafka, а для подключения к развернутой модели — пакет requests.

Затем мы определяем темы Kafka и URL-адрес службы bento.
URL-адреса не являются локальными, поскольку этот скрипт будет выполняться внутри другого контейнера.

Затем создается потребитель Kafka для исходной темы и производитель для темы-приемника.

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

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

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

Debezium прочитает это новое сообщение и создаст соответствующую запись в базе данных.

И мы можем назвать это днем!

Видя, как это работает

Наконец-то пришло время увидеть, как наш проект работает :)

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

Как видно из рисунка, при добавлении новой записи в таблицу car_data автоматически создается другая запись в таблице car_data_predicetd с предложенной ценой.

Если мы продолжим добавлять все больше и больше записей в таблицу car_data.

Они будут продублированы в таблице car_data_predicted с предложенной ценой.

В общем, работает!

Заключение

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

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

Машинное обучение по-прежнему является растущей областью, и по сравнению с другими областями, связанными с ИТ, такими как веб-разработка, сообществу еще предстоит многому научиться. К счастью, в последние годы появилось много новых технологий, которые помогают нам создавать приложения машинного обучения, такие как Mlflow, Mlib Apache Spark и BentoML, рассмотренные в этой статье.

В этом посте исследуется архитектура машинного обучения с некоторыми из этих технологий для создания системы рекомендаций по ценам в реальном времени. Чтобы воплотить эту концепцию в жизнь, нам понадобились не только инструменты, связанные с машинным обучением (BentoML и Scikit-learn), но и другие программные продукты (Postgres, Debezium, Kafka).

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

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

Спасибо за чтение! ;)

Рекомендации

Весь код доступен в этом репозитории GitHub.

[1] Официальная документация Debezium
[2] Иржи Печанец (2017), Потоковая передача данных в нижестоящую базу данных — блог Debezium
[3] Дескрипторы ввода-вывода Bentoml API — BentoML Docs
[4] Концепции BentoML — BentoML Docs
[5] Кай Вэнер (2020), Потоковое машинное обучение с развертыванием модели, родной для Kafka — Кай Вэнер
[6] Тим Лю , Почему люди говорят, что так сложно внедрить модель машинного обучения в производство? — Блог BentoML
[7] Примеры Debezium— Официальный репозиторий Debezium на Github
[8] Бен Диксон (2022 г.), GitHub Copilot — один из первых реальных продуктов, основанных на больших языковых моделях — Tech Talks
[9] Перечень подержанных автомобилей Ford, CC0: Public Domain — Kaggle