Мы уже рассказывали про важность переносимости ML-моделей, что является одним из аспектов MLOps-концепции. Сегодня разберем, почему популярный формат Pickle не лучший выбор для сохранения модели Machine Learning и что использовать вместо него.
Пара достоинств и 7 главных недостатков формата Pickle
Согласно концепции MLOps, направленной на сокращение разрыва между различными специалистами, участвующими в процессах разработки, развертывания и эксплуатации систем машинного обучения, переносимость ML-моделей очень важна. Обычно Data Scientist пишет код в блокнотах типа Jupyter Notebook, Google Colab или специализированных IDE. Чтобы перенести этот код в производственную среду, его надо преобразовать в легковесный формат обмена, сжатый и сериализованный, который не зависит от языка разработки. Одним из таких форматов является Pickle. Это бинарный вариант Python-объекта для сериализации и десериализации его структуры, преобразующий иерархию объектов Python в поток байтов и наоборот. Формат Pickle довольно популярен за счет своей легковесности. Он не требует схемы данных и весьма распространен.
По сути, Pickle представляет собой Python-библиотеку и очень прост для использования. Модуль отслеживает объекты, которые он уже сериализовал, чтобы последующие ссылки на тот же объект не сериализовались снова. Это важно для рекурсивных объектов, которые содержат ссылки на самих себя, и для их совместного использования. Совместное использование объектов происходит, когда имеется несколько ссылок на один и тот же объект в разных местах сериализуемой иерархии объектов. Pickle сохраняет такие объекты только один раз и гарантирует, что все остальные ссылки указывают на главную копию. Общие объекты остаются общими, что может быть очень важно для изменяемых объектов.
Формат Pickle гарантированно обратно совместим между версиями Python при условии, что выбран совместимый протокол pickle, а код сериализации и распаковки работает с различиями типов Python 2-й и 3-й версий. Однако, этот формат имеет ряд недостатков:
- Небезопасно. Можно распаковывать только те pickle-файлы, которым вы доверяете. Злоумышленник может создать вредоносные данные, которые будут выполнять произвольный код во время распаковки. Смягчить этот риск можно через подписи данных с помощью hmac, чтобы убедиться, что они не были подделаны. Ненадежность связана не с тем, что Pickle содержат код, а с тем, что они создают объекты, вызывая конструкторы, упомянутые в файле. Любой вызываемый объект может использоваться вместо имени класса для создания объектов. Вредоносный код будут использовать другие вызываемые объекты Python в качестве конструкторов. Например, вместо выполнения MyObject(17) вредоносный pickle может выполнить os.system(‘rm -rf /’). Распаковщик pickle не может отличить models.MyObject от os.system.
- Рассогласование кода. Если код изменится за время между упаковкой ML-модели в Pickle-файл и моментом его использования, объекты могут не соответствовать коду. Они по-прежнему будут иметь структуру, созданную старым кодом, но пытаться работать с его новой версией. Например, если атрибут был добавлен после создания Pickle, объекты из Pickle-файла не будут иметь этого атрибута. А если в новой версии кода предполагается его обработка, возникнут проблемы.
- Неявная сериализация. С одной стороны, формат Pickle удобен тем, что он сериализует любую структуру Python-объекта. Но при этом нет возможности указать предпочтения по сериализации того или иного типа даны. Pickle сериализует все в объектах, даже данные, которые не нужно сериализовать. Но пропустить сериализацию того или иного атрибута в Pickle нет возможности. Если объект содержит атрибут, который нельзя упаковать, например, объект с открытым файлом, Pickle не пропустит его, настаивая на попытке его упаковать, а затем выдаст исключение.
- Отсутствие инициализации. Pickle хранит всю структуру объектов. Когда модуль Pickle воссоздает объекты, он не вызывает метод __init__, поскольку объект уже создан, считая инициализацию вызванной, когда объект был впервые создан в процессе создания Pickle-файла. Но метод __init__ может выполнять некоторые важные действия, например открывать файловые объекты. В этом случае необработанные объекты будут находиться в состоянии, несовместимом с методом __init__. Или инициализация может регистрировать информацию о создаваемом объекте. Тогда невыбранные объекты не будут отображаться в общем логе.
- Нечитаемый. Pickle — это поток двоичных данных, т.е. инструкции для абстрактного механизма выполнения. Открыв Pickle как обычный файл, его содержимое нельзя прочитать. Чтобы узнать, что находится в нем, придется использовать модуль Pickle для загрузки. Это может затруднить отладку, поскольку сложно искать нужные данные в двоичных файлах.
- Привязка к Python. Будучи Python-библиотекой, Pickle специфичен для этого языка программирования. Хотя сам формат может использоваться для других языков программирования, найти пакеты, обеспечивающие такие возможности, довольно трудно. Кроме того, они будут ограничены межъязыковыми общими структурами объектов list/dict. Pickle без проблем сериализует объекты, содержащие вызываемые функции и классы. Но формат не хранит код, а только имя функции или класса. При распаковке данных, имена функций используются для поиска существующего кода в запущенном процессе.
- Скорость работы. Наконец, по сравнению с другими методами сериализации, Pickle работает намного медленнее.
Поэтому MLOps-инженеру для переноса ML-моделей лучше рассмотреть другие универсальные форматы упаковки алгоритмов машинного обучения. Некоторые из этих альтернатив мы разберем далее.
Альтернативы для MLOps-инженера
Помимо Pickle есть множество других способов сериализации объектов, от JSON до marshal, cattrs и protocol buffers. Впрочем, им тоже свойственны некоторые недостатки. В частности, marshal тоже специфичный для Python бинарный формат. Но он не зависит архитектуры компьютера. Детали формата намеренно не документированы; он может меняться между версиями Python и тоже не является безопасным. Модуль marshal существует в основном для поддержки чтения и записи псевдоскомпилированного кода для модулей Python файлов с расширением .pyc. Pickle поддерживает значительно более широкий спектр объектов, чем marshal.
Формат текстовой сериализации JSON удобен для чтения человеком и широко используется за пределами экосистемы Python. Однако, JSON по умолчанию может представлять только подмножество встроенных типов Python, а не пользовательские классы. Но, в отличие от Pickle, десериализация ненадежного JSON сама по себе не создает уязвимости выполнения произвольного кода.
Впрочем, лучше использовать специализированные форматы для Machine Learning, например, ONNX (Open Neural Network Exchange), который ориентирован на глубокое обучение, поддерживается Microsoft и Facebook, отлично работает с TensorFlow и PyTorch. Другими подходящими MLOps-форматами считаются XML-подобный PMML (Predictive Model Markup Language) и статистический PFA (Portable Format for Analytics). Наконец, NNEF (Neural Network Exchange Format) упростит процесс развертывания нейросетей. Подробнее об этих форматах мы писали здесь.
Как выбрать и внедрить наиболее подходящие форматы и инструменты MLOps в реальные проекты аналитики больших данных, вы узнаете на специализированных курсах в нашем лицензированном учебном центре обучения и повышения квалификации для разработчиков, менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data в Москве: