Модуль 12. Капстоун: задача на табличных данных
Цель модуля: пройти весь цикл курса одним проектом — от постановки задачи и метрики до таблицы моделей, отчёта «выкатывать или нет» и честного раздела «что деградировало». Проект выполняется локально на python со sklearn; уровень «advanced» — не из-за сложности отдельных шагов, а из-за объёма: это самостоятельная сборка модулей 1–11 за один уик-энд.
Перед началом: модули 1–11; опорные — 3 (логистическая регрессия), 7 (метрики), 8 (фичи и утечка), 10 (выбросы) и 11 (кросс-валидация). Инструменты: python3, pandas, scikit-learn (локально — код модуля в песочнице сайта не исполняется), git для артефактов. Трудоёмкость: уик-энд: ~8–10 ч чистого времени (суббота — этапы 0–2, воскресенье — 3–5).
🔥 «Покажите пилот к понедельнику»
Пятница, ретро команды: три открытые задачи — отток клиентов, срочность тикетов, прогноз нагрузки. Выбирать вечером, а в понедельник на демо спросят не «какая модель», а «насколько лучше и стоит ли выкатывать».
Соблазн уик-энда — крутить бустинг до красивой цифры. Ценность — в другом: пройти цикл и принести отчёт: честные числа, зафиксированные решения, названные слабости. «Пилот», который нельзя сравнить с baseline, — не пилот.
📖 12.1. Задача и метрика: фиксируем до первой строки кода
Выберите одну из трёх задач — все три проходимы за уик-энд и покрывают курс:
| Вариант | Таргет | Метрика | Камень |
|---|---|---|---|
| Отток клиентов | уйдёт за 30 дней, да/нет | ROC-AUC, порог отдельно | дисбаланс |
| Срочность тикетов | срочный, да/нет | recall при контроле precision | шумная разметка |
| Прогноз нагрузки | запросов в час | MAE (сначала RMSE) | сплит по времени |
Правила постановки (то, что фиксируется до вычислений):
- Задача и таргет — одной строкой: «предсказываем вероятность оттока клиента в течение 30 дней».
- Метрика — одна главная, из модуля 7: ROC-AUC для классификации, MAE/RMSE для нагрузки; ROC-AUC не зависит от порога.
- Критерий успеха — числом: «CV-метрика бустинга выше линейной модели больше чем на разброс» или «MAE не хуже сезонного baseline».
- Правило стопа — заранее: «две итерации без прироста — остаюсь на простой модели и пишу отчёт». Отчёт с честным «не выкатывать» — результат.
Откуда данные: лучше всего — рабочий датасет (тикеты, заказы, метрики сервиса); если рабочего нет — открытый (например, Telco Customer Churn). Синтетика — последний вариант: в ней легко не заметить утечку.
📖 12.2. Данные и разведка: пропуски, выбросы, гигиена
Разведка — не «построить графики», а принять и записать решения:
- Границы данных. Что такое одна строка (клиент? тикет? час?) и какое окно времени берём. Для оттока «уйдёт за 30 дней» требует горизонта ожидания: строки последнего месяца без ответа в обучение не идут.
- Пропуски. Посчитайте долю по каждой колонке. Спросите «почему пусто»: если признак недоступен в момент предсказания — он не фича. Заполнение — медианой/константой (модуль 8), но иногда пропуск сам сигнал: превратите его в флаг «пропуск» и передайте модели.
- Выбросы. IQR из модуля 10 — по каждой числовой колонке; решение (оставить / пометить флагом / отбросить) — по контексту и одинаково в обучении и в бою. Правило запишите в одну строку.
- Дубликаты и id.
ticket_idиuser_id— не признаки: константа или способ утечки. Одинаковые строки — посчитайте. - Дисбаланс. Доля положительного класса: 5% или 45%? Это определяет метрики, сплит (стратификация) и сравнение с baseline (модуль 7).
Артефакт этапа — eda.md: по каждому пункту одно решение и одна причина.
📖 12.3. Сплиты и главное правило: все преобразования fit на train
Делим данные один раз и до любых вычислений: train / val / test (например,
60/20/20). Для классификации — стратификация, для нагрузки — сплит по
времени (модуль 11). test запечатан: он открывается один раз в конце.
Главное правило уик-энда: медианы, one-hot словари, масштабы и любые
статистики считаются только на обучающей части. Посчитаете на полном
датасете — val и test протекут в обучение, а отчёт станет фикцией (модуль 8).
Технический приём — Pipeline, который делает подготовку шагом модели:
тогда утечку невозможно допустить случайно, ни в CV, ни в финальном
обучении. Контрольная проверка: смените seed и порядок строк — метрика не
должна меняться драматически, иначе у вас утечка или крошечные данные.
📖 12.4. Baseline: константа и логистическая регрессия
Baseline — обязательная ступень, а не формальность. Две модели, обе честные:
- Константа (
DummyClassifier(strategy="most_frequent")для классификации, среднее — для регрессии): планка, ниже которой модель сломана. Покажите её метрику особенно при дисбалансе: модель «всё не срочно» выглядит неплохо по accuracy и ужасно по recall. - Логистическая регрессия (модуль 3) с one-hot и заполнением пропусков
в
Pipeline: простая, объяснимая, и на табличных данных часто близка к бустингу. Если бустинг не бьёт её значимо — берите её.
Числа — по кросс-валидации на train (модуль 11) в формате «среднее ±
разброс», и отдельной колонкой — метрика на val. Разрыв train → val говорит
о переобучении; регресс от константы — о том, что сигнал есть. Артефакт —
baseline.md: таблица двух моделей и фраза «модель нужна / бесполезна».
📖 12.5. Бустинг: быстрый дефолт и честный подбор
Главный класс моделей табличных задач — градиентный бустинг (модуль 6).
Быстрый дефолт в sklearn — HistGradientBoostingClassifier: биннинг
признаков делает его заметно быстрее GradientBoostingClassifier и умеет
работать с пропусками без заполнения. Сверьте версию sklearn: в 1.4+
у него есть нативные категориальные признаки, в старых версиях придётся
кодировать вручную; python3 -c "import sklearn; print(sklearn.__version__)".
Код ниже — каркас; в песочнице сайта он не исполняется, запустите локально:
python# capstone.py — каркас капстоуна. Запускать локально (в песочнице сайта # код не исполняется): python3 + pandas + scikit-learn; версию сверьте: # python3 -c "import sklearn; print(sklearn.__version__)". import pandas as pd from sklearn.compose import ColumnTransformer from sklearn.ensemble import HistGradientBoostingClassifier from sklearn.impute import SimpleImputer from sklearn.linear_model import LogisticRegression from sklearn.metrics import confusion_matrix, roc_auc_score from sklearn.model_selection import (StratifiedKFold, cross_val_score, train_test_split) from sklearn.pipeline import Pipeline from sklearn.preprocessing import OneHotEncoder, StandardScaler df = pd.read_csv("data/tickets.csv") # ваши данные y = (df["priority"] == "urgent").astype(int) # таргет: 1 = срочный X = df.drop(columns=["priority", "ticket_id"]) # id — не признак # Сплит ДО преобразований; stratify — из-за дисбаланса (модуль 11) X_tr, X_te, y_tr, y_te = train_test_split( X, y, test_size=0.2, stratify=y, random_state=42) # Подготовка — шагами Pipeline: fit случится только на train (утечки нет) prep = ColumnTransformer([ ("num", Pipeline([("imp", SimpleImputer(strategy="median")), ("sc", StandardScaler())]), ["text_len", "hour", "client_age_days"]), ("cat", OneHotEncoder(handle_unknown="ignore"), ["channel", "plan"]), ]) skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) def cv(model, name): s = cross_val_score(Pipeline([("prep", prep), ("m", model)]), X_tr, y_tr, cv=skf, scoring="roc_auc") print(f"{name:22} AUC {s.mean():.3f} ± {s.std():.3f}") cv(LogisticRegression(max_iter=1000), "логрегрессия") gbm = HistGradientBoostingClassifier( learning_rate=0.05, max_iter=300, random_state=42) cv(gbm, "бустинг") # Финальный прогон на test — ОДИН раз, после выбора конфигурации best = Pipeline([("prep", prep), ("m", gbm)]).fit(X_tr, y_tr) proba = best.predict_proba(X_te)[:, 1] print("test ROC-AUC:", round(roc_auc_score(y_te, proba), 3)) print(confusion_matrix(y_te, proba >= 0.5))
Подбор гиперпараметров — по кросс-валидации на train, а не по test (модуль
11): начните с 5–10 случайных комбинаций learning_rate, max_iter или
max_leaf_nodes, зафиксируйте все попытки в таблице. Порог решения — тоже
гиперпараметр, но выбирается последним и по цене ошибки: для срочности
разумнее recall при приемлемом precision (модуль 7).
Проверка важности фич: permutation_importance честнее встроенной
impurity-важности (та смещена к признакам с большим числом уникальных
значений и размывается между коррелирующими). Важность — диагностика,
а не причина: пять строк «что удивило» сильнее красивого графика.
📖 12.6. Финальный отчёт: таблица, решение, «что деградировало»
Отчёт — главный артефакт капстоуна. Минимум: таблица моделей в формате «CV среднее ± разброс» плюс колонка test; ROC-AUC и confusion matrix на выбранном пороге; топ важных фич; решение «выкатывать или нет» по критерию из 12.1; и отдельный раздел «что деградировало» — именно он отличает инженера от продавца. Пишите как есть: «после добавления признака X метрика не выросла», «на редком классе модель ошибается чаще», «test хуже CV на 0.04 — сдвиг времени». И наоборот: если test внезапно много лучше CV — не радуйтесь, проверяйте утечку (признак из будущего, пересечение сплитов).
textПроект: <задача, таргет, метрика одной строкой> Данные: <источник, N строк; что исключено и почему> Сплит и разведка: <train/val/test; пропуски, выбросы; seed> Baseline: <константа и логрегрессия: CV mean ± std> Бустинг: <конфигурация: CV mean ± std> Test: <итоговая метрика; confusion/ROC-AUC на пороге> Важность фич: <топ-5; что удивило> Решение: выкатывать | не выкатывать — обоснование по критерию Что деградировало: <честный список; что не сработало и почему> Следующий шаг: <одно конкретное улучшение данных или фич>
📖 12.7. Компромиссы
- Логистическая регрессия vs бустинг. Логрегрессия проще, объяснимее и почти не требует настройки; бустинг точнее, но платить приходится подбором и поддержкой. Решает разброс CV, а не вкус.
- Простой пайплайн vs «сделать красиво». Скрипт, перезапускаемый одной командой, ценнее ноутбука, ячейки которого надо гонять руками по порядку.
- Время на признаки vs на тюнинг. Один осмысленный признак обычно даёт больше, чем час перебора сетки. Сначала фичи, потом сетка.
- Полные данные vs быстрое исследование. На разведку можно взять выборку, но финальные цифры — только на полных сплитах.
- Идеальные метрики vs честные. Подозрительно высокий результат — повод искать утечку; заниженный после честного сплита — не прятать его, а написать в раздел «что деградировало».
💻 Практика
План уик-энда: этапы 0–5, каждый заканчивается артефактом. Код модуля в песочнице не исполняется — всё делается локально (python3 + pandas/sklearn).
-
(30 мин) Задача и метрика. Вариант из 12.1, задача, таргет, метрика, критерий успеха, правило стопа. Артефакт:
README.md. -
(1.5–2 ч) Данные и разведка. Пропуски, выбросы (IQR), дубликаты, дисбаланс, проверка на утечку. Артефакт:
eda.md— решения и причины. -
(1–1.5 ч) Сплиты и пайплайн. train/val/test (стратификация или время),
Pipelineподготовки; fit только на train. Артефакт:prep.pyи три файла данных со сплитами. -
(1–1.5 ч) Baseline. DummyClassifier и логрегрессия в Pipeline; CV на train, метрика на val. Артефакт:
baseline.mdс таблицей. -
(2–3 ч) Бустинг и подбор. HistGradientBoosting (сверьте версию), 5–10 конфигураций по CV, один прогон на test, permutation importance. Артефакт:
boost.pyи таблица кандидатов с CV mean ± std. -
(1–1.5 ч) Отчёт. Шаблон из 12.6, включая «что деградировало»; воспроизводимость одной командой. Артефакт:
REPORT.md.
🧩 Quiz "Проверь себя"
✅ Итоги модуля
Проект считается принятым, когда выполнены все пункты:
- Задача, таргет, метрика, критерий успеха и правило стопа записаны до вычислений (этап 0).
- Разведка оформлена решениями: пропуски, выбросы, дубликаты, дисбаланс, подозрения на утечку.
- Сплиты корректны: стратификация для классификации, время для нагрузки; test открыт один раз.
- Все преобразования фитируются только на train — проверено.
- Baseline посчитан: константа и логистическая регрессия, CV ± разброс.
- Бустинг посчитан по тому же протоколу CV; версия sklearn записана.
- Подбор гиперпараметров не касался test; все попытки записаны.
- На test посчитаны метрика, ROC-AUC и confusion matrix на пороге.
- Важность фич проверена (permutation importance), пять строк выводов.
- Решение «выкатывать или нет» обосновано критерием из этапа 0.
- Раздел «что деградировало» написан честно и конкретно.
- Проект воспроизводится одной командой на чистом окружении.
🧭 Куда дальше
Курс заканчивается, маршрут — нет. Что изучать и строить следующим:
- Эволюция ML-инженера. Дальше начинается прод: мониторинг метрик и дрейфа, регулярное переобучение, теневые прогоны (shadow) до выката. Модуль 12 курса «Дообучение LLM» — про ту же дисциплину для LLM: версии, A/B, fallback, фидбек; читайте его как продолжение этого капстоуна.
- Feature stores. Когда фичи считают несколько команд, их централизуют в feature store (например, Feast): единое определение признака, одна витрина для обучения и боя, защита от train/serve skew. Навык модуля 8 — «преобразования fit на train» — там превращается в инфраструктурное правило.
- Наблюдаемость. За моделью в бою следят метриками: «Архитектурный полигон» (/courses/arch-polygon/) — практикум с алертами и SLO на живом стенде; опыт из модуля 10 («что считать аномалией») пригодится буквально.
AutoML — одним абзацем. Если табличная задача типовая, а времени мало, библиотеки (auto-sklearn, LightGBM с Optuna, облачные AutoML) сами переберут модели и гиперпараметры. Они не отменяют этот модуль: без понимания сплитов, утечки и метрик вы не отличите честный результат AutoML от подогнанного, а отчёт всё равно писать вам. Порядок: сначала руки и baseline, потом AutoML.
И мостик: за уик-энд вы построили модель на «цифрах» — табличных фичах. Но половина задач приходит текстом и диалогом, где бустинг слеп. Как выбирать между ML и LLM и что общего у двух подходов — в финальном модуле курса.