Часть 2. Использование MLflow для развертывания моделей

Автор Тигран Аветисян

Это ЧАСТЬ 2 из нашей серии из 3 ЧАСТЕЙ о платформе жизненного цикла машинного обучения MLflow.

См. ЧАСТЬ 1 здесь.

В ЧАСТИ 1 мы рассмотрели:

  • MLflow Tracking – набор инструментов для отслеживания и записи экспериментов по машинному обучению.
  • Проекты MLflow — набор инструментов для упаковки кода обработки данных в воспроизводимый формат.

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

Мы рассмотрим:

  • Как мы можем упаковать наши модели с помощью MLflow.
  • Как мы можем загружать и разворачивать наши упакованные модели в MLflow.

Обратите внимание, что в этом руководстве предполагается, что вы прочитали ЧАСТЬ 1. Поэтому обязательно ознакомьтесь с первой статьей серии, прежде чем читать дальше!

Что такое модели MLflow?

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

Модели опираются на понятие «ароматы». Ароматы в основном описывают, как упакованные модели должны запускаться в целевой среде. В MLflow существует три основных типа вариантов модели:

  • Функция Python. Модели, сохраненные в виде общей функции Python, можно запускать в любой среде MLflow независимо от того, с каким пакетом ML/DL они были созданы.
  • R-функция. ФункцияR является эквивалентом функции Python, но на языке программирования R.
  • Разновидности, зависящие от платформы. MLflow также может сохранять модели в том виде, в котором они были созданы. Например, модель scikit-learn можно загрузить в собственном формате, чтобы пользователи могли использовать функциональность scikit-learn.

Начиная с MLflow 1.21.0, полный список поддерживаемых вариантов выглядит следующим образом:

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

Содержимое упакованной модели будет выглядеть примерно так:

Компоненты в данном конкретном случае следующие:

  • conda.yaml — файл, в котором перечислены зависимости conda, используемые моделью.
  • input_example.json — файл, содержащий пример ввода для модели, показывающий ожидаемую сигнатуру входных данных.
  • MLmodel — файл, описывающий сохраненные варианты, путь к модели, входные и выходные подписи модели и многое другое.
  • model.pkl — протравленная модель.
  • requirements.txt — файл, определяющий зависимости пипсов модели. По умолчанию они выводятся из среды, в которой упакована модель.

Имея все это в виду, давайте создадим упакованную модель, как показано выше!

Предпосылки для моделей MLflow

Предварительные условия для моделей MLflow такие же, как и для отслеживания и проектов:

  • Пакет MLflow Python.
  • Пакеты платформ ML/DL, которые вы хотите использовать.
  • Менеджер пакетов conda — либо от Miniconda, либо от стандартной Anaconda.

Если вы уже пробовали «Отслеживание» и «Проекты», значит, вы готовы к прочтению оставшейся части этого руководства!

Как и в ЧАСТИ 1, мы собираемся использовать scikit-learn для демонстрации упаковки модели с помощью MLflow. С некоторыми адаптациями приведенный ниже код можно использовать и с другими поддерживаемыми платформами ML/DL.

Сохранение моделей с помощью моделей MLflow

В этом руководстве основное внимание будет уделено двум функциям моделей MLflow:

  • Сохранение обученных моделей в локальной среде.
  • Развертывание обученных моделей в локальной среде.

Давайте сначала посмотрим на сохранение моделей с помощью MLflow!

Импорт зависимостей

Для начала давайте импортируем зависимости:

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

Установка оценщика scikit-learn

Теперь нам нужно обучить оценщик scikit-learn. Это довольно просто — если вы знакомы с scikit-learn, приведенный ниже код будет вам понятен:

Мы используем набор данных scikit-learn о раке молочной железы, чтобы подобрать LogisticRegressionоценщик. Мы разделяем данные на обучающие и тестовые наборы, потому что нам нужны некоторые данные после обучения, чтобы показать, как MLflow можно использовать для развертывания моделей.

Предоставление входной подписи и примеров ввода

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

