Модуль 11. Кросс-валидация и подбор модели
Цель модуля: перестать верить одной цифре. После модуля вы делаете k-fold кросс-валидацию вручную и понимаете, что она возвращает, выбираете сплит под данные (стратификация, время, группы), подбираете гиперпараметры без утечки и знаете таблицу «какая модель когда» — от baseline до бустинга.
Перед началом: модули 2–3 (обучение, ошибки), 8 (утечка данных) и 10 (одни данные — разные вердикты). Инструменты: песочница сайта (чистый Python), сниппет ниже. Трудоёмкость: ~1.5 ч (чтение 45 мин · сниппет и практика 35 мин).
🔥 Оценка 0.89 оказалась удачным фолдом
Классификатор оттока показал на отложенной выборке ROC-AUC 0.89 — команда выдохнула и покатила пилот. Через месяц на новых данных метрика просела до 0.74, и никто не понимал почему: код тот же, данные похожи. Разбор показал две вещи. В отложенную половину случайно не попали сложные редкие случаи — те, что встретились позже. И по этой же половине выбирали модель и настройки, а потом её же докладывали как «независимую» проверку: выбор лучшего варианта по шумной метрике — уже подгонка под выборку.
Мораль: одна отложенная выборка — не оценка, а лотерейный билет, особенно на малых и упорядоченных данных. Честный ответ — интервал: среднее ± разброс.
📖 11.1. Почему один holdout — лотерея
Оценка на одной выборке — случайная величина. Она зависит от того, какие именно строки в неё попали: одна половина даёт 0.89, другая — 0.74, и обе формально «честные». Чем меньше данных, тем больше разброс; на сотне строк разница между половинами легко достигает десятков процентов.
Второй источник обмана — порядок строк. Данные почти всегда упорядочены: по времени, по id, по региону. «Первые 10 против последних 10» — чаще сравнение двух эпох, а не случайная проверка: в сниппете ниже holdout даёт 4.21, и цифра держится на том, какие строки попали в половину.
Третий, самый коварный: многократный выбор. Если вы подбираете модель, гиперпараметры или порог по отложенной выборке и затем докладываете лучшую цифру на ней же, выборка перестала быть проверкой. Лекарство то же, что в оценке LLM (модуль 8 курса «Дообучение LLM»): одна выборка — для выбора, другая — для экзамена.
📖 11.2. K-fold по шагам: каждая точка поработает валидацией
K-fold кросс-валидация чинит разом все три проблемы. Механика:
- Перемешать строки (если они независимы) и разбить на K равных фолдов.
- Повторить K раз: обучение на K−1 фолдах, метрика на оставшемся.
- Итог — среднее метрик по фолдам плюс стандартное отклонение.
Каждая точка ровно один раз побывала в валидации и K−1 раз — в обучении. Это честнее одного holdout, и данные используются полнее: каждая строка работает и в обучении, и в проверке. Цена — обучение дороже в K раз.
Главное, что нужно забрать: разброс — тоже результат. Он говорит, насколько оценке можно верить. Если средние двух моделей отличаются меньше разброса (3.9 ± 0.4 против 4.1 ± 0.5 — это не «вторая хуже», а «на этих данных мы их не различаем»), выбирайте более простую модель.
Дефолт — K = 5 или 10; малое K (3) быстрее, но грубее, leave-one-out дорог и шумно оценивает разброс. Число фолдов и seed перемешивания — часть протокола: не зафиксировали — цифры не воспроизводятся.
📖 11.3. Сплит под данные: стратификация, время, группы
Случайное перемешивание — не универсальный ответ. Три специальных случая:
Классификация → стратификация. Разбивайте так, чтобы доля классов сохранялась в каждом фолде: при редком классе (2% положительных) случайный фолд легко получится без положительных примеров — и метрика станет бессмысленной.
Временные данные → сплит по времени. Обучение — только на прошлом, валидация — на будущем; перемешивать нельзя, иначе модель «увидит будущее» и вы получите ту же утечку, что в модуле 8, только через порядок строк. Для прогнозов нагрузки, цен и оттока это единственный корректный протокол.
Группы → GroupKFold. Строки одного клиента или сессии не должны попадать и в обучение, и в валидацию: модель узнает клиента и «проверится» на нём же. В групповом сплите целые группы уходят в фолд целиком.
И привычка из llm-курса: утечка прячется в статистиках, посчитанных до разбиения. Все преобразования фитируются на обучающей части фолда (модуль 8).
📖 11.4. Сниппет: пять фолдов, две модели
Запустите kfold-cv.py: 20 точек y = 3x + 7 с шумом, две модели —
«константа» (предсказывает среднее) и «линия» (МНК). Вывод:
textK-fold, K=5: каждый фолд по 4 точек фолд RMSE среднего RMSE линии 1 19.10 4.28 2 14.66 3.47 3 14.58 3.21 4 19.20 4.26 5 21.29 4.10 Модель «константа»: RMSE = 17.76 ± 2.68 Модель «линия»: RMSE = 3.87 ± 0.44 Для контраста — один holdout (первые 10 против последних 10): RMSE 4.21 — цифра зависит от того, какие строки попали в половину: на отсортированных по x данных это особенно заметно.
Читайте вывод в три приёма.
Первое: модель вообще нужна? Константа ошибается на 17.76, линия — на 3.87: линия бьёт baseline более чем в четыре раза. Это замер, который всегда делают первым (11.6): пока простая модель не побита, сложная не нужна.
Второе: разброс — это информация. У константы ±2.68, у линии ±0.44: оценка линии держится в узком коридоре, у константы гуляет широко. Колонка фолдов показывает, откуда разброс: константа на фолде 3 даёт 14.58, на фолде 5 — 21.29 (разница почти в половину); у линии фолд 3 даёт 3.21, фолд 1 — 4.28. Докладывать лучший фолд нельзя — это тот же обман, что лучший день из недели.
Третье: holdout даёт 4.21 — цифра в стороне и от среднего, и от разброса линии. Причина: первые 10 точек против последних 10 — по x это левый край против правого, распределения разные. CV усредняет по всем кускам.
📖 11.5. Подбор гиперпараметров: grid, random, nested CV
Гиперпараметры (глубина дерева, K у kNN, learning rate у бустинга) не подбираются обучением, но и не берутся с потолка. Их выбирают по данным — значит, и выбор надо проводить внутри CV, иначе оценка снова оптимистична.
Grid search. Сетка значений каждого параметра, перебор всех комбинаций, сравнение по среднему CV. Число обучений = произведение размеров сетки × K: три параметра по пять значений при K = 5 — 625 обучений. Просто и предсказуемо, но дорого на многих параметрах.
Random search. Случайные комбинации из диапазонов при том же бюджете покрывают больше разных значений, и важные параметры находятся быстрее (неважные всё равно не влияют). Шаблон: сначала грубый случайный поиск, затем узкая сетка вокруг лучшей точки.
Nested CV — кратко. Внешний CV оценивает процедуру «подбор + обучение» целиком, внутренний выбирает настройки. Нужен, когда сравнивают сами процедуры; для отчёта «модель 1 против модели 2» достаточно одного уровня с одинаковыми фолдами.
Ловушка из модуля 8: преобразования — масштаб, кодирование, заполнение
пропусков — считаются только на обучающей части фолда; в sklearn это
Pipeline внутри cross_val_score. Финальная цифра — на test, который
трогали ровно один раз.
📖 11.6. Какая модель когда
Правило первое и последнее: сначала baseline, потом усложнение. Baseline (константа, затем линейная модель) задаёт планку честности: если бустинг не бьёт линейную модель значимо, берите линейную — она проще и объяснимее. Сравнение — всегда на одном протоколе: те же фолды, та же метрика.
| Семейство | Когда адекватно | Плюсы | Минусы |
|---|---|---|---|
| Линейные (2–3) | сотни строк | быстро, объяснимо | нелинейность — руками |
| Дерево (4) | сотни строк | правила, объяснимо | переобучается |
| Лес (5) | тысячи строк | стабильнее дерева | слабее бустинга |
| Бустинг (6) | тысячи+ | лучшая точность | настройка, сложнее |
Подробности. Линейные модели хороши как разведка знаков и величин (модуль 3); дерево — первый нелинейный инструмент (модуль 4); лес — способ усреднить нестабильность дерева (модуль 5); бустинг — рабочая лошадка табличных задач (модуль 6). Экзотика (kNN, SVM, нейросети) для табличных — не первая остановка: kNN чувствителен к масштабу (модуль 8), нейросети обычно не бьют бустинг.
📖 11.7. Компромиссы
- Holdout vs k-fold. Быстрый holdout годится для разведки, финальная цифра — по CV: меньше данных — сильнее перевес в пользу CV.
- K большой vs маленький. Больше фолдов — стабильнее оценка и дороже; малое K быстрее, но шумнее. K = 5 — рабочий дефолт.
- Стратификация vs случайность. Стратификация сохраняет редкие классы, но усложняет протокол; при дисбалансе она обязательна.
- Grid vs random search. Grid исчерпывающий и предсказуемый, но растёт произведением размеров; random при том же бюджете исследует шире.
- Тюнинг vs больше данных. Прирост от данных и фич обычно больше, чем от последних процентов гиперпараметров: сначала данные, потом сетки. И помните: CV — лабораторная оценка на прошлом, а в бою распределение дрейфует, поэтому метрику модели нужно мониторить после выката.
💻 Практика
- (15 мин) Сниппет. Запустите
kfold-cv.py. Найдите фолд, «счастливый» для константы, и фолд, «несчастный» — как меняется вывод, если смотреть только на них? Почему докладывают среднее? - (15 мин) K=10. Поменяйте
K = 5наK = 10(фолды станут по 2 точки) и перезапустите. Стал ли разброс уже? Что происходит с оценкой — и почему не стоит гнаться за большим K на маленьких данных? - (10 мин) Свой протокол. Для своей задачи выпишите: какой сплит нужен (стратификация / время / группы), какое K, какая метрика и как выглядит итоговая строка результата в отчёте.
- (10 мин) Бюджет поиска. Три гиперпараметра по пять значений, K = 5: сколько обучений у полного grid? А если бюджет — 25 обучений, что даст random search и почему?
🧩 Quiz "Проверь себя"
✅ Итоги модуля
- Объяснить, почему один holdout — лотерея, и назвать три причины.
- Провести k-fold по шагам и прочитать результат как «среднее ± разброс».
- Выбрать сплит под данные: стратификация, время или группы.
- Показать на сниппете, как разброс меняется между фолдами.
- Подобрать гиперпараметры внутри CV, не допустив утечки.
- Назвать таблицу «какая модель когда» и правило «сначала baseline».
🔗 Мостик к Модулю 12
Инструменты собраны: данные чистим (модули 8, 10), модель выбираем честно (модуль 11), мерим на отложенном test. Осталось собрать из этого один проект — от задачи и метрики до отчёта «выкатывать или нет». Модуль 12 — капстоун на табличных данных: полный цикл за уик-энд, с кодом на sklearn и критериями приёмки.