梯度提升原理 #

什么是 Boosting? #

Boosting 是一种集成学习方法,通过串行训练多个弱学习器,每个学习器都试图纠正前一个学习器的错误,最终组合成强学习器。

Boosting vs Bagging #

text
┌─────────────────────────────────────────────────────────────┐
│                    集成学习方法对比                          │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  Boosting(提升)                    Bagging(装袋)          │
│  ┌─────────────────┐               ┌─────────────────┐      │
│  │  弱学习器1       │               │  学习器1         │      │
│  └────────┬────────┘               └────────┬────────┘      │
│           ↓                                 │               │
│  ┌─────────────────┐               ┌────────┴────────┐      │
│  │  弱学习器2       │               │  学习器2    学习器3 │   │
│  └────────┬────────┘               └────────┬────────┘      │
│           ↓                                 │               │
│  ┌─────────────────┐               ┌────────┴────────┐      │
│  │  弱学习器3       │               │      投票/平均    │   │
│  └────────┬────────┘               └─────────────────┘      │
│           ↓                                                  │
│  ┌─────────────────┐                                        │
│  │  最终模型        │                                        │
│  └─────────────────┘                                        │
│                                                              │
│  串行训练,纠正错误                  并行训练,减少方差        │
│  降低偏差                           降低方差                  │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Boosting 的核心思想 #

text
初始模型 → 计算残差 → 训练新模型拟合残差 → 更新模型 → 重复

梯度提升算法 #

基本原理 #

梯度提升(Gradient Boosting)将 Boosting 思想与梯度下降结合,使用损失函数的负梯度作为残差的近似值。

text
┌─────────────────────────────────────────────────────────────┐
│                    梯度提升核心思想                          │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  1. 初始化模型 F₀(x)                                         │
│                                                              │
│  2. For m = 1 to M:                                         │
│     a) 计算负梯度(伪残差):                                  │
│        rᵢₘ = -∂L(yᵢ, Fₘ₋₁(xᵢ)) / ∂Fₘ₋₁(xᵢ)               │
│                                                              │
│     b) 拟合一个弱学习器 hₘ(x) 到残差                          │
│                                                              │
│     c) 线性搜索找到最优步长 γₘ                                │
│                                                              │
│     d) 更新模型: Fₘ(x) = Fₘ₋₁(x) + γₘhₘ(x)                  │
│                                                              │
│  3. 输出最终模型 Fₘ(x)                                       │
│                                                              │
└─────────────────────────────────────────────────────────────┘

数学推导 #

1. 目标函数 #

text
最小化损失函数:
min Σ L(yᵢ, F(xᵢ))

2. 梯度下降 #

在函数空间中进行梯度下降:

text
Fₘ(x) = Fₘ₋₁(x) + η · (-∂L/∂F)

其中:
- η 是学习率
- -∂L/∂F 是负梯度方向

3. 负梯度计算 #

对于不同损失函数,负梯度不同:

损失函数 表达式 负梯度
平方损失 L = ½(y-F)² y - F(残差)
绝对损失 L = |y-F| sign(y-F)
Log损失 L = log(1+exp(-yF)) y/(1+exp(yF))

Python 实现 #

python
import numpy as np
from sklearn.tree import DecisionTreeRegressor

class SimpleGradientBoosting:
    def __init__(self, n_estimators=100, learning_rate=0.1, max_depth=3):
        self.n_estimators = n_estimators
        self.learning_rate = learning_rate
        self.max_depth = max_depth
        self.trees = []
        
    def fit(self, X, y):
        n_samples = X.shape[0]
        
        # 初始化:使用均值
        self.F0 = np.mean(y)
        F = np.full(n_samples, self.F0)
        
        for i in range(self.n_estimators):
            # 计算负梯度(对于平方损失,就是残差)
            residual = y - F
            
            # 拟合残差
            tree = DecisionTreeRegressor(max_depth=self.max_depth)
            tree.fit(X, residual)
            
            # 更新预测
            update = tree.predict(X)
            F += self.learning_rate * update
            
            self.trees.append(tree)
            
    def predict(self, X):
        F = np.full(X.shape[0], self.F0)
        for tree in self.trees:
            F += self.learning_rate * tree.predict(X)
        return F

