Всем привет! Я CV-разработчик в КРОК. Уже 3 года мы реализуем проекты в области CV. За это время чего мы только не делали, например: мониторили водителей, чтобы во время движения они не пили, не курили, по телефону не разговаривали, смотрели на дорогу, а не сны или в облака; фиксировали любителей ездить по выделенным полосам и занимать несколько мест на парковке; следили за тем, чтобы работники носили каски, перчатки и т.п.; идентифицировали сотрудника, который хочет пройти на объект; подсчитывали всё, что только можно.
Я все это к чему?
В процессе реализации проектов мы набили шишки, много шишек, с частью проблем вы или знакомы, или познакомитесь в будущем.
Моделируем ситуацию
Представим, что мы устроились в молодую компанию “N”, деятельность которой связана с ML. Работаем мы над ML (DL, CV) проектом, потом по каким-либо причинам переключаемся на другую работу, в общем делаем перерыв, и возвращаемся к своей или чужой нейроночке.
- Наступает момент истины, нужно как-то вспомнить на чем ты остановился, какие гиперпараметры пробовал и, самое главное, к каким результатам они привели. Может быть множество вариантов, кто как хранил информацию по всем запускам: в голове, конфигах, блокноте, в рабочей среде в облаке. Мне довелось видеть вариант, когда гиперпараметры хранились в виде закомментированных строк в коде, в общем полет фантазии. А теперь представьте, что вы вернулись не к своему проекту, а к проекту человека, который покинул компанию и в наследство вам достался код и модель под названием model_1.pb. Для полноты картины и передачи всей боли, представим, что вы еще и начинающий специалист.
- Идем дальше. Для запуска кода нам и всем кто будет с ним работать необходимо создать окружение. Часто бывает, что и его нам в наследство также по каким-то причинам не оставили. Это тоже может стать нетривиальной задачей. На этот шаг не хочется тратить время, не так ли?
- Тренируем модель (например, детектор автомобилей). Доходим до момента, когда она становится очень даже ничего — самое время сохранить результат. Назовем ее car_detection_v1.pb. Потом тренируем еще одну — car_detection_v2.pb. Некоторое время спустя наши коллеги или мы сами обучаем ещё и ещё, используя различные архитектуры. В итоге формируется куча артефактов, информацию о которых нужно кропотливо собирать (но, делать мы это будем позже, у нас ведь пока есть более приоритетные дела).
- Ну вот и всё! У нас есть модель! Мы можем приступать к обучению следующей модели, к разработке архитектуры для решения новой задачи или можем пойти попить чай? А деплоить кто будет?
Выявляем проблемы
Работа над проектом или продуктом — это труд многих человек. А с течением времени люди уходят и приходят, проектов становится больше, сами проекты становятся сложнее. Так или иначе, ситуации из описанного выше цикла (и не только) в тех или иных комбинациях будут встречаться от итерации к итерации. Все это выливается в трату времени, путаницу, нервы, возможно — в недовольство заказчика, и в конечном счете — в упущенные деньги. Хоть все мы обычно ходим по старым граблям, но полагаю, что никто при этом не хочет раз за разом переживать эти моменты.
Итак, мы прошли по одному циклу разработки и видим, что есть проблемы, которые необходимо решить. Для этого нужно:
- удобно хранить результаты работы;
- сделать простым процесс вовлечения новых сотрудников;
- упростить процесс развертывания среды разработки;
- настроить процесс версионирования моделей;
- иметь удобный способ валидации моделей;
- найти инструмент управления состоянием моделей;
- найти способ доставки моделей в production.
Видимо необходимо придумать workflow, который бы позволял легко и удобно управлять этим жизненным циклом? У такой практики есть название MLOps
MLOps, или DevOps для машинного обучения, позволяет командам специалистов по обработке и анализу данных и ИТ-специалистов сотрудничать, а также увеличивать темпы разработки и развертывания моделей с помощью мониторинга, проверки и системы управления для моделей машинного обучения.
Можете почитать, что обо всем этом думают ребята из Google. Из статьи понятно, что MLOps, довольно, объемная штука.
Далее в своей статье я опишу лишь часть процесса. Для реализации я воспользуюсь инструментом MLflow, т.к. это open-source проект, для подключения необходимо небольшое количество кода и есть интеграция с популярными ml-фреймворками. Вы можете поискать на просторах интернета другие инструменты, например Kubeflow, SageMaker, Trains и т.д., и возможно, подобрать тот, который лучше подходит под ваши нужды.
"Cтроим" MLOps на примере использования инструмента MLFlow
MLFlow — это платформа с открытым исходным кодом для управления жизненным циклом ml моделей (https://mlflow.org/).
MLflow включает четыре компонента:
- MLflow Tracking — закрывает вопросы фиксации результатов и параметров, которые к этому результату привели;
- MLflow Project — позволяет упаковывать код и воспроизвести его на любой платформе;
- MLflow Models — отвечает за деплой моделей в прод;
- MLflow Registry — позволяет хранить модели и управлять их состоянием в централизованном хранилище.
MLflow оперирует двумя сущностями:
- запуск — это полный цикл обучения, параметры и метрики по которым мы хотим регистрировать;
- эксперимент — это “тема” которой объединены запуски.
Все шаги примера реализованы на операционной системе Ubuntu 18.04.
1. Разворачиваем сервер
Для того, чтобы мы могли легко управлять нашим проектом и получать всю необходимую информацию, развернем сервер. MLflow tracking server имеет два основных компонента:
- backend store — отвечает за хранение информации о зарегистрированных моделях (поддерживает 4 СУБД: mysql, mssql, sqlite, and postgresql);
- artifact store — отвечает за хранение артефактов (поддерживает 7 вариантов хранения: Amazon S3, Azure Blob Storage, Google Cloud Storage, FTP server, SFTP Server, NFS, HDFS).
В качестве artifact store для простоты возьмем sftp сервер.
- создаем группу
$ sudo groupadd sftpg
- добавляем пользователя и устанавливаем ему пароль
$ sudo useradd -g sftpg mlflowsftp $ sudo passwd mlflowsftp
- корректируем пару настроек доступа
$ sudo mkdir -p /data/mlflowsftp/upload $ sudo chown -R root.sftpg /data/mlflowsftp $ sudo chown -R mlflowsftp.sftpg /data/mlflowsftp/upload
- добавляем несколько строк в /etc/ssh/sshd_config
Match Group sftpg ChrootDirectory /data/%u ForceCommand internal-sftp
- перезапускаем службу
$ sudo systemctl restart sshd
В качестве backend store возьмем postgresql.
$ sudo apt update
$ sudo apt-get install -y postgresql postgresql-contrib postgresql-server-dev-all
$ sudo apt install gcc
$ pip install psycopg2
$ sudo -u postgres -i
# Create new user: mlflow_user
[postgres@user_name~]$ createuser --interactive -P
Enter name of role to add: mlflow_user
Enter password for new role: mlflow
Enter it again: mlflow
Shall the new role be a superuser? (y/n) n
Shall the new role be allowed to create databases? (y/n) n
Shall the new role be allowed to create more new roles? (y/n) n
# Create database mlflow_bd owned by mlflow_user
$ createdb -O mlflow_user mlflow_db
Для запуска сервера необходимо установить следующие python пакеты (советую создать отдельное виртуальное окружение):
pip install mlflow
pip install pysftp
Запускаем наш сервер
$ mlflow server
--backend-store-uri postgresql://mlflow_user:mlflow@localhost/mlflow_db
--default-artifact-root sftp://mlflowsftp:mlflow@sftp_host/upload
--host server_host
--port server_port
2. Добавляем трекинг
Для того, чтобы результаты наших тренировок не пропали, будущие поколения разработчиков понимали, что вообще происходило, а старшие товарищи и вы могли спокойно анализировать процесс обучения, нам необходимо добавить трекинг. Под трекингом подразумевается сохранение параметров, метрик, артефактов и любой дополнительной информации о запуске обучения, в нашем случае, на сервере.
Для примера я создал небольшой проект на github на Keras по сегментации всего, что есть в COCO датасете. Для добавления трекинга я создал файл mlflow_training.py.
Вот строки, в которых происходит самое интересное:
def run(self, epochs, lr, experiment_name):
# getting the id of the experiment, creating an experiment in its absence
remote_experiment_id = self.remote_server.get_experiment_id(name=experiment_name)
# creating a "run" and getting its id
remote_run_id = self.remote_server.get_run_id(remote_experiment_id)
# indicate that we want to save the results on a remote server
mlflow.set_tracking_uri(self.tracking_uri)
mlflow.set_experiment(experiment_name)
with mlflow.start_run(run_id=remote_run_id, nested=False):
mlflow.keras.autolog()
self.train_pipeline.train(lr=lr, epochs=epochs)
try:
self.log_tags_and_params(remote_run_id)
except mlflow.exceptions.RestException as e:
print(e)
Здесь self.remote_server это небольшая обвязка над методами mlflow.tracking. MlflowClient (я сделал для удобства), с помощью которых я создаю эксперимент и запуск на сервере. Далее указываю куда должны сливаться результаты запуска (mlflow.set_tracking_uri(self.tracking_uri)). Подключаю автоматическое логирование mlflow.keras.autolog(). На данный момент MLflow Tracking поддерживает автоматическое логирование для TensorFlow, Keras, Gluon XGBoost, LightGBM, Spark. Если вы не нашли своего фреймворка или библиотеки, то вы всегда можете логировать в явном виде. Запускаем обучение. Регистрируем теги и входные параметры на удаленном сервере.
Пара строк и вы, как и все желающие, имеете доступ к информации о всех запусках. Круто?
3. Оформляем проект
Теперь сделаем так, чтобы запустить проект было проще простого. Для этого добавим в корень проекта файл MLproject и conda.yaml.
MLproject
name: flow_segmentation
conda_env: conda.yaml
entry_points:
main:
parameters:
categories: {help: 'list of categories from coco dataset'}
epochs: {type: int, help: 'number of epochs in training'}
lr: {type: float, default: 0.001, help: 'learning rate'}
batch_size: {type: int, default: 8}
model_name: {type: str, default: 'Unet', help: 'Unet, PSPNet, Linknet, FPN'}
backbone_name: {type: str, default: 'resnet18', help: 'exampe resnet18, resnet50, mobilenetv2 ...'}
tracking_uri: {type: str, help: 'the server address'}
experiment_name: {type: str, default: 'My_experiment', help: 'remote and local experiment name'}
command: "python mlflow_training.py
--epochs={epochs}
--categories={categories}
--lr={lr}
--tracking_uri={tracking_uri}
--model_name={model_name}
--backbone_name={backbone_name}
--batch_size={batch_size}
--experiment_name={experiment_name}"
MLflow Project имеет несколько свойств:
- Name — имя вашего проекта;
- Environment — в моем случае conda_env указывает на то, что для запуска используется Anaconda и описание зависимостей находится в файле conda.yaml;
- Entry Points — указывает какие файлы и с какими параметрами мы можем запустить (все параметры при запуске обучения автоматически логируются)
conda.yaml
name: flow_segmentation
channels:
- defaults
- anaconda
dependencies:
- python==3.7
- pip:
- mlflow==1.8.0
- pysftp==0.2.9
- Cython==0.29.19
- numpy==1.18.4
- pycocotools==2.0.0
- requests==2.23.0
- matplotlib==3.2.1
- segmentation-models==1.0.1
- Keras==2.3.1
- imgaug==0.4.0
- tqdm==4.46.0
- tensorflow-gpu==1.14.0
В качестве среды исполнения вы можете использовать docker, за более подробной информацией обратитесь к документации.
4. Запускаем обучение
Клонируем проект и переходим в директорию проекта:
git clone https://github.com/simbakot/mlflow_example.git
cd mlflow_example/
Для запуска вам необходимо установить библиотеки
pip install mlflow
pip install pysftp
Т.к. в примере я использую conda_env на вашем компьютере должна быть установлена Anaconda (но и это можно обойти установив все необходимые пакеты самостоятельно и поигравшись с параметрами запуска).
Все подготовительные шаги закончены и мы можем приступить к запуску обучения. Из корня проекта:
$ mlflow run -P epochs=10 -P categories=cat,dog -P tracking_uri=http://server_host:server_port .
После ввода команды автоматически создастся conda окружение и запустится тренировка.
В примере выше я передал количество эпох для обучения, категории на которые мы хотим сегментировать (полный список можно посмотреть здесь) и адрес нашего удаленного сервера.
Полный список возможных параметров можно посмотреть в файле MLproject.
5. Оцениваем результаты обучения
После окончания обучения мы можем перейти в браузере по адресу нашего сервера http://server_host:server_port
Здесь мы видим список всех экспериментов (слева вверху), а также информацию по запускам (посередине). Мы можем посмотреть более подробную информацию (параметры, метрики, артефакты и какую-то доп. информацию) по каждому запуску.
По каждой метрике мы можем наблюдать историю изменения
Т.е. на данный момент мы можем анализировать результаты в "ручном" режиме, также вы можете настроить и автоматическую валидацию при помощи MLflow API.
6. Регистрируем модель
После того как мы проанализировали нашу модель и решили, что она готова к бою, приступаем к ее регистрации для этого выбираем нужный нам запуск (как показано в предыдущем пункте) и идем вниз.
После того как мы дали имя нашей модели, у нее появляется версия. При сохранении другой модели с этим же именем, версия автоматически повысится.
Для каждой модели мы можем добавить описание и выбрать одно из трех состояний (Staging, Production, Archived), впоследствии мы при помощи api можем обращаться к этим состояниям, что на ряду с версионированием дает дополнительную гибкость.
У нас также имеется удобный доступ ко всем моделям
и их версиям
Как и в предыдущем пункте все операции можно сделать при помощи API.
7. Деплоим модель
На данном этапе у нас уже есть натренированная (keras) модель. Пример, как можно её использовать:
class SegmentationModel:
def __init__(self, tracking_uri, model_name):
self.registry = RemoteRegistry(tracking_uri=tracking_uri)
self.model_name = model_name
self.model = self.build_model(model_name)
def get_latest_model(self, model_name):
registered_models = self.registry.get_registered_model(model_name)
last_model = self.registry.get_last_model(registered_models)
local_path = self.registry.download_artifact(last_model.run_id, 'model', './')
return local_path
def build_model(self, model_name):
local_path = self.get_latest_model(model_name)
return mlflow.keras.load_model(local_path)
def predict(self, image):
image = self.preprocess(image)
result = self.model.predict(image)
return self.postprocess(result)
def preprocess(self, image):
image = cv2.resize(image, (256, 256))
image = image / 255.
image = np.expand_dims(image, 0)
return image
def postprocess(self, result):
return result
Здесь self.registry это опять небольшая обвязка над mlflow.tracking.MlflowClient, для удобства. Суть в том, что я обращаюсь к удаленному серверу и ищу там модель с указанным именем, причем, самую последнюю production версию. Далее скачиваю артефакт локально в папку ./model и собираю модель из этой директории mlflow.keras.load_model(local_path). Всё теперь мы можем использовать нашу модель. CV (ML) разработчики могут спокойно заниматься улучшением модели и публиковать новые версии.
В заключение
Я представил систему которая позволяет:
- централизованно хранить информацию о ML моделях, ходе и результатах обучения;
- быстро развертывать среду разработки;
- следить и анализировать ход работы над моделями;
- удобно вести версионирование и управлять состоянием моделей;
- легко деплоить полученные модели.
Данный пример является игрушечным и служит точкой старта для выстраивания вашей собственной системы, которая, возможно, будет включать в себя автоматизацию оценки результатов и регистрации моделей (п.5 и п.6 соответственно) или вы добавите версионирование датасетов, или может ещё что-то? Я пытался донести мысль, что вам нужен MLOps в целом, MLflow лишь средство достижения цели.
Напишите какие проблемы, с которыми вы сталкивались, я не отобразил?
Что бы вы добавили в систему, чтобы она закрывала ваши потребности?
Какие инструменты и подходы используете вы, чтобы закрыть все или часть проблем?
P.S. Оставлю пару ссылок:
github проект — https://github.com/simbakot/mlflow_example
MLflow — https://mlflow.org/
Моя рабочая почта, для вопросов — ikryakin@croc.ru
У нас в компании периодически проводятся различные мероприятия для ИТ-специалистов, например: 8-го июля в 19:00 по МСК будет проходить митап по CV в онлайн-формате, если интересно, то можете принять участие, регистрация здесь .
Автор: Илья Крякин