Привет, друзья! Это Жека Никитин. Сегодня хочу с вами поделиться нашими практиками MLOPS – что по сути является модным словцом, а на самом деле есть ни что иное как жизненный цикл создания и функционирования ML-систем. 

Естественно, каждая модель и задача уникальна. Но в этой статье я постарался максимально разбить процесс развития ML-систем на основные этапы. Поговорим о том, какие требования мы предъявляем к этим этапам и какие инструменты при этом используем. Материал представляет собой текстовую версию доклада на LeanDS.

Какие этапы проходит ML-система за свою «жизнь»?

Сначала хотелось бы сделать небольшую ремарку. Несмотря на то, что мы рассматриваем общий ML-процесс, прошу учесть, что мы работаем с медицинскими изображениями: как 2D (рентген), так и 3D (КТ и МРТ исследования). Поэтому многие примеры реализации будут приводиться именно из этой области. Прошу отнестись с пониманием).

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

  1. Оценка осуществимости и значимости гипотезы.

  2. Сбор и очистка данных.

  3. Разметка данных.

  4. Обучение модели.

  5. Оценка качества и устойчивости модели.

  6. Развертывание в продакшн.

  7. Мониторинг.

При этом слово «цикл» я употребил не просто так: ведь данная последовательность этапов в абсолютном большинстве случаев не будет следовать один за другим линейно. Так, например, на этапе оценки качества и устойчивости модели мы можем сделать выводы о необходимости разметки определенного типа данных или же выдвинуть новую гипотезу по обучению нейронной сети с измененной архитектурой. А иногда на этом этапе вообще может быть принято решение о сворачивании гипотезы и переходе к концептуально другой.

Оценка осуществимости и значимости гипотезы

Прежде чем приступить к проекту, его необходимо оценить с разных сторон. Идеи могут рождаться как до начала работы, так и внутри процесса. При этом способность «убивать» идеи на начальном этапе очень важна – так как позволяет сэкономить много времени.

Если рассмотреть «нетехническую» составляющую, то сначала следует ответить на три основных вопроса:

  • Решает ли продукт важную и распространенную проблему?

  • Готова ли целевая аудитория продукта платить за решение этой проблемы?

  • Можем ли мы создать такой продукт в разумные сроки с адекватным уровнем затрат?

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

  • Доступность данных. Есть ли публичные данные? Возможно ли получать и размечать требуемые данные? Решались ли похожие задачи в похожих доменных областях?

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

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

Так может выглядеть реализация проверки гипотез внутри проекта.

Также стоит выделить подходы к сравнению идей внутри проекта - ICE/RICE, голосование, HiPPo.

Сбор и очистка данных

Допустим, мы отобрали гипотезы. Следующим (и одним из ключевых) этапом будет сбор и очистка данных. Почему он так важен и что мы хотим на нем получить?

Во-первых, мы хотим чтобы они надежно хранились без возможности несанкционированного доступа и искажения. Для нас выгрузка данных из медицинской организации – это не только сложный организационный, но и юридический вопрос. Поэтому утрата таких трудозатрат – просто непозволительная роскошь.

Во-вторых, мы хотим удобно и быстро их передавать (с централизованного хранилища на локальный компьютер ML-специалиста). В медицине трехмерные датасеты (например, компьютерные томографии) очень тяжеловесные, речь идет о терабайтах информации в одном датасете. Наверное, не очень прикольно, если данная процедура будет занимать по полдня, в течение которых инженер не сможет работать над задачей?)

В-третьих, необходимо иметь возможность версионировать данные: допустим, сначала мы размечали одни патологии на флюорограммах и сделали эту разметку в бибоксах, а на следующем этапе размечали масками и другие классы. Если не реализовать такую процедуру, то очень быстро начинается бардак. Рано или поздно захочется репродуцировать эксперимент – а окажется, что это физически невозможно ввиду отсутствия версионирования.

Ну и, наконец, в-четвертых, процедура должна быть в той или иной мере прозрачной, и мы в любой момент должны иметь возможность БЫСТРО ответить на простые вопросы из разряда: какие у нас есть данные, сколько их, из каких источников.

