Разработка программного обеспечения для науки о данных

О написании чистых конвейеров данных

Рекомендации по организации логики анализа данных

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

У этих монолитных конвейеров много проблем: они усложняют совместную работу, потому что большое количество логики концентрируется на одной и той же задаче. Во-вторых, вам нужно просмотреть исходный код, чтобы найти то, что вам нужно. Монолитные пайплайны также сложнее тестировать, так как нет четких ожиданий на выходе каждой задачи. Наконец, они неэффективны в вычислительном отношении, поскольку не полностью используют распараллеливание. Распространенная причина, с которой мы столкнулись, когда специалисты-практики строят конвейер такого типа, заключается в том, что используемая ими структура настолько сложна, что добавление новой задачи требует больших усилий, поэтому они минимизируют количество задач, чтобы добиться результата. Если вы столкнулись с этой проблемой, переключитесь на другой фреймворк, который позволяет быстро создавать детализированные задачи (Ploomber — отличный выбор).

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

Что такое конвейер данных?

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

Наш пример конвейера загружает некоторые данные, а затем очищает их. Обратите внимание, что конвейеры также называются рабочими процессами.

Что такое задача?

Задача — это наименьший компонент в конвейере. В зависимости от используемой вами структуры задачи могут принимать разные формы. Например, в Luigi каждый из них является методом в классе. В Airflow это экземпляр оператора. Наконец, в Ploomber задачей может быть функция, скрипт (Python, R, SQL) или блокнот. Стрелка показывает, как данные перемещаются от одной задачи к другой. Но имейте в виду, что механизм отличается от одной структуры к другой. В некоторых случаях это может быть через файлы или через память.

Написание чистых конвейеров данных

Концепции конвейера и задачи просты, но может быть сложно решить, что представляет собой задача при применении идеи к реальной проблеме. Вот наши три рекомендации:

  1. Создайте одну ветвь конвейера для каждого набора данных
  2. Создайте одну задачу для загрузки данных и одну для очистки
  3. При объединении наборов данных создайте новую задачу

Эти три простых правила позволят вам создавать более надежные и удобные в сопровождении пайплайны; давайте сделаем быстрый пример, чтобы проиллюстрировать нашу точку зрения. Допустим, вы работаете с музыкальными данными. У вас может быть таблица artists, таблица songs и таблица genres. Следуя рекомендациям, у нас получается такой пайплайн:

Каждая таблица содержит задачу load-* и задачу clean-*, и каждая часть представляет собой ветвь конвейера. Обратите внимание, что под ветвью мы подразумеваем набор задач в вашем конвейере (например, load-artists и clean-artists являются ветвями), не путайте с веткой git. Как только мы закончим очистку наших данных, мы можем понять, что нам нужно агрегировать песни на уровне исполнителя, чтобы получить статистику воспроизведения. Следовательно, мы создаем задачу artist-plays. Наконец, мы визуализируем наши данные в задаче visualize.

Следование перечисленным выше правилам позволяет нам четко определить ответственность за каждую задачу. Например, мы знаем, что задача load-* состоит в загрузке данных и больше ничего. clean-* очищает заданный набор данных, а artist-plays объединяет artists и songs. Кроме того, предположим, что мы обнаруживаем, что данный набор данных не нужен для нашего анализа. Мы можем быстро обрезать наш конвейер, поскольку нам нужно удалить только ветку, соответствующую такому набору данных.

Такая организация конвейеров упрощает совместную работу; например, если наши коллеги захотят изучить данные artists, они поймут, что лучше всего начать с задачи clean-artists, поскольку она дает чистые данные artists. Кроме того, если кто-то захочет внести свой вклад в часть очистки данных, он будет знать, какую задачу изменить в зависимости от набора данных, который он хочет очистить. Эти четкие определения ответственности каждой задачи уменьшают дублирование работы: неоднозначные определения приводят к дублированию логики, разбросанной по задачам.

Наконец, всякий раз, когда мы хотим агрегировать два или более набора данных, важно создать новую задачу, единственной целью которой является создание этого нового агрегированного набора данных. По сути, эта новая задача заключается в создании производного набора данных; так как мы прямо говорим об этом, другие люди могут использовать его для надстройки поверх него. Например, мы могли бы включить в задачу visualize логику агрегации данных, но тогда люди не могли бы узнать, что у нас уже есть логика агрегации песен на уровне исполнителя.

