Разработка программного обеспечения для науки о данных
Контрольный список по науке о данных: лучшие практики для поддерживаемых проектов по науке о данных
16 шагов для создания поддерживаемых проектов данных
За годы работы я разработал десятки проектов в области науки о данных и машинного обучения и усвоил несколько уроков на собственном горьком опыте. Без структуры и стандартов воцаряется хаос, что затрудняет последовательный прогресс. Я составил список лучших практик для обеспечения проектов и удобства обслуживания, и я делюсь им с вами, чтобы вы могли использовать его для своего следующего проекта. Давайте начнем!
Сводка
Прежде чем мы углубимся в детали каждого элемента, вот краткое изложение:
- Храните данные, артефакты и код в разных местах
- Объявите пути и параметры конфигурации в одном файле
- Безопасное хранение учетных данных
- Содержите свой репозиторий в чистоте, используя файл
.gitignore
- Полностью автоматизируйте рабочий процесс
- Объявить все программные зависимости
- Разделить зависимости производства и разработки
- Иметь информативный и краткий README
- Код документа
- Организуйте свой репозиторий в виде иерархической структуры
- Упакуйте свой код
- Сделайте свои блокноты Jupyter простыми
- Используйте модуль ведения журнала (не используйте печать)
- Протестируйте свой код
- Позаботьтесь о качестве кода
- Удалить мертвый и недоступный код
1. Храните данные, артефакты и код в разных местах
При работе над проектом мы столкнемся с тремя типами файлов:
- Данные. Все необработанные данные и любые результаты промежуточных данных (например, CSV, txt, дампы SQL и т. д.)
- Артефакты. Любые другие файлы, созданные во время выполнения конвейера (например, сериализованная модель, отчет HTML с показателями модели).
- Исходный код. Все файлы для поддержки выполнения конвейера (например,
.py
,.R
исходный код, блокноты Jupyter, тесты).
Чтобы все было организовано, разделите каждую группу на разные папки; это облегчит понимание структуры проекта:
2. Объявите пути и параметры конфигурации в одном файле
Ваш конвейер должен загружать необработанные данные из определенного источника; кроме того, вам может потребоваться взаимодействие с другими системами (например, хранилищем данных). Наконец, вам нужно сохранить результаты (либо в локальной файловой системе, либо в удаленном хранилище). Распространенным анти-шаблоном является встраивание этих параметров конфигурации в исходный код, что приводит к следующим проблемам:
- Двусмысленность. Нераскрытие внешних систем скрывает важные детали реализации, которые могут запутать соавторов: где находятся данные? Нужен ли этому проекту доступ к кластеру Kubernetes?
- Непрозрачность. Если нет единого места для хранения временных файлов, каждый соавтор может начать хранить результаты в произвольных местах, что затруднит организацию проекта.
- Избыточность. Ресурсы инфраструктуры (например, хранилище данных, кластер Kubernetes), скорее всего, будут использоваться несколько раз, что приведет к дублированию конфигурации, если параметры конфигурации не централизованы.
ДА: централизовать пути и параметры конфигурации
…тогда читаем оттуда:
НЕТ: жестко заданные значения сделают вашу жизнь сложнее
3. Надежно храните учетные данные
Вы никогда не должны хранить учетные данные базы данных (или любые другие типы учетных данных) в текстовых файлах (которые включают .py
исходный код или любые файлы конфигурации).
ДА: безопасно хранить свои учетные данные (в этом примере используется библиотека keyring
)
Затем в ваших сценариях:
Библиотека keyring
использует хранилище учетных данных вашей ОС, более безопасное, чем обычный текстовый файл. В предыдущем примере хранится только пароль, но вы можете сохранить любое другое поле.
НЕТ: учетные данные хранятся в исходном файле
4. Содержите свой репозиторий в чистоте, используя файл .gitignore
Скорее всего, вы храните свой код в репозитории git. Поэтому важно указать файл .gitignore
, чтобы он оставался чистым. Отсутствие этого файла может сделать ваш репозиторий ненужным беспорядком и нарушить проект для ваших коллег (например, если вы зафиксируете какой-либо файл конфигурации, зависящий от пользователя).
Что включить в файл .gitignore
? Во-первых, возьмите Python template from Github, который содержит почти все, что вам нужно. Затем убедитесь, что вы также включили общие расширения, чтобы избежать случайной фиксации данных или файлов конфигурации. Например, если исходные данные поступают в форматах CSV/JSON и у вас есть файлы конфигурации YAML, зависящие от пользователя, добавьте следующие строки:
Если вы уже зафиксировали некоторые из этих файлов, вы можете удалить их с помощью следующих команд:
Обратите внимание, что это не удалит файлы из предыдущих коммитов. Делать это из git
— это интерфейс командной строки, но инструмент BFG упрощает этот процесс.
5. Автоматизируйте рабочий процесс данных от начала до конца
Проект данных обычно требует нескольких преобразований данных. Поэтому автоматизация всего процесса необходима для обеспечения воспроизводимости кода. Отсутствие полностью автоматизированного анализа вызовет проблемы в будущем. Например, предположим, что вам нужно переобучить модель в проекте, написанном кем-то другим, поэтому вы видите следующую строку в README.md
:
Но тогда получите такую ошибку:
Что ж, имеет смысл создавать функции перед попыткой обучить модель, но в данный момент вам будет интересно, как это сделать.
Ploomber упрощает автоматизацию рабочих процессов с данными, вы можете добавить столько сценариев или блокнотов, сколько хотите, а затем организовать выполнение с помощью:
6. Объявите программные зависимости
Проекты Data Science обычно зависят от сторонних пакетов (например, pandas, scikit-learn); если их не указать, у любого нового соавтора возникнут проблемы. Сколько раз вы видели эту ошибку?
Хотя сообщение об ошибке очень описательное, рекомендуется задокументировать все зависимости, чтобы каждый мог быстро приступить к работе. В Python вы можете добиться этого с помощью файла requirements.txt
, который содержит имя пакета в каждой строке. Например:
Затем вы можете установить все зависимости с помощью:
Если вы используете Ploomber, вы можете использовать следующую команду, которая позаботится о настройке виртуальной среды и установке всех заявленных зависимостей:
7. Разделите зависимости производства и разработки
Лучше свести количество зависимостей к минимуму. Чем больше зависимостей требуется вашему проекту, тем выше вероятность возникновения проблем с установкой. Способ упростить установку — разделить наши зависимости на две группы:
- Пакеты, необходимые для запуска конвейера — это пакеты, необходимые для запуска конвейера в рабочей среде.
- Необязательные пакеты, необходимые для разработки конвейера. Это дополнительные пакеты, необходимые для работы над проектом (например, создание документации, запуск исследовательских блокнотов Jupyter, выполнение тестов и т. д.), но они не нужны в рабочей среде. .
В нашем случае вы можете захотеть сохранить два файла зависимостей (скажем, requirements.prod.txt
и requirements.dev.txt
). Затем во время разработки вы можете запустить:
И в производстве:
Важно! При работе в рабочей среде важно объявлять определенные версии каждого пакета; вы можете использовать команду pip freeze
для экспорта файла с определенными версиями каждого установленного пакета. Если вы хотите избавить себя от хлопот, вы можете использовать Ploomber и выполнить ploomber install
, в котором будет храниться отдельный requirements.txt
(dev и prod) с конкретными версиями каждого пакета.
8. Имейте информативный и краткий README
Файл README — это точка входа для всех, кто использует конвейер; важно иметь информативный, чтобы помочь соавторам понять, как исходные файлы сочетаются друг с другом.
Что включить в файл README? Основная информация о проекте, как минимум, включает краткое описание проекта (что он делает?), список источников данных (откуда поступают данные от?) и инструкции по сквозному запуску конвейера.
Пример:
### Boston house value estimation project This project creates a model to predict the value of a house using the Boston housing dataset: https://www.kaggle.com/c/boston-housing #### Running the pipeline ```sh # clone the repo git clone https://github.com/edublancas/boston-housing-project # install dependencies pip install -r requirements.txt # run the pipeline python run.py ```
9. Документация по коду
По мере того как к проекту присоединяются новые участники, документация становится все более важной. Хорошая документация помогает понять, как различные части сочетаются друг с другом. Кроме того, документация поможет вам быстро понять контекст кода, который вы написали некоторое время назад.
Но документация — палка о двух концах; это вызовет проблемы, если они устарели. Так что, как общая рекомендация, делайте свой код простым (хорошие имена переменных, небольшие функции), чтобы другие могли быстро понять его и иметь минимальную документацию для его поддержки.
Как документировать код? Добавьте строку рядом с определением функции (также известную как строка документации), чтобы предоставить высокоуровневое описание и передать как можно больше информации с помощью самого кода.
Существует множество форматов строк документации, из которых вы можете выбирать, но стандартным форматом в научном сообществе Python (используемым в приведенном выше примере) является numpydoc.
После добавления строки документации вы можете быстро получить к ней доступ из Jupyter/IPython следующим образом:
10. Организуйте свой репозиторий в виде иерархической структуры
По мере развития вашего проекта ваша кодовая база будет расти. У вас будет код для изучения данных, загрузки, создания функций, обучающих моделей и т. д. Хорошей практикой является размещение кода в нескольких файлах, и организация этих файлов имеет важное значение; взгляните на следующий пример:
Плоская структура затрудняет понимание того, что происходит и как файлы связаны друг с другом. Поэтому лучше иметь такую иерархическую структуру:
11. Упакуйте свой код
Всякий раз, когда вы запускаете pip install
, Python вносит необходимые корректировки в конфигурацию, чтобы обеспечить доступность пакета для интерпретатора, чтобы вы могли использовать его в любом сеансе Python с помощью операторов import
. Вы можете добиться такой же функциональности для своих служебных функций, создав пакет Python; это дает вам большую гибкость для организации вашего кода, а затем импортировать его куда угодно.
Создание пакета — это просто добавление файла setup.py
. Например, если ваш setup.py
существует под my-project/
, вы сможете установить его следующим образом:
Примечание. Мы передаем флаг --editable
для Python, чтобы перезагрузить содержимое пакета, поэтому вы будете использовать последний код при каждом обновлении кода и запуске сеанса.
После установки пакета вы можете импортировать его, как и любой другой пакет:
Если вы измените some_function
, вам придется перезапустить сеанс, чтобы увидеть ваши изменения (при условии, что вы устанавливаете пакет в редактируемом режиме), если вы используете IPython или Jupyter, вы можете избежать перезапуска сеанса, используя автоперезагрузку:
Подробнее об упаковке Python см. в нашем блоге.
12. Сделайте свои блокноты Jupyter простыми
Блокноты — фантастический способ взаимодействия с данными, но они могут быстро выйти из-под контроля. Если не позаботиться об этом должным образом, вы можете копировать код между блокнотами и создавать большие монолитные блокноты, которые ломаются. Поэтому мы написали целую статью о написании чистых блокнотов проверь это.
13. Используйте модуль logging
(не используйте print
)
Ведение журнала является фундаментальной практикой в разработке программного обеспечения; это делает диагностику и отладку намного более управляемой. В следующем примере показан типичный сценарий обработки данных:
Проблема с приведенным выше примером заключается в использовании оператора print
. print
по умолчанию отправляет поток на стандартный вывод. Итак, если у вас есть несколько операторов print
в каждом файле, ваш терминал будет print
выдавать десятки и десятки сообщений каждый раз, когда вы запускаете конвейер. Чем больше у вас печатных утверждений, тем сложнее увидеть сквозь шум, и в конечном итоге они станут бессмысленными сообщениями.
Вместо этого используйте модуль logging
«. Среди наиболее важных функций ведения журнала: фильтрация сообщений по серьезности, добавление временной метки к каждой записи, включая имя файла и строку, в которой возник вызов ведения журнала, среди прочего. В приведенном ниже примере показано его простейшее использование:
Обратите внимание, что использование logging
требует некоторой настройки. Ознакомьтесь с базовым учебным пособием.
14. Протестируйте свой код
Тестирование конвейеров данных является сложной задачей, но это хорошее вложение времени, поскольку оно окупается в долгосрочной перспективе, поскольку позволяет выполнять итерации быстрее. Хотя это правда, что большая часть кода изменится на экспериментальной стадии вашего проекта (возможно, ваши тесты устареют), тестирование поможет вам добиться более последовательного прогресса.
Мы должны учитывать, что проекты по науке о данных являются экспериментальными, и первая цель — увидеть, выполним ли проект, поэтому вы хотите получить представление как можно скорее. Но, с другой стороны, полное отсутствие тестирования может привести к неправильным выводам, поэтому необходимо найти баланс.
Как минимум убедитесь, что ваш конвейер работает от начала до конца каждый раз, когда вы сливаетесь с основной ветвью. Мы много писали о тестировании проектов данных. Ознакомьтесь с нашей статьей, посвященной тестированию проектов Data Science, и нашей статьей, посвященной особенностям тестирования проектов машинного обучения.
15. Позаботьтесь о качестве кода
Люди чаще читают код, чем пишут его. Чтобы сделать ваш код более читабельным, используйте руководство по стилю.
Руководства по стилю касаются последовательности; они устанавливают стандартный набор правил для улучшения удобочитаемости. Такие правила связаны с максимальной длиной строки, пробелами или именами переменных. Официальное руководство по стилю для Python называется PEP8; если вы откроете ссылку, вы заметите, что это длинный документ. К счастью, есть лучшие способы ускорить процесс.
pycodestyle — это инструмент, который автоматически проверяет ваш код на соответствие правилам PEP8. Он просканирует ваши исходные файлы и покажет вам, какие строки кода не соответствуют PEP8. Хотя pycodestyle ограничивает поиск несовместимых с PEP8 строк для улучшения удобочитаемости, существуют более общие инструменты.
pyflakes — инструмент, который также проверяет наличие возможных ошибок во время выполнения; он может найти синтаксические ошибки или неопределенные переменные.
Если вы хотите использовать и pycodestyle
, и pyflakes
, я предлагаю вам использовать flake8, который сочетает в себе оба плюс еще один инструмент для проверки сложности кода (метрика, связанная с количеством линейно независимых путей).
Вы можете использовать все предыдущие инструменты через их интерфейс командной строки. Например, после установки flake8
вы можете проверить файл через flake8 myfile.py
. Для автоматизации этого процесса текстовые редакторы и IDE обычно предоставляют подключаемые модули, которые автоматически запускают эти инструменты при редактировании или сохранении файла и выделяют несоответствующие строки. Вот инструкция для VSCode.
Автоматическое форматирование
В дополнение к линтерам стилей автоформатеры позволяют исправлять несоответствующие строки. Рассмотрите возможность использования черного. Автоформатеры могут исправить большинство проблем с вашим кодом, чтобы сделать его совместимым; тем не менее, могут быть вещи, которые вам нужно исправить вручную, но они значительно экономят время.
Другие линтеры
Доступны и другие линтеры; один из самых популярных — pylint, он похож на flake8
, но обеспечивает более сложный анализ. Кроме того, есть некоторые другие специальные инструменты, такие как бандит, которые сосредоточены на поиске проблем безопасности (например, жестко запрограммированных паролей). Наконец, если вы хотите узнать больше, посетите веб-сайт Управления качества кода Python на Github.
16. Удалите мертвый и недоступный код
Заведите привычку удалять мертвый (код, выходные данные которого никогда не используются) и недостижимый код (код, который никогда не выполняется).
Пример мертвого кода возникает, когда мы вызываем функцию, но никогда не используем ее вывод:
В нашем предыдущем примере compute_statistics
является мертвым кодом, поскольку мы никогда не используем вывод ( stats
). Итак, удалим его:
Мы избавились от stats = compute_statistics(data)
, но нам нужно проверить, не вызываем ли мы compute_statistics
где-нибудь еще. Если нет, мы должны удалить его определение.
Очень важно удалить код на месте, потому что чем больше времени проходит, тем сложнее найти такие проблемы. Некоторые библиотеки помогают выявлять мёртвый код, но лучше делать это сразу, когда мы его замечаем.
Заключительные комментарии
Надеюсь, эти советы помогут вам улучшить рабочий процесс. Как только вы привыкнете к ним, вы увидите, что поддержка вашей кодовой базы повышает вашу производительность в долгосрочной перспективе. Пишите нам в Slack, если у вас есть комментарии или вопросы.
Первоначально опубликовано на ploomber.io