Программисты всегда были увлечены своими предпочтениями, независимо от того, обсуждают ли они пробелы против табов, Vim против Emacs или светлый режим против темного режима. Эти дебаты выдержали испытание временем, показывая, что есть место для каждого решения, и никакие окончательные аргументы не могут объявить одно превосходящим другое.
Однако, когда дело доходит до парадигм программирования, аргументы, как правило, более пылкие. Объектно-ориентированные языки долгое время доминировали в мире программирования, отстаивая возможность повторного использования кода в различных проектах. Напротив, в последние годы функциональное программирование появилось как альтернативный стиль, многообещающий код, с которым легче рассуждать. При углублении в проекты машинного обучения возникает вопрос: какая парадигма лучше всего подходит для создания приложения MLOps?
Эта статья призвана пролить свет на преимущества обоих стилей программирования и помочь вам определить наиболее подходящий для вашего проекта MLOps. Мы начнем с представления двух основных стилей программирования в нашей отрасли. Впоследствии мы изучим конкретные требования проектов MLOps, чтобы направлять наш процесс принятия решений. Наконец, я выскажу свое мнение о лучшем общем стиле и представлю привлекательный компромисс, известный как «гибридный стиль».
Краткое введение в парадигмы программирования
На протяжении всей своей карьеры мне приходилось работать с различными языками программирования, начиная с объектно-ориентированных, таких как C++, Java, PHP, Python, Ruby и Groovy. Каждый язык предлагает свой набор преимуществ и недостатков, в зависимости от глубины его возможностей:
Плюсы:
- Моделирование реального мира: объектно-ориентированная парадигма точно отражает объекты реального мира, что позволяет разработчикам интуитивно моделировать и разрабатывать приложения на основе предметной области.
- Модульность и возможность повторного использования: инкапсуляция объектно-ориентированного программирования позволяет проектировать модульный код, обеспечивая возможность повторного использования и удобство сопровождения. Это помогает управлять большими кодовыми базами и способствует сотрудничеству между членами команды.
- Богатая экосистема. Объектно-ориентированные языки программирования, такие как Java и C#, имеют обширные платформы и шаблоны проектирования, предоставляя разработчикам мощные инструменты для эффективного создания сложных приложений.
Минусы:
- Общее изменяемое состояние: объектно-ориентированное программирование часто полагается на общее изменяемое состояние, что приводит к потенциальным ошибкам и проблемам, связанным с доступом к изменяемым объектам из нескольких мест.
- Хрупкие иерархии наследования: чрезмерное использование наследования может привести к хрупкости иерархий классов, что затруднит изменение или расширение функциональности без непреднамеренных побочных эффектов.
- Сложность и накладные расходы. Объектно-ориентированные кодовые базы могут становиться сложными, особенно в крупных проектах, что приводит к увеличению времени разработки и отладки.
Ниже приведена диаграмма, иллюстрирующая приложение MLOps, реализованное в объектно-ориентированном стиле. Программист должен тщательно обрабатывать атрибуты объекта и предоставлять методы получения/установки для управления доступом к ним. Хотя представление программы является интуитивно понятным, оно также многословно и иногда негибко из-за приверженности принципам объектно-ориентированного программирования.
По мере продвижения по карьерной лестнице я увлекся функционально-ориентированными языками, такими как Clojure, Haskell и Elixir. Эти языки пробудили во мне интерес своим уникальным подходом к управлению состоянием и другими концепциями, которые казались адаптированными для приложений с данными.
Плюсы:
- Расширенные возможности рассуждений. Фокус функционального программирования на чистых функциях гарантирует, что выполнение зависит исключительно от входных данных, что значительно упрощает тестирование и отладку.
- Предсказуемый параллелизм. Неизменяемость и безгражданство по своей сути снижают вероятность возникновения условий гонки и проблем с одновременным доступом к данным, что делает его более подходящим для параллельного и параллельного программирования.
- Простота в дизайне: функциональное программирование требует меньше сложных шаблонов проектирования, вместо этого полагаясь на другие формы полиморфизма (например, специальный или параметрический), функции высокого порядка и даже монады для расширения и укрепления программ.
Минусы:
- Кривая обучения: функциональное программирование может быть сложным для разработчиков, которые более привыкли к императивным и объектно-ориентированным парадигмам. Изменение мышления и понимания таких понятий, как функции высшего порядка и рекурсия, может занять некоторое время.
- Накладные расходы на производительность: некоторые конструкции функционального программирования, такие как создание множества промежуточных структур данных во время вычислений, могут привести к снижению производительности по сравнению с оптимизированными императивными реализациями.
На приведенной ниже диаграмме показано приложение MLOps, придерживающееся стиля функционального программирования. Структура программы более проста благодаря четкому разделению между структурами данных и операциями. Однако этот стиль требует конструкций функционального программирования, таких как специальный полиморфизм, для надежной поддержки добавления как новых типов, так и функций.
Овладение обеими парадигмами оказывается ценным вложением, предоставляя разработчикам разнообразный набор инструментов для разработки оптимальных решений. Давайте теперь рассмотрим конкретные требования, уникальные для проектов MLOps, которые помогут нам выбрать наиболее подходящий стиль программирования для этого типа приложений.
Требования к проектам MLOps
Приложения MLOps представляют собой уникальное сочетание простоты и сложности. С одной стороны, они имеют общие концепции, такие как наборы данных, модели и задания, которые можно повторно использовать в проектах с небольшими вариациями. Однако решение таких проблем, как случайность, большие структуры данных и сложные внутренние объекты, такие как нейронные сети, усложняет эти проекты. Чтобы избежать возможных проблем и дорогостоящего рефакторинга, крайне важно запускать приложения MLOps с хорошо продуманной основой.
Ниже мы перечисляем ключевые требования, ранжированные по важности для приложения MLOps (на мой взгляд):
- Воспроизводимость: убедитесь, что ваше приложение MLOps дает согласованные и воспроизводимые результаты.
- Модульность. Используйте модульность, разбивая приложение MLOps на более мелкие повторно используемые компоненты.
- Настраиваемость: возможность изменения поведения программы посредством внешних конфигураций, а не прямых модификаций кода.
- Расширяемость: упрощение добавления новых моделей и источников данных.
- Keep It Simple (KISS): не усложняйте приложение, так как не все участники MLOps имеют продвинутый опыт программирования.
Имея в виду эти требования, давайте углубимся в обсуждение того, какой стиль программирования лучше всего подходит для разработки приложений MLOps.
Итак, какой стиль лучше?
Изучив плюсы и минусы как объектно-ориентированного, так и функционального программирования в предыдущем разделе, мы видим, что не существует единого критерия, который явно отдавал бы преимущество одному перед другим для приложений MLOps. Оба стиля могут соответствовать выявленным требованиям, что является удачей, учитывая, что большинство языков программирования являются полными по Тьюрингу и предлагают эквивалентную выразительность.
Однако у меня есть веский аргумент. Хотя для разработки приложений MLOps можно применять все стили программирования, не все языки программирования могут эффективно поддерживать обе парадигмы. Например, Python, один из самых популярных языков для проектов по науке о данных, лучше всего подходит для объектно-ориентированного программирования при создании больших приложений. Хотя он может обрабатывать функции и даже функции высокого порядка, эти функции представляют собой абсолютный минимум для поддержки функциональной парадигмы. Python не поддерживает ключевые элементы функционального программирования, такие как 1) специальный или параметрический полиморфизм, 2) оптимизация хвостового вызова и 3) эффективные неизменяемые структуры данных (например, постоянные структуры данных). Напротив, он превосходно использует полиморфизм подтипов и изменчивость для различных операций Python.
В результате я предпочитаю объектно-ориентированную парадигму при создании приложений MLOps с помощью Python, даже если я предпочитаю функциональную парадигму для других типов приложений. Создание проекта MLOps не является тривиальным, поскольку для выполнения определенных требований требуются расширенные и идиоматические языковые функции. Тем не менее, есть прием, который можно применить для включения элементов обеих парадигм, установив баланс, который использует сильные стороны каждого подхода.
Гибридный стиль
Гибридный стиль стремится объединить лучшие аспекты функционального программирования с объектно-ориентированной парадигмой, создавая выгодный компромисс для языков программирования, таких как Python, которые поддерживают оба стиля. Применяя этот подход, ваш код может стать более идиоматичным, расширяемым и простым для понимания.
Чтобы эффективно реализовать этот стиль, придерживайтесь следующих принципов:
- Неизменяемые атрибуты: объекты не должны обновлять свои атрибуты после инициализации, рассматривая их как доступные только для чтения, чтобы избежать прямого изменения состояния объекта.
- Методы, ориентированные на вывод: каждый метод должен возвращать свои выходные данные, а не обновлять атрибуты, позволяя другим объектам обрабатывать изменения состояния программы.
- Вызовы идемпотентных методов: методы должны постоянно возвращать один и тот же результат для заданных входных данных, что соответствует принципам функционального программирования.
- Централизованные императивные операторы: классы высокого уровня, такие как класс Job, должны обрабатывать императивные операторы, концепция, напоминающая монаду IO в Haskell. Это обеспечивает четкое разграничение действий, которые взаимодействуют с реальным миром, таких как ведение журнала или обновление базы данных.
- Использовать другие преимущества объектно-ориентированного программирования: используйте преимущества объектно-ориентированного программирования, такие как полиморфизм подтипов и интуитивно понятные представления, когда это необходимо.
На этой последней диаграмме представлено приложение MLOps в соответствии с рекомендациями гибридного стиля. С одной стороны, мы возвращаемся к полиморфизму подтипов и представлениям классов для поддержки расширяемости программы. С другой стороны, мы уменьшаем накладные расходы на управление состоянием программы за счет использования и совместного использования атрибутов только для чтения, отделяя классы, которые могут иметь побочный эффект (т. е. задания), от остальной части приложения.
Помните, что это руководящие принципы, а не строгие правила. Вдумчиво применяя их, вы можете разработать надежное приложение. Пакет MLOps Python был разработан с использованием этих принципов и может продемонстрировать, как гибридный стиль может быть эффективно применен к приложениям MLOps.
Выводы
В данной статье были исследованы сильные и слабые стороны двух популярных парадигм программирования: Функциональной и Объектно-ориентированной. Оба стиля могут быть успешно применены к проектам MLOps, учитывая их уникальные требования. Основной критерий выбора должен соответствовать характеристикам выбранного вами языка программирования, что позволит вам реализовать самые идиоматические решения. Например, выберите функциональный стиль с Haskell или Clojure и объектно-ориентированный стиль с Java или Python.
В качестве альтернативы вы можете использовать гибридный стиль, чтобы объединить преимущества функционального и объектно-ориентированного подходов в преимущественно объектно-ориентированном языке. Этот выбор учитывает возможности языка, обслуживая приложения для работы с данными, где идемпотентность и параллелизм играют решающую роль в выводе вашего приложения на новый уровень.
Лично я считаю, что объектно-ориентированному стилю Python не хватает элегантности. Тем не менее, я высоко ценю Python за его адаптируемость к новым концепциям с течением времени, таким как постепенная типизация или асинхронное программирование. Чтобы улучшить объектно-ориентированный стиль Python, я рекомендую использовать такой набор инструментов, как Pydantic, замечательную библиотеку, которую я широко использовал при разработке MLOps Python Package. Это преодолевает вышеупомянутое ограничение и значительно улучшает процесс разработки.