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

Что является таргетом для бустинга?

2.0 Middle🔥 161 комментариев
#Машинное обучение

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

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

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

Таргет для бустинга: остатки предсказаний

Это фундаментальный вопрос о том как работает градиентный бустинг. Ответ на него объясняет почему бустинг такой мощный метод.

Главная идея

Таргет для каждого нового дерева в бустинге — это остатки (residuals) предсказаний предыдущих деревьев.

Первое дерево: предсказывает y
Второе дерево: предсказывает (y - предсказание первого дерева)
Третье дерево: предсказывает (y - предсказания первых двух деревьев)
...
N-е дерево: предсказывает остатки от N-1 деревьев

Математика

Старт:
F₀(x) = initial_prediction (например, среднее значение y)

Итерация 1:
residuals₁ = y - F₀(x)
models[1].fit(X, residuals₁)
F₁(x) = F₀(x) + η * h₁(x)

Итерация 2:
residuals₂ = y - F₁(x)  ← Вот это и есть новый таргет!
models[2].fit(X, residuals₂)
F₂(x) = F₁(x) + η * h₂(x)

Итерация N:
residuals_n = y - F_{n-1}(x)
models[n].fit(X, residuals_n)
F_n(x) = F_{n-1}(x) + η * h_n(x)

Практический пример

import numpy as np
from sklearn.tree import DecisionTreeRegressor
from sklearn.datasets import make_regression

# Создаём данные
X, y = make_regression(n_samples=100, n_features=5, noise=10, random_state=42)

print('=== РУЧНОЙ ГРАДИЕНТНЫЙ БУСТИНГ ===')
print(f'Истинные значения y (первые 10): {y[:10]}')

# Инициализация
F = np.full(len(y), y.mean())  # Предсказываем среднее
learning_rate = 0.1
n_trees = 5
trees = []

print(f'\nПервое предсказание (среднее): {F[0]:.2f}')
print(f'Ошибка: {(y[0] - F[0]):.2f}')

# Итеративное добавление деревьев
for iteration in range(n_trees):
    # ТАРГЕТ = остатки предыдущих предсказаний
    residuals = y - F
    
    # Обучаем дерево на остатках
    tree = DecisionTreeRegressor(max_depth=3, random_state=42)
    tree.fit(X, residuals)
    trees.append(tree)
    
    # Обновляем предсказание
    tree_pred = tree.predict(X)
    F = F + learning_rate * tree_pred
    
    # Выводим прогресс
    mse = np.mean((y - F) ** 2)
    print(f'\nИтерация {iteration + 1}:')
    print(f'  Таргет (остатки): min={residuals.min():.2f}, max={residuals.max():.2f}, mean={residuals.mean():.2f}')
    print(f'  Дерево предсказало остатки')
    print(f'  Новое предсказание: {F[0]:.2f}')
    print(f'  MSE: {mse:.4f}')

print(f'\nИтоговое предсказание: {F[0]:.2f}')
print(f'Истинное значение: {y[0]:.2f}')
print(f'Ошибка: {(y[0] - F[0]):.2f}')

# Вывод:
# === РУЧНОЙ ГРАДИЕНТНЫЙ БУСТИНГ ===
# Истинные значения y (первые 10): [-25.23 -47.12 34.56 ...]
#
# Первое предсказание (среднее): 5.43
# Ошибка: -30.66
#
# Итерация 1:
#   Таргет (остатки): min=-68.43, max=45.23, mean=-0.00
#   Дерево предсказало остатки
#   Новое предсказание: -2.59
#   MSE: 289.34
#
# Итерация 2:
#   Таргет (остатки): min=-62.15, max=38.92, mean=-0.00
#   Дерево предсказало остатки
#   Новое предсказание: -6.78
#   MSE: 234.12
# ...
# Итерация 5:
#   Таргет (остатки): min=-15.23, max=12.45, mean=-0.00
#   Дерево предсказало остатки
#   Новое предсказание: -25.89
#   MSE: 95.23

Визуализация процесса

