1. Краткое описание ML проекта:
Реальная цель проекта - освоить подходы MLOps на основе практической ML-задачи. Важно понимать, что целью проекта не является реализация эффективной ML части, а только поэтапное построение ее обертки в виде MLOps, в свою очередь, данная обертка должна позволять, в дальнейшем, эффективно проводить исследования и итеративно улучшать работу целевого сервиса. Как следствие, ML часть сводится, к написанию наиболее простого, но работающего бейзлайна.
В качестве ML задачи, выбрана задача матчинга геолокации из активного соревнования на Kaggle Foursquare - Location Matching (
https://www.kaggle.com/competitions/foursquare-location-matching). На входе имеем датасет, состоящий из точек интереса (POI) и информации о них. Датасет сформирован автоматизированными методами, путем получения информации из различных открытых и не только источников. Информация о POI (из разных источников) часто имеет несоответствия, избыточность, конфликты, двусмысленность, позиционную неточность и т.п. Необходимо решить задачу определения дубликатов POI (для дальнейшей их обработки, получения максимально полной и достоверной информации о каждой POI).
Постановка задачи, в рамках текущего проекта (легенда)
Представим себе, что разработчики проекта являются сотрудниками компании Foursquare (один из лидеров в части поставок информации по глобальным POI). Необходимо создать внутренний сервис сопоставления POI, доступный внутри организации посредством API, а так же дизайн проекта, позволяющий итеративно улучшать работу сервиса, в том числе, эффективно проводить исследования (не затрагивая при этом работу основного сервиса).
Рамки проекта и задач:
- сервис должен функционировать в виде api, пользовательский интерфейс не требуется;
- сервис должен принимать на вход .csv файл, заданного формата (формат описан ниже, в соответствующем разделе), содержащий в себе информацию о собранных POI;
- сервис должен возвращать в качестве ответа .csv файл, заданного формата, с данными о найденных дубликатах POI;
- сервис будет функционировать исключительно внутри организации и доверенной инфраструктуры, предпринимать меры безопасности, требуемые для возможности публикации сервиса в Интернет, не требуется;
- проект должен предоставлять возможность проведения исследований и экспериментов (включая всевозможные изменения ML пайплайна, предобработки, фиче-инжинеринга и т.п.), не затрагивая при этом штатную работу текущей версии сервиса;
Описание исходных данных
Имеется набор данных из более чем полутора миллионов записей мест (POI), в датасете присутствует шум, дублирование, посторонняя и иногда неверная информация.
train.csv - представляет собой обучающий набор, состоящий из одиннадцати атрибутивных полей для более чем миллиона записей о POI, в том числе:
- id - уникальный идентификатор для каждой записи;
- point_of_interest - уникальные идентификаторы POI, сформированные предварительной разметкой (преимущественно экспертной); т.е. все строки, с одним и тем же значением point_of_interest, являются дублями; по сути, колонка является источником нашей целевой переменной, на основе которой мы сможем обучить модель; в реальных данных, с которыми в дальнейшем будет работать сервис, эта колонка отсутствует;
- latitude, longitude - географические координаты;
- name, address, city, state, zip, country, url, phone, categories - данные о названии, адресе, городе, регионе, индексе, стране, url-адресе, телефонном номере и категории соответственно.
sample_submission.csv - файл-пример требуемого выходного формата, количество строк, должно соответствовать количеству строк поданного на вход датасета, содержит в себе следующие колонки
- id - исходные id
- matches - список id найденных дублей, через пробел (список в том числе должен включать в себя сам исходный id, если дублей не найдено, то в ячейке будет значиться только исходный id)
Результаты анализа и обработки данных
Первичный EDA-анализ показал, что в исходном датасете существуют колонки с множеством пропущенных значений, все составляющие ключа POI - категориальные, кроме географических координат долготы и широты. Больше всего пропущенных значений в колонках url, phone, и zip. В колонках имени, долготы и широты нет пропущенных значений, но могут быть неверные данные, кроме того, для одних и тех же POI, но полученных из разных источников мы можем наблюдать существенные различия по всем колонкам.
Описание бейзлайна
Сводим задачу к бинарной классификации, путем отбора кандидатов (строк) для последующего их сравнения на предмет идентичности. Сравнить все пары строк между собой не представляется возможным, т.к. мы для нашего случая получилы бы парный датасет из более чем 10^12 строк.
В бейзлайне, отбор кандидатов производим путем округления координат latitude, longitude до заданного знака после запятой (соответствующего например расстоянию 100м, 1 км, и т.п.), с последующим объединением строк в пары, через merge на основе одинаковых значений колонки = конкатенации округленных latitude, longitude.
Далее, для каждой строки полученного парного датасета, формируем следующие фичи:
- географическое расстояние в км. между исходными точками;
- расстояние Левенштейна между строками-названиями исходных точек.
Колонку целевой переменной формируем путем сравнения point_of_interest_1, point_of_interest_1, если они идентичны, целевая переменная выставляется в 1, иначе в 0.
Обучаем XGBClassifier на полученных данных.
На основе полученных предсказаний, формируется выходной .csv файл формата sample_submission.csv.
Метрики качества
В качестве основной метрики используется Jaccard score, который равен среднему значению из Jaccard index всех строк. Формула расчета Jaccard index приведена с статье на википедии
https://en.wikipedia.org/wiki/Jaccard_indexВ процессе обучения, Jaccard score расчитывается на двух стадиях:
- на стадии формирования датасета пар кандидатов, это своего рода максимальный Jaccard score, который мы сможем получить, решая задачу бинарной классификации на текущем парном датасете;
- на финальной стадии, после получения предсказаний модели, это реальный, итоговый Jaccard score
2. Описание MLOPS подходов
Система контроля версий
В качестве системы контроля версий, выбран git. Причина выбора - наиболее распространенная система, отвечающий всем заявленным к проекту требованиям.
В качестве хранилища удаленного репозитория, выбран GitLab. Причины выбора - необходимость получения практического опыта работы с GitLab (до курса, имел только незначительный опыт работы с GitHab).
Инструменты контроля codestyle
В качестве инструментов codestyle выбраны следующие:
- форматеры - Black (максимальная длина строки 100 символов); кроме black были опробованы форматеры autopep8 и yapf (от Google), в итоге выбран black, как наиболее безкомпромисный;
- линтеры:
- pylint - наиболее строгий и требовательный линтер (осуществляет как логические, так и стилистические проверки);
- flake8 - очень распространенный и гибко настраиваемый линтер;
- mypy - осуществляет логические проверки, касающиеся корректности работы с типами данных (основной источник информации это аннотации типов);
- bandit - выполняет анализ кода на предмет выявления небезопасного кода/
В качестве точек контроля выбраны:
- IDE (в моем случае VSCode);
- в пайплайне gitlab-ci.yml в задаче (job) test_lint стейджа tests, следующим образом
- poetry run mypy --ignore-missing-imports src
Кроме того, был рассмотрен вариант добавления дополнительно точки проверки линтерами через git pre commit hooks, но посчитал этот вариант избыточным, т.к. у нас основная цель защитить основные ветки удаленного репозитория (в нашем случае main) от "плохого" кода, в свою очередь, иногда требуется сделать быстрый локальный коммит, без отработки всех замечаний линтеров, а в случае pre commit hooks этого сделать не удастся.
Шаблон проекта / шаблонизаторы / структура проекта
В качестве менеджера зависимостей выбран poetry.
Исходный код проекта в соответствии с выбранным шаблоном располагается в подкаталогах директории src и оформлен в виде модуля. Для сборки модуля в пакет в фале pyproject.toml для poetry добавлены следующие настройки:
[tool.poetry]
name = "mlops23regproject"
version = "0.1.0"
description = "MLOps cource project by 23reg team."
authors = ["MLOps-23reg-team"]
license = "MIT"
packages = [
{ include = "src", from = "." }
]
Данные, обрабатываемые в рамках проекта располагаются в подкаталогах директории data и так же соответствуют концепциям Cookiecutter DS шаблона.
Workflow менеджеры
В качестве workflow менеджера и инструмента версионирований данных выбран DVC.
Заметки к версионированию данных:
- бэкэндом для версионирования данный является локально развернутый s3 сервис на базе minio (сервис состоит из 2-х докер контейнеров, непосредственно minio, и nginx в качестве кэширующего прокси);
- для DVC в сервисе s3 выделен бакет dvc;
- учетные данные для доступа к s3 хранятся не в конфигурационном файле уровня проекта .dvc/config, а в локальном типе конфигурационного файла .dvc/config.local, который исключен из контроля git; для целей unit тестирования в ходе исполнения gitlab ci пайплайна, соответствующий файл генерируется "налету" в ходе исполнения самой задачи раннером, путем исполнения следующих команд:
- poetry run dvc remote modify --local s3minio access_key_id $AWS_ACCESS_KEY_ID
- poetry run dvc remote modify --local s3minio secret_access_key $AWS_SECRET_ACCESS_KEY
- переменные $AWS_ACCESS_KEY_ID/$AWS_SECRET_ACCESS_KEY хранятся в разделе Variables гитлаба
- пайплайн DVC настроен таким образом, что бы в s3 версионировались не все промежуточные файлы, а только те, для которых это действительно целесообразно
- исходные файлы датасетов, а так же файлы после разбиения на train/test;
- файлы метрик (исключительно в образовательных целях);
- файлы моделей (так же больше в образовательных целях, т.к. итоговое версионирование моделей осуществляется через MLFlow);
- для всех остальных файлов, фигурирующих в outs пайплайна dvc.yaml выставлен параметр cache: false (в этом случае dvc продолжает хранить копии файлов в локальном кэше, но не отправляет их на хранение в s3; при запуске dvc repro и отсутствии подобных файлов, стейджи пайплайна, которые генерируют необходимые файлы, будут перезапущены).
Заметки к workflow пайплайну:
- пайплайн реализован через файл dvc.yaml и дополнительный файл params.yaml;
- код проекта разбит на логические модули, предполагающие возможность их исполнения из CLI (реализовано путем декарирования соответствующих функций с помощью click);
- dvc.yaml содержит описание стейджей (стадий), в виде запуска соответствующих им python модулей с заданными параметрами, а так же описанием файлов от которых зависит каждый стейдж и выходных файлов, которы должны быть сгенерированы по итогу исполнения стеджа;
- исполнение пайплайна осуществляется через вызов команды из CLI: dvc repro;
- после получения соответствующие команды, DVC строит DAG, и запускает стейджи в соответствующем порядке (в том числе параллельно, если это допустимо).
Инструменты трекинга экспериментов
Трекинг экспериментов и моделей осуществляется с помощью MLFlow. MLFlow развернут локально, в докер контейнере, по сценарию 4:
- в качестве СУБД (хранилище трекинга экспериментов) выступает локально развернутый контейнер с PostgreSQL;
- в качестве s3 хранилища сертификатов выступает локально развернутый minio (используется бакет mlflow).
Методы и инструменты тестирования
Unit-тестирование осуществляется слудующими инструментами:
- pytest, c двумя скриптами в директории ./tests
- test_evaluate.py - осуществляет имитацию вызова функции обернутой в декораторы click из CLI;
- test_predict_model.py - осуществляет имитацию вызова функции обернутой в декораторы click из CLI, дополнительно, через библиотеку great_expectations, проверяет формат выходного файла, генерируемого по итогу запуска модуля (конкретно добавлены проверки на соответсвие списку колонок, уникальность значений, отсутсвие пропусков NaN);
- через запуск всего пайплайна командой dvc repro (тест будет пройден при отсутствии ошибок).
Указанные тесты выполняются в ходе исполнения задачи (job) pytest, стеджа tests пайплайна gitlab-ci.yml
Описание CI пайплайна
CI стейджи:
- tests - выполняется при коммите/пуше в любую ветку (кроме main), в рамках него параллельно запускаются следующие задачи:
- test_lint - аудит кода линтерами;
- pytest - юнит-тесты;
- build - выполняется только для коммитов в ветку main, в нашем случае, т.к. прямые коммиты запрещены, будет запускаться сразу после успешного отработанного merge-request в main; в рамках него запускается единственная задача:
- docker_build - сборка и доставка в gitlab registry докер образа нашего сервиса dev_ml_service.
3. Описание получившегося сервиса/продукта
Сервис сервис реализован в виде докер контейнера dev_ml_service, предоставляющий API, функционал которого описан ниже.
API
API доступен по адресу
http://<ip or hostname>:8004/invocations
Принимает на вход POST запрос входными данными в виде одного параметра file представляющего из себя .csv файл исходного формата.
По итогу работы, возвращает файл требуемого по условию задачи формата.
Описание CD пайплайна
CD часть пайплайна реализована в виде следующего стейджа:
- deploy - выполняется загрузка готового докер образа нашего сервиса с gitlab registry и его деплой (запуск соответствующего контейнера); в рамках стейджа запускается единственная задача docker_deploy:
- выполняется только удачного merge request в ветку main и только после успешного завершения CI стейджа build;
- предварительно останавливаются и удаляются предыдущие версии контейнеров с заданным префиксом (_dev).
Итоговый технологический стек
Язык разработки: Python 3.9.
Менеджмент зависимостей: poetry.
Шаблон проекта: cookiecutter data science.
Cистема контроля версий кода – git/gitlab.
Cистема контроля версий данных – dvc + minio.
workflow менеджер – dvc.
Инструмент контроля codestyle.
Линтеры – pylint, flake8, mypy, bandit;
Форматтер - black.
Трекинг экспериментов – mlflow по сценарию 4 (PostgreSQL, s3 minio).
CLI: click.
Модель – XGBClassifier.
Тестирование: pytest, great expectations, dvc repro.
Api – FastAPI+Uvicorn.
Runtime – docker.
4. Проблемы и недостатки текущего workflow и получившихся результатов. Возможные улучшения.
- Для стабильного функционирования API сервиса необходимо дополнительно использовать какой-либо менеджер очередей, возможно в связке nginx;
- Необходимо добавить end-to-end тестирование работы сервиса, в том числе напрашивается dev и prod среда;