Давайте сначала создадим входную подпись для нашей модели. Есть два способа сделать это:

  • Использование внутреннихинструментов MLflowдля определения подписи автоматически.
  • Указание входной подписи вручную.

Автоматическое определение входной подписи

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

  • model_input – ожидаемые входные данные для модели. Вы можете использовать свой обучающий набор данных в качестве входных данных модели. Поддерживаемые типы аргументов: pandas.DataFrame, numpy.ndarray, словари подписи {name: numpy.ndarray} и pyspark.sql.DataFrame.
  • model_output ожидаемый результат модели. Вы должны получить прогнозы из своей модели, чтобы использовать их в качестве выборки выходных данных.

Мы будем использовать pandas.DataFrameкак model_input. Основное преимущество pandas.DataFrame заключается в том, что он может содержать имена столбцов входных объектов, что делает будущим пользователям более понятными, какими должны быть входные данные для нашей модели.

Вот как выглядит infer_signature() в действии с pandas.DataFrame:

Мы тут:

  • Направьте X_trainк pandas.DataFrameобъекту X_train_df.
  • Передайте X_train_dfвместе с прогнозами модели infer_signature().

infer_signature()возвращает объект MLflow ModelSignature, который мы назначаемв переменную signature.

Давайте теперь осмотрим signature:

inputs:
['mean radius': double, 'mean texture': double, 'mean perimeter': double, 'mean area': double, 'mean smoothness': double, 'mean compactness': double, 'mean concavity': double, 'mean concave points': double, 'mean symmetry': double, 'mean fractal dimension': double, 'radius error': double, 'texture error': double, 'perimeter error': double, 'area error': double, 'smoothness error': double, 'compactness error': double, 'concavity error': double, 'concave points error': double, 'symmetry error': double, 'fractal dimension error': double, 'worst radius': double, 'worst texture': double, 'worst perimeter': double, 'worst area': double, 'worst smoothness': double, 'worst compactness': double, 'worst concavity': double, 'worst concave points': double, 'worst symmetry': double, 'worst fractal dimension': double]
outputs:
[Tensor('int32', (-1,))]

Мы видим, что наши входные данные относятся к типу double, а выходные данные относятся к типу Tensor int32.

Указание входной подписи вручную

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

  • ColSpec — класс, определяющий тип данных и имя столбца в наборе данных. Альтернативой ColSpec является TensorSpec, который используется для представления Tensor наборов данных.
  • Schema — класс, представляющий набор данных, состоящий из ColSpec или TensorSpecобъектов.

Нам нужно создать отдельные объекты Schemaдля представления входных и выходных данных. Вот как вы могли бы сделать это с набором данных Iris:

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

Теперь давайте создадим схемы для набора данных рака молочной железы. Поскольку в этом наборе данных много столбцов функций, мы не будем создавать для них ColSpecобъекты вручную — вместо этого мы будем использовать генератор списков:

Содержание наших схем следующее:

['mean radius': double, 'mean texture': double, 'mean perimeter': double, 'mean area': double, 'mean smoothness': double, 'mean compactness': double, 'mean concavity': double, 'mean concave points': double, 'mean symmetry': double, 'mean fractal dimension': double, 'radius error': double, 'texture error': double, 'perimeter error': double, 'area error': double, 'smoothness error': double, 'compactness error': double, 'concavity error': double, 'concave points error': double, 'symmetry error': double, 'fractal dimension error': double, 'worst radius': double, 'worst texture': double, 'worst perimeter': double, 'worst area': double, 'worst smoothness': double, 'worst compactness': double, 'worst concavity': double, 'worst concave points': double, 'worst symmetry': double, 'worst fractal dimension': double]
[long]

Наконец, чтобы создать подпись, мы создаем объект ModelSignature, передавая ему наши схемы:

Предоставление примеров ввода

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

Вы можете предоставить примеры входных данных как pandas.DataFrame, numpy.ndarray, dict или list.