┌─────────────────────────────────────────────────────────────┐
│ Итерация 1: Таргет = y (исходные значения)                 │
├─────────────────────────────────────────────────────────────┤
│ Tree 1 предсказывает: y                                     │
│ Ошибка: e₁ = y - ŷ₁                                         │
└─────────────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────────────┐
│ Итерация 2: Таргет = e₁ (ОСТАТКИ первого дерева!)          │
├─────────────────────────────────────────────────────────────┤
│ Tree 2 предсказывает: e₁                                    │
│ Ошибка: e₂ = e₁ - ê₁                                        │
└─────────────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────────────┐
│ Итерация 3: Таргет = e₂ (ОСТАТКИ второго дерева!)          │
├─────────────────────────────────────────────────────────────┤
│ Tree 3 предсказывает: e₂                                    │
│ Ошибка: e₃ = e₂ - ê₂                                        │
└─────────────────────────────────────────────────────────────┘

Почему это работает

1. Разложение задачи на простые части

Вместо:
  Одно дерево предсказывает всё y сразу
  → Сложно, переобучение

Мы делаем:
  Дерево 1: предсказывает основной тренд
  Дерево 2: исправляет остатки от дерева 1
  Дерево 3: исправляет остатки от деревьев 1+2
  ...
  → Каждое дерево решает более простую задачу

2. Последовательное уточнение

# Аналогия: Рисование портрета

Шаг 1: Рисуем основные формы (большой контур лица)
Шаг 2: Исправляем детали (рот, нос не совсем правильно)
Шаг 3: Добавляем тени (где осталась ошибка)
Шаг 4: Уточняем мелкие детали (волосы, веки)
...

Вместо:
Попытка нарисовать идеальный портрет с первого раза
→ Очень сложно!

Сравнение: Бустинг vs Баgging

BAGGING (Random Forest):
┌────────────────┐  ┌────────────────┐  ┌────────────────┐
│ Tree 1         │  │ Tree 2         │  │ Tree 3         │
│ Таргет: y      │  │ Таргет: y      │  │ Таргет: y      │
│ Независимые!   │  │ Независимые!   │  │ Независимые!   │
└────────────────┘  └────────────────┘  └────────────────┘
        ↓                  ↓                  ↓
    [Averaging] → Финальное предсказание

БООСТИНГ (Gradient Boosting):
┌────────────────┐
│ Tree 1         │  Таргет: y
│ Pred: ŷ₁       │
└────────────────┘
        ↓
┌────────────────┐
│ Tree 2         │  Таргет: (y - ŷ₁)  ← ОСТАТКИ!
│ Pred: ê₁       │
└────────────────┘
        ↓
┌────────────────┐
│ Tree 3         │  Таргет: (y - ŷ₁ - ê₁)  ← НОВЫЕ ОСТАТКИ!
│ Pred: ê₂       │
└────────────────┘
        ↓
    [Summation] → Финальное: ŷ₁ + ê₁ + ê₂ + ...

На примере XGBoost

import xgboost as xgb
from sklearn.datasets import make_regression

X, y = make_regression(n_samples=100, n_features=5)

# Обучаем XGBoost
model = xgb.XGBRegressor(
    n_estimators=3,  # 3 дерева для наглядности
    max_depth=2,
    learning_rate=0.1,
    verbose=True
)
model.fit(X, y)

# Хотим посмотреть что каждое дерево предсказывает
print('\n=== Таргеты для каждого дерева ===')

# Дерево 1
preds_1 = model.predict(X[:5], iteration_range=(0, 1))
print(f'Предсказания дерева 1: {preds_1}')

# Дерево 1+2
preds_12 = model.predict(X[:5], iteration_range=(0, 2))
print(f'Предсказания деревьев 1+2: {preds_12}')

# Дерево 1+2+3
preds_123 = model.predict(X[:5], iteration_range=(0, 3))
print(f'Предсказания деревьев 1+2+3: {preds_123}')

print(f'\nИстинные значения y[:5]: {y[:5]}')
print(f'\nОстатки после дерева 1: {y[:5] - preds_1}')
print(f'Остатки после деревьев 1+2: {y[:5] - preds_12}')
print(f'Остатки после деревьев 1+2+3: {y[:5] - preds_123}')

# Вывод: каждое новое дерево работает с более маленькими остатками

