Data-science развивается очень быстро, в том числе благодаря росту объема доступных данных для анализа или построения моделей. Но для создания сложных моделей командам аналитиков нужно работать совместно и эффективно управлять большими датасетами. И вот здесь может помочь, например, DVC — open-source система контроля версий для проектов машинного обучения.
Нашел не так много информации по ней в рунете, поэтому под катом на примере простого ML-проекта расскажу, как работать с инструментом для хранения и обновления датасета.
Что ещё за DVC?
DVC (Data Version Control) предназначен для версионирования данных, моделей и экспериментов с их загрузкой в удаленное хранилище, а также специализированные инструменты для воспроизводимости экспериментов.
Инструмент нужен, если изменений в экспериментах становится много, а знания о версиях датасетов и моделей хранятся часто только в головах data-scientist'ов. DVC может хранить данные в локальном кэше или удаленном репозитории. Вариантов remote много: s3, gdrive, свой сервер с доступом по ssh и так далее.
По сути, мы отделяем код от данных и экспериментов вторым инструментом версионирования. DVC хранит данные отдельно от Git, но в Git-репозитории необходимо хранить сами хеши (md5) актуальных данных и экспериментов. Таким образом, в Git хранятся лишь ссылки на данные и эксперименты последней закомиченной версии — это позволяет работать с разными данными или моделями в разных ветках просто сохраняя нужные хеши в файлах .dvc.
Далее разберемся, как работать с DVC на примере простого ML-проекта.
ML-проектом в нашем случае будет классификатор котов и собак по фотографии — рассмотрим применение DVC как системы версионирования датасета с этими животными. По аналогии DVC может версионировать другие датасеты или сами модели классификаторов. Например, в FunCorp мы используем DVC для хранения размеченного датасета контента с nsfw-классами для задач автоматической модерации.
Установка
DVC можно установить для MacOS, Linux, Windows. Или он может быть загружен как библиотека в Python:
pip install dvc
В conda:
conda install -c conda-forge dvc
Подробнее про установку можно прочитать на официальном сайте.
Но есть нюанс: если заранее знаете, что будете использовать как удаленное хранилище — лучше ставить пакет с необходимыми зависимостями: [s3], [azure], [gdrive], [gs], [oss], [ssh] или [all] для загрузки всех возможных. Для такой загрузки нужно набрать, например, pip install "dvc[s3]" — в таком случае дополнительно будет установлена библиотека boto3 для работы с s3.
Для удаленных хранилищ могут потребоваться ключи доступа и предварительная настройка.
Запуск
Предполагается, что DVC будет использоваться в корне папки под контролем Git, то есть рядом должна быть папка .git/. Но есть и другие варианты. Например, можно для экспериментов инициализировать DVC без Git, используя флаг --no-scm. Далее предполагается первый вариант настройки в корне и совместно с Git.
Инициализируем DVC в папке проекта:
dvc init
Тут как раз и возникает ошибка, так как папка не под Git.
olegsokolov@Olegs-MacBook-Pro dvc_demo_project % dvc init
ERROR: failed to initiate DVC - /Users/olegsokolov/dvc_demo_project is not tracked by any supported SCM tool (e.g. Git).
Use `--no-scm` if you don't want to use any SCM or `--subdir` if initializing inside a subdirectory of a parent SCM repository.
Возьмем проект под контроль Git и попробуем снова.
olegsokolov@Olegs-MacBook-Pro dvc_demo_project % git init
Initialized empty Git repository in /Users/olegsokolov/dvc_demo_project/.git/
olegsokolov@Olegs-MacBook-Pro dvc_demo_project % dvc init
You can now commit the changes to git.
Список файлов и папок внутри проекта теперь выглядит следующим образом:
olegsokolov@Olegs-MacBook-Pro dvc_demo_project % ls -f
. .. .dvc .git .dvcignore
То есть при инициализации DVC создал папку .dvc с дефолтной конфигурацией и прочими необходимыми файлами, а также свой аналог .gitignore — .dvcignore.
Структура папок внутри .dvc:
olegsokolov@Olegs-MacBook-Pro dvc_demo_project % tree -a .dvc
.dvc
+-- .gitignore
+-- config
+-- plots
¦ +-- confusion.json
¦ +-- confusion_normalized.json
¦ +-- default.json
¦ +-- linear.json
¦ +-- scatter.json
¦ L-- smooth.json
L-- tmp
Теперь можно сделать коммит с изменениями проекта и добавить в проект наш датасет.
Добавление данных
По ссылке скачаем и добавим первый датасет в наш проект внутрь папки для датасетов datasets/. Он содержит фотографии котов и собак.
Структура папки датасетов проекта выглядит так:
.
L-- ./datasets
L-- ./datasets/PetImages
+-- ./datasets/PetImages/Cat
L-- ./datasets/PetImages/Dog
Внутри папок Cat и Dog лежит по 12501 изображений в jpg-формате.
Добавим этот датасет под трекинг DVC.
dvc add ./datasets/PetImage
После этого DVC создает файл .dvc — слепок текущего состояния папки с данными, а также сохраняет сами данные в локальный кэш.
olegsokolov@Olegs-MacBook-Pro dvc_demo_project % ls ./datasets/
PetImages PetImages.dvc
Файл PetImages.dvc выглядит следующим образом:
olegsokolov@Olegs-MacBook-Pro dvc_demo_project % cat ./datasets/PetImages.dvc
outs:
- md5: b2c461b1b0bcb7a0a2318d5be1f13758.dir
size: 866190740
nfiles: 25002
path: PetImages
А внутри папки .dvc/ появился локальный кэш данных, где DVC хранит реальные партиции данных, а не ссылки. Важно, что данный кэш находится в .gitignore, то есть файлы локального кэша, не попадут в Git-репозиторий.
Попробуем запушить локальный кэш в удаленное DVC хранилище командой dvc push.
dvc push data/
ERROR: failed to push data to the cloud - config file error: no remote specified. Create a default remote with
dvc remote add -d <remote name> <remote url>
Ошибка! Конечно, ведь мы не добавили никакого удаленного хранилища. Хоть DVC и создал дефолтную конфигурацию, но нам также нужно настроить наш удаленный remote, где будут хранится данные и откуда мы будем их забирать на других машинах.
Добавление remote
Повторюсь, как и Git, DVC может работать, сохраняя все изменения локально. Но для работы в команде необходимо удаленное хранилище (remote) для данных и экспериментов. И вариантов remote много. У каждого могут быть свои специфичные настройки и требования. Ниже приведен пример c настройкой remote как bucket на s3.
Предварительно создаем bucket в s3. Я назвал dvc-demo-project:
olegsokolov@Olegs-MacBook-Pro dvc_demo_project % dvc remote add -d myremote s3://dvc-demo-project
Setting 'myremote' as a default remote.
Флаг -d значит, что этот репозиторий будет дефолтным (можно настроить несколько репозиториев, например, для разных датасетов).
Попробуем запушить оригиналы снова:
dvc push
ERROR: failed to push data to the cloud - Unable to find AWS credentials. <https://error.dvc.org/no-credentials>: Unable to locate credentials
Данная ошибка говорит о том, что ни в локальных, ни в глобальных файлах конфигурации DVC нет информации о способе аутентификации в AWS. Необходимо добавить один из вариантов доступа.
Я выбрал доступ по профилю:
dvc remote modify myremote profile myprofile
Теперь файл конфигурации DVC выглядит так:
olegsokolov@Olegs-MacBook-Pro dvc_demo_project % cat .dvc/config
[core]
remote = myremote
['remote "myremote"']
url = s3://dvc-demo-project
profile = myprofile
Теперь можно пушить файлы в удаленное хранилище.
olegsokolov@Olegs-MacBook-Pro dvc_demo_project % dvc push
24972 files pushed
Для того, чтобы убедиться в наличие данных удаленно, можно почистить локальный кэш или клонировать Git-проект с нуля.
Важно! Не забывайте добавлять .dvc файлы и конфигурации (кроме приватных) под версионирование в .git. Чтобы после команды git pull можно было выполнить загрузку датасетов через dvc pull. Без этих файлов локально DVC не сможет скачать файлы при стандартной настройке. Также важно не забывать коммитить в Git .dvc-файлы после обновления датасетов (после каждой команды dvc add), иначе DVC скачает прошлую версию файла по хэшам из .dvc файлов, только если вы специально не хотите подгрузить старую версию модели или датасета из DVC-хранилища.
Загрузка данных
В нашем примере для загрузки датасета локально из удаленного хранилища можно выполнить загрузку всех данных.
dvc pull
Или конкретного датасета.
dvc pull datasets/PetImages.dvc
После этого папка PetImages с оригиналами изображений появится в datasets/
Обновление данных
Если мы убрали часть данных в датасете, то DVC обсчитывает именно входящие файлы в папку и сам это мониторит.
При удалении делаем dvc add, в локальный кэш добавляются сведения об удалении файлов, и когда потом делаем пуш — он просто особым образом пересчитывает хэш, а при загрузке на новых машинах по новому хэшу скачает то, что нужно без дополнительных манипуляций. Хэш привязывается к текущему состоянию файлов и отражает все изменения — весь датасет качать не придется, а изменения скачиваются достаточно быстро. Таким образом, упрощается работа с датасетом, папками и их дистрибуцией на разные машины.
Также можно хранить и модели. Есть, конечно, Git Large File, но обычный Git имеет ограничение 100 мб, а DVC имеют массу специфичных фич для data-science
Например, DVC позволяет делать оценки и тест моделей, сохранить их состояние до обучения и это состояние потом воспроизвести. Легко организовать воспроизводимые эксперименты. Бывают задачи по воспроизведению экспериментов, чтобы посмотреть, что все нормально при, допустим, изменении окружения. Есть пайплайн, который это запускает.
Особенности и минусы
У DVC отличная документация, все основные команды описаны, поэтому лучше первым делом прочитать ее на официальном сайте.
DVC часто выкатывает апдейты и фиксит баги, но нужно следить, чтобы функциональность не пострадала. Если в Docker файле делать pip install dvс, он поставит последнюю версию — так что стоит зафиксировать стабильную версию и ее периодически обновлять, чтобы старые конфигурации не сломали новую версию. А меняться там может многое, например, в файлы .dvc добавляются новые поля и названия конфигураций.Также могут быть проблемы с кэшем — он может разрастаться. Если датасет весит 1 Гб и его периодически меняем, совсем не факт, что удаленный кэш будет равен всем изменениям — может быть больше, биться на файлы. Будет непонятно, из-за чего это произошло и придется чистить.Еще могут быть проблемы с данными при переезде в новый remote — их нужно перезагружать и проверять. Бывает легче запушить по новой, будто это новые файлы, чем синхронизировать кэш на новом удаленном хранилище.
Следующий момент связан с использованием инструмента в команде. Вещь может показаться очевидной, но она очень важная. Обо всех обновлениях и изменениях нужно сообщать команде где-то еще. Например, обновление датасета или создание новой ветки описывать словами — что за изменения и зачем они нужны. Сам DVC не особо много подписывает — хэш из файла dvc вам ничего не скажет (видны только файлы, но не суть изменений). Но в каждой команде это может работать по-разному.
Заключение
DVC быстроразвивающийся (7500 звезд на GitHub) и удобный инструмент для нужд data-science и не только. Он отлично подходит для хранения датасетов и моделей машинного обучения. Документация понятная и содержит информацию по частым ошибкам конфигурации. Инструмент имеет множество прочих специальных команд для организации экспериментов (см. dvc run).
Также для более сложных ML-пайплайнов с деплоем сервисов у iterative.ai есть еще инструмент для CICD моделей — CML.
jezzarax
Пробовал dvc года полтора назад, идея хранения и версионирования датасетов понравилась и в теории и на практике в связке с s3 (пытался пользоваться с ssh для персонального проекта, получилось мучительно долго).
Отказался в пользу mlflow с ручным отслеживанием хешей датасетов по трем причинам:
По итогу, дизайн и идея — прекрасны, в практическом использовании нужно быть готовым к сюрпризам и долгим разговорам с коллегами.
skleg Автор
Согласен, с философией настройки пайплайнов и метрик в DVC сложнее разбираться.
А как у вас сохраняются датасеты с mlflow? Как артефакты?
jezzarax
Раньше пробовали сохранять датасеты как артефакты, но api у MLflow для их извлечения показался слишком неудобным, поэтому стали просто вливать в версионируемый Minio-bucket (в s3 нас legal не пускает, но суть та же), постепенно пришли к обёртке над таким доступом.
Теперь у нас в команде есть внутренняя библиотека по мотивам tensorflow datasets. Она помогает отслеживать имена и версии датасетов, мы их в MLflow в виде параметров и пишем. К ней дописали небольшой кусок, чтобы можно было быстро и безболезненно вливать и использовать свои датасеты без публикации новой версии библиотеки (в итоге нам не нужен аналог tfds-nightly, если знаешь название и версию временного датасета).
А пайплайны dvc мы просто переложили на luigi, с этим стало проще потом модели в prod запускать.
skleg Автор
luigi тоже нравится, особенно наблюдать исполнение в UI графа задач.
У нас подобный подход на этапах препроцессинга: каждый шаг идет как luigi.Task и заодно main каждого шага как MLflow run. То есть одновременно можно собирать в коде цепочку задач подготовки датасета и хранить артефакты и гиперпараметры с MLflow.