Организуем ML-проект с помощью Ocean

    image


    Вступление


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


    Очень важно, чтобы все эти материалы были не только в головах разработчиков, но и в читаемом виде на диске. Это позволит эффективнее обучить новых сотрудников, ввести их в курс дела и погрузить в проект.


    Конечно, так было не всегда. Мы столкнулись с множеством проблем на первых этапах


    • Каждый проект был организован по-разному, особенно если их инициировали разные люди.
    • Недостаточно отслеживали, что делает код, как его запустить и кто его автор.
    • Не использовали виртуализацию в должной степени, зачастую мешая своим коллегам установкой существующих библиотек другой версии.
    • Забывались выводы, сделанные по графикам, которые осели и умерли в горé jupyter-тетрадок.
    • Теряли отчеты по результатам и прогрессу в проекте.

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


    Вишенка на торте — логи проекта, которые агрегируются и превращаются в красивый сайт, автоматически собранный с помощью выполнения одной команды.


    В статье мы расскажем на маленьком искусственном примере, из каких частей состоит Ocean и как его использовать.


    Почему Ocean


    В мире ML существуют и другие варианты, которые мы рассматривали. Прежде всего нужно упомянуть cookiecutter-data-science (далее CDS) как идейного вдохновителя. Начнем с хорошего: CDS не только предлагает удобную структуру проекта, но и рассказывает, как вести проект, чтобы всё было хорошо, — поэтому здесь мы рекомендуем отвлечься и посмотреть в оригинальной статье CDS основные ключевые идеи этого подхода.


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


    Однако в процессе работы стали всплывать и минусы подхода CDS:


    • Папка data может разрастаться, но какой из скриптов или тетрадей порождает очередной файл — не до конца понятно. В большом количестве файлов легко запутаться. Не ясно, нужно ли в рамках реализации новой функциональности использовать какие-то файлы из существующих, так как нигде не хранится описание или документация по их предназначению.
    • В data не хватает подпапки features, в которую можно складировать признаки: посчитанные статистики, векторы и другие характеристики, из которых собирались бы разные конечные представления данных. Об этом уже замечательно написано в блог-посте.
    • src — другая папка-проблема. В ней есть функции, которые актуальны для всего проекта, например, подготовка и чистка данных модуля src.data. Но есть и модуль src.models, который содержит все модели от всех экспериментов, а их могут быть десятки. В итоге src очень часто обновляется, расширяясь совсем незначительными изменениями, а согласно философии CDS после каждого обновления необходимо пересобирать проект, а это тоже время..., — ну, вы поняли.
    • references представлен, но все ещё стоит открытый вопрос: кто, когда и в каком виде должен заносить туда материалы. А рассказать можно много по ходу проекта: какие работы проведены, каков их результат, каковы дальнейшие планы.

    Для решения вышеперечисленых проблем в Ocean представлена следующая сущность: эксперимент. Эксперимент — хранилище всех данных, участвовавших в проверке некоторой гипотезы. Сюда можно отнести: какие данные использовались, какие данные (артефакты) получились в результате, версия кода, время начала и завершения эксперимента, исполняемый файл, параметры, метрики и логи. Часть этих сведений можно трекать с помощью специальных утилит, например, MLFlow. Однако структура экспериментов, которая представлена в Ocean, богаче и гибче.


    Модуль одного эксперимента выглядит следующим образом:


    <project_root>
        └── experiments
            ├── exp-001-Tree-models
            │   ├── config            <- yaml-файлы с настройками
            │   ├── models            <- сохраненные модели
            │   ├── notebooks         <- ноутбуки для экспериментов
            │   ├── scripts           <- скрипты, например, train.py или predict.py
            │   ├── Makefile          <- для управления экспериментом из консоли
            │   ├── requirements.txt  <- список зависимых библиотек
            │   └── log.md            <- лог проведения эксперимента
            │
            ├── exp-002-Gradient-boosting
         ...

    Мы разделяем кодовую базу: переиспользуемый хороший код, актуальный во всем проекте, остается в src-модуле уровня проекта. Он обновляется редко, поэтому реже приходится собирать проект. А модуль scripts одного эксперимента должен содержать код, актуальный только для текущего эксперимента. Таким образом, его можно изменять часто: работу коллег в других экспериментах он никак не затрагивает.


    Рассмотрим возможности нашего фреймворка на примере абстрактного ML/DL-проекта.


    Workflow проекта


    Инициализация


    Итак, клиент — полиция Чикаго, — выгрузил нам данные и задачу: проанализировать преступления, совершенные в городе на протяжении 2011-2017 годов и сделать выводы.


    Начинаем! Заходим в терминал и выполняем команду:


    ocean project new -n Crimes


    Фреймворк создал соответствующую папку проекта crimes. Смотрим на её структуру:


    crimes
      ├── crimes        <- src-модуль с переиспользуемым кодом, одноименный с проектом
      ├── config        <- настройки, актуальные во всем проекте
      ├── data          <- данные
      ├── demos         <- демо для заказчика
      ├── docs          <- Sphinx-документация
      ├── experiments   <- эксперименты
      ├── notebooks     <- ноутбуки для EDA
      ├── Makefile      <- простые команды для запуска из консоли
      ├── log.md        <- проектный лог
      ├── README.md
      └── setup.py

    Выполнять навигацию по всем этим папкам помогает Coordinator из одноименного модуля, который уже написан и готов. Для его использования проект нужно собрать:


    make package


    Это баг: если make-команды не хотят выполняться, то добавьте к ним флажок -B, например “make -B package”. Это относится и ко всем дальнейшим примерам.

    Логи и эксперименты


    Начинаем работу с того, что данные клиента, — в нашем случае файл crimes.csv, — мы помещаем в папку data/raw.


    На сайте Чикаго есть карты с разделениями города на посты (“beats” — наименьшая по размеру локация, за которой закреплена одна патрульная машина), секторы (“sectors”, состоят из 3-5 постов), участки (“districts”, состоят из 3 секторов), административных районов (“wards”) и, наконец, общественные зоны (“community area”). Эти данные можно использовать для визуализации. В то же время json-файлы с координатами полигонов-участков каждого из типа не являются данными, присланными заказчиком, поэтому мы помещаем их в data/external.


    Далее нужно ввести понятие эксперимента. Все просто: рассматриваем отдельную задачу как отдельный эксперимент. Нужно распарсить/выкачать данные и подготовить их для использования в дальнейшем? Это стоит поместить в эксперимент. Подготовить много визуализации и отчетов? Отдельный эксперимент. Проверить гипотезу, подготовив модель? Ну, вы поняли.


    Для создание нашего первого эксперимента из папки проекта выполняем:


    ocean exp new -n Parsing -a ivanov


    Теперь в папке crimes/experiments появилась новая папка с именем exp-001-Parsing, её структура приведена выше.


    После этого надо посмотреть на данные. Для этого создаем ноутбук в соответствующей папке notebooks. В Surf мы придерживаемся именования “номер ноутбука — название”, и созданный ноутбук будет называться 001-Parse-data.ipynb. Внутри мы подготовим данные для последующей работы.


    Код подготовки данных
    import numpy as np
    import pandas as pd
    pd.options.display.max_columns = 100
    
    # Используем наш проект как источник полезного кода:
    from crimes.coordinator import Coordinator
    coord = Coordinator()
    coord.data_raw.contents()
    >  ['/opt/jupyterhub/notebooks/aolferuk/crimes/data/raw/crimes.csv']
    
    # Синтаксический сахар для загрузки файлов:
    df = coord.data_raw.join('crimes.csv').load()
    df['Date'] = pd.to_datetime(df['Date'])
    df['Updated On'] = pd.to_datetime(df['Updated On'])
    df['Location X'] = np.nan
    df['Location Y'] = np.nan
    df.loc[df.Location.notnull(), 'Location X'] = df.loc[df.Location.notnull(), 'Location'].apply(lambda x: eval(x)[0])
    df.loc[df.Location.notnull(), 'Location Y'] = df.loc[df.Location.notnull(), 'Location'].apply(lambda x: eval(x)[1])
    df.drop('Location', axis=1, inplace=True)
    df['month'] = df.Date.apply(lambda x: x.month)
    df['day'] = df.Date.apply(lambda x: x.day)
    df['hour'] = df.Date.apply(lambda x: x.hour)
    
    # Синтаксический сахар для сериализации файлов:
    coord.data_interim.join('crimes.pkl').save(df)

    Чтобы ваши коллеги были в курсе, что вы сделали и могут ли ваши результаты быть ими использованы, нужно прокомментировать это в логе: файле log.md. Структура лога (по сути являющегося привычным markdown-файлом) выглядит следующим образом:


    log.md


    Цветом выделены части, которые заполнены от руки. Основная мета эксперимента (светло-сливовый цвет) — автор и объяснение своей задачи, результата, к которому эксперимент идет. Ссылки на данные, как взятые, так и порожденные в процессе (зеленый цвет), помогают следить за файлами данных и понимать, кто, в рамках чего и зачем их использует. В самом логе (желтый цвет) рассказывается итог работ, выводы и рассуждения. Все эти данные позже станут наполнением сайта проектного лога.


    Дальше— этап EDA (Exploratory Data Analysis — “разведывательный анализ данных”). Возможно, его будут проводить разные люди, и, конечно, нам понадобятся результаты в виде отчетов и графиков в последствии. Эти доводы повод создать новый эксперимент. Выполняем:


    ocean exp new -n Eda -a ivanov


    В папке notebooks эксперимента создаем тетрадь 001-EDA.ipynb. Полный код приводить не имеет смысла, но он и не нужен, например, вашим коллегам. Зато нужны графики и выводы. В тетради выходит много кода, и она сама по себе не то, что хочется показывать клиенту. Поэтому наши находки и инсайты запишем в файл log.md, а картинки графиков сохраним в references.


    Вот, например, карта безопасных районов Чикаго, если судьба вас занесет туда:


    chicagoMap


    Она как раз была получена в тетради и перенесена в references.


    В логе добавлена следующая запись:


    19.02.2019, 18:15
    EDA conclusion: 
    
    * The most common and widely spread crimes are theft (including burglary), battery and criminal damage done with firearms.
    * In 1 case out of 4 the suspect will be set free after detention.
    
    [!Criminal activity in different beats of the city](references/beats_activity.jpg)
    
    Actual exploration you can check in [the notebook](notebooks/001-Eda.ipynb)

    Обратите внимание: график оформлен просто как вставка изображения в md-файл. А если оставить ссылку на тетрадь, то она будет конвертирована в html-формат и сохранена как отдельная страничка сайта.


    Чтобы его собрать из логов экспериментов, выполняем следующую команду на уровне проекта:


    ocean log new


    После этого создается папка crimes/project_log, и index.html в нём — лог проекта.


    Это баг: при отображении в Jupyter сайт внедряется как iframe для пущей безопасности, в связи с чем шрифты не отображаются корректно. Поэтому с помощью Ocean можно сразу сделать архив с копией сайта, чтобы было удобно её скачать и открыть на локальном компьютере или отправить по почте. Вот так:
    ocean log archive [-n NAME] [-p PASSWORD]

    Документация


    Посмотрим на формирование документации с помощью Sphinx. Создадим функцию в файле crimes/my_cool_module.py и задокументируем её. Обратите внимание, что в Sphinx используется reStructured Text-формат (RST):


    my_cool_module.py
    def my_super_cool_random(max_value):
        '''
            Returns a random number from [0; max_value) interval.
    
            Considers the number to be taken from uniform distribution.
    
            :param max_value: Maximum value that defines range.
            :returns: Random number.
        '''
        return 4  # Good enough to begin with

    А дальше все очень просто: на уровне проекта выполняем команду формирования документации, и готово:


    ocean docs new


    Вопрос из зала: Почему, если мы собирали проект через make, собирать документацию приходится через ocean?
    Ответ: процесс генерации документации — не только выполнение команды Sphinx, которую можно поместить в make. Ocean берет на себя сканирование каталога ваших исходных кодов, по ним строит индекс для Sphinx, и только потом сам Sphinx берется за работу.

    Готовая html-документация ждет вас по пути crimes/docs/_build/html/index.html. И наш модуль с комментариями там уже появился:


    genDoc


    Модели


    Следующий шаг — построение модели. Выполняем:


    ocean exp new -n Model -a ivanov


    И на этот раз взглянем на то, что лежит в папке scripts внутри эксперимента. Файл train.py — заготовка для будущего процесса обучения. В файле уже приведен boilerplate-код, который делает сразу несколько вещей.


    • Функция обучения принимает несколько путей к файлам:
      • К файлу конфигурации, в который разумно вынести параметры модели, параметры обучения и прочие опции, которыми удобно управлять снаружи, не вникая в код.
      • К файлу с данными.
      • Путь к директории, в которой необходимо сохранить итоговый дамп модели.
    • Трекает метрики, полученные в процессе обучения, в mlflow. На все, что было затрекано, можно посмотреть через UI mlflow, выполнив команду make dashboard в папке эксперимента.
    • Отправляет оповещение в ваш Телеграм о том, что процесс обучения завершен. Для реализации этого механизма использован бот Alarmerbot. Чтобы это заработало, нужно сделать совсем немного: отправить боту команду /start, а затем перенести токен, выданный ботом, в файл crimes/config/alarm_config.yml. Строка может иметь такой вид:
      ivanov: a5081d-1b6de6-5f2762
    • Управляется из консоли.

    Зачем управлять нашим скриптом из консоли? Все организовано для того, чтобы процесс обучения или получения предсказаний любой модели был легко организован сторонним разработчиком, не знакомым с деталями реализации вашего эксперимента. Чтобы все кусочки паззла сошлись, после оформления train.py нужно оформить Makefile. В нем есть заготовка команды train, и вам остается только правильно расставить пути к перечисленным выше требуемым файлам конфигурации, а в значении параметра username перечислить всех желающих получать Telegram-оповещения. В частности, работает алиас all, который отправит оповещение всем членам команды.


    Как только все готово, наш эксперимент стартует с помощью команды make train, просто и изящно.


    На случай, если хочется применить чужие нейросети, вполне помогут виртуальные окружения (venv). Создавать и удалять их в рамках эксперимента очень легко:


    • ocean env new создаст новое окружение. Оно не только активно по умолчанию, но еще и создает дополнительное ядро (kernel) для тетрадей и проведения дальнейших исследований. Называться оно будет так же, как и название эксперимента.
    • ocean env list отобразит список ядер.
    • ocean env delete удалит созданное в эксперименте окружение.

    Чего не хватает?


    • Ocean не дружит с conda (потому что мы ее не используем).
    • Шаблон проекта только на английском.
    • Проблема локализации пока относится и к сайту: построение проектного лога предполагает, что все логи на английском.

    Заключение


    Исходный код проекта лежит здесь.


    Если вы заинтересовались — здорово! Больше информации вы можете почерпнуть в README в репозитории Ocean.


    И как обычно говорят в таких случаях, contributions are welcome, мы будем только рады, если вы поучаствуете в улучшении проекта.

    Surf
    31.72
    Company
    Share post

    Comments 0

    Only users with full accounts can post comments. Log in, please.