← Назад к вопросам

Почему смещение и разброс либо низкие либо высокие у градиентного бустинга?

1.3 Junior🔥 151 комментариев
#Машинное обучение

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Bias-Variance Trade-off в Gradient Boosting

Это глубокий вопрос о природе бустинга. Действительно, Gradient Boosting (GB) интересен тем, что управляет bias-variance trade-off иначе, чем другие алгоритмы. Разберу детально.

Что такое Bias и Variance

Помимо случайного шума, полная ошибка модели состоит из двух компонентов:

Total Error = Bias² + Variance + Noise

Bias (смещение): ошибка от неправильного предположения алгоритма
         Модель систематически неправильна (недообучается)
         
Variance (разброс): чувствительность к данным обучения
         Модель нестабильна, зависит от конкретной выборки (переобучается)

Пример:

  • Низкий bias, высокая variance: глубокая нейросеть на малых данных (переобучение)
  • Высокий bias, низкая variance: линейная регрессия на нелинейных данных (недообучение)
  • Низкий bias, низкая variance: идеальный сценарий (редко достижимый)

Почему градиентный бустинг особенный

GB строит ансамбль слабых моделей (обычно небольших деревьев) последовательно. Каждое новое дерево фиксит ошибки предыдущих.

1. Начальная стадия: снижение Bias

Первые деревья фокусируют на систематических ошибках основного тренда:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.ensemble import GradientBoostingRegressor

# Нелинейные данные
X = np.linspace(0, 10, 100).reshape(-1, 1)
y = np.sin(X).ravel() + np.random.randn(100) * 0.2

plt.figure(figsize=(15, 4))

# Первое дерево
gb_1 = GradientBoostingRegressor(n_estimators=1, learning_rate=0.1)
gb_1.fit(X, y)
y_pred_1 = gb_1.predict(X)

plt.subplot(1, 3, 1)
plt.scatter(X, y, alpha=0.5, label='data')
plt.plot(X, y_pred_1, 'r-', linewidth=2, label='1st tree')
plt.title('N=1: Высокий Bias (недообучена)')
plt.legend()

# 10 деревьев
gb_10 = GradientBoostingRegressor(n_estimators=10, learning_rate=0.1)
gb_10.fit(X, y)
y_pred_10 = gb_10.predict(X)

plt.subplot(1, 3, 2)
plt.scatter(X, y, alpha=0.5)
plt.plot(X, y_pred_10, 'g-', linewidth=2, label='10 trees')
plt.title('N=10: Bias ↓ (лучше!')
plt.legend()

# 100 деревьев
gb_100 = GradientBoostingRegressor(n_estimators=100, learning_rate=0.1)
gb_100.fit(X, y)
y_pred_100 = gb_100.predict(X)

plt.subplot(1, 3, 3)
plt.scatter(X, y, alpha=0.5)
plt.plot(X, y_pred_100, 'b-', linewidth=2, label='100 trees')
plt.title('N=100: Bias↓ но Variance↑')
plt.legend()

plt.tight_layout()
plt.show()

Первые ~10-20 деревьев быстро снижают bias, захватывая основную структуру в данных.

2. Среднее: золотая середина

Есть оптимальная точка, где модель одновременно имеет:

  • Низкий bias (хорошо адаптирована к данным)
  • Низкую variance (генерализирует хорошо)

3. Позднее: растёт Variance (переобучение)

Когда деревьев слишком много, они начинают запоминать шум:

from sklearn.metrics import mean_squared_error

n_estimators_range = [1, 5, 10, 20, 50, 100, 200, 300]
train_errors = []
test_errors = []

for n in n_estimators_range:
    gb = GradientBoostingRegressor(n_estimators=n, learning_rate=0.1)
    gb.fit(X[:80], y[:80])  # Тренируемся на 80%
    
    train_pred = gb.predict(X[:80])
    test_pred = gb.predict(X[80:])  # Тестируемся на 20%
    
    train_errors.append(mean_squared_error(y[:80], train_pred))
    test_errors.append(mean_squared_error(y[80:], test_pred))

plt.figure(figsize=(10, 6))
plt.plot(n_estimators_range, train_errors, 'o-', label='Train Error (Bias↓)')
plt.plot(n_estimators_range, test_errors, 's-', label='Test Error (Variance↑)')
plt.axvline(x=20, color='r', linestyle='--', alpha=0.5, label='Оптимум')
plt.xlabel('Количество деревьев')
plt.ylabel('MSE')
plt.title('Bias-Variance Trade-off в Gradient Boosting')
plt.legend()
plt.grid()
plt.show()

На графике видно:

  • Train error падает (bias уменьшается)
  • Test error сначала падает (bias↓ > variance↑), потом растёт (variance↑ > bias↓)
  • Оптимум обычно в точке, где test error минимален

Почему бустинг работает именно так

Идея Sequential Correction

Раунд 1: Предсказываем основной тренд
  y_pred_1 = weak_model_1(X)
  error_1 = y - y_pred_1  (большие ошибки на сложных участках)

Раунд 2: Обучаем новое дерево на остатках
  y_pred_2 = y_pred_1 + learning_rate * weak_model_2(X, error_1)
  error_2 = error_1 - weak_model_2(X, error_1)  (меньше)

Раунд 3: Ещё точнее
  y_pred_3 = y_pred_2 + learning_rate * weak_model_3(X, error_2)
  error_3 = error_2 - weak_model_3(X, error_2)  (совсем мало)