Если говорить об инструментах, то для хранения мы используем Yandex Object Storage, Azure Storagе. Среди плюсов: надежность, данные неограничены. Из минусов: трансфер данных и их хранение может влететь в копеечку. Есть также и локальные решения: Minio, SSH Fileserver. Они быстрые, бесплатные, но у них нет версионирования «из коробки», они не так надежны и ограничены локальным железом.

В нашем случае мы используем комбинацию решений, поскольку медицинские данные терять или оставлять незащищенными никак нельзя. При этом «тренируемся» мы на локальном сервере с GPU, базирующимся у нас в офисе.

Также медицинские данные подразумевают хранение их разметки. Тут также возможны вариации. 

  1. Разметку можно хранить в CSV/XML/JSON. В этом случае сама разметка будет очень легковесной. Однако сам такой формат будет не унифицирован, и сложно будет организовать автоматическую документацию этих данных.

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

Мы – безусловно, как и многие ML- проекты – сначала все хранили в первом формате. Потом начали активнее интегрировать БД для хранения датасетов. Сейчас же мы целиком переехали на базы данных. 

Как же быть с версионированием?

Казалось бы, самый простой ответ на этот вопрос – попросту на него забить и вообще не версионировать данные. С одной стороны, это просто: не нужно тратить дополнительное место. Но есть и очевидный минус: если вы не будете версионировать данные, то ваше сумасшествие – всего лишь вопрос времени. 

Приведем пример. Число данных и экспериментов разрастается. Кто-то из сотрудников докинул данных в валидационный сет. Метрики по эксперименту не бьются. Вы не понимаете, почему так происходит, перепроверяете – и, разумеется, не можете найти причину. Вуаля! Привет, 13 палата – приехали.

Второй подход – версионирование сплитов в репозиториях. Основное назначение подхода – обеспечение воспроизводимости экспериментов при неизменности самих данных.

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

Немного инструментов для работы с данными

EDA

Когда мы говорим про датасеты, так или иначе на память приходит EDA. При этом концептуальных инструментов для реализации exploration data analysis может быть два. Первый – простой и привычный Jupiter Notebook. Кто-то его ненавидит, кто-то очень любит. Я, например, очень люблю и считаю, что лучшего инструмента для  того, чтобы поиграть с данными, попрототипировать, просто не существует. 

Есть более продвинутый инструмент – Streamlit. Грубо говоря, он дает возможность создания быстрых веб-приложений и интерактивных отчетов. Безусловно, он уже более наглядный, там можно подвигать ползунки и понять как меняются наши данные.

Второй атрибут, который поможет вам не сойти с ума (или уже помогает) – это Dataset Card. Основное его назначение – документирование данных. Карточка датасета содержит важнейшую информацию о нем: что это за данные, как нами получены, какой объем и специфика, какая у него лицензия и т.п.

Помимо решения задачи прямого документирования, карточки датасетов помогают быстрее проходить процесс онбординга новичкам в отделе, помогает ML-отделу и прочим связанным отделам принимать решения относительно данных и вектора их развития. 

Реализация dataset Card также может быть различной: это может быть Google-документ, Markdown или даже Streamlit приложение, которое в онлайн-режиме выгружает из той же базы данных информацию.

Разметка данных

Разметка данных  – один из важнейших этапов. Но стоит оговориться: не во всех задачах он нужен и не во всех целесообразен. Учитывая нашу специфику и необходимость показывать врачам-рентгенологам контуры патологических объектов на исследованиях, качественная разметка очень важна.

Качественная разметка – это какая?

В нашем понимании – это та, которая максимально приближена к наибольшей точности выделения истинных объектов, которая производится с максимально возможной скоростью и за минимальные деньги.

Для организации процедуры максимизации качества мы реализуем процесс кросс-разметки и процедуру отбора врачей-разметчиков, их дальнейшего обучения и итогового тестирования.

Для максимизации скорости мы стараемся влиять на процесс изнутри, определяя наиболее эффективные и интуитивные инструменты разметки (в нашем случае supervisely). В задачах, где достаточно размечать «коробками», может быть вполне достаточно и бесплатных инструментов – таких как labellmg

