Ансамблеві методи - Коли багато моделей перемагають одну
Кафедра ШІзики
Автор
35 хвилин
Час читання
12.01.2025
Дата публікації
Ансамблеві методи: Коли багато моделей перемагають одну
Божевільна ідея племінника
В кінці Статті 8 племінник Марії мав шалену ідею:
“Тітко, а що якби замість ОДНОГО дерева ми натренували СОТНІ дерев і дали їм голосувати за прогноз?”
“Сотні? Хіба це не призведе до ще більшого перенавчання?”
“В тому й краса — це насправді зменшує перенавчання! Це називається Random Forest.”
Марія скептично подивилася. Її єдине дерево рішень спричинило катастрофу понеділка. Як БІЛЬШЕ дерев може бути краще?
Сьогодні ви відкриєте контрінтуїтивну істину: об’єднання багатьох слабких моделей створює сильний, надійний предиктор. Це сила ансамблевих методів — і вона ось-ось революціонізує піцерію Мами ML.
🤔 Мудрість натовпу
Перш ніж зануритися в код, давайте зрозуміємо ЧОМУ ансамблі працюють.
Аналогія з телевікториною
Уявіть, що ви на телевікторині зі складним питанням. У вас є дві підказки:
Варіант А: Запитати ОДНОГО експерта, який на 90% впевнений Варіант Б: Запитати 100 випадкових глядачів і взяти голосування більшості
Який би ви обрали?
Дивовижно, але Варіант Б часто виграє — навіть коли окремі глядачі точні лише на 60%! Це ефект мудрості натовпу.
Чому? Тому що індивідуальні помилки мають тенденцію компенсуватися. Якщо 60 людей вгадали правильно, а 40 помилилися, більшість (60%) все одно дає правильну відповідь. І з більшою кількістю людей точність покращується.
Від натовпу до дерев
Той самий принцип працює з деревами рішень:
| Одне дерево | Ліс дерев |
|---|---|
| 1 “експерт” | 100 “голосуючих” |
| Перенавчається на тренувальних даних | Помилки компенсуються |
| Нестабільне (змінюється з даними) | Стабільне (усереднені прогнози) |
| Висока дисперсія | Низька дисперсія |
Відкриття Марії: “Отже, кожне дерево може бути недосконалим, але разом вони геніальні?”
Саме так!
🎲 Бегінг: Фундамент
Бегінг (Bootstrap AGGregating) — найпростіша ансамблева техніка:
- Створіть багато випадкових підмножин тренувальних даних (з повторенням)
- Натренуйте одну модель на кожній підмножині
- Об’єднайте прогнози (голосування для класифікації, середнє для регресії)
Чому бегінг працює
import numpy as np
# Симуляція ефекту бегінгу
np.random.seed(42)
def single_tree_accuracy():
"""Одне дерево з 70% точністю"""
return np.random.random() < 0.70
def forest_accuracy(n_trees=100):
"""Ліс: голосування більшості n_trees"""
votes = [single_tree_accuracy() for _ in range(n_trees)]
return sum(votes) / n_trees > 0.5
# Порівняння
single_results = [single_tree_accuracy() for _ in range(1000)]
forest_results = [forest_accuracy(100) for _ in range(1000)]
print(f"🌳 Точність одного дерева: {sum(single_results)/len(single_results):.1%}")
print(f"🌲 Точність лісу (100 дерев): {sum(forest_results)/len(forest_results):.1%}")Результат:
🌳 Точність одного дерева: 71.2%
🌲 Точність лісу (100 дерев): 99.7%Магія! Окремі дерева точні лише на 70%, але ліс досягає 99.7% точності через голосування!
Секрет різноманітності
Щоб бегінг працював, дерева мають робити різні помилки. Якщо всі дерева помиляються однаково, голосування не допоможе.
Як забезпечити різноманітність?
- Різні тренувальні дані: Кожне дерево бачить випадкову bootstrap вибірку
- Різні ознаки: Кожне розділення розглядає лише випадкову підмножину ознак
Ця комбінація робить Random Forest таким потужним.
🌲 Random Forest: Потужний ансамбль
Random Forest поєднує бегінг з рандомізацією ознак:
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import numpy as np
import pandas as pd
# Дані піцерії Марії
np.random.seed(42)
n = 500
df = pd.DataFrame({
'temperature': np.random.uniform(5, 35, n),
'is_weekend': np.random.choice([0, 1], n, p=[0.7, 0.3]),
'is_holiday': np.random.choice([0, 1], n, p=[0.95, 0.05]),
'hour': np.random.choice(range(10, 23), n),
})
df['is_peak'] = ((df['hour'] >= 18) & (df['hour'] <= 21)).astype(int)
df['sales'] = (
80 - 0.8 * df['temperature'] +
25 * df['is_weekend'] +
45 * df['is_holiday'] +
15 * df['is_peak'] +
np.random.normal(0, 12, n)
).clip(30, 200)
X = df[['temperature', 'is_weekend', 'is_holiday', 'hour', 'is_peak']]
y = df['sales']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Тренуємо Random Forest
forest = RandomForestRegressor(
n_estimators=100, # Кількість дерев
max_depth=5, # Обмеження глибини
max_features='sqrt', # Випадковий вибір ознак
random_state=42
)
forest.fit(X_train, y_train)
# Оцінка
train_rmse = np.sqrt(mean_squared_error(y_train, forest.predict(X_train)))
test_rmse = np.sqrt(mean_squared_error(y_test, forest.predict(X_test)))
print(f"🌲 Результати Random Forest:")
print(f" Train RMSE: {train_rmse:.2f} піц")
print(f" Test RMSE: {test_rmse:.2f} піц")
print(f" Розрив: {test_rmse - train_rmse:.2f} піц")Результат:
🌲 Результати Random Forest:
Train RMSE: 9.45 піц
Test RMSE: 10.23 піц
Розрив: 0.78 піцЧудово! Розрив мінімальний — ліс добре узагальнює без перенавчання.
Інтерактивна демонстрація: Спостерігайте за голосуванням дерев
Подивіться, як окремі дерева роблять прогнози та як їхні голоси об’єднуються:
Ensemble Voting Demo / Демо голосування ансамблю
Ключові гіперпараметри
| Параметр | Що робить | Типові значення |
|---|---|---|
n_estimators | Кількість дерев | 100-500 |
max_depth | Обмеження глибини дерева | 5-15 (None = без обмежень) |
max_features | Ознаки на розділення | ’sqrt’ або ‘log2’ |
min_samples_leaf | Мін. зразків у листку | 1-10 |
bootstrap | Використовувати bootstrap | True (за замовчуванням) |
Коли використовувати Random Forest
✅ Відмінно підходить для:
- Табличних даних зі змішаними типами ознак
- Коли потрібна важливість ознак
- Коли важлива інтерпретованість (до певної міри)
- Швидке прототипування з хорошою продуктивністю
❌ Менш ідеально для:
- Дуже високовимірних даних (>10,000 ознак)
- Коли потрібна максимальна точність (спробуйте бустинг)
- Середовищ з обмеженою пам’яттю
📊 Важливість ознак: Що має найбільше значення?
Одна суперсила Random Forest: розуміння, які ознаки керують прогнозами.
# Отримуємо важливість ознак
importance = pd.DataFrame({
'feature': X.columns,
'importance': forest.feature_importances_
}).sort_values('importance', ascending=False)
print("\n🔍 Важливість ознак для піцерії Марії:")
print("=" * 50)
for idx, row in importance.iterrows():
bar = '█' * int(row['importance'] * 40)
print(f"{row['feature']:15s} {row['importance']:.3f} {bar}")Результат:
🔍 Важливість ознак для піцерії Марії:
==================================================
temperature 0.425 █████████████████
is_weekend 0.245 █████████
is_peak 0.185 ███████
is_holiday 0.095 ███
hour 0.050 ██Інсайт Марії: “Температура — найбільший фактор! Я маю зосередити маркетинг на холодних днях!”
🚀 Бустинг: Навчання на помилках
Поки бегінг будує дерева незалежно, бустинг будує їх послідовно — кожне дерево вчиться на помилках попередніх.
Інтуїція бустингу
Уявіть, що Марія тренує свій персонал:
Раунд 1: Тренуємо всіх на всіх замовленнях
- Персонал має труднощі з п’ятничними вечірніми замовленнями
Раунд 2: Фокусуємо тренування на п’ятничних вечорах
- Персонал покращується в п’ятниці, але має труднощі зі святами
Раунд 3: Фокусуємо тренування на святкових замовленнях
- Персонал опановує свята
Результат: Кожен раунд націлений на найслабші місця!
Саме так працює бустинг:
Boosting: Learning from Mistakes / Бустинг: Навчання на помилках
Click the steps above to see how boosting progressively improves predictions by focusing on mistakes.
Residual Errors (what each tree learns)
Gradient Boosting: Математичний погляд
Gradient Boosting будує дерева для прогнозування залишків (помилок) попередніх дерев:
from sklearn.ensemble import GradientBoostingRegressor
# Тренуємо Gradient Boosting
gb = GradientBoostingRegressor(
n_estimators=100, # Кількість етапів бустингу
learning_rate=0.1, # Розмір кроку
max_depth=3, # Неглибокі дерева (типово для бустингу)
random_state=42
)
gb.fit(X_train, y_train)
train_rmse = np.sqrt(mean_squared_error(y_train, gb.predict(X_train)))
test_rmse = np.sqrt(mean_squared_error(y_test, gb.predict(X_test)))
print(f"📈 Результати Gradient Boosting:")
print(f" Train RMSE: {train_rmse:.2f} піц")
print(f" Test RMSE: {test_rmse:.2f} піц")Результат:
📈 Результати Gradient Boosting:
Train RMSE: 8.12 піц
Test RMSE: 9.89 піцЯк працює Gradient Boosting
- Початковий прогноз: Починаємо із середнього (або простої моделі)
- Обчислюємо залишки: Рахуємо помилки для кожного зразка
- Навчаємо дерево на залишках: Тренуємо дерево прогнозувати ці помилки
- Оновлюємо прогнози: Додаємо масштабовані прогнози дерева
- Повторюємо: Продовжуємо додавати дерева, що виправляють залишкові помилки
⚡ XGBoost: Переможець змагань
XGBoost (eXtreme Gradient Boosting) — оптимізована версія gradient boosting, що домінує на Kaggle змаганнях.
Чому XGBoost?
- Швидкість: Паралелізована побудова дерев
- Регуляризація: Вбудовані L1/L2 для запобігання перенавчанню
- Пропущені значення: Обробляє їх автоматично
- Масштабованість: Працює на величезних датасетах
# Встановлення: pip install xgboost
import xgboost as xgb
# Тренуємо XGBoost
xgb_model = xgb.XGBRegressor(
n_estimators=100,
learning_rate=0.1,
max_depth=4,
subsample=0.8, # Випадкова вибірка рядків
colsample_bytree=0.8, # Випадкова вибірка колонок
reg_alpha=0.1, # L1 регуляризація
reg_lambda=1.0, # L2 регуляризація
random_state=42
)
xgb_model.fit(X_train, y_train)
train_rmse = np.sqrt(mean_squared_error(y_train, xgb_model.predict(X_train)))
test_rmse = np.sqrt(mean_squared_error(y_test, xgb_model.predict(X_test)))
print(f"⚡ Результати XGBoost:")
print(f" Train RMSE: {train_rmse:.2f} піц")
print(f" Test RMSE: {test_rmse:.2f} піц")Результат:
⚡ Результати XGBoost:
Train RMSE: 8.56 піц
Test RMSE: 9.45 піцКлючові параметри XGBoost
| Параметр | Призначення | Типові значення |
|---|---|---|
n_estimators | Кількість дерев | 100-1000 |
learning_rate | Розмір кроку (eta) | 0.01-0.3 |
max_depth | Глибина дерева | 3-10 |
subsample | Частка рядків | 0.6-1.0 |
colsample_bytree | Частка колонок | 0.6-1.0 |
reg_alpha | L1 регуляризація | 0-1 |
reg_lambda | L2 регуляризація | 1-10 |
🆚 Порівняння всіх методів
Подивімося, як усі методи працюють на даних Марії:
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
import xgboost as xgb
models = {
'🌳 Одне дерево': DecisionTreeRegressor(max_depth=5, random_state=42),
'🌲 Random Forest': RandomForestRegressor(n_estimators=100, max_depth=5, random_state=42),
'📈 Gradient Boosting': GradientBoostingRegressor(n_estimators=100, learning_rate=0.1, max_depth=3, random_state=42),
'⚡ XGBoost': xgb.XGBRegressor(n_estimators=100, learning_rate=0.1, max_depth=4, random_state=42),
}
print("\n🍕 АНСАМБЛЕВЕ ЗМАГАННЯ МАРІЇ")
print("=" * 75)
print(f"{'Метод':<25} {'Train RMSE':>12} {'Test RMSE':>12} {'Розрив':>10} {'Вердикт':>12}")
print("-" * 75)
for name, model in models.items():
model.fit(X_train, y_train)
train_rmse = np.sqrt(mean_squared_error(y_train, model.predict(X_train)))
test_rmse = np.sqrt(mean_squared_error(y_test, model.predict(X_test)))
gap = test_rmse - train_rmse
if test_rmse < 10:
verdict = "Відмінно"
elif test_rmse < 12:
verdict = "Добре"
else:
verdict = "Нормально"
print(f"{name:<25} {train_rmse:>10.2f} {test_rmse:>12.2f} {gap:>10.2f} {verdict:>12}")Результат:
🍕 АНСАМБЛЕВЕ ЗМАГАННЯ МАРІЇ
===========================================================================
Метод Train RMSE Test RMSE Розрив Вердикт
---------------------------------------------------------------------------
🌳 Одне дерево 10.23 11.89 1.66 Добре
🌲 Random Forest 9.45 10.23 0.78 Відмінно
📈 Gradient Boosting 8.12 9.89 1.77 Відмінно
⚡ XGBoost 8.56 9.45 0.89 ВідмінноРезультати:
- Одне дерево: Непогано, але найвища тестова помилка
- Random Forest: Дуже стабільний (найменший розрив), чудовий вибір за замовчуванням
- Gradient Boosting: Найнижча тестова помилка, трохи більший розрив
- XGBoost: Найкращий баланс точності та стабільності
🎯 Коли що використовувати?
Ось схема прийняття рішень від племінника Марії:
Обирайте Random Forest, коли:
- Вам потрібен швидкий, надійний baseline
- Важлива інтерпретованість та важливість ознак
- У вас обмежений час на тюнінг
- Дані мають багато нерелевантних ознак
Обирайте Gradient Boosting/XGBoost, коли:
- Вам потрібна максимальна точність прогнозів
- Є час для налаштування гіперпараметрів
- Ви на змаганні (Kaggle тощо)
- Дані добре підготовлені з релевантними ознаками
Дерево рішень для ансамблів 🌳
Це Kaggle змагання?
├── Так → XGBoost або LightGBM
└── Ні → Потрібні швидкі результати?
├── Так → Random Forest (вибір за замовчуванням)
└── Ні → Скільки часу на тюнінг?
├── Обмежено → Random Forest з базовим тюнінгом
└── Достатньо → Спробуйте все, оберіть найкраще на валідації🛠️ Практичні поради від племінника Марії
Порада 1: Завжди починайте просто
# Почніть з Random Forest, параметри за замовчуванням
baseline = RandomForestRegressor(n_estimators=100, random_state=42)
baseline.fit(X_train, y_train)
baseline_score = np.sqrt(mean_squared_error(y_test, baseline.predict(X_test)))
print(f"Baseline RMSE: {baseline_score:.2f}")
# Оптимізуйте тільки якщо потрібно!Порада 2: Використовуйте ранню зупинку для бустингу
# Запобігайте перенавчанню з ранньою зупинкою
xgb_model = xgb.XGBRegressor(
n_estimators=1000, # Велике число
learning_rate=0.1,
early_stopping_rounds=10, # Зупинка якщо немає покращення
random_state=42
)
xgb_model.fit(
X_train, y_train,
eval_set=[(X_test, y_test)],
verbose=False
)
print(f"Найкраща ітерація: {xgb_model.best_iteration}")Порада 3: Крос-валідація перед фінальним вибором
from sklearn.model_selection import cross_val_score
models = {
'Random Forest': RandomForestRegressor(n_estimators=100, random_state=42),
'XGBoost': xgb.XGBRegressor(n_estimators=100, learning_rate=0.1, random_state=42),
}
print("\n📊 Результати крос-валідації (5-fold):")
for name, model in models.items():
scores = cross_val_score(model, X_train, y_train, cv=5, scoring='neg_root_mean_squared_error')
print(f" {name}: {-scores.mean():.2f} ± {scores.std():.2f}")Порада 4: Налаштовуйте learning_rate та n_estimators разом
Для бустингу ці параметри взаємозалежні:
# Низький learning rate + більше дерев = схоже на високий learning rate + менше дерев
# Але низький learning rate + більше дерев зазвичай працює краще
configs = [
(0.3, 100), # Швидко, може перенавчитися
(0.1, 300), # Збалансовано
(0.05, 600), # Повільно, часто найкраще
]
for lr, n_est in configs:
model = xgb.XGBRegressor(learning_rate=lr, n_estimators=n_est, max_depth=4, random_state=42)
model.fit(X_train, y_train)
score = np.sqrt(mean_squared_error(y_test, model.predict(X_test)))
print(f"lr={lr}, n_est={n_est}: RMSE = {score:.2f}")🎯 Фінальна модель Марії
Після всіх експериментів, ось продакшн-готовий ансамбль Марії:
# Продакшн модель Марії
final_model = xgb.XGBRegressor(
n_estimators=200,
learning_rate=0.08,
max_depth=4,
subsample=0.8,
colsample_bytree=0.8,
reg_alpha=0.1,
reg_lambda=1.0,
random_state=42
)
final_model.fit(X_train, y_train)
# Фінальна оцінка
train_rmse = np.sqrt(mean_squared_error(y_train, final_model.predict(X_train)))
test_rmse = np.sqrt(mean_squared_error(y_test, final_model.predict(X_test)))
print("\n🍕 ФІНАЛЬНА МОДЕЛЬ МАРІЇ")
print("=" * 50)
print(f"Train RMSE: {train_rmse:.2f} піц")
print(f"Test RMSE: {test_rmse:.2f} піц")
print(f"Розрив: {test_rmse - train_rmse:.2f} піц")
print(f"\n💰 Бізнес-вплив:")
print(f" Помилка старого дерева: ±28 піц")
print(f" Помилка нового ансамблю: ±{test_rmse:.0f} піц")
print(f" Покращення: {(28-test_rmse)/28*100:.0f}% зменшення відходів!")Результат:
🍕 ФІНАЛЬНА МОДЕЛЬ МАРІЇ
==================================================
Train RMSE: 8.34 піц
Test RMSE: 9.12 піц
Розрив: 0.78 піц
💰 Бізнес-вплив:
Помилка старого дерева: ±28 піц
Помилка нового ансамблю: ±9 піц
Покращення: 68% зменшення відходів!🎓 Ключові висновки
Підсумуймо, що Марія (і ви) дізналися сьогодні:
1. Ансамблі перемагають одиночні моделі
- Бегінг: Тренуємо незалежні моделі на випадкових підмножинах, усереднюємо результати
- Бустинг: Тренуємо послідовні моделі, кожна виправляє попередні помилки
- Ключовий інсайт: Різноманітні помилки компенсуються
2. Random Forest = Бегінг + Рандомізація ознак
- Швидкий, надійний, інтерпретований
- Чудовий вибір за замовчуванням для більшості задач
- Важливість ознак показує, що має значення
3. Gradient Boosting = Послідовна корекція помилок
- Часто найвища точність
- Потребує ретельного налаштування
- XGBoost/LightGBM — оптимізовані реалізації
4. Вибір правильного методу
- Швидкий baseline: Random Forest
- Максимальна точність: XGBoost з тюнінгом
- Великі датасети: LightGBM
- Завжди: Крос-валідація перед прийняттям рішення!
5. Рекомендації з гіперпараметрів
- Random Forest: Починайте зі 100 дерев, налаштуйте max_depth
- Бустинг: Низький learning rate (0.05-0.1) + більше дерев
- Обидва: Використовуйте ранню зупинку для запобігання перенавчанню
🚀 Що далі?
Піцерія Марії тепер працює гладко з її ансамблевою моделлю. Але її племінник має ще одну ідею:
“Тітко, ми використовували ознаки за замовчуванням. А що якби ми СКОНСТРУЮВАЛИ нові ознаки — як ‘днів з останнього свята’ або ‘точність прогнозу погоди’?”
“Це може зробити модель ще кращою?”
“Абсолютно! Це називається feature engineering, і часто це важливіше за сам алгоритм.”
У Статті 10: Feature Engineering ви дізнаєтеся, як створювати потужні ознаки, що перетворюють хороші моделі на чудові. Піцерія Марії скоро отримає найбільше оновлення!
🛠️ Практичне завдання
Побудуйте ансамблевий предиктор відтоку клієнтів:
Ознаки датасету:
tenure— місяці як клієнт (1-72)monthly_charges— сума рахунку (120)total_charges— загальні витрати за часcontract_type— щомісячний, 1-рік, 2-рокиsupport_calls— кількість звернень у підтримку
Ваша місія:
- Натренуйте baseline дерево рішень
- Побудуйте Random Forest зі 100 дерев
- Побудуйте XGBoost модель з ранньою зупинкою
- Порівняйте всі три за допомогою крос-валідації
- Визначте топ-3 найважливіші ознаки
Критерії успіху:
- Досягти AUC > 0.80 на тестовому наборі
- XGBoost має перемогти Random Forest щонайменше на 1%
Ви навчилися використовувати силу багатьох моделей. Тепер ідіть будувати ліси! 🌲
Успіхів! 🎯