Модуль 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)сплит по времени

Правила постановки (то, что фиксируется до вычислений):

  1. Задача и таргет — одной строкой: «предсказываем вероятность оттока клиента в течение 30 дней».
  2. Метрика — одна главная, из модуля 7: ROC-AUC для классификации, MAE/RMSE для нагрузки; ROC-AUC не зависит от порога.
  3. Критерий успеха — числом: «CV-метрика бустинга выше линейной модели больше чем на разброс» или «MAE не хуже сезонного baseline».
  4. Правило стопа — заранее: «две итерации без прироста — остаюсь на простой модели и пишу отчёт». Отчёт с честным «не выкатывать» — результат.

Откуда данные: лучше всего — рабочий датасет (тикеты, заказы, метрики сервиса); если рабочего нет — открытый (например, 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).

  1. (30 мин) Задача и метрика. Вариант из 12.1, задача, таргет, метрика, критерий успеха, правило стопа. Артефакт: README.md.

  2. (1.5–2 ч) Данные и разведка. Пропуски, выбросы (IQR), дубликаты, дисбаланс, проверка на утечку. Артефакт: eda.md — решения и причины.

  3. (1–1.5 ч) Сплиты и пайплайн. train/val/test (стратификация или время), Pipeline подготовки; fit только на train. Артефакт: prep.py и три файла данных со сплитами.

  4. (1–1.5 ч) Baseline. DummyClassifier и логрегрессия в Pipeline; CV на train, метрика на val. Артефакт: baseline.md с таблицей.

  5. (2–3 ч) Бустинг и подбор. HistGradientBoosting (сверьте версию), 5–10 конфигураций по CV, один прогон на test, permutation importance. Артефакт: boost.py и таблица кандидатов с CV mean ± std.

  6. (1–1.5 ч) Отчёт. Шаблон из 12.6, включая «что деградировало»; воспроизводимость одной командой. Артефакт: REPORT.md.

🧩 Quiz "Проверь себя"

1. Зачем метрику и критерий успеха фиксируют до первой строки кода?
2. Какую роль играет baseline-константа?
3. Где считаются медианы пропусков, one-hot словари и масштаб?
4. Бустинг показал на test 0.99 при CV 0.72. Первое действие?
5. Почему HistGradientBoostingClassifier — быстрый дефолт для капстоуна?

✅ Итоги модуля

Проект считается принятым, когда выполнены все пункты:

  • Задача, таргет, метрика, критерий успеха и правило стопа записаны до вычислений (этап 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 и что общего у двух подходов — в финальном модуле курса.