Также на эффективность разметки влияет и само оборудование (монитор, рабочая станция врача, на которой эта разметка проводится). Свою лепту вносит и инструментарий псевдоразметки и авторазметки (auto annotation tools). Эта группа методов позволяет отдавать разметчикам в работу не рандомные исследования, а те, разметка которых предположительно принесет нам максимальную ценность с точки зрения роста целевых метрик.

На снижение стоимости процесса также влияют процедуры активного обучения, интегрированные в модель.

Обучение модели

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

  • Воспроизводимость экспериментов.

  • Удобство сравнения экспериментов.

  • Скорость тестирования гипотез.

При этом на последний пункт можно вынести основной фокус – так как это процесс, который не имеет смыслового конца. Чем быстрее итерируемость и проверка гипотез в команде – тем выше эффективность.

Инструменты

Если говорить про DL-фреймворки, то их можно разделить на условные три класса: 

  1. Голые фреймворки – PyTorch, TensorFlow. Которые, с одной стороны, будут весьма гибкими, а с другой – обеспечивать дублирование кода и лишней работы.

  2. High-level фреймворки – Lightning, Catalyst. Тут ситуация обратная: меньше дублирования кода и меньше гибкости.

  3. Самописные. Гибкие, но их очень тяжело и дорого поддерживать.

Тут мы прошли весь путь, однако пришли к первому пункту, решая вопрос дублирования кода вынесением его элементов в библиотеки – и не очень паримся по поводу того, что CTRL+С и CTRL+V происходит время от времени.

Если говорить про трекинг экспериментов, то реализовать данный процесс, безусловно, можно в Excel таблице. 

Неплохо для начала, но хочется же чего-то большего. Тогда на помощь могут прийти специализированные платформы – такие как ClearML, Neptune, MLFlow. Для себя мы остановили выбор на ClearML. В нем хорошо реализован функционал трекинга экспериментов, есть весьма быстрая поддержка пользователей. Ну и самый главный плюс: система OpenSource полностью бесплатна.

(компоненты ClearM – Код DS-специалиста, Серверная часть, Агент (GPU))

Что нам дает такая автоматизация?

  • Трекинг – код (commit id + git diff), гиперпараметры, входные модели, данные; 

  • Логирование – все артефакты (модели, картинки и т.д.), метрики, потребление ресурсов, аутпут питона;

  • Web UI –можно ставить и трекать эксперименты откуда угодно;

  • Оркестрирование – автоматический старт экспериментов внутри докер-контейнеров;

  • Пайплайны – объединение экспериментов в связанные цепочки;

  • Интерактивные сессии –Jupyter-ноутбуки на нашем железе с любого компьютера с доступом в интернет.

Дашборд

Графики сравнения экспериментов в ClearML

Ну и, конечно, по готовности экспериментов нам необходимо их оценивать – что также является частью оптимизационного процесса.

Инструменты для оценки экспериментов

В отличие от предыдущих технических инструментов, для оценки внедряются, как правило, процессные инструменты. Среди них стоит выделить процедуры:

  • Code Review (на этапе мерджа в мастер или релизную ветку проекта).

  • Experiment Review.

Карточка эксперимента со ссылкой на ClearML, включающая основные метрики и выводы. Также может быть реализовано в формате поста в вашей рабочей группе в Telegram или Slack.

  • Findings

Оценка качества и устойчивости модели.

Итак, на данном этапе мы обучили модель, выбрали лучший эксперимент. Но перед тем, как выводить новую версию клиентам, мы хотим убедиться, что у нас все хорошо, корректно и устойчиво работает.

Что на этом этапе для нас важно?

  • Качественная работа системы – отсутствие багов и адекватные предсказания.

  • Скорость и надежность работы под нагрузкой.

  • Отсутствие критических ошибок с точки зрения пользователя.

  • Гарантия рабочего и качественного тренировочного кода.

