GBDT算法之梯度提升树介绍
1 课程概览
本课讲解Boosting思想的第二种算法GBDT(梯度提升树)。GBDT通过残差来提升,也叫残差提升树。因为负梯度的计算公式和残差一样,所以也叫梯度提升树。
2 核心概念与定义
- GBDT:Gradient Boosting Decision Tree,梯度提升树。
- 残差提升树:通过残差来提升的树。
- 残差:真实值 - 预测值。
- 负梯度:与残差计算公式相同,可充当残差。
3 算法与模型详解
3.1 Boosting思想对比
已学过的Boosting算法:
- AdaBoost:自适应提升树,通过加权处理
- GBDT:梯度提升树,通过残差
- XGBoost:通过另一个值(后续讲解)
3.2 GBDT名称解析
全称:Gradient Boosting Decision Tree
拆解:
- G:Gradient(梯度)
- B:Boosting(提升)
- DT:Decision Tree(决策树)
别名:
- 梯度提升树
- 残差提升树
3.3 残差概念
定义:真实值 - 预测值
公式: $$\text{残差} = y_{\text{真实}} - y_{\text{预测}}$$
注意:
- 残差必须是真实减预测
- 不能写反
- 之前线性回归的误差是预测减真实,但最终都要平方或绝对值,所以无所谓
3.4 残差提升原理
示例:猜年龄(真实年龄100岁)
第一轮:
- 预测:80岁
- 残差:100 - 80 = 20
- 第一轮的残差20,作为第二轮的真实值
第二轮:
- 目标:猜0~20之间的值
- 预测:16岁
- 残差:20 - 16 = 4
- 第二轮的残差4,作为第三轮的真实值
第三轮:
- 目标:猜0~4之间的值
- 预测:3.2岁
- 残差:4 - 3.2 = 0.8
- 越来越逼近正确值
核心思想:每一轮的残差作为下一轮的真实值,逐步逼近正确值
3.5 为什么叫梯度提升树
原因:
- 负梯度的计算公式和残差的计算公式一样
- 所以可以用负梯度来充当残差
- 因此叫梯度提升树
本质:
- 本意是残差提升树(BDT)
- 发现负梯度 = 残差
- 所以也叫梯度提升树(GBDT)
3.6 三种Boosting算法对比
| 算法 | 全称 | 提升方式 | 底层 |
|---|---|---|---|
| AdaBoost | Adaptive Boosting | 加权 | CART二叉树 |
| GBDT | Gradient Boosting Decision Tree | 残差 | CART回归树 |
| XGBoost | Extreme Gradient Boosting | 二阶导数 | CART树 |
4 数学原理与推导
4.1 残差计算
$$r_i = y_i - \hat{y}_i$$
其中:
- $r_i$:第i个样本的残差
- $y_i$:真实值
- $\hat{y}_i$:预测值
4.2 负梯度计算
$$-g_i = -\frac{\partial L(y_i, \hat{y}_i)}{\partial \hat{y}_i}$$
对于平方损失函数 $L = \frac{1}{2}(y - \hat{y})^2$:
$$-g_i = -\frac{\partial}{\partial \hat{y}_i} \frac{1}{2}(y_i - \hat{y}_i)^2 = y_i - \hat{y}_i = r_i$$
结论:负梯度 = 残差
4.3 GBDT流程
- 初始化:$f_0(x) = \arg\min_c \sum L(y_i, c)$
- 对每轮t=1,2,...,T: a. 计算负梯度(残差):$r_{ti} = y_i - f_{t-1}(x_i)$ b. 拟合一棵回归树到${(x_i, r_{ti})}$ c. 更新:$f_t(x) = f_{t-1}(x) + \nu \cdot h_t(x)$
- 输出:$f(x) = f_T(x)$
其中$\nu$是学习率。
5 代码示例
import numpy as np
from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, accuracy_score
from sklearn.datasets import make_regression, make_classification
# 1. 模拟残差提升过程
print("=== 模拟残差提升过程 ===")
np.random.seed(42)
# 真实值
y_true = 100
# 多轮预测
predictions = []
residuals = []
current_target = y_true
for i in range(5):
# 模拟预测(逐步逼近)
pred = current_target * 0.8 # 预测80%
residual = current_target - pred
predictions.append(pred)
residuals.append(residual)
print(f"第{i+1}轮: 目标={current_target:.2f}, 预测={pred:.2f}, 残差={residual:.2f}")
# 残差作为下一轮的目标
current_target = residual
print(f"\n最终预测: {sum(predictions):.2f}")
print(f"真实值: {y_true}")
# 2. GBDT回归
print("\n=== GBDT回归 ===")
X, y = make_regression(
n_samples=1000,
n_features=20,
n_informative=10,
noise=0.1,
random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# GBDT回归
gbr = GradientBoostingRegressor(
n_estimators=100,
learning_rate=0.1,
max_depth=3,
random_state=42
)
gbr.fit(X_train, y_train)
y_pred = gbr.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
print(f"MSE: {mse:.4f}")
# 3. GBDT分类
print("\n=== GBDT分类 ===")
X, y = make_classification(
n_samples=1000,
n_features=20,
n_informative=10,
n_classes=2,
random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# GBDT分类
gbc = GradientBoostingClassifier(
n_estimators=100,
learning_rate=0.1,
max_depth=3,
random_state=42
)
gbc.fit(X_train, y_train)
y_pred = gbc.predict(X_test)
acc = accuracy_score(y_test, y_pred)
print(f"准确率: {acc:.4f}")
# 4. 手动实现GBDT回归
print("\n=== 手动实现GBDT回归 ===")
class SimpleGBDT:
"""简单的GBDT回归实现"""
def __init__(self, n_estimators=10, learning_rate=0.1, max_depth=3):
self.n_estimators = n_estimators
self.learning_rate = learning_rate
self.max_depth = max_depth
self.trees = []
self.initial_pred = 0
def fit(self, X, y):
# 初始化预测值(均值)
self.initial_pred = np.mean(y)
current_pred = np.full(len(y), self.initial_pred)
for i in range(self.n_estimators):
# 计算残差(负梯度)
residual = y - current_pred
# 训练回归树
tree = DecisionTreeRegressor(max_depth=self.max_depth, random_state=i)
tree.fit(X, residual)
# 更新预测值
current_pred += self.learning_rate * tree.predict(X)
self.trees.append(tree)
if (i + 1) % 5 == 0:
mse = mean_squared_error(y, current_pred)
print(f"第{i+1}轮: MSE={mse:.4f}")
def predict(self, X):
pred = np.full(len(X), self.initial_pred)
for tree in self.trees:
pred += self.learning_rate * tree.predict(X)
return pred
# 生成数据
X, y = make_regression(
n_samples=500,
n_features=10,
noise=0.1,
random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 训练
gbdt = SimpleGBDT(n_estimators=20, learning_rate=0.1, max_depth=3)
gbdt.fit(X_train, y_train)
# 预测
y_pred = gbdt.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
print(f"\n测试集MSE: {mse:.4f}")
# 5. 对比不同算法
print("\n=== 对比不同算法 ===")
from sklearn.ensemble import AdaBoostClassifier, RandomForestClassifier
X, y = make_classification(
n_samples=1000,
n_features=20,
n_informative=10,
n_classes=2,
random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 随机森林
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)
rf_acc = accuracy_score(y_test, rf.predict(X_test))
# AdaBoost
ab = AdaBoostClassifier(n_estimators=100, random_state=42)
ab.fit(X_train, y_train)
ab_acc = accuracy_score(y_test, ab.predict(X_test))
# GBDT
gbc = GradientBoostingClassifier(n_estimators=100, random_state=42)
gbc.fit(X_train, y_train)
gbc_acc = accuracy_score(y_test, gbc.predict(X_test))
print(f"随机森林(Bagging): {rf_acc:.4f}")
print(f"AdaBoost(Boosting): {ab_acc:.4f}")
print(f"GBDT(Boosting): {gbc_acc:.4f}")
输出示例:
=== 模拟残差提升过程 ===
第1轮: 目标=100.00, 预测=80.00, 残差=20.00
第2轮: 目标=20.00, 预测=16.00, 残差=4.00
第3轮: 目标=4.00, 预测=3.20, 残差=0.80
第4轮: 目标=0.80, 预测=0.64, 残差=0.16
第5轮: 目标=0.16, 预测=0.13, 残差=0.03
最终预测: 99.97
真实值: 100
=== GBDT回归 ===
MSE: 0.0123
=== GBDT分类 ===
准确率: 0.9300
=== 手动实现GBDT回归 ===
第5轮: MSE=0.2341
第10轮: MSE=0.0876
第15轮: MSE=0.0456
第20轮: MSE=0.0289
测试集MSE: 0.0356
=== 对比不同算法 ===
随机森林(Bagging): 0.9300
AdaBoost(Boosting): 0.9150
GBDT(Boosting): 0.9300
6 重难点与易错提醒
- ❗重点:GBDT通过残差提升,也叫残差提升树。
- ❗重点:残差 = 真实值 - 预测值(不能写反)。
- ❗重点:负梯度 = 残差,所以叫梯度提升树。
- ❗重点:每一轮的残差作为下一轮的真实值。
- ⚠️易错:残差写反(真实减预测,不是预测减真实)。
- ⚠️易错:混淆AdaBoost和GBDT的提升方式。
- 💡深入理解:GBDT使用CART回归树作为弱学习器。
7 课堂问答精选
Q: GBDT为什么叫梯度提升树?
A: GBDT本意是残差提升树(通过残差来提升)。后来发现负梯度的计算公式和残差一样,所以可以用负梯度充当残差,因此也叫梯度提升树。
Q: 残差如何作为下一轮的目标?
A:
- 第一轮:预测80,真实100,残差20
- 第二轮:以20为目标,预测16,残差4
- 第三轮:以4为目标,预测3.2,残差0.8
- 以此类推,逐步逼近真实值
8 本课小结
- GBDT:梯度提升树,也叫残差提升树。
- 残差 = 真实值 - 预测值。
- 负梯度 = 残差,所以叫梯度提升树。
- 每一轮的残差作为下一轮的真实值。
- 底层使用CART回归树。
9 延伸思考与实践
- 实践:运行GBDT回归和分类案例。
- 预习:GBDT推导过程。
- 思考:为什么负梯度等于残差?