В нашем случае давайте снова используем X_train_df.Это гарантирует, что пример ввода будет соответствовать нашей входной подписи. .

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

Указание зависимостей conda и pip

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

Здесь:

  • conda_env – это словарь, определяющий целевую среду conda.
  • pip_requirements — это список, содержащий имена зависимостей пипов.

В качестве альтернативы для любого из них вы можете предоставить:

  • Строковый путь к файлу среды conda YAML (для conda_env) или текстовому файлу требований (для pip_requirements).
  • Ничего, и в этом случае MLflow будет использовать mlflow.models.infer_pip_requirements()для определения зависимостей среды.

Вы можете узнать больше об использовании mlflow.models.infer_pip_requirements()в Документации MLflow.

Сохранение модели

Наконец-то пришло время собрать все воедино и упаковать нашу модель scikit-learn!

В scikit-learn для упаковки модели можно использовать mlflow.sklearn.log_model()или mlflow.sklearn.save_model(). Вот чем отличаются эти две функции:

  • log_model()заносит в журнал модель текущего запуска как артефакт.
  • save_model()сохраняет модель в указанном каталоге относительно пути вашего проекта.

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

Сохранение модели в локальный путь

Вот как использовать save_model():

После запуска этого оператора модель вместе с файлами среды и входным примером будет сохранена по пути /model в текущем рабочем каталоге.

Запись модели как артефакта

Что касается log_model(), вот как вы можете его использовать:

Здесь мы начинаем прогон с mlflow.start_run(), потому что нам нужно получить идентификатор прогона, чтобы позже было легче загрузить зарегистрированную модель.

Загрузка наших сохраненных моделей

Ранее мы установили, что MLflow сохраняет модели scikit-learn в двух форматах или вариантах:

  • Как функция Python.
  • Как нативная модель scikit-learn.

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

Перед загрузкой наших моделей укажем пути к ним:

Вы можете использовать любой из этих путей для загрузки модели как функции Python или стандартной модели scikit-learn:

Здесь следует помнить несколько вещей:

  • Чтобы загрузить модель, которая была сохранена с mlflow.sklearn.log_model(), вы можете добавить к каталогу префикс runs:/. Далее укажите идентификатор прогона, под которым была сохранена модель (в нашем случае {run_id}), а затем укажите путь, который вы передали в artifact_path ранее (model). Если вы укажете путь с префиксом runs:/, MLflow будет искать модель в пути artifacts указанного запуска.
  • Чтобы загрузить модель, сохраненную с помощью mlflow.sklearn.save_model(), укажите путь к модели относительно каталога, в котором вы запускаете скрипт Python.
  • Есть несколько способов указать расположение модели — проверьте ссылку на API mlflow.pyfunc.load_model() (здесь) или mlflow.sklearn.load_model() (здесь) для получения дополнительной информации.

Вывод с загруженными моделями

Чтобы сделать вывод с загруженными моделями, вам просто нужно вызвать их predict()методы, передав некоторые данные:

Здесь мы использовали наши тестовые функции. Обратите внимание: поскольку MLflow применяет входные подписи при использовании PyFuncModel, нам нужно преобразовать наши numpy.ndarrayданные в pandas.DataFrame.

sklearn_predictionsи pyfunc_predictions имеют одинаковое содержание:

[0 0 1 0 0 0 1 0 1 0 1 0 0 1 1 1 1 1 0 1 1 1 1 1 0 0 1 0 0 1 1 1 0 1 1 0 1
1 0 1 0 1 1 1 1 1 0 1 0 0 1 1 0 0 1 1 0 0 0 1 1 1 1 0 1 1 1 0 0 1 1 1 1 0
0 1 1 0 0 1 0 1 1 1 0 0 1 0 0 0 0 0 1 1 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1
1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 1 1 0 1 1 1 1 1 0 0]
True

Во многих моделях scikit-learn (включая нашу sklearn_model) можно также использовать predict_proba для получения вероятностей классов. pyfunc_modelне имеет метода predict_proba.

Обслуживание моделей с помощью моделей MLflow

