MLOps и переносимость ML-моделей с помощью ONNX и Apache Spark

MLOPS Spark примеры курсы обучение, Spark MLLib, курсы Spark для дата-инженеров, обучение Apache Spark, Spark ML MLOps, обучение инженеров Machine Learning, Школа Больших Данных Учебный Центр Коммерсант

Обучая специалистов по Data Science, аналитиков и инженеров данных лучшим практикам MLOps, сегодня поговорим про переносимость моделей машинного обучения между разными этапами жизненного цикла ML-систем, от разработки до развертывания в production. А в качестве примера разберем, как использовать обученную ML-модель из Apache Spark за пределами кластера, упаковав ее в ONNX или контейнер с контекстом Spark.

Сложности MLOps: почему нельзя просто так взять и развернуть ML-систему

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

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

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

Ответить на эти и другие аналогичные вопросы помогает концепция MLOps для автоматизации и мониторинга масштабируемых ML-систем. Применяя принципы DevOps к разработке и развертыванию решений машинного обучения, MLOps включает оркестровку экспериментов, отслеживание показателей, реестр моделей, инструменты автоматизации рутинных операций и мониторинг производительности моделей, а также их переносимость между разными этапами жизненного цикла, от разработки до развертывания в production. Как MLOps решает это на практике, рассмотрим далее.

Переносимость моделей Machine Learning: популярные форматы

Например, Data Scientist пишет код в блокнотах типа Jupyter Notebook или Google Colab. При переносе этого кода в производственную среду его следует преобразовать в легковесный формат обмена, сжатый и сериализованный, который не зависит от языка разработки. Такими форматами являются следующие:

  • Pickle – бинарный вариант Python-объекта для сериализации и десериализации его структуры, т.е. преобразования иерархии объектов Python в поток байтов и наоборот. О достоинствах и недостатках формата Pickle читайте в нашей новой статье.
  • ONNX (Open Neural Network Exchange) — формат с открытым исходным кодом для ML-моделей, обеспечивающий общий набор операторов и универсальный формат файла для различных платформ и инструментов. ONNX-формат описывает граф вычислений (ввод, вывод и операции) и является автономным. Он ориентирован на глубокое обучение, поддерживается Microsoft и Facebook, отлично работает с TensorFlow и PyTorch.
  • PMML (Predictive Model Markup Language) — формат обмена предиктивными моделями на основе XML, позволяющий разработать модель в одной системе для одного приложения и развернуть ее в другой с помощью другого приложения, передав конфигурационный XML-файл.
  • PFA (Portable Format for Analytics) – стандарт для статистических моделей и механизмов преобразования данных, который отличается легкостью переносимости между различными системами и моделями. Функции предварительной и последующей обработки могут быть объединены в цепочку и встроены в сложные рабочие процессы. PFA может быть простым преобразованием необработанных данных или сложным набором параллельных моделей интеллектуального анализа данных с файлом конфигурации JSON или YAML.
  • NNEF (Neural Network Exchange Format) – формат, который облегчает процесс развертывания машинного обучения, позволяя использовать набор инструментов обучения нейросетей для приложений на различных устройствах и платформах.

Также есть форматы, специфичные для отдельных фреймворков, например, POJO/MOJO для AutoML-платформы H2O и Spark MLWritable для Apache Spark. Как именно работать с этими форматами, реализуя идеи MLOps, поговорим далее. А в нашей новой статье вы узнаете, как можно вместо этого использовать открытую ML-библиотеку сериализации MLeap. 

Упаковка моделей Machine Learning в ONNX и контейнер с контекстом Spark

