Есть два подхода к работе с машинным обучением (Machine Learning, ML): быть человеком-оркестром и задействовать «зоопарк технологий» для каждого этапа, или работать с готовым набором инфраструктурных решений, который позволяет выстроить MLOps-конвейер в рамках одной платформы. Для реализации первого подхода нужны senior-специалисты и большие ресурсы, для второго достаточно найти сервис с нужным набором возможностей.
Меня зовут Станислав Кипрюшин, я ведущий программист в VK Cloud. В этой статье на примере Cloud ML Platform мы разберём, как создать MLOps-конвейер для обучения моделей и построения сервиса распознавания лиц.
Выбор подхода и задачи
Удивить кого-либо классическими свёрточными моделями вида модифицированного перцептрона сложно. Но при правильно заданных условиях можно и с такой архитектурой получить удовлетворительные результаты. При этом надо учитывать требования к работе с данными для простых ML-моделей:
- датасет должен быть подготовлен и иметь минимальное количество выбросов;
- входные данные для датасета должны быть предварительно обработаны;
- выходные данные для прикладного использования должны проходить постобработку.
Следование этим рекомендациям снижает требования к подготовке всего ML-пайплайна и позволяет улучшить показатели модели без избыточного усложнения её архитектуры.
В качестве прикладной задачи рассмотрим построение сервиса на основе компьютерного зрения (Computer Vision, CV) с применением Cloud ML Platform. Для наглядности выберем классический алгоритм работы CV-решения. Сервис будет получать на вход изображения, а возвращать информацию обо всех распознанных в кадре людях, указывая при этом процент схожести реальных лиц и их изображений в базе данных. Принцип универсален, поэтому такой сервис вполне можно использовать в большинстве сценариев, подразумевающих распознавание лиц.
Перед обучением модели и построением сервиса надо решить три основных вопроса:
- выбрать основной конвейер инференса модели и способ её хранения;
- выбрать, на каких данных обучать ML-модель и в каком формате получать выходные данные;
- определить архитектуру модели, в том числе функцию потерь.
Выбор конвейера и способа хранения
В качестве конвейера для обучения используем PaaS-сервис Cloud ML Platform, в рамках которого можно развернуть JupyterHub и MLflow. Инстанс JupyterHub используем для обучения модели, а связанный с ним MLflow — для хранения модели, её гиперпараметров и всех артефактов: датасета, изображения графиков обучения, логов обучения.
Примечательно, что Cloud ML Platform позволяет развернуть сохранённую MLflow-модель в виде REST-сервиса и проводить её инференс удалённо, используя набор базовых команд.
Таким образом, используя Cloud ML Platform, мы закрываем все задачи конвейера:
- обучаем модель в JupyterHub;
- сохраняем её в MLflow и разворачиваем как отдельный инференс-сервис;
- выносим обращение к сервису в качестве клиента в отдельный сервис, который сравнивает каждое распознанное лицо с базой данных лиц.
Выбор модели и её обучение
В качестве модели выбрали простую свёрточную нейронную сеть, состоящую из 11 слоёв. Они построены на основе:
- 7 свёрточных слоёв;
- 1 линейного выходных слоя;
- 1 слоя dropout для избежания переобучения.
Выбор такой композиции слоёв обусловлен предыдущим опытом работы с компьютерным зрением. В качестве функции активации применяем relu.
Далее нужно было научить модель выделять признаки, то есть векторизировать изображение лица в числовое представление. Это позволяло в последующем интерпретировать лицо как вектор в многомерном пространстве признаков и сравнивать векторы любой удобной метрикой. Благодаря этому можно научить ML-модель определять похожесть двух лиц на изображении.
Для тренировки ML-модели выбрали функцию потерь triplet loss, с помощью которой можно сравнивать текущее изображение (anchor) с изображением лица того же человека (positive) и изображением лица другого человека (negative). Это самый простой и действенный вариант для выделения признаков из изображений лиц.
С выбором оптимизатора в рамках реализации модели для сервиса распознавания лиц тоже всё довольно очевидно: взяли стохастический градиентный спуск. Изображения — более тяжёлый источник информации, чем текст или таблицы с численными значениями. Поэтому обычный градиентный спуск отрабатывает их гораздо медленнее. Для небольших проектов, где скорость не критична, этот фактор иногда игнорируют, но лучше всё же его учесть.
Изначально в качестве основного датасета для ML-модели мы выбрали Digi-Face 1M, но уже после первой итерации столкнулись с проблемой плохой сходимости. Это обусловлено тем, что в датасете много лиц, похожих друг на друга.
В результате мы решили заменить датасет на LWF, который сформирован на основе открытых источников (интернета) и содержит 13 233 лица. Одно из основных его преимуществ — большое цветовое разнообразие изображений, которое позволяет модели лучше работать с реальными данными.
Пример изображений, формирующих датасет LWF
Следующий этап — запустить обучение модели. Чтобы получить объективные результаты и сравнить скорость, мы решили обучать модель как на GPU, так и на CPU. Интервал определили на уровне 30 эпох. Такая продолжительность оказалась необходимостью: на меньших циклах обучения мы получили низкую сходимость, что недопустимо в рамках реализации сервиса. Обучение на CPU заняло около 10 часов, тогда как обучение на GPU — всего 20–30 минут.
В итоге модель вместе с обученными весами сохранили в MLflow, а затем подняли в качестве REST-сервиса с помощью того же MLflow. Сохранение мы реализовали с помощью следующего фрагмента Jupyter-ноутбука:
import mlflow
import mlflow.pytorch
mlflow.pytorch.autolog(log_every_n_epoch=3, log_models=True)
mlflow.set_experiment("Face_Recog")
mlflow.start_run()
train_paths = [os.path.abspath("./lfw_train")]
test_paths = [os.path.abspath("./lfw_test")]
trainer = TripletLossTrainer(train_paths, test_paths, 16, epochs=30)
model = FaceFeatures()
loss_func = torch.nn.TripletMarginLoss(margin=1.0, p=2)
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
trainer.assign_model(model)
trainer.assign_loss_func(loss_func)
trainer.assign_optimizer(optimizer)
trainer.prepare()
trainer.fit()
trainer.save_model_weights("new_model")
mlflow.pytorch.log_model(trainer.model, "model", registered_model_name="Face_Recog")
mlflow.end_run()
Примечательно, что мы предусмотрели автоматическое логирование модели PyTorch в MLflow с помощью команды
mlflow.pytorch.autolog(lon_every_n_epoch=3, log_models=True)
. Результат выглядит так:Для развёртывания inference server полученной модели можно использовать следующий код:
from mlflow.tracking import MlflowClient
cli = MlflowClient()
# Так можно получить список всех моделей и вывести их с сортировкой по имени
results = cli.search_registered_models(order_by=["name ASC"])
print("-" * 80)
for res in results:
for mv in res.latest_versions:
print("name={}; run_id={}; version={}; source={};".format(mv.name, mv.run_id, mv.version, mv.source))
model_source_uri = "mlflow-artifacts:/5/728aed0e817b4b1e955151dc56355a08/artifacts/model"
# Создаём Client из Deployments-модуля MLflow для работы в VK Cloud ML Platform
from mlflow.deployments import get_deploy_client
client = get_deploy_client('vk-cloud-mlplatform')
# endpoint в терминологии VK Cloud MLflow Deploy — это деплой сервер.
# Отдельная машина, на которой разворачивается контейнер с моделью
# client.create_endpoint(name, performance="low", disk_size=50, disk_type="ceph-ssd", av_zone=None)
# Выше перечислен полный список параметров. Создать Deploy сервер можно и с указанием только имени сервера.
# av_zone в этом случае будет взята аналогичная зоне, в которой расположен связанный MLflow-сервис
deploy_server_name = "deploy_server"
client.create_endpoint(name=deploy_server_name)
auth_value = "user:Password!123$"
auth_deployment_name = "face-7"
client.create_deployment(deploy_server_name, auth_deployment_name, model_source_uri, auth=auth_value)
Разработка сервиса распознавания лиц
Решение для распознавания лиц мы реализовали в виде отдельного REST-сервиса с тремя основными функциями: добавление и удаление лиц из внутренней БД, сравнение входного изображения с лицами в БД. На этом этапе надо было решить несколько вопросов и определить:
- как сравнивать лица между собой эффективно;
- как выполнять быстрый поиск лица в базе;
- как повысить точность сравнения для лиц, смотрящих не под прямым углом в камеру.
Эффективное сравнение лиц между собой
Для решения первого вопроса достаточно в качестве метрики выбрать косинусное расстояние. Эта метрика может сравнивать векторы лиц без нормализации, экономя вычислительные ресурсы, и при этом выдавать требуемый результат.
Быстрый поиск лиц в базе
Поиск ответа на второй вопрос оказался сложнее. Классический алгоритм поиска для такой задачи не подходит: поскольку лица поступают в базу в произвольном порядке, нельзя гарантировать, что все изображения одного человека будут размещены рядом, как в стандартной реляционной БД. То есть сложность задачи будет О(n). Соответственно, чем больше лиц в базе, тем выше сложность и ниже скорость поиска.
Альтернативой являются численные методы, которые позволяют построить упорядоченный направленный граф (поиск ближайшего соседа). Этот алгоритм реализует, например, библиотека hnswlib. Она позволяет построить граф необходимой размерности и разместить в нём векторизированные признаки для дальнейшего поиска ближайшего соседа. Это помогает снизить сложность поиска до O(nlog(n)).
Повышение точности сравнения для лиц, смотрящих не под прямым углом
Решение третьего вопроса заключается в сравнении лиц одного и того же человека с эталоном в базе. Например, человек сделал фотографию, смотря прямо в камеру. Затем он подходит к камере, которая расположена не на уровне глаз — на фото он будет смотреть в камеру не под прямым углом. Конечно, эта задача решается ещё на этапе обучения, если в датасете есть примеры изображений одного и того же человека с разных ракурсов. Но этот метод не всегда показывает хорошие результаты.
Для сервиса распознавания лиц больше подходит метод прокрустова анализа. Берутся две матрицы, и после ряда математических операций находится композиция аффинных преобразований над первой матрицей таким образом, чтобы она стала как можно больше похожа на вторую матрицу. Фактически метод позволяет «развернуть» лица на фото в максимально удобное для нейросети положение, чтобы обеспечить наибольшую похожесть двух лиц. Таким образом, повышение точности сравнения лиц с применением метода прокрустова анализа сводится к простому алгоритму:
- Датасет с изображениями лиц обрабатывает готовая нейросеть, которая определяет ключевые точки на лице.
- Выявленные ключевые точки поэлементно складываются, и в результате определяется среднее положение ключевых точек на всех лицах датасета.
- Изображение отправляется на инференс нейросети.
- Ответ от нейросети используется для поиска ближайшего соседа через библиотеку hnswlib.
Отдельно разберём этап предобработки. Мы выстроили такой конвейер:
- На изображении находим лица.
- Вырезаем их из исходного в отдельные изображения.
- На каждом из изображений лиц находим ключевые точки (landmarks), отвечающие за глаза, нос и уголки рта.
- Все изображения лиц трансформируем с помощью аффинных преобразований, смещая ключевые точки максимально близко к эталону (среднему значению среди всех ключевых точек датасета).
- Список лиц отправляем на инференс в нейронную сеть.
Реализация всего описанного пайплайна позволяет повысить точность при сравнении одинаковых лиц, снятых с разных ракурсов. Но одновременно с этим немного нарушается классический вид изображений лица в целом, из-за чего падает точность при сравнении одинаковых лиц, снятых под одним и тем же углом. Но поскольку люди редко смотрят в камеру строго напрямую, этим уменьшением точности можно пожертвовать в угоду увеличению точности в более частом сценарии.
Варианты применения
Наш пример наглядно показывает, как с помощью готового датасета и платформенных инфраструктурных решений, позволяющих выстроить полный MLOps-конвейер, можно быстро и с минимальными ручными доработками создать универсальный сервис. Например, его можно применить для управления СКУД (система контроля и управления доступом), если добавить возможность открыть дверь или турникет на входе по лицу сотрудника. Также решение можно использовать с небольшой модификацией для подсчёта бизнес-метрик, например количества уникальных посетителей. Поскольку векторизация лица в нейронной сети является «чёрным ящиком», то векторизированные представления лиц не представляют собой персональных данных и, следовательно, работа с ними не запрещена законодательно.
Из минусов можно выделить то, что обученные сторонние модели поиска ключевых точек на лице показали неудовлетворительные результаты. Это может сказаться на точности распознавания. Для улучшения этих слабых сторон необходимо использовать более продвинутые архитектуры нейронных сетей или пользоваться готовыми моделями и дообучать их на исходном датасете.
От автора
В условиях повышенного спроса разработка решений на основе ML начинает походить на олимпийские забеги, в которых важна не только техника, но и скорость реализации. Поэтому работа с готовыми компонентами и платформами полного цикла ML-разработки, такими как Cloud ML Platform, вполне очевидный путь. Выбор в пользу готовых платформенных решений хорош и тем, что позволяет разделить зоны ответственности, в том числе снять с разработчиков задачи администрирования и настройки инструментов, что снижает порог входа в разработку ML-сервисов и сокращает Time-to-market.