Написание чистых конвейеров машинного обучения

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

  1. Одна задача на группу функций
  2. Одна задача для консолидации обучающей выборки
  3. Одна ветвь для каждого типа эксперимента, каждая ветвь имеет две задачи: обучение и оценка
  4. По желанию задание на выбор лучшей модели

При написании функций для модели машинного обучения мы обычно можем их сгруппировать. Эта группировка субъективна, но она помогает нам организовать конвейер. Самый простой способ сгруппировать объекты — по их входным данным. Например, если вы используете набор данных clean-artists в качестве входных данных, вы можете создать одну задачу, которая генерирует функции и назвать ее features-artists. Однако в тех случаях, когда вы можете сгенерировать множество признаков из одних и тех же входных данных, вы можете захотеть сгруппировать их более детально. Например, иногда у вас могут быть функции, использующие одни и те же входные данные. Тем не менее, каждый столбец признаков может быть рассчитан с использованием другого преобразования (например, порогового значения или аффинного преобразования). Если вы можете сгруппировать функции в зависимости от высокоуровневого определения преобразования, вы можете определить группу функций.

Например, вы можете агрегировать данные о песнях на уровне исполнителя, а затем создавать функции на основе агрегации количества: сколько песен у исполнителя, сколько песен за 5 минут, сколько песен было выпущено. до 2020 года и т. д. Здесь количество – это преобразование, позволяющее создать группу объектов со всеми функциями подсчета. Кроме того, мы рекомендуем добавлять префикс ко всем столбцам в группе функций; это позволит вам легко выбрать (или отменить выбор) группу полностью на основе префикса при обучении модели.

После функциональных задач создайте еще одну задачу, чтобы объединить все функции для создания обучающего набора; затем параллельные обучающие задачи используют это в качестве входных данных. Чтобы найти лучшую модель, вам может понадобиться обучить многие из них, поэтому вы можете определить задачу обучения как задачу, которая обучает модель определенного типа (например, XGBoost), и повторить ту же логику для других моделей ( например, случайный лес). Примечание. Если вы хотите оптимизировать параметры и сделать выбор модели, используйте вложенную перекрестную проверку.

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

Совет: отличный способ упростить этап обучения и оценки — создать шаблоны задач. Например, у вас может быть tasks.py, который принимает тип модели в качестве входного параметра и повторно использует тот же файл для создания задач train-xgboost, train-random-forest, train-catbost, просто передавая другой параметр в файл task.py. Вот пример того, как этого добиться с помощью Ploomber. Наконец, вы можете повторно использовать один и тот же сценарий evaluate.py, если вы сделаете так, чтобы каждая задача train-* возвращала свои выходные данные в одном и том же формате (пример: файл model.pkl и набор данных с прогнозами, прошедшими перекрестную проверку).

Поддержание ваших трубопроводов в чистоте

На практике соблюдение этих правил требует дисциплины. Например, предположим, что вы работаете над добавлением функций на уровне художника (то есть задача features-artists). Вы можете столкнуться с тем, что данные не так чисты, как вы ожидали, поэтому у вас может возникнуть соблазн добавить несколько дополнительных очищающих кодов в задачу feature-artists; не делайте этого. Вместо этого перейдите к соответствующей задаче очистки ( clean-artists), добавьте туда новый код очистки, затем продолжайте работать над кодом функций в feature artists. Хранение логики очистки в одном месте имеет еще одно преимущество: все последующие задачи выиграют от нового кода очистки. В нашем случае и feature-artists, и artist-plays зависят от clean-artists, поэтому мы гарантируем, что все потребители получат выгоду от самой чистой версии, сосредоточив логику очистки на clean-artists.

Соблюдение правил упрощает обслуживание, поскольку расширение конвейера становится легкой задачей. Вы хотите вычислить особенности жанра на уровне исполнителя? Захватить clean-artist и clean-genres? Вы нашли проблему в наборе данных песен? Откройте запрос на вытягивание, чтобы изменить задачу clean-songs.

Если вы считаете, что ваш вариант использования не подходит под эти определения, отправьте нам запрос, и мы с радостью поможем вам разработать чистый конвейер данных.

Первоначально опубликовано на ploomber.io.