- О курсе
- Jupyter в ProductCode
- Версионирование процесса
- API для модели
- СI/CD
- Часть 5
- Часть 6
- Часть 7
- Часть 8
Основная цель данного курса — сформировать у слушателей понимание того, как применяются модели машинного обучения и как их использовать для решения бизнес-задач.
Знание этих процессов поможет аналитикам и специалистам по машинному обучению максимально адаптировать свои решения к промышленной эксплуатации. По итогам курса вы сможете лучше ориентироваться в производственной среде и понимать жизненный цикл моделей машинного обучения.
В ходе прохождения курса вы научитесь выстраивать процесс разработки модели для продуктивного решения бизнес-задач, пройдете путь трансформации от jupyter notebook кода до контейнеризации модели и автоматизации необходимых для её работы процессов. В курсе также будет уделено внимание метрикам и способам мониторинга моделей.
В результате вы получите полное представление о процессе продуктивизации моделей, что позволит сократить время выхода на рынок ваших решений и поможет понять инженерный процесс разработки.
Курс разработан при поддержке НИУ ВШЭ
Ссылка на курс на сайте НИУ ВШЭ
Знания для прохождения курса
-
Язык программирования Python (базовые библиотеки для Python, которые используются для работы с данными: numpy, pandas, sklearn)
-
Знание принципов построения моделей машинного обучения
-
Командная строка Linux
Вступительная часть к процессу продуктивизации моделей машинного обучения.
Отвечая на вопрос, какие умения разработчика важны для DS, можно вспомнить статью software dev skills for ds от Trey Causey и сказать:
- уметь тестировать
- уметь версионировать
- уметь логгировать -> отличный пример
- уметь писать модули
Курс базируется на архитекутуре ML - train by batch, predict on fly
Будут рассмотрены основные виды ML Pipeline:
-
Процедурные (на основе набора функций) с запуском через main.py
-
Scikit-learn pipeline, построение и использование функций и классов в библиотеке Scikit-learn (sklearn.pipeline.Pipeline)
-
Custom (или объектно-ориентированный pipeline)
Разбор основных видов тесто для ML проекта. В этой части рассматриваем 6 основных вилов тестов, которые должны быть у модели и её процесса.
Тесты помогут: найти баги, подтвердить ваши ожидания от результата, ещё разв взглянуть на код (и может написать его проще), упрощают совместную работу, меньше удивляться от результата.
Перед знакомством с тестами, научимся делать дебаг и поиск ошибок в коде на примере игры о pdb
Познакомимся с тестами через учебный проект по тестированию кода для теннисного турнира ссылка
Позапускаем тесты
Run
pytest /path_to_test_script.py
Run + html отчет
pytest /path_to_test_script.py --html=report.html --self-contained-html
Run + code coverage report (результат по каждой функции)
pytest /path_to_test_script.py --cov=src --verbose
Run + multi CPU
pytest /path_to_test_script.py -n 5
- Объекты извлекающие данные
- Объекты трансформирующие данные
- Модель (если она самописная)
- Обекты сохранения данных
- Тестировать всегда один объект из процесса (кода). Т.е. один юнит за один тест
- Не использовать зависимости в тестируемом коде из других процессов (юнитов)
- Минимизировать запросы к API / Базе данных
- Тестировать свойства, не значения (для DS|DE)
- Тестировать типы и размерности (для DS|DE)
- Генерируйте фичи для теста (feature forge), а не создавайте руками (для DS|DE)
- Используйте базовые методы из Numpy и Pandas для тестирования (для DS|DE)
# тесты должны быть осмысленные
def mean(some_list):
# на примере:
# тест на пустой список и деление на 0
# TypeError тест
# OverFlow тест
return sum(some_list) / len(some_list)
# dev тест
import pytest
def test_dev_mean():
assert(sum(mean([1,2,3])) / len(3) == 2)
# Но для DS
from hypothesis import given
import hypothesis.strategies as st
@given(st.lists(st.integers()))
def test_ds_mean(some_list):
assert mean(some_list) == sum(some_list) / len(some_list)
# тесты с проверкой типов / размерности
@is_shape(5, 10)
@is_monotonic(strict = True)
@non_missing()
def my_funct(df, columns):
pass
К основным видам тестов относятся:
1 Smoke test - тесты для опредления метода
import pytest
from sklearn.linear_model import LinearRegression
def test_smoke():
try:
assert LinearRegression() is not None
except NameError:
pytest.fail("Model does not exist")
2 Проверка коннекторов к БД (если нужны для проекта)
Ссылка на Хабр, с большим объяснением
import mock
def get_connect():
engine = sqlalchemy.create_engine('connection_string')
return df.read_sql("select * from *", con = engine)
@mock.patch('pytest_ex.text_connection.sqlalchemy.create_engine')
def test_connection(engine_mock, df):
new_df = get_connect(engine_mock)
pandas.testing.assert_frame_equal(new_df, df)
# или тестировать функции чтения
def df_from_csv(filename):
return pd.read_csv(filename)
@mock.patch.object(pd, 'read_csv')
def test_df_from_csv(read_csv_mock, df):
read_csv_mock.return_value = df
actual = df_from_csv('file_name.csv')
# ожидаемый результат
expected = df
# assertions
pd.testing.assert_frame_equal(actual, expected)
3 Тесты на равенство / соответствие результатов после трансформации объекта
-
Использовать pandas testing:
-- pandas.testing.assert_frame_equal (сравнение DataFrame)
-- pandas.testing.assert_series_equal (сравнение колонок)
-- pandas.testing.assert_index_equal (сравнение строк по индексам)
-- pandas.testing.assert_extension_array_equal (сравнение любых массивов numpy)
-- Использование assert + numpy (методы сравнения объектов):
-- np.allclose
-- np.isclose
-- np.any
-- np.equal
-- Использование numpy testing методов:
-- (Ссылка на документацию numpy asserts)[https://numpy.org/doc/stable/reference/routines.testing.html]
4 Тестирование существования файлов
def df_from_csv(filename):
"""читаем все DataFrame в формате csv"""
return pd.read_csv(filename)
def test_df_from_csv(filename):
assert df_from_csv(filename) is not FileNotFoundError
5 Тестирование API
import responses
@responses.activate
def test_api_404():
responses.add(
responses.GET,
'https://your_path',
json='ex_json',
status=404,
)
@responses.activate
def test_api_200():
responses.add(
responses.GET,
'https://your_path',
json={'ex_json'},
status=200,
)
6 Генерируйте данные для тестов на основе hypothesis
import pandas as pd
from hypothesis import given
from hypothesis import strategies
from hypothesis.extra.pandas import column, data_frames
import builder
@given(
# создавайте Pandas DataFrame для тестов
data_frames(
columns=[
column(
name='prog_start',
elements=strategies.datetimes(
min_value=pd.Timestamp(2020, 1, 1),
max_value=pd.Timestamp(2020, 1, 10)
)
, unique=True),
column(
name='code',
elements=strategies.just(float('nan'))
)
])
)
def test_fix_new_boxes_nan_replaced(raw_prog):
prog = builder.fix_new_boxes(raw_prog)
assert (prog.mat_code == builder.NO_MAT_CODE).all()
assert prog.shape == raw_prog.shape
- Воспользуйтесь шаблонами для sklearn.pipeline и процедурного pipeline, переделайте свою модель из эксперементального jupyter notebook в 3 вида pipeline
Задание
Разбор основных способов версионирования в GIT с проекцией на деятельность DS/DE.
Предложена мультиветвенная система версиониования для монолитного проекта. ! Определитесь с неймингом процессов!
О Git flow для DE/DS и основными шаблонами можно ознакомиться в другом репозитории
По ссылке вы найдете:
-
шаблон для проекта
-
шаблон для adhoc
-
шаблон для Spark проекта
-
набор готового кода для git
Ещё немного информации о git - больше волшебства
Рассмотрим инструмент dvc, отличный для экспериментов и фиксации процессов
dvc init
# отключаем аналитику наших процессов (чтобы не собиралась статистика)
dvc config core.analytics false
# устанавливаем наше хранилище для файлов
dvc remote add -d localremote /data/dvc-storage
Создаем params.yaml по шаблону:
# file params.yaml
название модели (эксперимента):
параметры для эксперимента
Создаем шаги наших экспериментов (для трекинга):
dvc run -n STAGE_NAME \
-d все файлы от которых зависит процесс
-O все файлы, которые будут являться результатами процесса ( но не будут версионироваться)
-o все файлы, которые будут являться результатами процесса (будут версионироваться)
-M файл с метрикой
Основные команды для процессинга
# воспроизведение процесса (повторение от шага 1 до финального)
dvc repro
# сравнение параметров / метркиа
dvc params diff
dvc metrics diff
# визуализация процесса
dvc dag
Отличный проект kedro, на основе которого будут выстроеные процессы на данном курсе
Проект является набором правил для разработки в МЛ. Но во время работы следует учитывать:
- Не удалять (только дополнять) файл
.gitignore
- Работать в рамках конвенции DE разработки
- Переменные и конфиги эфемерные
- Конфиги в
conf/local/
# создать структуру проекта
kedro new
# в src/projectname/
# нужно создать pipeline (для каждого типа процессов - свой пайплайн)
# запуск jupyter notebook в kedro
kedro jupyter notebook
В проекте должны быть следующие pipelines:
-
data engineering(etl + обработка данных)
-
data science(обучение модели)
-
predict_pipeline(предикт по модли и проверка качестве)
-
predictapi_pipeline(предикт для работы через API)
# добавляем созданные pipeline
# в hook для запуска
de_pipe = de.create_pipeline()
ds_pipe = ds.create_pipeline()
pr_pipe = pr.create_pipeline()
pra_pipe = pra.create_pipeline()
return {
"de": de_pipe,
"ds": ds_pipe,
"predict": pr_pipe,
"predict_api": pra_pipe,
"__default__": de_pipe + ds_pipe + pr_pipe,
}
Это позволяем запускать код по имени pipeline
# в bash
kedro run --pipeline='predict_api'
# в функции python
context.run(pipeline_name='predict_api')
Pipeline модели в Kedro (у данного проекта)
Задание
Зачем DS/DE знать про API?
Для выполнения данной работы вам может понадобиться: сервер API, получатель API. Вы сможет найти их простые реализации здесь
В данной работе мы сделаем выбор 1 из 3 основных реализаций API.
Мы будем использовать паттерны, которые поставляются с Kedro.
# подключаем контекст Kedro
from kedro.context import load_context
from kedro.extras.datasets.api import APIDataSet
import json
# загружаем проект
context = load_context("../")
catalog = context.catalog
# используя APIDataSet из Kedro
# и устанавливаем конфиг
st = APIDataSet(
url = "http://127.0.0.1:9876/data",
headers = {"content-type": "application/json"}
).load()
# записываем в DF для работы из json
df = pd.DataFrame.from_dict(st.json()['data'], orient='index').T
# формируем результат для отправки назад по API
answer = {
"predict_date": st.headers['date']
"row_index": st.json()['index']
"predict": model.predict(df)
}
Задание
Разработка через тестирование для DS - реальность!
Рассмотрим, какие тесты нужно делать для DS/DE пайплайнов и как их делать. И немного погрузимся в методологию TDD.
Опять переделаем пайплайн, так как рассмотрев пример проблемы и поймем, что делали работу не по TDD методологии.
Рассмотрим пример разработки теста для функции:
# функция для тестирования
def index_creator(df, mind, maxd, freq):
by = 'Station'
newCol = 'Datetime'
return pd.MultiIndex \
.from_product([df[by].sort_values().unique(),
pd.Index(pd.date_range(mind,
maxd,
freq=freq))],
names=[by, newCol])
Скрипт по разработке теста
import pandas as pd
import numpy as np
from hypothesis import given
from hypothesis import strategies as st
from hypothesis.extra.pandas import data_frames, column
from scipy.special import expit
import matplotlib.pyplot as plt
import seaborn as sns
# создадим DF для работы (генерация случайного ДатаФРейма)
# генерация данных для функции
# получаем элементы из given
# создаем случайный
import string
df = data_frames(
[
column('Station', dtype=str,
elements=st.text(alphabet=f"{string.ascii_letters}{string.ascii_lowercase}", min_size=5)),
column('DateOfPeriod',
elements=st.datetimes(min_value=datetime.datetime.strptime('2019-01-01 00:00:00', '%Y-%m-%d %H:%M:%S'),
max_value=datetime.datetime.strptime('2021-01-01 00:00:00', '%Y-%m-%d %H:%M:%S'))),
]
).example()
# создаем переменные для даты и время
mn = st.datetimes(min_value=datetime.datetime.strptime('2019-01-01 00:00:00', '%Y-%m-%d %H:%M:%S'),
max_value=datetime.datetime.strptime('2021-01-01 00:00:00', '%Y-%m-%d %H:%M:%S')).example()
mx = st.datetimes(min_value=datetime.datetime.strptime('2019-01-01 00:00:00', '%Y-%m-%d %H:%M:%S'),
max_value=datetime.datetime.strptime('2021-01-01 00:00:00', '%Y-%m-%d %H:%M:%S')).example()
# что нам нужно получить для функции
# переменные
mind = mn
maxd = mx
freq = 'H'
# константы
by = 'Station'
newCol = 'Datetime'
pd.MultiIndex \
.from_product([df[by].sort_values().unique(),
pd.Index(pd.date_range(mind,
maxd,
freq=freq))],
names=[by,
newCol])
# тесты
# соответсвие класса (smoke)
index_creator(df, mn, mx, 'D').__class__ == pd.core.indexes.multi.MultiIndex
# правильный способы проверки класса
isinstance(index_creator(df, mn, mx, 'D'), pd.MultiIndex)
# что будет в нужной структуре и не пустой индекс с уровнями, именами и определителями
try:
pd.testing.assert_index_equal(index_creator(df, mn, mx, 'D'),
pd.core.indexes.multi.MultiIndex(levels = [['', '0'], []],
names = ['Station', 'Datetime'],
codes=[[], []]))
except AssertionError:
True
with pytest.raises(AssertionError):
pd.testing.assert_index_equal(index_creator(df, mn, mx, 'D'),
pd.core.indexes.multi.MultiIndex(levels = [['', '0'], []],
names = ['Station', 'Datetime'],
codes=[[], []]))
Задание