Модуль 5. Баггинг и случайный лес

Цель модуля: понять, откуда берётся нестабильность одного дерева и как ансамбль из деревьев, обученных на bootstrap-выборках и случайных подмножествах признаков, усредняет разброс; разобрать голосование на выводе сниппета и запомнить, что такое out-of-bag.

Перед началом: модуль 4 (деревья решений) — обязательно; модуль 1 (сплиты train/val/test). Инструменты: песочница сайта (чистый Python), сниппет ниже. Трудоёмкость: ~1.5 ч (чтение 45 мин · сниппет и практика 30 мин).

🔥 Один свидетель капризен — спросите семерых

Модуль 4 закончился неудобным фактом: дерево перестраивается от одной новой строки. Представьте консультанта, который честно отвечает по данным, но при малейшем изменении датасета выдаёт другой ответ — в прод такого не возьмут.

Идея баггинга — как в жизни: один свидетель ошибается и путается, а совет многих независимых свидетелей устойчивее. Построим случайный лес: десятки деревьев, каждое училось на своей слегка искажённой копии данных. Деревья ошибаются в разных местах, а большинство голосов вытаскивает правильный ответ.

Сразу честно, без магии: лес снижает разброс (variance) ответов, но не лечит систематическую ошибку. Он спасает от капризности, а не от плохих данных. Разберём механику по частям.

📖 5.1. Одно дерево капризно: откуда берётся нестабильность

Глубокое дерево — это модель с низким смещением и высоким разбросом. Смещение (bias) — систематическая ошибка: модель в среднем промахивается мимо истины независимо от выборки. Разброс (variance) — разболтанность: на чуть другую выборку модель отвечает по-другому. У дерева bias небольшой (оно может выучить любую границу), а variance огромный.

Почему так? Каждый узел выбирает лучший вопрос жадно, по текущим данным. Уберите одну строку или исказите признак — и лучшим наверху может стать другой вопрос. Всё дерево ниже перестраивается. На маленьком наборе это заметно особенно: в сниппете модуля 4 глубину 2 задавал один конкретный пример.

Плохая новость: бороться с variance, урезая дерево, жалко — теряем нелинейности. Хорошая новость: разброс побеждается не урезанием, а усреднением множества разных деревьев. Именно об этом вся эта теория.

📖 5.2. Bootstrap: те же данные, но с повторами

Первый источник разнообразия — bootstrap: выборка размера n из тех же n строк, но с возвращением. Строку вытягивают случайно, а потом кладут обратно, поэтому часть строк попадает дважды, часть не попадает вовсе.

Сколько строк пропустит одна выборка? Вероятность конкретной строке не попасть — (1 − 1/n)^n, при больших n это ≈ 1/e ≈ 0.37. Итог из вывода сниппета: около 37% строк не попали в bootstrap каждого дерева. Эти строки — бесплатный отложенный набор, out-of-bag (OOB): каждое дерево можно честно проверить на примерах, которых оно не видело.

Важно: bootstrap — не просто «шум ради шума». Деревья, обученные на разных подвыборках, видят чуть разные версии закономерности и перестают копировать друг друга. Как именно это использовать для оценки — покажем в модуле 11 (кросс-валидация и подбор модели).

📖 5.3. Случайные признаки и голосование: ошибаться по-разному

Одного bootstrap мало. Деревья жадные: даже на разных выборках они часто выбирают один и тот же сильный признак — и тогда их ошибки совпадают. Усреднять одинаковые мнения бессмысленно.

Поэтому случайный лес добавляет вторую порцию случайности: при выборе каждого разбиения дереву показывают не все признаки, а случайное подмножество. В сниппете каждому дереву досталось 2 признака из 3; в библиотеках принят похожий дефолт (квадратный корень от числа признаков для классификации). Теперь деревья вынуждены пробовать разные ходы — и получаются по-настоящему разными.

Дальше — голосование. Классификация: каждый лист голосует классом, побеждает большинство (или усредняются вероятности). Регрессия — среднее предсказаний. Слагаемые: у каждого дерева variance большой, но ошибки не совпадают — при голосовании случайные промахи гасятся. Bias ансамбля остаётся около уровня одного дерева, а variance падает тем сильнее, чем меньше деревья коррелируют. Ключевая фраза для запоминания: лес силён не тем, что деревья умные, а тем, что они ошибаются по-разному.

Практический перевод: деревья должны быть достаточно глубокими (пусть переобучаются) — усреднение это вылечит; а вот «причёсывать» их одинаковыми признаками — вредно. Ещё следствие: лес строится параллельно — деревья не зависят друг от друга, это удобно железом (в отличие от героя модуля 6).

📖 5.4. Разбор сниппета: лес из 7 деревьев против одиночки

Сниппет random-forest.py берёт те же 14 «прогулочных» примеров, строит одиночное дерево и лес из 7 деревьев (каждое — свой bootstrap и свои 2 признака из 3), а затем проверяет их на отложенных 6 примерах. Главное — таблица голосов; читаем её построчно.