# 测试
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

X, y = make_regression(n_samples=1000, 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)

model = SimpleGradientBoosting(n_estimators=100, learning_rate=0.1)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)

print(f"MSE: {mean_squared_error(y_test, y_pred):.4f}")

梯度提升决策树(GBDT) #

GBDT 流程 #

text
┌─────────────────────────────────────────────────────────────┐
│                    GBDT 训练流程                             │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  Step 1: 初始化                                              │
│  F₀(x) = argmin_c Σ L(yᵢ, c)                                │
│  (对于平方损失,c = mean(y))                                │
│                                                              │
│  Step 2: For m = 1 to M:                                    │
│                                                              │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  a) 计算负梯度(伪残差)                               │  │
│  │     rᵢₘ = -∂L(yᵢ, Fₘ₋₁(xᵢ))/∂Fₘ₋₁(xᵢ)              │  │
│  │                                                       │  │
│  │  b) 拟合回归树到残差                                   │  │
│  │     将数据划分为 Rⱼₘ 个区域                           │  │
│  │                                                       │  │
│  │  c) 计算每个区域的最优值                               │  │
│  │     γⱼₘ = argmin_γ Σ L(yᵢ, Fₘ₋₁(xᵢ) + γ)           │  │
│  │                                                       │  │
│  │  d) 更新模型                                          │  │
│  │     Fₘ(x) = Fₘ₋₁(x) + η · Σ γⱼₘ·I(x ∈ Rⱼₘ)        │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                              │
│  Step 3: 输出 Fₘ(x)                                         │
│                                                              │
└─────────────────────────────────────────────────────────────┘

GBDT 分类 #

对于分类问题,使用对数损失:

python
import numpy as np

def binary_logistic_gradient(y, F):
    """
    二分类对数损失的负梯度
    
    L = log(1 + exp(-y*F))
    负梯度 = y / (1 + exp(y*F))
    """
    return y / (1 + np.exp(y * F))

def softmax_gradient(y, F, K):
    """
    多分类 Softmax 损失的负梯度
    """
    prob = np.exp(F) / np.sum(np.exp(F), axis=1, keepdims=True)
    return y - prob

GBDT 回归 #

对于回归问题,常用平方损失:

python
def squared_error_gradient(y, F):
    """
    平方损失的负梯度
    
    L = 0.5 * (y - F)^2
    负梯度 = y - F
    """
    return y - F

def absolute_error_gradient(y, F):
    """
    绝对损失的负梯度
    
    L = |y - F|
    负梯度 = sign(y - F)
    """
    return np.sign(y - F)

XGBoost 的改进 #

XGBoost 在传统 GBDT 基础上做了多项改进:

1. 正则化目标函数 #

text
Obj = Σ L(yᵢ, ŷᵢ) + Σ Ω(fₖ)

其中:
Ω(f) = γT + ½λ||w||²

- T: 叶子节点数量
- w: 叶子节点权重
- γ: 叶子节点数惩罚
- λ: L2 正则化权重

2. 二阶泰勒展开 #

XGBoost 使用二阶泰勒展开近似损失函数:

text
L(y, ŷ) ≈ L(y, ŷ⁽ᵗ⁻¹⁾) + gᵢfₜ(xᵢ) + ½hᵢfₜ²(xᵢ)

其中:
- gᵢ = ∂L/∂ŷ⁽ᵗ⁻¹⁾  (一阶导数)
- hᵢ = ∂²L/∂ŷ²⁽ᵗ⁻¹⁾ (二阶导数)
python
def compute_gradients(y, pred, objective='regression'):
    """
    计算一阶和二阶梯度
    """
    if objective == 'regression':
        g = pred - y
        h = np.ones_like(y)
    elif objective == 'binary':
        prob = 1 / (1 + np.exp(-pred))
        g = prob - y
        h = prob * (1 - prob)
    elif objective == 'multiclass':
        prob = np.exp(pred) / np.sum(np.exp(pred), axis=1, keepdims=True)
        g = prob - y
        h = prob * (1 - prob)
    
    return g, h

3. 最优分裂点 #

XGBoost 使用二阶信息找到最优分裂点:

text
增益 = ½ [ (Σgᵢ)²/(Σhᵢ+λ) + (Σgⱼ)²/(Σhⱼ+λ) - (Σgᵢ+Σgⱼ)²/(Σhᵢ+Σhⱼ+λ) ] - γ
python
def calculate_split_gain(G_left, H_left, G_right, H_right, gamma, lambda_):
    """
    计算分裂增益
    """
    gain_left = G_left**2 / (H_left + lambda_)
    gain_right = G_right**2 / (H_right + lambda_)
    gain_total = (G_left + G_right)**2 / (H_left + H_right + lambda_)
    
    return 0.5 * (gain_left + gain_right - gain_total) - gamma

4. 稀疏感知 #

XGBoost 自动学习缺失值的最优分裂方向:

python
def find_best_split_with_missing(X, g, h, feature_idx, gamma=0, lambda_=1):
    """
    考虑缺失值的分裂
    """
    # 非缺失值
    mask = ~np.isnan(X[:, feature_idx])
    X_valid = X[mask, feature_idx]
    g_valid = g[mask]
    h_valid = h[mask]
    
    # 缺失值
    g_missing = g[~mask]
    h_missing = h[~mask]
    
    # 尝试将缺失值分到左子树
    gain_left = find_split(X_valid, g_valid, h_valid, gamma, lambda_)
    
    # 尝试将缺失值分到右子树
    gain_right = find_split(X_valid, g_valid, h_valid, gamma, lambda_)
    
    # 选择最优方向
    return max(gain_left, gain_right)

学习率的作用 #

学习率(η)控制每棵树的贡献:

python
import matplotlib.pyplot as plt
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.datasets import make_regression

X, y = make_regression(n_samples=1000, n_features=10, noise=0.1, random_state=42)

learning_rates = [0.01, 0.1, 0.5, 1.0]
fig, axes = plt.subplots(2, 2, figsize=(12, 10))

for ax, lr in zip(axes.flat, learning_rates):
    model = GradientBoostingRegressor(
        n_estimators=100,
        learning_rate=lr,
        max_depth=3,
        random_state=42
    )
    model.fit(X, y)
    
    train_score = model.train_score_
    ax.plot(train_score)
    ax.set_xlabel('Iterations')
    ax.set_ylabel('Training Loss')
    ax.set_title(f'Learning Rate = {lr}')
    ax.grid(True)

plt.tight_layout()
plt.show()

学习率与迭代次数的关系 #

text
┌─────────────────────────────────────────────────────────────┐
│               学习率 vs 迭代次数                             │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  学习率小 (η=0.01)                                           │
│  - 需要更多迭代次数                                          │
│  - 收敛慢,但更稳定                                          │
│  - 泛化性能通常更好                                          │
│                                                              │
│  学习率大 (η=0.3)                                            │
│  - 需要较少迭代次数                                          │
│  - 收敛快,但可能过拟合                                      │
│  - 需要更早停止                                              │
│                                                              │
│  经验法则:                                                   │
│  η × n_estimators ≈ 常数                                    │
│                                                              │
└─────────────────────────────────────────────────────────────┘

梯度提升的优缺点 #

优点 #

text
1. 高准确率
   - 通过纠正错误不断改进
   - 在许多任务上表现优异

2. 灵活性
   - 可使用不同的损失函数
   - 可与其他模型结合

3. 特征重要性
   - 自动计算特征重要性
   - 便于特征选择

4. 处理缺失值
   - 自动处理缺失值
   - 无需额外处理

缺点 #

text
1. 训练时间
   - 串行训练,不能并行
   - 大数据集训练较慢

2. 过拟合风险
   - 需要仔细调参
   - 需要早停策略

3. 超参数多
   - 需要调参经验
   - 调参时间较长

4. 可解释性
   - 虽然比神经网络好
   - 但不如单棵决策树

梯度提升 vs 其他方法 #

特性 梯度提升 随机森林 AdaBoost
集成方式 Boosting Bagging Boosting
基学习器 任意 决策树 决策树
权重更新 梯度下降 样本权重
并行性 串行 并行 串行
过拟合 需控制 较少 需控制
异常值 敏感 较稳健 敏感

下一步 #

现在你已经理解了梯度提升原理,接下来学习 决策树基础 了解 XGBoost 的基本构建块!

最后更新:2026-04-04