На каком таргете учатся деревья в случае алгоритма boosting в задаче бинарной классификации
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
На каком таргете учатся деревья в случае алгоритма boosting в задаче бинарной классификации
Основная идея Boosting
В отличие от других ensemble методов (например, Random Forest, где все деревья независимы), boosting работает последовательно. Каждое новое дерево обучается на остатках (residuals) или ошибках предыдущих деревьев, а не на оригинальном таргете.
Это фундаментальное отличие — каждое дерево пытается исправить ошибки ансамбля предыдущих деревьев.
Случай 1: GradientBoosting для регрессии (как понять, как работает для классификации)
Для регрессии это просто:
import numpy as np
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.tree import DecisionTreeRegressor
# Данные
X = np.array([[1], [2], [3], [4], [5]])
y_true = np.array([2, 4, 6, 8, 10]) # y = 2*x
# Обучение
gb_reg = GradientBoostingRegressor(n_estimators=3, learning_rate=0.1, random_state=42)
gb_reg.fit(X, y_true)
print("Процесс обучения GradientBoosting для регрессии:")
print(f"Истинный таргет: {y_true}")
# Шаг 1: Первое дерево учится на y_true
first_tree = DecisionTreeRegressor(max_depth=1)
first_tree.fit(X, y_true)
y_pred_1 = first_tree.predict(X)
print(f"\nПредсказание 1-го дерева: {y_pred_1}")
print(f"Ошибка (остаток): {y_true - y_pred_1}")
# Шаг 2: Второе дерево учится на остатках
residuals_1 = y_true - y_pred_1
second_tree = DecisionTreeRegressor(max_depth=1)
second_tree.fit(X, residuals_1) # <-- ТАРГЕТ = ОСТАТКИ!
y_pred_2 = second_tree.predict(X)
print(f"\nПредсказание 2-го дерева (на остатках): {y_pred_2}")
# Обновляем предсказания
y_ensemble = y_pred_1 + 0.1 * y_pred_2 # learning_rate = 0.1
print(f"Ансамбль после 2-го дерева: {y_ensemble}")
print(f"Новые остатки: {y_true - y_ensemble}")
Случай 2: GradientBoosting для БИНАРНОЙ классификации
Для классификации все сложнее. Есть несколько подходов:
Подход 1: Вероятности (AdaBoost, Gradient Boosting с log loss)
Деревья учатся предсказывать вероятность класса 1 или логарифм шансов (log-odds).
from sklearn.ensemble import GradientBoostingClassifier, AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import log_loss
# Бинарная классификация
X = np.array([[1], [2], [3], [4], [5]])
y_true = np.array([0, 0, 1, 1, 1]) # Бинарный таргет
# GradientBoosting для классификации
gb_clf = GradientBoostingClassifier(
n_estimators=3,
learning_rate=0.1,
max_depth=1,
random_state=42
)
gb_clf.fit(X, y_true)
print("Процесс обучения GradientBoosting для классификации:")
print(f"Истинный таргет (классы): {y_true}")
# Получить вероятности
y_proba = gb_clf.predict_proba(X)[:, 1] # Вероятность класса 1
print(f"\nПредсказанные вероятности: {y_proba}")
# На что учатся деревья?
print(f"\nДеревья учатся на: логарифм шансов (log-odds)")
log_odds_target = np.log(y_true / (1 - y_true + 1e-10)) # Для уникальных точек
print(f"Log-odds таргет: {log_odds_target}")
Подход 2: AdaBoost для классификации (более явный)
AdaBoost изменяет веса примеров, заставляя следующее дерево больше внимания уделять ошибочным примерам.
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
X = np.array([[1], [2], [3], [4], [5], [6], [7], [8]])
y_true = np.array([0, 0, 0, 1, 1, 1, 1, 0])
adaboost = AdaBoostClassifier(
estimator=DecisionTreeClassifier(max_depth=1),
n_estimators=3,
random_state=42
)
adaboost.fit(X, y_true)
print("AdaBoost процесс:")
print(f"Истинные классы: {y_true}\n")
# Шаг 1: Первое дерево
first_tree = DecisionTreeClassifier(max_depth=1)
first_tree.fit(X, y_true)
y_pred_1 = first_tree.predict(X)
errors_1 = (y_pred_1 != y_true).astype(int)
error_rate_1 = errors_1.sum() / len(y_true)
print(f"Предсказания 1-го дерева: {y_pred_1}")
print(f"Ошибки: {errors_1}")
print(f"Общая ошибка: {error_rate_1:.4f}")
# Шаг 2: Вычисляем веса ошибочных примеров
sample_weight = np.ones(len(y_true)) # Начальные веса = 1
alpha = 0.5 * np.log((1 - error_rate_1) / (error_rate_1 + 1e-10))
sample_weight[errors_1 == 1] *= np.exp(alpha) # Увеличиваем вес ошибок
sample_weight /= sample_weight.sum() # Нормализуем
print(f"\nВеса примеров для 2-го дерева:")
for i, w in enumerate(sample_weight):
print(f" Пример {i}: {w:.4f} (была ошибка: {errors_1[i]})")
# Шаг 3: Второе дерево учится на данных с переназначенными весами
second_tree = DecisionTreeClassifier(max_depth=1)
second_tree.fit(X, y_true, sample_weight=sample_weight)
y_pred_2 = second_tree.predict(X)
print(f"\nПредсказания 2-го дерева: {y_pred_2}")
print(f"(обучено на переназначенных весах, больше внимания ошибкам)")
Три ключевых понимания
1. Таргет для каждого дерева
| Алгоритм | Таргет для дерева |
|---|---|
| GradientBoosting (регрессия) | Остатки (residuals): y_true - y_pred_ensemble |
| GradientBoosting (классификация) | Псевдо-остатки (градиенты): d(loss)/d(pred) |
| AdaBoost | Оригинальные классы, но с переназначенными весами |
| XGBoost | Градиенты и гессианы loss функции |
2. GradientBoosting для классификации подробнее
Для бинарной классификации с log loss:
from sklearn.ensemble import GradientBoostingClassifier
import numpy as np
X_train = np.random.randn(100, 5)
y_train = np.random.randint(0, 2, 100)
# GradientBoosting
gb = GradientBoostingClassifier(n_estimators=5, learning_rate=0.1)
# Инициализация: начальное предсказание
init_prediction = np.log(y_train.sum() / (len(y_train) - y_train.sum()))
print(f"Начальное log-odds: {init_prediction:.4f}")
# Каждое дерево предсказывает корректировку к log-odds
# Итоговое предсказание = sum(learning_rate * tree_prediction)
gb.fit(X_train, y_train)
y_proba = gb.predict_proba(X_train)[:, 1]
y_pred = gb.predict(X_train)
print(f"Вероятности класса 1: {y_proba[:5]}")
print(f"Предсказанные классы: {y_pred[:5]}")
3. Визуализация процесса
import matplotlib.pyplot as plt
# Простой пример
np.random.seed(42)
X = np.linspace(0, 10, 50).reshape(-1, 1)
y_true = np.array([1 if (x[0] > 5) else 0 for x in X]).astype(int)
# Обучим GradientBoosting
gb = GradientBoostingClassifier(
n_estimators=5,
learning_rate=0.5,
max_depth=1,
random_state=42
)
gb.fit(X, y_true)
# Получить вероятности
y_proba_ensemble = gb.predict_proba(X)[:, 1]
# Визуализировать
plt.figure(figsize=(12, 6))
plt.scatter(X, y_true, label='True labels', alpha=0.5)
plt.plot(X, y_proba_ensemble, 'r-', linewidth=2, label='Ensemble probability')
plt.xlabel('X')
plt.ylabel('Probability of class 1')
plt.legend()
plt.title('GradientBoosting для классификации')
plt.show()
print(f"Деревья учились корректировать вероятности")
print(f"На каждом шаге: новый таргет = остаток от предыдущего ансамбля")
Практический пример с XGBoost
import xgboost as xgb
X_train = np.random.randn(200, 10)
y_train = np.random.randint(0, 2, 200)
# XGBoost для бинарной классификации
model = xgb.XGBClassifier(
n_estimators=10,
learning_rate=0.1,
max_depth=3,
objective='binary:logistic', # Объективная функция
random_state=42
)
model.fit(X_train, y_train)
print("XGBoost для бинарной классификации:")
print(f"Objective: binary:logistic")
print(f"Каждое дерево предсказывает поправку к log-odds")
print(f"\nВероятности: {model.predict_proba(X_train[:5])[:,1]}")
print(f"Классы: {model.predict(X_train[:5])}")
РЕЗЮМЕ
В boosting для бинарной классификации деревья учатся на:
- AdaBoost: на оригинальных классах (0/1), но с переназначенными весами примеров
- GradientBoosting/XGBoost: на псевдо-остатках — градиентах loss функции (log loss, hinge loss, etc.)
- Фактически: каждое дерево предсказывает поправку к логарифму шансов (log-odds), которая затем преобразуется в вероятность через сигмоиду
Основная идея остается одна: каждое новое дерево исправляет ошибки ансамбля, но таргет для каждого дерева — это не оригинальные классы 0/1, а остатки или градиенты предыдущих предсказаний.