В качестве науки о науке и машине приобретают более широкое усыновление отрасли, практикующие понимают, что развертывание продуктов данных поставляется с высокими (и часто неожиданными) стоимостью обслуживания. Как Скулье и соавторы Спорите в их известной работе:
(ML Systems) имеют все проблемы технического обслуживания традиционного кода плюс дополнительный набор проблем с ML.
Как ни парадоксально, хотя данные, интенсивные данные имеют более высокую стоимость обслуживания, чем их традиционные программные коллеги, лучшие практики разработки программного обеспечения в основном упущены из виду. Исходя из моих разговоров с коллегами-учеными данных, я считаю, что такие практики игнорируются в первую очередь, потому что они воспринимаются как ненужные дополнительные работы из-за неправильных стимулов.
Проекты данных Конечная цель – влиять на диск, но это влияние действительно трудно оценить во время развития. Какое влияние будет приборная панель? А как насчет влияния прогностической модели? Если продукт еще не в производстве, трудно оценить влияние бизнеса, и мы должны прибегать к метрикам доверенности: для инструментов для принятия решений бизнес-заинтересованные стороны могут субъективно судить, насколько новая приборная панель может помочь им улучшить свои решения для Предсказуемая модель, мы могли бы придумать грубую оценку, основанную на производительности модели.
Это приводит к тому, что инструмент (например, приборная панель или модель) воспринимается как уникальная ценная часть в конвейере данных, поскольку именно то, что действует метрика прокси. Вследствие, в большинстве случаев и усилия делаются в попытке улучшить этот окончательный результат, в то время как все предыдущие промежуточные шаги получают меньше внимания.
Если проект доставлен на производство, в зависимости от общего качества кода, команда может придеться рефактору много кодовой базы, чтобы быть готовыми к производству. Этот рефакторинг может варьироваться от выполнения небольших улучшений до полного капитального ремонта, тем больше изменений проект проходит, тем сложнее будет воспроизводить оригинальные результаты. Все это может серьезно задержать или привести к риску запуска.
Лучшим подходом – это всегда держать наш код в развертывании (или почти) в любое время. Это требует рабочего процесса, который гарантирует, что наш код проверяется, и результаты всегда воспроизводимы. Эта концепция называется непрерывной интеграцией и является широко принятой практикой в разработке программного обеспечения. Этот пост блога представляет адаптированную процедуру CI, которая может быть эффективно применена в проектах данных с существующими инструментами с открытым исходным кодом.
- Структура вашего трубопровода в нескольких задачах, каждый экономят промежуточные результаты на диск
- Реализовать свой трубопровод таким образом, чтобы вы могли параметризировать его
- Первый параметр должен пробовать необработанные данные, чтобы обеспечить быстрые концептуальные прогоны для тестирования
- Вторым параметром следует изменить местоположение артефактов в отдельную среду тестирования и производства
- На каждом толчке, Служба CI управляет модульными тестами, которые проверяют логику внутри каждой задачи
- Затем трубопровод выполнен с образцом обработки данных и интеграционные тесты проверки целостности промежуточных результатов
Непрерывная интеграция (CI) – это практика разработки программного обеспечения, где небольшие изменения постоянно интегрированы в кодовую базу проекта. Каждое изменение автоматически проверяется, чтобы гарантировать, что проект будет работать, как ожидается, для конечных пользователей в производственной среде.
Чтобы сопоставить разницу между традиционным программным обеспечением и проектом данных, мы сравниваем два случая использования: инженер-программист, работающий на веб-сайте электронной коммерции, и ученый-ученый с данными, разработающий конвейеров данных, который выводит отчет с ежедневными продажами.
В случае использования портала электронной коммерции, производственная среда – это живой веб-сайт и конечные пользователи – это люди, которые его используют; В корпусе используемого трубопровода данных производственная среда является сервером, который запускает ежедневный трубопровод для генерации отчета, а конечные пользователи являются бизнес-аналитиками, которые используют отчет для информирования решений.
Мы определяем конвейер данных в качестве ряда упорядоченных задач, входы которых являются набором данных, промежуточные задачи генерируют преобразованные наборы данных (сохраненные на диск), и окончательная задача создает продукт данных в этом случае отчет с ежедневными продажами (но это может быть что-то еще, как модель обучения машины). Следующая диаграмма, показывает наш ежедневный пример трубопровода отчета:
Каждый синий блок представляет собой задачу трубопровода, зеленый блок представляет скрипт, который генерирует окончательный отчет. Оранжевые блоки содержат схемы для сырья. Каждая задача генерирует один продукт: синие блоки генерируют файл данных (но это также может быть таблицы/представления в базе данных), когда зеленый блок генерирует отчет с диаграммами и таблицами.
Как я уже упоминал в прелюдии, последняя задача в конвейере данных часто является тем, что привлечет наибольшее внимание (например, тренированная модель в машинном обучении). Неудивительно, что существующие статьи на CI для науки/изучения данных/машины также сосредоточены на этом; Но для эффективного применения структуры CI мы должны думать с точки зрения всей вычислительной цепочки: от получения необработанных данных для доставки продукта данных. Неспособность признать, что конвейер данных имеет более богаче структуры, приводит к тому, что ученые данные сосредоточены на самом конце и игнорируют качество кода в остальных задачах.
По моему опыту большинство ошибок генерируют по пути, даже хуже, во многих случаях ошибки не сломают трубопровод, но загрязняют ваши данные и компромисс ваши результаты. Каждый шаг по пути следует указать равное значение.
Давайте сделаем все более бетонные с описанием предложенного рабочего процесса:
- Ученый данные толкает изменения кода (например, модифицирует одну из задач в трубопроводе)
- Нажатие запускателей CI Service для запуска коннемонов трубопровода и тестируйте каждый генерированный артефакт (например, один тест мог проверить, что все строки в Customers
Customersтаблицы не пустыеCustomer_idценить) - Если проходят тесты, код кода следует
- Если изменения утверждены рецензентом, код объединен
- Каждое утро, «производственный» трубопровод (последний коммит в главной отрасли) работает с конца до конца и отправляет отчет на бизнес-аналитики
Такой рабочий процесс имеет два первичных преимущества:
- Раннее обнаружение ошибок: ошибки обнаруживаются в этапе развития, а не производства
- Всегда готово к производству: Поскольку мы требуем изменения кода, чтобы пройти все тесты, прежде чем интегрировать их в главную ветку, мы гарантируем, что мы можем непрерывно развернуть нашу последнюю стабильную функцию, просто развернув новейшие фиксаторы в главной отрасли
Этот рабочий процесс – какие программные инженеры делают в традиционных программных проектах. Я называю этот идеальный рабочий процесс, потому что это то, что мы будем делать, если бы мы могли выполнить конец трубопроводы в разумном количестве времени. Это не относится к большому количеству проектов из-за масштаба данных: если наш трубопровод займет несколько часов, чтобы запустить конец к концу, это невозможно запустить его каждый раз, когда мы делаем небольшое изменение. Вот почему мы не можем просто применить стандартный рабочий процесс CI (шаги от 1 до 4) в науку на данных. Мы сделаем несколько изменений, чтобы сделать его возможным для проектов, в которых время работы – это проблема.
CI позволяет разработчикам постоянно интегрировать изменения кода, запустив автоматизированные тесты: если какой-либо из тестов выходит из строя, отказ отбирается. Это гарантирует, что у нас всегда есть рабочий проект в главной отрасли.
Традиционное программное обеспечение разработано в небольших, в основном независимых модулях. Это разделение естественно, так как имеются четкие границы среди компонентов (например, зарегистрироваться, выставление счетов, уведомлений и т. Д.). Возвращаясь к корпусу веб-сайта электронной коммерции, список TO-DO MACHINGHER может выглядеть следующим образом:
- Люди могут создавать новые учетные записи, используя электронную почту и пароль
- Пароли могут быть восстановлены, отправив сообщение на зарегистрированный адрес электронной почты
- Пользователи могут войти в систему, используя ранее сохраненные учетные данные
Как только инженер записывает код для поддержки таких функций ( или даже раньше! ) Он/она удостоверится, что код работает, написав несколько тестов, которые будут выполнять тестирование кода и проверять его, как ожидалось,
from my_project import create_account, users_db
def test_create_account():
# simulate creating a new account
create_account('someone@ploomber.io', 'somepassword')
# verify the account was created by qerying the users database
user = users_db.find_with_email('someone@ploomber.io')
assert user.exists()
Но тестирование подразделения не является единственным типом тестирования, так как мы увидим в следующем разделе.
Уровни тестирования
Есть четыре уровня программного тестирования. Важно понимать различия для разработки эффективных испытаний на наши проекты. Для этого поста мы сосредоточимся на первых двух.
Установка тестирования
Фрагмент, который я показал в предыдущем разделе, называется единичной тестом. Установка модулей Убедитесь, что один единица работает. Там нет строгого определения единица Но часто эквивалентно призвать одну процедуру, в нашем случае мы тестируем create_account процедура.
Тестирование подразделения эффективно в традиционных программных проектах, поскольку модули предназначены для в значительной степени независимы друг от друга; По блоку тестирования их отдельно, мы можем быстро определить ошибки. Иногда новые изменения в разрыве тестирования не из-за изменений самих изменений, но потому что у них есть побочные эффекты, если модуль независим, он дает нам гарантию, что мы должны искать ошибку в рамках модуля.
Утилита имеющих процедур состоит в том, что мы можем повторно использовать их, параметризуя их поведение с помощью входных параметров. Входное пространство для нашего create_account Функция – это сочетание всех возможных адресов электронной почты и все возможные пароли. Существует бесконечное количество комбинаций, но разумно сказать, что если мы проверяем наш код против представительного количества случаев, мы можем заключить процедуру работы (и если мы найдут случай, когда это не так, мы исправляем код и добавить Новый тестовый случай). На практике это сводится к процедуре тестирования против набора репрезентативных случаев и известных краевых случаев.
Учитывая, что тесты запускаются автоматически, нам нужен критерии Pass/Fail для каждого. В инженерии программного обеспечения жаргон это называется Тест Oracle Отказ Подойдя с хорошими испытательными оракулами, необходима для тестирования: тесты полезны в той мере, в которой они оценивают правильный результат.
Тестирование интеграции
Второй уровень тестирования – это интеграционные тесты. Установки тесты немного упрощены, поскольку они самостоятельно тестируют единицы самостоятельно, это упрощение полезно для эффективности, так как нет необходимости запускать всю систему для тестирования небольшой части.
Но иногда ошибки возникают при входов и выводит границы перекрестного модуля. Несмотря на то, что наши модули в значительной степени независимы, им все равно приходится взаимодействовать друг с другом в какой-то момент (например, модуль счетов должен взаимодействовать с модулем уведомлений для отправки квитанции). Чтобы поймать потенциальные ошибки во время этого взаимодействия, мы используем тестирование интеграции.
Письменные интеграционные тесты являются более сложными, чем писать модульные тесты, так как больше элементов для рассмотрения. Вот почему традиционные программные системы предназначены для того, чтобы быть свободно связан Ограничивая количество взаимодействий и избегая побочных эффектов кроссмодула. Как мы увидим в следующем разделе, тестирование интеграции важно для тестирования проектов данных.
Эффективное тестирование
Письменные тесты – это искусство собственного искусства, цель тестирования – это поймать как большинство ошибок, как мы можем во время развития, поэтому они не появляются в производстве. Таким образом, тесты моделируют действия пользователя и проверяют, что система ведет себя как ожидалось, по этой причине эффективный тест – это тот, который имитирует реалистичные сценарии и соответствующим образом оценивает ли система правильной вещи или нет.
Эффективный тест должен соответствовать четырем требованиям:
1. Имитационное состояние системы должно быть представитель системе, когда пользователь взаимодействует с ним
Целью тестов является предотвращение ошибок в производстве, поэтому мы должны точно представлять состояния системы как можно ближе. Несмотря на то, что наша веб-сайт электронной коммерции может иметь десятки модулей (регистрация пользователя, биллинг, список продуктов, поддержка клиентов и т. Д.), Они предназначены как можно более независимыми, это облегчает имитацию нашей системе. Мы могли бы утверждать, что имея базу данных фиктивных баз данных для имитации системы, когда новый пользователь подписывается, существование или отсутствие любого другого модуля не должны иметь никакого эффекта в тестированном модуле. Чем больше взаимодействий между компонентами, тем труднее тестировать реалистичный сценарий в производстве.
2. Входные данные являются представитель реального ввода пользователя
При тестировании процедуры мы хотим знать, если учесть вклад, процедура делает то, что она должна делать. Поскольку мы не можем запустить каждый возможный вход, мы должны подумать о достаточном случаях, которые представляют обычную работу, а также возможные ключевые случаи (например, что происходит, если пользователь подписывает неверный адрес электронной почты). Чтобы проверить наше create_account Процедура, мы должны пройти несколько обычных учетных записей электронной почты, но и некоторые недействительные и истинные, которые он либо создает учетную запись, либо показывает соответствующее сообщение об ошибке.
3. Соответствующий тест Oracle.
Как мы упоминали в предыдущем разделе, тест Oracle – наши критерии Pass/Fail. Прощественнее и меньшая процедура для тестирования, тем легче подняться с одним. Если мы не проверяем правильный результат, наш тест не будет полезен. Наш тест на create_account Подразумевает, что проверка таблицы пользователей в базе данных является подходящим способом оценки нашей функции.
4. Разумное время выполнения
Во время прогона тестов разработчик должен подождать, пока результаты не вернутся. Если тестирование медленное, нам придется еще долго ждать, что может привести к разработчикам, просто игнорируйте систему CI в целом. Это приводит к изменению кода, чтобы накапливать отладку намного сложнее (легче найти ошибку, когда мы изменили 5 строк, чем когда мы изменились 100)
В предыдущих разделах мы описали первые два уровня проверки программного обеспечения и четыре свойства эффективного теста. В этом разделе обсуждаются, как адаптировать методы тестирования от традиционного разработки программного обеспечения для проектов данных.
Устройство тестирования для трубопроводов для передачи данных
В отличие от модулей в традиционном программном обеспечении, наши трубопроводные задачи (блоки на нашей диаграмме) не являются независимыми, у них есть логическое исполнение. Чтобы точно представлять состояние нашей системы, мы должны уважать такой заказ. Поскольку вход для одной задачи зависит от вывода из их восходящих зависимостей, корневая причина для ошибки может быть либо в сбое, либо в любой задаче выше по потоку. Это не хорошо для нас, так как это увеличивает количество потенциальных мест для поиска ошибок, абстрагированная логика в меньших процедурах и тестировании подразделения им помогает снизить эту проблему.
Скажи, что наша задача add_product_information Выполняет некоторую очистку данных перед присоединением к продажам с продуктами:
import pandas as pd
from my_project import clean
def add_product_information(upstream):
# load
sales = pd.read_parquet(upstream['sales'])
products = pd.read_parquet(upstream['products'])
# clean
sales_clean = clean.fix_timestamps(sales)
products_clean = clean.remove_discontinued(products)
# join
output = sales_clean.merge(products_clean, on='product_id')
output.to_parquet('clean/sales_w_product_info.parquet')
Мы абстрагировали логику уборки в двух подпроцентах clean.fix_timestamps и Clean.Remove_Discontineed , ошибки в любом из подпроцертов будут распространяться на вывод и в результате любых недостаточных задач. Чтобы предотвратить это, мы должны добавить несколько модульных тестов, которые проверяют логику для каждой подпроцерки в изоляции.
Часто трубопроводные задачи, которые преобразуют данные, состоят из всего в нескольких вызовах с небольшим количеством пользовательских логики к внешним пакетам (например, Pandas). В таких случаях тестирование единиц не будет очень эффективным. Представьте, что одна из задач в вашем трубопроводе выглядит так:
# cleaning
# ...
# ...
# transform
series = df.groupby(['customer_id', 'product_category']).price.mean()
df = pd.DatFrame({'mean_price': series})
return df
Предполагая, что вы уже едините, протестировали логику очистки, нет большого удачного теста о ваших преобразованиях, написание модульных тестов для таких простых процедур не является хорошей инвестицией вашего времени. Вот где приходит тестирование интеграции.
Тестирование интеграции для трубопроводов для передачи данных
Наш трубопровод течет входы и выходы, пока не генерирует конечный результат. Этот поток может сломаться, если ожидания задач введены не соответствия (например, имена столбцов), кроме того, каждая преобразование данных кодирует определенные предположения Мы Сделайте о данных. Интеграционные тесты помогают нам и верен, что выводы правильно проходят через трубопровод.
Если бы мы хотели проверить Группа по Преобразование, показанное выше, мы могли бы запустить задачу трубопровода и оценивать наши ожидания с использованием выходных данных:
# since we are grouping by these two keys, they should be unique assert df.customer_id.is_unique assert df.product_category.is_unique # price is always positive, mean should be as well assert df.mean_price > 0 # check there are no NAs (this might happen if we take the mean of # an array with NAs) assert not df.mean_price.isna().sum()
Эти четыре утверждения быстро пишут и четко кодируют наши выходные ожидания. Давайте теперь посмотрим, как мы можем написать эффективные интеграционные тесты подробно.
Состояние системы
Как мы упоминали в предыдущем разделе, трубопроводные задачи имеют зависимости. Чтобы точно представлять состояния системы в наших тестах, мы должны уважать заказ выполнения и запустить наши тесты в интеграции после выполнения каждой задачи, давайте изменим нашу оригинальную диаграмму, чтобы отразить это:
Тестировать оракуль
Задача при тестировании задач трубопровода состоит в том, что нет одного правильного ответа. При разработке create_user Процедура, мы можем утверждать, что проверяя базу данных для нового пользователя является подходящей мерой успеха, но как насчет процедуры, которая очищает данные?
Нет уникального ответа, потому что концепция Чистые данные зависит от специфики нашего проекта. Лучше всего мы можем сделать, это явный код наших выходных ожиданий как серия тестов. Общие сценарии, которые следует избегать, включающие неверные наблюдения в анализе, нулевые значения, дубликаты, неожиданные имена столбцов и т. Д. Такие ожидания – хорошие кандидаты для тестов интеграции, чтобы предотвратить утечку грязных данных в наш трубопровод. Даже задачи, которые вытягивают необработанные данные, должны быть проверены на обнаружение изменений данных: столбцы удаляются, переименованы и т. Д. Тестирующие необработанные свойства данных помогают нам быстро идентифицировать, когда наши исходные данные изменились.
Некоторые изменения, такие как переименование колонны, сломают наш трубопровод, даже если мы не пишу тест, но явное тестирование имеет большое преимущество: мы можем исправить ошибку в нужном месте и избегать избыточных исправлений. Представьте, что произойдет, если переименование столбца разбивает две нисходящие задания, каждый из которых разрабатывается другим коллегой, как только они столкнутся с ошибкой, они будут соблазнены переименовать столбец в их коде (два позанимания), когда правильный подход исправить в задании вверх по течению.
Кроме того, ошибки, которые нарушают наш трубопровод, должны быть наименьшими нашими заботами, самые опасные ошибки в трубопроводах данных подлые; Они не сломают ваш трубопровод, но будут загрязнять все задачи вниз по течению в тонких способах, которые могут сильно получить анализ данных и даже переворачивать ваш вывод, что является наихудшим возможным сценарием. Из-за этого я не могу подчеркнуть, насколько важны кодировать ожидания данных как часть любого проекта анализа данных.
Задачи трубопроводов не должны быть процедурами Python, они часто будут сценариями SQL, и вы должны проверить их таким же образом. Например, вы можете проверить, что в определенном столбце нет нулей в определенном столбце со следующим запросом:
SELECT NOT EXISTS(
SELECT * FROM some_table
WHERE some_column IS NULL
)
Для процедур, выход которых не является набором данных, поступающий с тестом Oracle, становится сложнее. Общим выходом в трубопроводах передачи данных являются читаемое человеком документы (I.E. Отчеты). Хотя это технически возможно проверить графические выходы, такие как таблицы или диаграммы, это требует дополнительной настройки. Первый (и часто хороший) подход – это подход к установлению устройства, который генерирует визуальный вывод (например, тестирование функции, которая готовит данные для построения вместо фактического сюжета). Если вам интересно тестирование участков, Нажмите здесь Отказ
Реалистичные входные данные и время работы
Мы упомянули, что реалистичные входные данные важны для тестирования. В Data Projects у нас уже есть реальные данные, которые мы можем использовать в наших тестах, однако, прохождение полного набора данных для тестирования невозможно, поскольку трубопроводы данных имеют вычислительную дорогую задачу, которые требуют до конца.
Чтобы уменьшить время работы и сохранить наши входные данные реалистичными, мы передаем образец данных. Как получается этот образец, зависит от специфики проекта. Цель состоит в том, чтобы получить представитель образец данных, свойства которого аналогичны полным набором данных. В нашем примере мы могли бы сделать случайную выборку вчерашних продаж. Затем, если мы хотим проверить определенные свойства (например, что наш трубопровод обрабатывает NAS правильно), мы могли бы либо вставить некоторые NAS в случайном образце, либо использовать другой метод выборки, как Стратифицированная выборка Отказ Проблемки должны произойти только в задачах, которые вытягивают необработанные данные, задачи вниз по течению будут просто обрабатывать любой объем выхода из их восходящих зависимостей.
Отбор проб включен только во время тестирования. Убедитесь, что ваш трубопровод предназначен для легкого переключения этой настройки и продолжать сгенерированные артефакты (тестирование VS Production) четко обозначено:
from my_project import daily_sales_pipeline
def test_with_sample():
# run with sample and stores all artifacts in the testing folder
pipeline = daily_sales_pipeline(sample=True, artifacts='/path/to/output/testing')
pipeline.build()
Вышеупомянутое выше, делает предположение, что мы можем представлять наш трубопровод как «объект трубопровода» и назвать его параметрами. Это очень мощная абстракция, которая делает ваш трубопровод гибким для выполнения в разных настройках. После успешного выполнения задачи вы должны запустить соответствующий тест интеграции. Например, скажем, мы хотим проверить наши add_product_information Процедура, наш трубопровод должен вызывать следующую функцию, которую выполняется одна такая задача:
import pandas as pd
def test_product_information(product):
df = pd.read_parquet(product)
assert not df.customer_id.isna().sum()
assert not df.product_id.isna().sum()
assert not df.category.isna().sum()
assert not (df.price < 0).sum()
Обратите внимание, что мы передаем путь к данным в качестве аргумента к функции, это позволит нам легко переключить путь для загрузки данных. Это важно, чтобы избежать трубопровода, чтобы вмешиваться друг с другом. Например, если у вас есть несколько филиалов Git, вы можете организовать артефакты ветвей в папке под названием /data/{филиал-имя} ; Если вы разделяете сервер с коллегой, каждый из них может спасти своих артефактов в /data/{имя пользователя} Отказ
Если вы работаете с SQL Scripts, вы можете применить такую же тестирование:
def test_product_information_sql(client, relation):
# Assume client is an object to send queries to the db
# and relation the table/view to test
query = """
SELECT EXISTS(
SELECT * FROM {product}
WHERE {column} IS NULL
)
"""
assert not client.execute(query.format(relation=relation, column='customer_id'))
assert not client.execute(query.format(relation=relation, column='product_id'))
assert not client.execute(query.format(relation=relation, column='category'))
Помимо отбора проб, мы можем еще больше ускорить тестирование с помощью рабочих задач параллельно. Хотя существует ограниченное количество распараллеливания, которое мы можем сделать, которая дается структурой трубопровода: мы не можем запускать задачу, пока их заканчивающиеся зависимости.
Параметрированные трубопроводы и выполнение испытаний при выполнении задач поддерживаются в нашей библиотеке Ploomber Отказ
Проекты данных имеют гораздо больше неопределенности, чем традиционное программное обеспечение. Иногда мы даже не знаем, что проект даже возможен технически, поэтому мы должны инвестировать некоторое время, чтобы дать ответ. Эта неопределенность приходит в ущерб хорошей практике программного обеспечения: мы хотим снизить влияние неопределенности и оценить влияние проекта, что сделало столь же прогресс, как мы можем ответить на вопросы о возможности, таким образом, хорошие программные практики (такие как тестирование), как правило, не воспринимаются как фактический прогресс и обычно упускаются из виду.
Моя рекомендация состоит в том, чтобы постепенно увеличивать тестирование, когда вы добились прогресса. На ранних этапах важно сосредоточиться на интеграционных тестах, поскольку они быстро реализуют и эффективны. Наиболее распространенными ошибками в преобразованиях данных легко обнаружить использование простых утверждений: Убедитесь, что идентификаторы уникальны, нет дубликатов, нет пустых значений, столбцы падают в ожидаемых диапазонах. Вы будете удивлены, сколько ошибок вы поймаете с несколькими строками кода. Эти ошибки очевидны, как только вы посмотрите на данные, но даже не сломаете свой трубопровод, они просто будут производить неправильные результаты, тестирование интеграции предотвращает это.
Во-вторых, используйте пакеты на полках максимально возможным, особенно для высоко сложных преобразований данных или алгоритмов; Но остерегайтесь качества и одолжения поддерживали пакеты, даже если они не предлагают современные характеристики. Сторонние пакеты приходят со своими собственными тестами, которые снижают работу для вас.
Также могут быть детали, которые не так критичны или очень трудно проверить. Процедуры построения являются распространенным примером: если вы не выпускаете весьма индивидуальный сюжет, мало пользуется для тестирования небольшой функции построения, которая просто звонит MATPLOTLIB и немного настраивает ось. Сосредоточьтесь на тестировании ввода, который попадает в функцию построения.
Когда ваш проект созревает, вы можете начать сосредоточить внимание на увеличении вашего охвата тестирования и оплата некоторыми техническими долгами.
Когда тесты не удаются, пора отлаживать. Наша первая строка защиты – это ведение журнала: всякий раз, когда мы запускаем наш трубопровод, мы должны создавать соответствующие набор записей для регистрации для нас. Я рекомендую вам взглянуть на Регистрация Модуль в стандартной библиотеке Python Что обеспечивает гибкую структуру для этого (не используйте Print для ведения журнала), хорошая практика заключается в том, чтобы сохранить файл с журналами из каждого запуска трубопровода.
Во время ведения журнала может наметить вас, где проблема, разработав ваш трубопровод для легкой отладки, имеет решающее значение. Напомним наше определение конвейера данных:
Серия упорядоченных задач, входы которых представляют собой наборы данных RAW, промежуточные задачи генерируют преобразованные наборы данных (сохраненные на диск ) и окончательная задача создает продукт данных.
Сохранение всех промежуточных результатов в памяти определенно быстрее, так как дисковые операции медленнее, чем память. Тем не менее, сохранение результатов к диску зарабатывает отладки намного проще. Если мы не упорствуем промежуточные результаты на диск, отладки означает, что мы должны снова повторно выполнить наш трубопровод для репликации условий ошибок, если мы сохраним промежуточные результаты, нам просто нужно перезагрузить зависимости от вышеупомянутых. Давайте посмотрим, как мы можем отладить наше add_product_information Процедура с использованием Отладчик Python Из стандартной библиотеки:
import pdb
pdb.runcall(add_product_information,
upstream={'sales': path_to_sales, 'product': path_to_product})
Поскольку наши задачи изолированы друг от друга и взаимодействуют только через входы и выходы, мы можем легко повторить условия ошибки. Просто убедитесь, что вы передаете правильные входные параметры к вашей функции. Вы можете легко применить этот рабочий процесс, если вы используете Возможности отладки PloMber.
Отладка сценариев SQL сложнее, так как у нас нет отладки, как мы делаем в Python. Моя рекомендация состоит в том, чтобы сохранить ваши сценарии SQL разумным размером: после того, как сценарий SQL становится слишком большим, вы должны рассмотреть разрыв в двух отдельных задачах. Организация вашего кода SQL, используя С Помогает с читаемостью и может помочь вам отлаживать сложные заявления:
WITH customers_subset AS (
SELECT * FROM customers WHERE ..
), products_subset AS (
SELECT * FROM products WHERE ...
),
SELECT *
FROM customers
JOIN products
USING (product_id)
Если вы найдете ошибку в скрипте SQL, организованный, как это, вы можете заменить последнее Выберите Заявление для чего-то вроде Выберите * от Customers_subset взглянуть на промежуточные результаты.
В традиционном программном обеспечении тесты работают только в среде разработки, предполагается, что если кусок кода достигает производства, он должен был проверен и работает правильно.
Для трубопроводов для передачи данных интеграционные тесты являются частью самой трубопровода, и вам решать, следует ли выполнять их или нет. Две переменные, которые играют здесь, – это время отклика и конечные пользователи. Если частота запуска низкая (например, трубопровод, который выполняет ежедневно), а конечные пользователи являются внутренними (например, бизнес-аналитики), вы должны рассмотреть возможность сохранения тестов в производстве. Обучающий трубопровод ML также следует этой структуре, он имеет низкую частоту работы, потому что она выполняет по требованию (всякий раз, когда вы хотите тренировать модель), а конечные пользователи вы и любой другой человек в команде. Это важно, учитывая, что мы проводим наши тесты с образцом данных, выполняющие их с полным набором данных, могут дать другой результат, потому что наш метод отбора проб не зарабатывает определенные свойства в данных.
Другим распространенным (и часто непредвзятым) сценарием являются изменениями данных. Важно, чтобы вы информировали о запланированных изменениях в вышеупомянутых данных (например, миграция на другую складскую платформу) Но есть еще шанс, что вы увидите изменения данных, пока вы не передадите новые данные через трубопровод. В лучшем случае ваш трубопровод поднимет исключение, что вы сможете обнаружить, худший случай, ваш трубопровод будет выполнен в порядке Но вывод будет содержать неправильные результаты. По этой причине важно сохранить ваши интеграционные тесты, работающие в производственной среде.
Нижняя строка: если вы можете позволить трубопроводу задержать свой последний вывод (например, ежедневный отчет о продажах), сохраняйте тесты в производстве и убедитесь, что вы правильно уведомляете о них Самое простое решение заключается в том, чтобы сделать ваш трубопровод отправить вам электронную почту.
Для трубопроводов, где вывод ожидается часто и быстро (например, API) Вы можете изменить свою стратегию. Для некритических ошибок вы можете воспользоваться в систему вместо поднятия исключений, но для критических случаев, в том, что вы знаете, что вы не можете вернуть соответствующие результаты (например, пользователь ввел отрицательное значение для «возраста» столбца), вы должны вернуть соответствующее сообщение об ошибке. Обработка ошибок в производстве является частью Модель мониторинга , который мы рассмотрим предстоящий пост.
Теперь мы пересмотрим рабочий процесс на основе наблюдений от предыдущих разделов. На каждом толчке, тесты на единиц, затем выполняется трубопровод, затем выполняется с образцом данных при каждом выполнении задач, тесты интеграции выполняются для проверки каждого вывода, если все тесты проходят, коммит помечен как успешно. Это конец процесса CI и должен заниматься всего несколько минут.
Учитывая, что мы постоянно проверяем каждый изменение кода, мы сможем развернуть в любое время. Эта идея постоянного развертывания программного обеспечения называется Непрерывное развертывание Это заслуживает выделенного поста, но вот резюме.
Поскольку нам нужно генерировать ежедневный отчет, трубопровод проходит каждое утро. Первый шаг состоит в том, чтобы вытащить (из хранилища репозитория или артефакта) последняя стабильная версия. послал. Если все пойдет хорошо, поправляет трубопровод отчета к бизнес-аналитикам.
Детали реализации
Этот раздел предоставляет общие руководящие принципы и ресурсы для реализации рабочего процесса CI с существующими инструментами.
Установка тестирования
Чтобы установить тестовую логику внутри каждой задачи трубопровода данных, мы можем использовать существующие инструменты. Я настоятельно рекомендую использовать питиш . Он имеет небольшую кривую обучения для базового использования; Но и как вам становится удобнее с этим, я посоветовал исследовать больше своих функций (например Светильники ). Стать энергетическим пользователем любой системы тестирования, поставляется с большими преимуществами, поскольку вы будете инвестировать меньшие временные тесты и максимизировать их эффективность, чтобы поймать ошибки. Продолжайте практиковать, пока писать тесты не станут естественным первым шагом перед записью любого фактического кода. Эта техника написания тестов сначала называется Разработка тестов (TDD) Отказ
Запуск тестов интеграции
Тесты интеграции имеют больше требований к инструментам, поскольку они должны воспользоваться структурой трубопровода данных (запускать задания по порядку), параметризацию (для отбора проб) и выполнение тестирования (прогона прогона после каждой задачи). Был недавний разрыв инструментов управления рабочими процессами, которые могут быть полезны для этого в некоторой степени.
Наша библиотека Ploomber Поддерживает все функции, необходимые для реализации этого рабочего процесса: представляющий ваш трубопровод как Даг , разделение среды dev/test/production, параметризующие трубопроводы, работающие тестовые функции при выполнении задач, интеграция с отладчиком Python, среди других функций.
Внешние системы
В одном сервере разработана много простых в умеренно сложных данных. Эта архитектура позволяет легко содержать и выполнять трубопровод в другой системе: тестировать локально, просто запустите трубопровод и сохранить артефакты в папке по вашему выбору, чтобы запустить его на сервере CI, просто скопируйте исходный код и выполнить Трубопровод там, нет зависимости от любой внешней системы.
Однако для случаев, когда масштабирование данных представляет собой проблему, трубопровод может просто служить координатором выполнения, который не имеет большого количества исходных вычислений, думаю, например, чисто трубопровод SQL, который отправляет только сценарии SQL в аналитическую базу данных и ждет для завершения.
Когда выполнение зависит от внешних систем, реализация CI сложнее, поскольку вы зависите от другой системы для выполнения вашего трубопровода. В традиционных программных проектах это решается путем создания Макеты , что имитирует поведение другого объекта. Подумайте о веб-сайте электронной коммерции: производственная база данных – это большой сервер, который поддерживает всех пользователей. Во время разработки и тестирования нет необходимости в такой большой системе, меньший с некоторыми данными (возможно, образец реальных данных или даже поддельных данных) достаточно, до тех пор, пока он точно подразумевает поведение производственной базы данных.
Это часто невозможно в проектах данных. Если мы используем большой внешний сервер для ускорения вычислений, мы, скорее всего, имеем только эту систему (например, кластер Hadoop Company Chadoop) и насмешливой. Один из способов решения этого – хранить артефакты трубопроводов в разных «средах». Например, если вы используете большую аналитическую базу данных для вашего проекта, храните производственные артефакты в A прод Схема и тестирование артефактов в Тест схема Если вы не можете создавать схемы, вы также можете префикс все свои таблицы и представления (например, prod_customers и test_customers ). Параметрирование вашего трубопровода может помочь вам легко переключать схемы/суффиксы.
CI Server.
Автоматизировать тестирование выполнения, вам нужен сервер CI. Всякий раз, когда вы нажимаете в репозиторий, сервер CI будет проходить тесты против нового коммита. Есть много вариантов доступны, проверьте, есть ли у вас компания, у вас уже есть сервис CI. Если нет ни одного, вы не получите автоматический процесс, но вы все равно можете запустить его на полпути, запустив тесты локально на каждом коммите.
Давайте модифицируем наш предыдущий трубопровод ежедневного отчета, чтобы охватить важное значение использования: Разработка модели машинного обучения. Скажите, что теперь мы хотим прогнозировать ежедневные продажи на следующий месяц. Мы могли бы сделать это, получая исторические продажи (вместо того, чтобы только вчерашние продажи), генерируя функции и подготовку модели.
Наш конец-конечный процесс имеет два этапа: во-первых, мы обрабатываем данные для генерации тренировочного набора, затем мы тренируем модели и выбираем лучший. Если мы будем следовать тому же строгим подходу на тестирование для каждой задачи по пути, мы сможем ловить грязные данные от попадания в нашу модель, помните: мусор, мусор . Иногда практики практикующие сосредоточены слишком много на тренировке, пробуя много модных моделей или сложных гиперпараметрических схем тюнинга. Хотя этот подход, безусловно, ценен, обычно в процессе подготовки данных существует много малоусимых фруктов, которые могут значительно повлиять на работу нашей модели. Но чтобы максимизировать это влияние, мы должны обеспечить, чтобы этап подготовки данных надежным и воспроизводимым.
Ошибки в подготовке данных вызывают либо результаты, которые являются слишком хорошо, чтобы быть правдой (то есть утечка данных) или субоптимальные модели; Наши тесты должны решить оба сценария. Чтобы предотвратить утечку данных, мы можем проверить наличие проблемных колонн в учебном наборе (например, столбец, значение которого известно только после видимости нашей целевой переменной). Чтобы избежать неоптимальных характеристик, тесты интеграции, которые подтверждают наши предположения для данных, играют важную роль, но мы можем включить другие тесты, чтобы проверить качество в нашем окончательном наборе данных, такого как проверки, у нас есть данные по всем годам, и что данные, рассматриваемые как неподходящие для обучения.
Получение исторических данных будет увеличить время работы CI в целом, но дискретирование данных (поскольку мы сделали в ежедневном дочере). Даже лучше, вы можете кэшировать локальную копию с образец данных, чтобы избежать приема образца каждый раз, когда вы запускаете свои тесты.
Для обеспечения полной модели воспроизводимости мы должны использовать только модели, используя артефакты, которые генерируются из автоматизированного процесса. После того, как пропуск тестов процесс может автоматически вызвать сквозное выполнение трубопровода с полным набором данных для создания данных тренировки.
Сохранение исторических артефактов также может помочь с моделью слышимостью, учитывая хэш-фиксацию, мы должны иметь возможность найти сгенерированные данные тренировок, более того, повторное выполнение трубопровода из одного и того же коммита должны давать идентичные результаты.
Оценка модели как часть рабочего процесса CI
Наш текущий рабочий процесс CI тестирует наш трубопровод с образец данных, чтобы убедиться, что окончательный выход подходит для обучения. Не было бы неплохо, если бы мы также могли проверить процедуру обучения?
Напомним, что цель CI заключается в том, чтобы позволить разработчикам интегрировать небольшие изменения, итеративно, для этого будет эффективным, обратная связь должна вернуться быстро. Обучение ML моделей обычно поставляется с длительным временем работы; Если у нас нет способа заканчивания нашей процедуры обучения за несколько минут, нам придется подумать, как быстро проверить.
Давайте проанализируем два тонко разных сценария, чтобы понять, как мы можем интегрировать их в рабочий процесс CI.
Тестирование алгоритма обучения
Если вы реализуете свой собственный алгоритм обучения, вы должны проверить вашу реализацию независимой от остальной части вашего трубопровода. Эти тесты проверяют правильность вашей реализации.
Это то, что какая-то ML Framework делает (Scikit-Survey, Keras и т. Д.), Поскольку они должны убедиться, что улучшения текущих реализаций их не нарушают. В большинстве случаев, если вы не работаете с очень голодным алгоритмом данных, это не придет с проблемой работы с временем работы, потому что вы можете установить тестирование вашей реализации со синтетическим/игрушечным набором данных. Эта же логика применяется к любым тренировкам препроцессоров (таких как масштабирование данных).
Тестирование вашего учебного трубопровода
На практике обучение не является одноэтапной процедурой. Первый шаг – загрузить ваши данные, то вы можете выполнить некоторую окончательную очистку, такую как удаление идентификаторов или категорических функций, связанных с горячей кодировкой. После этого вы передаете данные многоступенчатым тренировочным трубопроводом, который включает в себя разделение, предварительная обработка данных (например, стандартизация PCA, и т. Д.), Гиперпараметрицевая настройка и выбор модели. Вещи могут ошибаться ни в одном из этих шагов, особенно если ваш трубопровод имеет очень индивидуальные процедуры.
Тестирование вашего тренировочного трубопровода трудно, потому что нет очевидного теста Oracle. Мой совет должен попытаться сделать свой трубопровод максимально простым, используя существующие реализации (Scikit-Surve, имеет удивительные инструменты для это ), чтобы уменьшить количество кода для тестирования.
На практике я нашел полезное для определения критериев тестирования относительный до предыдущих результатов. Если в первый раз я обучал модель, я получил точность X, то я сохраняю этот номер и использую его как ссылку. Последующие эксперименты должны потерпеть неудачу в пределах Разумный диапазон X: внезапные падения или прибыль в исполнении вызывают оповещение для просмотра результатов вручную. Иногда это хорошая новость, это означает, что производительность улучшается, потому что мои новые функции работают, в других случаях, это плохие новости: внезапные прирост в производительности могут исходить из утечки информации, в то время как внезапные падает из неправильной обработки данных или случайно падает строки/столбцы.
Чтобы осуществить время выполнения работы, запустите тренировочный трубопровод с образцом данных и провести свой тест сравнить производительность с помощью метрики, полученной с использованием той же процедуры выборки. Это более сложное, чем звучит, потому что дисперсия результатов увеличится, если вы тренируетесь с меньшими данными, которые придумывают с Разумный диапазон более сложной.
Если вышеупомянутая стратегия не работает, вы можете попробовать использовать суррогатную модель в вашем трубопроводе CI, который быстрее тренируйся и увеличить размер образца данных. Например, если вы тренируете нейронную сеть, вы можете тренироваться с использованием более простой архитектуры, чтобы сделать обучение быстрее и увеличить образец данных, используемый в CI, чтобы уменьшить дисперсию на пробегах CI.
CI позволяет нам интегрировать код в короткие циклы, но это не конец истории. В какой-то момент мы должны Развертывание Наш проект, это где Непрерывная доставка и Непрерывное развертывание Войдите.
Первый шаг к развертыванию – выпуская наш проект. Выпуск предпринимает все необходимые файлы (то есть исходный код, файлы конфигурации и т. Д.) и помещают их в формат, который можно использовать для установки нашего проекта в производственной среде. Например, выпуск пакета Python требует загрузки нашего кода в индекс пакета Python.
Непрерывная доставка гарантирует, что программное обеспечение может быть Выпустив в любое время, но развертывание все еще является ручным процессом (то есть кто-то должен выполнить инструкции в производственной среде), другими словами, он только автоматизирует процесс выпуска. Непрерывное развертывание включает в себя автоматическое выброс и развертывание. Давайте теперь проанализируем эту концепцию с точки зрения проектов данных.
Для трубопроводов, которые производят читаемые человеком документы (например, отчет), постоянное развертывание простым. После прохождения CI другой процесс должен захватить все необходимые файлы и создать установочный артефакт, затем производственная среда может использовать этот артефакт для настройки и установки нашего проекта. В следующий раз проходит трубопровод, он должен использовать новейшую стабильную версию.
С другой стороны, постоянное развертывание для ML трубопроводов гораздо сложнее. Выход трубопровода не является уникальной моделью, но несколько моделей кандидатов, которые следует сравнить, чтобы развернуть лучший. Вещи становятся еще более сложными, если у нас уже есть модель в производстве, потому что может быть, что развертывание не является лучшим вариантом (например, если новая модель не улучшает прогнозную мощность, но поставляется с увеличением времени выполнения или дополнительных зависимостей) Отказ
Еще более важный (и сложнее) свойство для оценки, чем прогнозируемая мощность Модель справедливости Отказ Каждое новое развертывание должно быть оценено для уклона к чувствительным группам. Придумываясь с автоматическим способом оценки модели как в прогностической власти, так и справедливости очень сложно, и она заслуживает собственного поста. Если вы хотите узнать больше о справедливости модели, Это отличное место для начала Отказ
Но непрерывная доставка для ML по-прежнему является управляемым процессом. После того, как Communs проходит все тесты с образцом данных (CI), другой процесс запускает трубопровод с полным набором данных и сохраняет конечный набор данных в хранилище объектов (CD-этап 1).
Процедура обучения затем загружает артефакты и находит оптимальные модели путем настройки гиперпараметров для каждого выбранных алгоритмов. Наконец, он сериализует наилучшую спецификацию модели (I.E. Algorithm и его лучшие гиперпараметры) наряду с отчетами об оценке (CD-этап 2). Когда все это сделано, мы посмотрим на отчеты и выбираем модель для развертывания.
В предыдущем разделе мы обсуждали, как мы можем включать оценку модели в рабочем процессе CI. Предлагаемое решение ограничено требованиями времени эксплуатации CI; После того, как первый этап в процессе CD сделан, мы можем включить более надежное решение, обучение новейшей спецификации лучшего модели с полным набором данных, это будет ловят ошибки, вызывающие падения производительности, вместо того, чтобы дождаться второго этапа Чтобы закончить, учитывая, что он имеет гораздо более высокое время работы. Рабочий процесс CD выглядит так:
Запуск компакт-диска из успешного запуска CI может быть руководством, ученый данных может не захотеть генерировать наборы данных для каждого премиирования, но его можно легко сделать с помощью Commit Hash (то есть с одним щелчком мыши или командой).
Также удобно разрешить ручное выполнение второго этапа, потому что данные ученые часто используют один и тот же набор набора данных для запуска нескольких Эксперименты Настройка тренировочного трубопровода, таким образом, один набор данных может потенциально вызвать многие тренировочные работы.
Воспроизводимость по экспериментам имеет решающее значение для трубопроводов ML. Существует однозначное взаимосвязь между фиксацией, пробегом CI и бегом CI (этап CD 1), таким образом, мы можем однозначно идентифицировать набор данных Commit Hash, который создал его. И мы должны быть в состоянии воспроизвести любой эксперимент, используя шаг приготовления данных снова и запустив стадию тренировок с одинаковыми параметрами обучения.
В качестве процессов CI/CD для науки данных начнут зрелым и стандартизировать, мы начнем видеть новые инструменты для облегчения реализации. В настоящее время многие данные ученых даже не рассматривают Ci/CD в рамках своего рабочего процесса. В некоторых случаях они просто не знают об этом, в других, потому что реализация Ci/Cd эффективно требует процесса установки для регресса существующих инструментов. Ученые данные не должны беспокоиться о создании сервиса CI/CD, они должны просто сосредоточиться на написании их кода, тестов и толкающих.
Помимо инструментов CI/CD, специально предназначенных для проектов данных, нам также нужен инструменты управления трубопроводом данных для стандартизации разработки трубопровода. За последние пару лет я видел много новых проектов, к сожалению, большинство из них сосредоточены на таких аспектах, как планирование или масштабирование, а не опыт пользователя, который имеет решающее значение, если мы хотим методам программного обеспечения, такие как модуляризация и тестирование, чтобы быть обнял всеми данными учеными. Это причина, по которой мы построили Ploomber , чтобы помочь данные ученых легко и постепенно принять лучшие практики развития.
Сокращение циклов обратной связи CI-разработчика имеет решающее значение для успеха CI. Хотя выборка данных является эффективным подходом, мы можем сделать лучше, используя инкрементные прогоны: изменение одной задачи в трубопроводе должно вызывать только наименьшее количество работы, повторное использование ранее вычисленных артефактов. Ploomber уже предложит это в некоторой степени, и мы экспериментирующие способы улучшить эту функцию.
Я считаю, что CI является наиболее важным недостающим произведением в стеке программного обеспечения Data Science: у нас уже есть отличные инструменты для выполнения Automal, развертывание модели одним щелчком мыши и мониторинга модели. CI закроет пробел, чтобы позволить команды уверенно и постоянно тренироваться и развертывать модели.
Чтобы переместить нашу поле вперед, мы должны начать уделять больше внимания нашим процессам развития.
Нашел ошибку в этом посте? Нажмите здесь, чтобы сообщить нам об этом Отказ
Первоначально опубликовано в плюбил
Оригинал: “https://dev.to/edublancas/rethinking-continuous-integration-for-data-science-1c0c”