Промышленный Machine Learning: 10 принципов разработки
В наше время каждый день создаются новые сервисы, приложения и другие важные программы, позволяющие создавать невероятные вещи: от софта для управления ракетой SpaceX до взаимодействия с чайником в соседней комнате через смартфон.
И, порой, каждый начинающий программист, будь он страстным стартапером или же рядовым Full Stack или Data Scientist, рано или поздно приходит к осознанию того, что есть определенные правила программирования и создания софта, которые сильно упрощают жизнь.
В этой статье я кратко опишу 10 принципов того, как стоит программировать промышленный machine learning, чтобы его можно было легко встроить в приложение/сервис, основываясь на методике 12-factor App, предложенной командой Heroku. Моя инициатива — это повысить узнаваемость этой методики, что может помочь многим разработчикам и людям из Data Science.
Эта статья — это пролог к серии статей про промышленный Machine Learning. В них я дальше буду рассказывать о том, как, собственно, сделать модель и запустить ее в продакшн, создать для нее API, а также примеры из различных областей и компаний, которые имеют встроенный ML в их системы.
Принцип 1. Одна кодовая база
Некоторые программисты на первых этапах из-за лени разобраться (или по каким-то своим соображениям) забывают про Git. Забывают либо от слова совсем, то есть кидают файлы друг другу в драйве/просто кидают текст/отправляют голубями, либо не продумывают свой workflow, и делают commit каждый в свою ветку, а потом и в мастера.
Этот принцип гласит: имейте одну кодовую базу и много развертываний.
Git можно использовать как в production, так и в research and development (R&D), в котором он используется не так часто.
Например, в R&D фазе Вы можете оставлять коммиты с разными методами обработки данных и моделями, для того, чтобы потом выбрать наилучший и легко продолжить работать с ним дальше.
Во-вторых, в продакшене это незаменимая вещь — Вам нужно будет постоянно смотреть на то, как изменяется Ваш код и знать, какая модель выдавала лучшие результаты, какой код работал в конце и что случилось, из-за чего он перестал работать или начал выдавать неверные результаты. Для этого и существуют коммиты!
А также Вы можете создать package своего проекта, разместив его, например, на Gemfury, и дальше просто импортируя функции из него для других проектов, чтобы не переписывать их 1000 раз, но об этом чуть позже.
Принцип 2. Четко объявляйте и изолируйте dependencies
В каждом проекте есть разные библиотеки, которые вы импортируете извне для того, чтобы где-то их применить. Будь то это библиотеки Python, или библиотеки других языков разных предназначений, или системные инструменты — Ваша задача:
- Четко объявить dependencies, то есть файл, в котором будут прописаны все библиотеки, инструменты, и их версии, которые используются в Вашем проекте и которые должны быть установлены (например, в Python это можно сделать с помощью Pipfile или requirements.txt. Ссылка, позволяющая хорошо разобраться: realpython.com/pipenv-guide)
- Изолировать dependencies специально для Вашей программы во время разработки. Вы же не хотите постоянно менять версии и переустанавливать, например, Tensorflow?
Таким образом, разработчики, которые будут присоединяться к Вашей команде в будущем, смогут быстро ознакомиться с библиотеками и их версиями, которые используются в Вашем проекте, а также у Вас будет возможность управлять версиями и самими библиотеками, установленными для определенного проекта, что поможет Вам избежать несовместимости библиотек или их версий.
Ваше приложение также не должно опираться на системные инструменты, которые могут быть установлены на определенной ОС. Эти инструменты нужно также объявлять в манифесте dependencies. Это нужно для того, чтобы избежать ситуаций, когда версия инструментов (а также их наличие), не совпадает с системными инструментами определенной ОС.
Таким образом, даже если практически на всех компьютерах можно использовать curl, то следует все равно объявить его в dependencies, так как при миграции на другую платформу его может не быть или версия будет не той, которая Вам изначально была нужна.
Например, Ваш requirements.txt может выглядеть вот так:
# Model Building Requirements
numpy>=1.18.1,<1.19.0
pandas>=0.25.3,<0.26.0
scikit-learn>=0.22.1,<0.23.0
joblib>=0.14.1,<0.15.0
# testing requirements
pytest>=5.3.2,<6.0.0
# packaging
setuptools>=41.4.0,<42.0.0
wheel>=0.33.6,<0.34.0
# fetching datasets
kaggle>=1.5.6,<1.6.0
Принцип 3. Конфигурации
Многие слышали об историях, когда разные ребята-девелоперы случайно загружали код на GitHub в открытые репозитории с паролями и другими ключами от AWS, просыпаясь на следующий день с задолженностью в 6000$, а то и со всеми 50000$.
![](https://habrastorage.org/webt/mz/w0/dd/mzw0ddkc86pwjm3h1pm_hbrp-nk.jpeg)
Конечно, эти случаи крайние, но очень показательные. Если Вы храните Ваши учетные данные или другие данные, нужные для конфигурации внутри кода — Вы совершаете ошибку, и я думаю, не стоит объяснять почему.
Альтернатива этому — это хранить конфигурации в переменных среды. Более подробно о переменных среды Вы можете прочитать здесь.
Примеры данных, которые обычно хранят в переменных среды:
- Имена доменов
- API URL/URI’s
- Публичные и приватные ключи
- Контакты (почта, телефоны и тд)
Таким образом, Вы можете не менять постоянно код, если Ваши переменные конфигурации будут меняться. Это поможет сохранить Вам время, силы и деньги.
Например, если вы используете Kaggle API для проведения тестов (например, скачиваете дотаяет и прогоняете через него модель, чтобы протестировать при запуске, что модель работает хорошо), то приватные ключи от Kaggle, такие как KAGGLE_USERNAME и KAGGLE_KEY надо хранить в переменных среды.
Принцип 4. Сторонние службы
Идея здесь — это создавать программу таким образом, чтобы не было различий между локальными и сторонними ресурсами в плане кода. Например, вы можете подключить как локальную MySQL, так и стороннюю. То же самое касается и различных API, таких как Google Maps или Twitter API.
Для того, чтобы отключить сторонний сервис или подключить другой, нужно всего лишь поменять ключи в конфигурации в переменных среды, о которой я рассказывал в пункте выше.
Так, например, вместо того, чтобы указывать каждый раз путь к файлам с датасетами внутри кода, лучше воспользоваться библиотекой pathlib и объявить путь к датасетам в config.py, чтобы не зависимо от того, каким сервисом Вы выпользуетесь (например, CircleCI), программа смогла узнать путь к датасетам с учетом строения новой файловой системы в новом сервисе.
Принцип 5. Сборка, релиз, runtime
Многим людям из Data Science полезно качать навыки по написанию софта. Если мы хотим, чтобы наша программа как можно реже «падала», и как можно дольше работала без сбоев, нам нужно разделить процесс релиза новой версии на 3 этапа:
- Этап сборки. Вы преобразуете Ваш голый код с отдельными ресурсами в так называемый пакет, который содержит весь необходимый код и данные. Этот пакет и называется сборкой.
- Этап релиза — к сборке здесь мы подключаем наш config, без которого мы бы не смогли выпустить нашу программу. Теперь это полностью готовый к запуску релиз.
- Далее идет этап выполнения. Здесь мы выпускаем приложение с помощью запуска необходимых процессов из нашего релиза.
Подобная система релиза новых версий модели или всего пайплайна позволяет разделять роли между администраторами и разработчиками, позволяет отслеживать версии и предотвращает нежелательные остановки работы программы.
Для задачи релиза создано множество различных сервисов, в которых Вы можете записать процессы для запуска сами в .yml файл (например, в CircleCI это config.yml для обеспечения самого процесса). Wheely отлично создает package для проектов.
Вы сможете создавать пакеты с различными версиями вашей machine learning модели, и после чего пакетировать их и отсылаться к нужным пакетам и их версиям, чтобы использовать оттуда функции, которые Вы написали. Это поможет Вам создать API для Вашей модели, а Ваш пакет можно разместить например на Gemfury.
Принцип 6. Запускаем Вашу модель как один или несколько процессов
Причем процессы не должны иметь разделяемых данных. То есть процессы должны существовать отдельно, а всевозможные данные — отдельно, например, на сторонних службах по типу MySQL или других, смотря на то, что Вам нужно.
То есть данные внутри файловой системы процесса хранить точно не стоит, иначе это может привести к очищению от этих данных при следующем релизе/изменении конфигураций или переноса системы, на которой работает программа.
Но есть исключение: для machine learning проектов можно хранить кэш библиотек, чтобы не переустанавливать их каждый раз при запуске новой версии, если дополнительных библиотек или каких-либо изменений в их версиях произведено не было. Таким образом, вы сократите время запуска вашей модели в промышленности.
Чтобы запустить модель как несколько процессов, можно создать .yml файл, в котором вы как раз и укажете необходимые процессы и их последовательность.
Принцип 7. Утилизируемость
Процессы, которые работают в вашем приложении с моделью, должны легко запускаться и останавливаться. Таким образом, это позволит быстро развертывать изменения кода, изменения конфигураций, быстро и гибко масштабироваться и предотвратит от возможных поломок рабочей версии.
То есть ваш процесс с моделью должен:
- Минимизировать время запуска. В идеале время запуска (от момента, когда была подана команда запуска до момента, когда процесс придет в рабочее состояние) должно иметь не более нескольких секунд. Кэширование библиотек, описанное выше — это одна из методик уменьшения времени запуска.
- Завершаться корректно. То есть фактически приостанавливается прослушивание порта сервиса, и новые запросы, поданные в этот порт, не будут обрабатываться. Здесь уже нужно либо настраивать неплохую связь с DevOps инженерами, или самому понимать, как это работает (желательно, конечно, второе, но связь поддерживать нужно всегда, в любом проекте!)
Принцип 8. Непрерывное развертывание/интеграция
Во многих компаниях используют разделение между командами разработки приложения и его развертывания (делающее приложение доступным для конечных пользователей). Это может сильно замедлять разработку софта и прогресса в его улучшении. А также это портит культуру DevOps, где разработка и интеграция, грубо говоря, объединены.
Поэтому этот принцип гласит о том, что среда Вашей разработки должна быть максимально приближена к среде Вашего продакшена.
Это позволит:
- Снизить время релиза в десятки раз
- Снизить количество ошибок из-за несовместимости кода.
- Также это позволяет снизить нагрузку на персонал, так как разработчики и люди, развертывающие приложение — теперь одна команда.
Инструменты, которые позволяют с этим работать — CircleCI, Travis CI, GitLab CI и другие.
Вы сможете быстро делать дополнения для модели, обновлять ее, и сразу же запускать, при этом будет легко, в случае сбоев, вернуться очень быстро к рабочей версии, так, чтобы конечный пользователь даже этого не заметил. Особенно это можно легко и быстро сделать, если у Вас есть хорошие тесты.
Минимизируйте различия!!!
Принцип 9. Ваши логи
Logs (или «Логи») — это записанные обычно в текстовом формате события, которые происходят внутри приложения (поток событий). Простой пример: «2020-02-02 — уровень системы — имя процесса». Они созданы для того, чтобы разработчик мог буквально видеть то, что происходит, когда программа работает. Он видит ход процессов, и понимает, таков ли он, как сам разработчик задумывал.
Этот принцип гласит о том, что не стоит хранить свои логи внутри вашей файловой системы — их нужно просто «выводить на экран», например делать это в стандартном выводе системы stdout. И за потоком таким образом можно будет следить в терминале во время разработки.
Значит ли это что не нужно сохранять логи совсем? Конечно нет. Просто этим не должно заниматься Ваше приложение — оставьте это сторонним сервисам. Ваше приложение может лишь перенаправить логи в определенный файл или терминал для просмотра в реальном времени или перенаправить его в систему для хранения данных общего назначения (например, Hadoop). Само Ваше приложение не должно хранить или взаимодействовать с логами.
Принцип 10. Тестируйте!
Для промышленного machine learning эта фаза крайне важна, так как Вам нужно понять, что модель работает правильно и выдает то, что Вы хотели.
Тесты можно создать с помощью pytest, и протестировать с помощью небольшого датасета, если у вас задача регрессии/классификации.
Не забывайте ставить одинаковый seed для deep learning моделей, чтобы они не выдавали постоянно разные результаты.
Это было краткое описание 10 принципов, и, конечно, сложно пользоваться ими не попробовав и не увидев то, как они работают, поэтому эта статья — это лишь пролог к серии интересных статей, в которые я буду раскрывать то, как создавать промышленные machine learning модели, как их интегрировать в системы, и как эти принципы могут упростить нам всем жизнь.
Я также постараюсь использовать крутые принципы, которые кто-то по желанию может оставить в комментариях.