Вы можете развертывать модели, сохраненные с помощью MLflow, в различных средах, от локального компьютера до облачных платформ, таких как Amazon SageMaker или Microsoft Azure.

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

Развертывание моделей MLflow

Чтобы обслуживать вашу модель через командную строку, сначала перейдите в каталог над ней. Например, если ваша модель сохранена в разделе mlruns/your_experiment_id/your_run_id/artifacts/model, перейдите к mlruns/your_experiment_id/your_run_id/artifacts/.

Откройте свой терминал в этом месте и выполните следующее:

mlflow models serve -m model

mlflow models serveэто команда, которая обслуживает модели. -m modelуказывает путь к модели, которую мы хотим обслуживать.

Вы можете передать другие аргументы mlflow models serve, например — port или — host, чтобы указать целевой URL-адрес для запуска модели. Подробнее об этой команде здесь.

По умолчанию MLflow обслуживает модели на сервере REST API по адресу http:/127.0.0.1:5000. Сервер принимает данные в качестве ввода POST для http:/127.0.0.1:5000/invocations. Модель обслуживается как функция Python, а это означает, что наши входные данные должны следовать входной сигнатуре, которую мы указали ранее.

Вывод с помощью обслуживаемой модели

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

Программный вывод с помощью моделей MLflow

Чтобы сделать программный вывод, нам нужно сначала определить URL-адрес нашей конечной точки и функцию запроса:

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

Теперь вы можете использовать функциюqueryдля вывода. Серверы MLflow принимают данные в следующих четырех форматах:

Только первые три типа данных имеют отношение к нашей модели scikit-learn. Давайте посмотрим, как MLflow работает с каждым из этих типов данных в действии!

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

Затем давайте отправим запрос нашей модели и проверим ответ:

[0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0]

Чтобы сделать вывод с сериализованным CSV-файлом DataFrame, вам нужно будет передать {“Content-Type”: “text/csv”}в качестве заголовка в запросе POST. В нашей реализации функции query это делается следующим образом:

[0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0]

Вывод из командной строки с моделями MLflow

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

curl http://localhost:5000/invocations -H 'Content-Type: application/json' -d '{
"columns": ["mean radius", "mean texture", "mean perimeter",
"mean area", "mean smoothness", "mean compactness",
"mean concavity", "mean concave points", "mean symmetry",
"mean fractal dimension", "radius error", "texture error",
"perimeter error", "area error", "smoothness error",
"compactness error", "concavity error", "concave points error",
"symmetry error", "fractal dimension error", "worst radius",
"worst texture", "worst perimeter", "worst area",
"worst smoothness", "worst compactness", "worst concavity",
"worst concave points", "worst symmetry", "worst fractal dimension"],
"data": [[17.99, 10.38, 122.8,
1001.0, 0.1184, 0.2776,
0.3001, 0.1471, 0.2419,
0.07871, 1.095, 0.9053,
8.589, 153.4, 0.006399,
0.04904, 0.05373, 0.01587,
0.03003, 0.006193, 25.38,
17.33, 184.6, 2019.0,
0.1622, 0.6656, 0.7119,
0.2654, 0.4601, 0.1189]]}'

Следующие шаги

Это было все для нашего введения в модели MLflow! Теперь вы знаете основы, но есть много других аспектов моделей, которые мы на самом деле не исследовали — обязательно ознакомьтесь с документацией MLflow, если вам интересно.

В ЧАСТИ 3 этой серии мы собираемся объединить концепции, которые мы исследовали в ЧАСТИ 1 и ЧАСТИ 2, чтобы создать простое приложение React!

Наше приложение сможет:

  • Отслеживание экспериментов. Приложение будет принимать значения для нескольких гиперпараметров, автоматически отслеживать эксперименты и позволит вам просматривать результаты в пользовательском интерфейсе MLflow.
  • Разверните модели и сделайте выводы. При желании вы сможете использовать приложение для развертывания обученных моделей и создания на их основе логических выводов.

Следите за обновлениями!