Первый пример дождь=0 t=26 ветер=0: истина 1, лес 1, голоса [1, 0, 1, 1, 1, 1, 1] — шесть деревьев за, одно против. Второй дождь=0 t=17 ветер=1: истина 0, лес 0, [0, 0, 0, 1, 0, 0, 0] — снова одиночка-противник. Третий дождь=1 t=20 ветер=1: истина 0, лес 0, [0, 0, 0, 0, 1, 0, 0] — та же картина. Четвёртый t=22: истина 1, лес 1, [1, 0, 0, 1, 1, 1, 1] — уже два противника, большинство всё равно верно. Пятый дождь=1 t=13 ветер=0: истина 0 и единодушие [0, 0, 0, 0, 0, 0, 0]. Шестой t=29: истина 1, лес 1, [1, 1, 1, 1, 1, 0, 1].

В пяти строках из шести хотя бы одно дерево ошиблось — и ни разу это не сломало ответ леса. Это и есть ансамблевый эффект в действии: ошибки деревьев не совпадают, и большинство держит правильный ответ.

Теперь честная часть. Точность в выводе: дерево 6/6, лес 6/6 — одиночное дерево справилось не хуже. Набор маленький и чистый: четырнадцати примеров не хватает, чтобы разброс успел навредить. Выигрыш леса проявится на шумных и больших данных, где у дерева есть где ошибиться. Не делайте из этого вывода «лес не нужен» — делайте вывод «на крошечном чистом наборе разницы не увидеть».

И подарок напоследок, из выводов сниппета: около 37% строк не попали в bootstrap каждого дерева — это OOB-примеры из 5.2, готовый бесплатный валидатор (как использовать честно — модуль 11).

📖 5.5. Компромиссы

  • Лес vs одиночное дерево. Лес устойчивее и точнее на шумных данных, но тяжелее (десятки деревьев) и почти нечитаем как «регламент». Дерево берите для объяснения, лес — для предсказания.
  • Больше деревьев vs скорость. n_estimators в сотнях: качество растёт, затем выходит на плато, а время обучения и предсказания растёт линейно. Лес зато легко параллелится — деревья независимы.
  • Случайность vs воспроизводимость. И bootstrap, и подмножества фич бросают жребий: без фиксированного seed два прогона не совпадут. В проде seed фиксируют, иначе не отладить.
  • OOB-оценка vs честный holdout. OOB бесплатен и уже встроен в лес, но это не замена отдельному test-сплиту (модуль 1): подбор гиперпараметров по OOB начинает «подглядывать» так же, как по обычной валидации.
  • Баггинг vs бустинг. Баггинг усредняет независимые модели и снижает разброс; бустинг (модуль 6) строит модели последовательно и снижает систематическую ошибку. Это два разных лекарства от двух разных болезней.

💻 Практика

  1. (20 мин) Голоса руками. Запустите random-forest.py. Для каждой из шести строк выпишите, сколько деревьев проголосовало против истины. Есть ли дерево, которое ошибается в нескольких строках сразу?
  2. (20 мин) Эксперимент в сниппете. Замените random.sample(range(3), 2) на range(3) — все деревья увидят все признаки. Перезапустите: стали ли голоса единодушнее? Что это говорит о важности разнообразия?
  3. (15 мин) Другой жребий. Поменяйте random.seed(3) на другое число. Меняются ли точности дерева и леса? Объясните через размер набора данных.
  4. (10 мин) OOB вслух. Выберите одну строку данных и по коду прикиньте, какие деревья её не видели (не попалась в их bootstrap). Как на таких строках получить оценку качества бесплатно?

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

1. В выводе сниппета и дерево, и лес дали 6/6. Как это понимать?
2. Что такое out-of-bag?
3. Что такое bootstrap-выборка?
4. Что снижает голосование деревьев в случайном лесу?
5. Почему деревья в лесу получаются разными?

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

  • Объяснить, почему одно дерево нестабильно, через bias и variance.
  • Собрать bootstrap руками и сказать, почему пропускается ~37% строк.
  • Объяснить, зачем лесу случайные подмножества признаков.
  • Разобрать таблицу голосов из сниппета: где были противники и что решило большинство.
  • Честно сказать, почему на выводе 6/6 у дерева и леса.
  • Сформулировать, что такое OOB и чем он полезен.

🔗 Мостик к Модулю 6

Лес строится параллельно: деревья независимы и голосуют. Это снижает разброс, но каждое дерево учится само по себе и не использует ошибки товарищей. А что, если построить деревья цепочкой, где каждое следующее прицельно исправляет ошибки предыдущих? Получится бустинг — алгоритм, который почти всегда выигрывает соревнования на табличных данных. Как работает градиентный бустинг и при чём здесь градиентный спуск из модуля 2 — в модуле 6.