Какие инструменты способны нам обеспечить реализацию таких целей?Как несложно догадаться, это тесты. Но тесты бывают разные. Самые лучшие – автоматические тесты. По направлениям их можно разбить следующим образом:

  • Интеграционные тесты – проверяют работоспособность приложения в целом.

  • Метрик-тесты – ML-метрики.

  • Стиль-тесты – соответствие кода стандартам.

  • Тесты на воспроизводимость обучения.

  • Стресс-тесты – надежность работы под нагрузкой.

  • Юнит-тесты – тесты отдельных частей приложения.

Среди инструментов реализации тестов можно выделить: Docker Compose,  PyTest и Black.

Но не стоит забывать что тесты могут быть и ручными – особенно когда мы говорим про такую сферу деятельности как медицина. 

В первую очередь нам важно визуальное тестирование предсказаний (причем как сотрудниками, так и фокус-группой врачей). Также нам важна корректная работа UI приложения и корректная работа на проблемных случаях (на которых сталкивалась с проблемами более ранняя версия системы). Ну и, безусловно, корректность с работой out-of distribution случаев.

Если ваша система детектирует рак на канистре, сканированной в рентген-аппарате, едва ли врач – пользователь системы – будет этому рад. А ведь такие рентгенограммы не редкость на этапе  настройки нового оборудования в клиническом учреждении.

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

Развертывание в продакшн

Далеко не все ML-системы доживают до этого этапа, но если вам повезло и все предыдущие этапы пройдены, то здесь тоже нужно уделить внимание деталям.

На этапе развертывания нам важно:

  • Удобно запускать процесс. Желательно одной кнопкой

  • Иметь возможность быстро «откатиться» до предыдущей версии

  • Быстро и легко масштабировать разные части приложения, чтобы подстраиваться под нагрузку

  • Менять конфигурацию

Если рассмотреть схематично, то процесс можно проиллюстрировать следующими этапами. Клиент посылает запрос на API, реализованное с помощью Fast API или Flask, далее задача попадает в очередь на обработку – с помощью, например, Salary. Далее запускается ее обработка. Очередь реализована в Redis, где нейронка уже бачами забирает на обработку предобработанные картинки, после чего выдается результат.

Инструменты для деплоя

При выборе инструментов на этом этапе также существует высокая вариативность – в зависимости от ваших целей и задач.

В нашем случае это:

  • Docker, который служит для контейнеризации всех сервисов;

  • Kubernetes & Helm – легкость скейлинга, конфигурации и взаимодействия сервисов.

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

Мониторинг

Когда мы осуществили сборку и релиз, нам нужно быть уверенными, что в один прекрасный момент это все не обвалится. И что если обвалится – мы сможем точно понять, когда это произошло и из-за чего.

Среди основных требований к мониторингу:

  • Удобство;

  • Скорость реакции;

  • Низкое число требований к человеческому ресурсу.

Инструменты:

  • Sentry 

  • Slack

  • Zabbix

  • Кастомные дашборды и отчеты

  • Kubernetes Dashboard

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

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

  1. Отчеты об обработке исследований – 3 раза в день: утром, днем и вечером. В них мы видим число успешно обработанных исследований и обработанных с ошибками. Также в них отражается количество запросов, гистограмма предсказаний и другие статистики.

  2. В дашбордах отражаются сами запросы и визуализация предсказаний. В нашем случае – маски на исследованиях с контурами тех или иных объектов (например, рак).

  3. Кастомные уведомления, включающие, например, резкое изменение входных данных, переменных или же предсказаний нейронной сети.

Заключение и рекомендации.

Несмотря на то, что процесс MLOPS весьма многогранен и вариативен, не стоит внедрять сразу кучу новых инструментов и практик. Лучше делать это по мере необходимости. Целесообразно постоянно изучать чужие решения и практики, экспериментировать – но не заниматься слепым копированием (особенно если у вас специфический продукт).

Вам часто придется сталкиваться с тем, что готовых решений «из коробки» для вас не будет. Придется использовать несколько инструментов – готовые и самописные. Это нормально. 

Старайтесь максимально автоматизировать все, что съедает время, но не перебарщивайте с алертами. Иначе можно сойти с ума.  

Если вы хотите узнать ещё больше об организации процессов ML-разработки, подписывайтесь на наш Телеграм-канал Варим ML

Желаю крутых сеточек и драйвовых проектов!

Жека Никитин.