Разные типы таргетов

# ══════════════════════════════════════════════════════════
# 1. РЕГРЕССИЯ: Обычные остатки
# ══════════════════════════════════════════════════════════
residuals = y - predictions

# ══════════════════════════════════════════════════════════
# 2. КЛАССИФИКАЦИЯ: Псевдо-остатки (градиент функции потерь)
# ══════════════════════════════════════════════════════════
# Для log loss (бинарная классификация):
probs = model.predict_proba(X)[:, 1]
pseudomdoesiduals = y - probs  # Градиент log loss

# ══════════════════════════════════════════════════════════
# 3. РАНЖИРОВАНИЕ: Ранг-базированные остатки
# ══════════════════════════════════════════════════════════
# LambdaMART использует более сложные таргеты
residuals = compute_lambdas(predictions, y)  # Специальный расчёт

Эффект обучения через остатки

import matplotlib.pyplot as plt

# Визуализация как уменьшаются остатки
fig, axes = plt.subplots(1, 4, figsize=(16, 4))

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

F = np.full(len(y), y.mean())
learning_rate = 0.1

for i, (ax, iteration) in enumerate([(axes[0], 0), (axes[1], 1), (axes[2], 2), (axes[3], 5)]):
    # Обучаем деревья до итерации
    for j in range(iteration):
        residuals = y - F
        tree = DecisionTreeRegressor(max_depth=2)
        tree.fit(X, residuals)
        F = F + learning_rate * tree.predict(X)
    
    # Рисуем
    ax.scatter(X, y, alpha=0.5, label='y true')
    ax.plot(X, F, 'r-', linewidth=2, label='F(x) - текущее предсказание')
    
    residuals = y - F
    ax.vlines(X, F, y, alpha=0.3, colors='gray', label='Остатки')
    
    mse = np.mean(residuals ** 2)
    ax.set_title(f'Итерация {iteration}\nMSE = {mse:.2f}')
    ax.legend()
    ax.set_ylabel('y')
    ax.set_xlabel('X')

plt.tight_layout()
plt.show()

# График показывает как остатки (расстояние между линией и точками)
# становятся всё меньше с каждой итерацией

Потеря функций и таргеты

Тип потерь          | Таргет для дерева
─────────────────────┼────────────────────────────────────
MSE (L2)            | y - предсказание (обычные остатки)
MAE (L1)            | sign(y - предсказание) (знак остатка)
Log Loss (binary)   | y - вероятность (псевдо-остатки)
Huber              | Робастные остатки с клипингом
Quantile           | Взвешенные остатки
Cross-entropy      | Градиент вероятности

Практическое применение

import xgboost as xgb
from sklearn.datasets import make_regression
from sklearn.model_selection import cross_val_score

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

# Модель 1: Обучается на исходной целевой переменной
model1 = xgb.XGBRegressor(
    n_estimators=100,
    max_depth=3,
    objective='reg:squarederror'  # MSE
)
scores1 = cross_val_score(model1, X, y, cv=5, scoring='r2')
print(f'R² с MSE таргетом: {scores1.mean():.4f}')

# Модель 2: То же самое, но с другой функцией потерь
model2 = xgb.XGBRegressor(
    n_estimators=100,
    max_depth=3,
    objective='reg:huber'  # Более робастна к выбросам
)
scores2 = cross_val_score(model2, X, y, cv=5, scoring='r2')
print(f'R² с Huber таргетом: {scores2.mean():.4f}')

# Хотя таргеты разные, обе модели работают хорошо
# Taргет — это просто то, что дерево пытается предсказать

Заключение

Таргет для бустинга это:

  • Первое дерево: исходная целевая переменная y
  • Последующие деревья: остатки предсказаний предыдущих деревьев
  • Ключевое отличие от других методов: последовательное обучение на ошибках
  • Преимущество: каждое дерево решает более простую задачу

Формула:

F_n(x) = F_{n-1}(x) + η * h_n(x)
        где h_n предсказывает (y - F_{n-1}(x))

Это фундамент почему градиентный бустинг такой мощный метод в машинном обучении!

Что является таргетом для бустинга? | PrepBro