Каждое новое дерево исправляет систематические ошибки предыдущего ансамбля. Это особенно эффективно для снижения bias.

Управление Bias-Variance в GB

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

1. Глубина деревьев (max_depth) - главный рычаг

from sklearn.ensemble import GradientBoostingRegressor
from sklearn.datasets import make_regression
from sklearn.model_selection import learning_curve

X, y = make_regression(n_samples=500, n_features=20, noise=20, random_state=42)

plt.figure(figsize=(12, 4))

for idx, depth in enumerate([2, 4, 8]):
    gb = GradientBoostingRegressor(
        n_estimators=100,
        max_depth=depth,
        learning_rate=0.1,
        random_state=42
    )
    
    # Кривые обучения показывают bias-variance
    train_sizes, train_scores, val_scores = learning_curve(
        gb, X, y, cv=5, train_sizes=np.linspace(0.1, 1.0, 10),
        scoring='neg_mean_squared_error', n_jobs=-1
    )
    
    plt.subplot(1, 3, idx+1)
    plt.plot(train_sizes, -train_scores.mean(axis=1), 'o-', label='Train')
    plt.plot(train_sizes, -val_scores.mean(axis=1), 's-', label='Val')
    
    bias = -val_scores.mean(axis=1)[-1]  # Ошибка на большом наборе
    variance = (val_scores.std(axis=1)[-1])**2
    
    plt.title(f'Depth={depth}\nBias~{bias:.1f}, Var~{variance:.2f}')
    plt.xlabel('Training set size')
    plt.ylabel('MSE')
    plt.legend()
    plt.grid()

plt.tight_layout()
plt.show()
  • max_depth = 2: HIGH BIAS, LOW VARIANCE (недообучение)
  • max_depth = 4: BALANCED
  • max_depth = 8: LOW BIAS, HIGH VARIANCE (переобучение)

2. Learning Rate (shrinkage)

# Низкий learning_rate делает обучение медленнее, но стабильнее
gb_slow = GradientBoostingRegressor(
    n_estimators=200,
    learning_rate=0.01,  # Очень малые шаги
    max_depth=4
)

gb_fast = GradientBoostingRegressor(
    n_estimators=200,
    learning_rate=0.1,  # Большие шаги
    max_depth=4
)

# Низкий learning_rate → Низкая variance, нужно больше деревьев

3. Количество деревьев (n_estimators)

# Early Stopping - лучший способ контролировать bias-variance
from sklearn.ensemble import GradientBoostingRegressor

gb = GradientBoostingRegressor(
    n_estimators=1000,  # Можно много
    learning_rate=0.05,
    max_depth=4,
    validation_fraction=0.2,
    n_iter_no_change=20  # Остановить, если 20 раундов нет улучшения
)

gb.fit(X_train, y_train)

# Модель остановилась, когда достигла оптимума
print(f'Trained with {gb.n_estimators_} trees')

Практический пример: диагностирование проблемы

from sklearn.metrics import mean_squared_error
from sklearn.model_selection import cross_val_score

X_train, X_test = X[:300], X[300:]
y_train, y_test = y[:300], y[300:]

# Обучаем GB
gb = GradientBoostingRegressor(n_estimators=100, max_depth=5)
gb.fit(X_train, y_train)

train_pred = gb.predict(X_train)
test_pred = gb.predict(X_test)

train_mse = mean_squared_error(y_train, train_pred)
test_mse = mean_squared_error(y_test, test_pred)

print(f'Train MSE: {train_mse:.4f}')
print(f'Test MSE: {test_mse:.4f}')
print(f'Разница: {test_mse - train_mse:.4f}')

if test_mse - train_mse > 0.5:  # Большая разница
    print('\n→ HIGH VARIANCE (переобучение!)')
    print('  Решение: увеличить learning_rate, уменьшить max_depth или добавить regularization')
elif test_mse > 0.3:  # Высокая общая ошибка
    print('\n→ HIGH BIAS (недообучение!)')
    print('  Решение: больше деревьев, увеличить max_depth, улучшить признаки')
else:
    print('\n✓ Good balance (bias ≈ variance)')

Реальный пример из практики

Разрабатывал модель для прогнозирования спроса товаров:

Сценарий 1 - HIGH BIAS:

GradientBoostingRegressor(n_estimators=10, max_depth=2, learning_rate=0.5)
Train RMSE: 125
Test RMSE: 128
→ Модель недообучена, не учитывает сложные паттерны

Сценарий 2 - OPTIMIZED:

GradientBoostingRegressor(
    n_estimators=200,
    max_depth=4,
    learning_rate=0.05,
    subsample=0.8,  # Случайные выборки
    colsample_bytree=0.8  # Случайные признаки
)
Train RMSE: 18
Test RMSE: 22  ← Хорошая генерализация!
→ Низкий bias, контролируемая variance

Сценарий 3 - HIGH VARIANCE:

GradientBoostingRegressor(n_estimators=500, max_depth=10, learning_rate=0.1)
Train RMSE: 5
Test RMSE: 120
→ Запомнила шум в тренировочных данных

Вывод

Gradient Boosting не имеет фиксированного bias-variance profile. Это зависит от гиперпараметров:

  • Малозависимых моделей (глубоко обученных): LOW BIAS, HIGH VARIANCE
  • Переуregularized: HIGH BIAS, LOW VARIANCE
  • Оптимально настроенный GB: LOW BIAS, LOW VARIANCE ✓

Золотое правило: использовать early stopping или learning curve для поиска оптимума, где test error минимален — это точка, где bias и variance сбалансированы.