Предположим, необходимо использовать обученную ML-модель из Apache Spark за пределами кластера. При этом есть несколько вариантов:

  • если модель обучена с помощью TensorFlow, PyTorch, Scikit-Learn и пр., можно упаковать ее модель в различные переносимые форматы, которые не зависят напрямую от Spark;
  • если же модель обучалась с помощью MLLib, например, в пространстве имен PySpark.ml.*, можно экспортировать ее в переносимый формат типа ONNX, а затем использовать среду выполнения ONNX для запуска модели. Однако, не все модели Spark MLLib поддерживают ONNX. Также можно сохранить модель и загрузить ее из контейнера, создав объект контекста Spark без кластера.
  • применение MLFlow для сохранения модели и ее упаковки с использованием спецификации MLModel. MLFlow поддерживает Apache Spark и может упаковывать модель с использованием спецификации MLModel, чтобы развернуть ее в любом месте. О том, что представляет собой спецификация ML-модели в MLFlow, читайте в нашей новой статье.

В Spark ML-модель машинного обучения представлена преобразователем (​​трансформером), который преобразует входной датафрейм с фичами в другой датафрейм с прогнозами. Трансформер также может выводить другой набор фичей. Поскольку в машинном обучении обычно нужно выполнить несколько шагов для получения желаемого прогноза, в Spark есть объект PipelineMode, который представляет собой последовательность шагов или трансформеров, объединяя их в конвейер преобразователей. Он объединяет несколько трансформеров вместе, чтобы задать рабочий процесс машинного обучения.

Рассмотрим пример с PipelineModel из 3 этапов:

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

Получим PipelineModel, обучая Pipeline с помощью метода fit():

tokenizer = Tokenizer(inputCol="text", outputCol="words")
hashingTF = HashingTF(inputCol=tokenizer.getOutputCol(), outputCol="features")
lr = LogisticRegression(maxIter=10, regParam=0.01)
pipeline = Pipeline(stages=[tokenizer, hashingTF, lr])model = pipeline.fit(training)

Чтобы запустить этот объект PipelineModel вне Spark, можно экспортировать его в ONNX и запустить модель с использованием среды выполнения ONNX. Хотя ONNX изначально был разработан для моделей глубокого обучения, он поддерживает и обычный ML, как и Spark. Сюда входят алгоритмы индексирования строк, OneHotEncoding, Word2Vec, Scalers, Inputer, Binarizer, Bucketizer, линейные модели, случайный лес, градиентное усиление, наивный байесовский анализ, SVM, PCA.

NLP с Python

Код курса
PNLP
Ближайшая дата курса
в любое время
Продолжительность
40 ак.часов
Стоимость обучения
90 000

Чтобы экспортировать модель в формат ONNX, нужно сначала установить набор инструментов onnxmltools, доступный только для PySpark. Затем можно экспортировать модель конвейера в ONNX следующим образом:

from onnxmltools
import convert_sparkml
from onnxmltools.convert.sparkml.utils 
import buildInitialTypesSimpleinitial_types = buildInitialTypesSimple(test_df.drop("label"))
onnx_model = convert_sparkml(model, 'Pyspark model', initial_types, spark_session = spark)

Прокомментируем вышеприведенный участок кода:

  • Функция buildInitialTypesSimple создает список всех ожидаемых входных данных от модели (фичей). Она принимает образец DataFrame в качестве параметра. В примере это датафрейм test_df.
  • Model – имя установленного конвейера (PipelineModel), Pyspark model – описание модели.
  • initial_types — ожидаемые входные имена функций и типы, которые могут быть представлены с помощью функции buildInitialTypesSimple или созданы вручную, например [(‘education’, StringTensorType([1, 1]))]
  • spark_session=spark передает методу контекст SparkSession.

Сохраним эту модель конвейера в файл:

with open(os.path.join("/tmp/", "model.onnx"), "wb") as f:
f.write(onnx_model.SerializeToString())

Далее можно загрузить эту модель, используя среду выполнения ONNX в Python, например:

import onnxruntime
session = onnxruntime.InferenceSession(model_file_path, None)
output = session.get_outputs()[0]
inputs = session.get_inputs()input_data= {i.name: v for i, v in zip(inputs, input_sample.values.reshape(len(inputs),1,1).astype(np.float32))}}results = session.run([output.name], input_data)

input_data — это словарь с ключами в качестве имен функций и тензорами (1,1) в качестве значений. Функция reshape преобразует входные данные в массив Tensor с формой (feature_count,1,1), что и ожидается. Также важно приводить значения как float32. Наконец, можно отправить эту модель в Docker-контейнер с установленным Python и нужными библиотеками, например, из набора ONNXMLTools. Файл загрузит модель с помощью метода onnxruntime.InferenceSession(), а процедура скоринга вызовет метод session.run().

При этом можно столкнуться с некоторыми ограничениями конвертера Spark для ONNX, такими как отсутствие экспорта для Feature Hashing, TFIDF, RFormula, NGram, SQLTransformer и некоторых других моделей (кластеризация, FP, ALS и пр.). А также проблемы с поддержкой других языков программирования, помимо PySpark и отсутствием поддержки Tokenizer в спецификации ONNX.

ML Практикум: от теории к промышленному использованию

Код курса
PYML
Ближайшая дата курса
26 мая, 2025
Продолжительность
24 ак.часов
Стоимость обучения
54 000

Другой способ запустить PipelineModel внутри контейнера, самостоятельно реализовав идеи MLOps, — экспортировать модель и создать контекст Spark внутри контейнера, даже если кластер недоступен. В этом случае при сохранении PipelineModel, объект реализует интерфейс MLWritable, который предоставляет методы save(path) и write().overwrite().save(path). Этот метод сохраняет модель в иерархической структуре папок, где подробно описаны все шаги в конвейере. Вся структура необходима для загрузки и восстановления обученной модели. Для использования с MLFlow или Azure Machine Learning Services можно заархивировать этот каталог:

import shutilmodel.write().overwrite().save(model_path)
path_drv = shutil.make_archive(model_name, format='zip', base_dir=model_path)

Метод Shutil.make_archive создаст файл в узле драйвера в своей локальной файловой системе. Чтобы использовать его внутри контейнера, нужно установить Python-библиотеку PySpark в образ контейнера, т.к. модель Spark будет загружаться напрямую. Далее в скрипте ML-прогнозирования следует создать Spark-сессию, распаковать архив в папку и загрузить объект PipelineModel:

import pyspark
from pyspark.ml 
import PipelineModelspark = pyspark.sql.SparkSession
.builder.appName("pyspark_runtime").getOrCreate()model_unpacked = "./" + model_name
shutil.unpack_archive(model_path, model_unpacked)trainedModel = PipelineModel.load(model_unpacked)

Переменные spark и trainingModel должны быть доступны во всех подпрограммах, т.е. это глобальные переменные. Наконец, можно запустить ML-модель следующим образом:

input_df = spark.createDataFrame(pandas_df)
predictions = trainedModel.transform(input_df).collect()
preds = [x['prediction'] 
for x in predictioprint('[INFO] Results was ' + json.dumps(preds))

Читайте в нашей новой статье, как самостоятельно внедрить MLOps-подход без сложных сторонних решений, автоматизировав задачу пакетного прогнозирования и планирование ее запуска по расписанию с помощью Apache Spark. А практически освоить лучшие практики MLOps с Apache Spark и других инструментов аналитики больших данных, вам помогут специализированные курсы в нашем лицензированном учебном центре обучения и повышения квалификации для разработчиков, менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data в Москве:

Я даю свое согласие на обработку персональных данных и соглашаюсь с политикой конфиденциальности.

Источники

  1. https://medium.com/swlh/productionizing-machine-learning-models-bb7f018f8122
  2. https://towardsdatascience.com/how-to-containerize-models-trained-in-spark-f7ed9265f5c9
  3. https://pypi.org/project/onnxmltools/
  4. https://github.com/santiagxf/portable-sparkml/blob/master/score_pyspark.py