|
### 什么是 LightGBM
LightGBM(Light Gradient Boosting Machine)是微软于 2017 年开源的**分布式梯度提升框架**,基于 GBDT 算法。
**核心特点**:
- **速度快**:采用直方图算法,将连续特征离散化为桶,大幅减少计算量
- **效率高**:支持并行学习,充分利用多核 CPU 资源,高效的内存管理
- **准确性好**:引入 GOSS 和 EFB 等优化技术,有效防止过拟合
- **支持类别特征**:无需独热编码,直接处理类别型变量
**适用场景**:
- 分类任务(二分类、多分类)
- 回归任务
- 排序任务(LambdaRank)
**安装**:
```bash
pip install lightgbm
```
**两种调用方式**:
| 方式 | 说明 | 适用场景 |
|------|------|---------|
| `lgb.train()` | LightGBM 原生 API | 更灵活,参数通过 dict 传入 |
| `LGBMClassifier` | sklearn 风格 API | 与 sklearn 生态兼容,使用方便 |
|
|
### 集成思想
集成学习的核心思想:**综合多个弱分类器的判断,比使用一组特征一次训练出结果更稳健**。
#### 为什么随机抽取特征
在 AI 中,随机抽取特征列的目的在于让算法变得**健壮(鲁棒性)**:
- 使用全部的列未必最好,现实中提取的特征**既重又漏**
- 有些特征实际上是噪声,以为有用,实际多余
- 随机选取 90% 的特征,有的效果好有的差,多轮下来就能区分重要和无关特征
#### 弱分类器
- 将一组特征拆分为多组,每组分别入模训练,出结果
- 每个基于子特征集训练的单模型就是一个**弱分类器**
- 综合多个弱分类器去评价 → **集成思想**
- "三个臭皮匠,顶个诸葛亮"
#### Bagging vs Boosting
| 对比项 | Bagging(随机森林) | Boosting(LightGBM/GBDT) |
|--------|-------------------|--------------------------|
| 训练方式 | 多棵树并行独立训练 | 串行训练,每棵树纠正前一棵的错误 |
| 目标 | 降低方差 | 降低偏差 |
| 树的关系 | 各树独立 | 每棵树基于前一棵的残差 |
> LightGBM 属于 **Boosting** 方法,每轮训练一棵新树来弥补当前模型的不足。
#### LightGBM 是怎样"集成"的?
上面的"随机抽特征 → 各自训练 → 综合投票"更接近 **Bagging**(随机森林)的逻辑。那 LightGBM 作为 Boosting 算法,它的"集成"体现在哪里?
**1. 串行纠错,不是并行投票**
LightGBM 中每棵树并不是独立训练的。第 1 棵树用原始标签训练,输出预测值;第 2 棵树看到的**不是原始标签**,而是第 1 棵树的**残差**(预测偏差);第 3 棵树再纠正前两棵树的残差……以此类推。最终预测 = 所有树的输出之和。
```
最终预测 = 树1的输出 + 树2的输出 + 树3的输出 + ...
```
集成效果来自"**不断补短板**",而非"多角度投票"。
**2. 特征子采样——有,但只是辅助手段**
LightGBM 确实有 `feature_fraction` 参数,让每棵树随机选用部分特征列。但这**不是集成的核心机制**,而是从 Bagging 借鉴的正则化技巧:
- 防止每棵树都依赖同样的强特征 → 过拟合
- 加速训练(列少了,分裂计算更快)
- 引入多样性,让残差修正不局限于固定的特征组合
可以理解为:LightGBM 的"集成灵魂"是串行残差修正,特征子采样只是给它加的"调味料"。
**3. "弱"的含义不同**
| | Bagging(随机森林) | Boosting(LightGBM) |
|--|-------------------|---------------------|
| 弱在哪 | 只用了部分特征/部分数据,信息不完整 | 树很浅(如 max_depth=3),故意限制每棵树的复杂度 |
| 为什么弱 | 随机性造成的"被迫变弱" | 人为限制,让每棵树只学一小块规律 |
| 怎么集成 | 并行投票,少数服从多数 | 串行叠加,逐步逼近真实值 |
**一句话总结**:Bagging 是"多人各看一部分,投票表决";LightGBM 是"一个人不断反思错误,逐轮修正",特征子采样让每轮反思的视角略有不同,但本质上仍是**纠错式集成**。
|
|
### 快速上手
#### 方式一:lgb.train() 原生 API
```python
import lightgbm as lgb
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import numpy as np
# 加载数据
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, test_size=0.3, random_state=42)
# 创建数据集
train_data = lgb.Dataset(X_train, label=y_train)
test_data = lgb.Dataset(X_test, label=y_test, reference=train_data)
# 参数设置
params = {
'objective': 'multiclass', # 多分类
'num_class': 3, # 类别数
'metric': 'multi_logloss', # 评估指标
'verbosity': -1 # 不输出日志
}
# 训练
gbm = lgb.train(params, train_data,
num_boost_round=100,
valid_sets=[test_data])
# 预测(返回概率矩阵)
y_pred = gbm.predict(X_test)
y_pred_classes = [int(np.argmax(line)) for line in y_pred]
# 评估
print(f'Accuracy: {accuracy_score(y_test, y_pred_classes):.4f}')
# Accuracy: 1.0000
```
- 虽然不是LGBMClassifier,但'objective': 'multiclass' 已经指定了多分类任务,模型会自动处理。
#### 方式二:LGBMClassifier(sklearn 风格)
```python
from lightgbm import LGBMClassifier
model = LGBMClassifier(
objective='multiclass',
num_class=3,
num_leaves=31,
learning_rate=0.1,
n_estimators=100,
verbose=-1
)
model.fit(X_train, y_train)
print(f'Accuracy: {model.score(X_test, y_test):.4f}')
```
|
|
### 直方图算法
LightGBM 的核心加速技术:**将连续特征值离散化为有限个桶(Bins)**。
#### 工作原理
```
1. 训练前,对每个特征的值进行排序
2. 将连续值划分为最多 max_bin 个桶(默认 255)
3. 后续训练中,只在桶上进行决策树构建
4. 大幅减少对原始数据的扫描次数 → 训练速度提升
```
#### 分桶的含义
- 每个桶代表一个**特征值区间**
- 区间内的所有特征值被视为**等价**(在分裂上下文中)
- 两个数据放入同一个桶**不是**合并为一个数据,而是在分裂决策时视为相似
#### Total Bins 示例
```
[LightGBM] [Info] Total Bins 86
[LightGBM] [Info] Number of data points in the train set: 105, number of used features: 4
```
- iris 数据集有 4 个特征,总共划分了 86 个桶
- 桶的分配不平均,由 LightGBM 根据特征值分布自动决定
- `max_bin` 参数控制每个特征的**最大桶数**,实际桶数通常更少
#### 正则化效果
分桶起到一种正则化作用:将相似特征值归入同一桶,模型对数据细节不那么敏感,有助于防止过拟合。
|
|
### GOSS 与 EFB
LightGBM 的两大核心优化技术。
#### GOSS(Gradient-based One-Side Sampling)
**梯度单边采样**:只关注梯度较大的样本进行决策树构建。
- 梯度大的样本 = 模型预测误差大的样本 = 更需要学习的样本
- 保留所有大梯度样本,随机丢弃部分小梯度样本
- 减少了样本采样的复杂度,提高训练速度
- 实验证明效果优于纯随机采样
#### EFB(Exclusive Feature Bundling)
**互斥特征捆绑**:将稀疏矩阵中大概率同时取 0 的特征绑定为一个新的特征。
**互斥的含义**:多个特征很少同时取非零值,可以合并
**实现步骤**:
1. 将每个特征视为图中的顶点,非互斥的特征连边
2. 按冲突程度(度)对特征降序排序
3. 遍历排序后的特征,将互斥特征捆绑入同一簇
4. 为不同特征分配不重叠的取值空间(通过偏移常量)
**个人理解示例**:
```
假设12列数据排成一行:
000000001000
000000000100
000000000010
000000000001
000000000000
这12列的取值模式互不相同(互斥),
可以绑定为1个新特征,值域为 {0,1,2,3,4,5}
```
**优点**:
- 减少特征数量 → 降低计算复杂度
- 降低内存消耗
- 由于捆绑的是互斥特征,不影响模型性能
|
|
### Leaf-wise 分裂策略
LightGBM 与传统 GBDT 的重要区别:采用 **Leaf-wise**(叶子节点分裂)而非 **Level-wise**(层级分裂)。
#### 对比
| 策略 | 说明 | 特点 |
|------|------|------|
| **Level-wise** | 按层生长,每层所有节点同时分裂 | XGBoost 使用,容易过拟合,但不易产生很深的树 |
| **Leaf-wise** | 每次选择增益最大的叶子节点分裂 | LightGBM 使用,精度更高,但可能产生深度很大的树 |
#### 分裂过程
```
1. 选择分裂特征:评估所有特征,计算每个特征作为分裂特征时的增益
2. 确定分裂点:基于直方图快速找到最佳分裂点
3. 分裂数据集:按选定特征和分裂点将数据分为两个子集
4. 递归分裂:重复上述过程,直到满足停止条件
```
#### max_depth 与分裂的关系
`max_depth` 直接限制了树的最大深度:
- 当树的深度达到 `max_depth` 时,无论增益有多大,都不再分裂
- 是控制树生长和防止过拟合的重要手段
- LightGBM 中 `max_depth` 不包含叶子节点,只控制分裂节点
- 默认 `-1`(不限制)
|
|
### 残差与梯度提升
#### 梯度提升框架
LightGBM 采用梯度提升(Gradient Boosting)框架:
```
1. 训练第1棵树,得到预测值
2. 计算残差 = 真实值 - 预测值(负梯度)
3. 用残差作为目标值训练第2棵树
4. 重复步骤2~3,直到满足条件
5. 最终预测 = 所有树的预测值之和
```
**每棵新树都在纠正前一棵树的错误**,逐步优化预测能力。
#### num_boost_round 的含义
`num_boost_round` = 提升轮次 = 构建多少棵树 = 训练次数
- 每轮构建一棵新树来弥补当前误差
- 设置太低 → 欠拟合
- 设置太高 → 过拟合 + 浪费计算
#### early_stopping(早停)
通过验证集监控,当连续 N 轮性能不再提升时自动停止:
```python
gbm = lgb.train(params, train_data,
num_boost_round=1000,
valid_sets=[test_data],
early_stopping_rounds=10, # 连续10轮不提升则停止
verbose_eval=10) # 每10轮输出一次日志
```
> **经验**:损失函数持续下降,但准确率可能在较早的轮次就不再提升了。后面构建的树可能是多余的。因此需要通过交叉验证确定最优轮次。
|
|
### LightGBM 参数总览
#### 核心参数
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `objective` | `'regression'` | 目标函数:`binary`、`multiclass`、`regression` 等 |
| `metric` | `None` | 评估指标:`binary_logloss`、`auc`、`multi_logloss`、`rmse` 等 |
| `boosting_type` | `'gbdt'` | 提升类型:`gbdt`、`rf`、`dart`、`goss` |
| `num_leaves` | `31` | 每棵树的最大叶子节点数 |
| `max_depth` | `-1` | 树的最大深度,-1 不限制 |
| `learning_rate` | `0.1` | 学习率 |
| `n_estimators` / `num_boost_round` | `100` | 提升轮数(树的数量) |
| `max_bin` | `255` | 特征直方图最大桶数 |
#### 采样参数
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `feature_fraction` | `1.0` | 每棵树随机选择的特征比例 |
| `bagging_fraction` | `1.0` | 每棵树随机选择的数据比例 |
| `bagging_freq` | `0` | 每 N 轮执行一次 bagging |
#### 正则化参数
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `lambda_l1` / `reg_alpha` | `0` | L1 正则化权重 |
| `lambda_l2` / `reg_lambda` | `0` | L2 正则化权重 |
| `min_gain_to_split` | `0` | 分裂最小增益 |
| `min_data_in_leaf` | `20` | 叶子节点最小样本数 |
| `min_child_samples` | `20` | 叶子节点最小样本数(与 min_data_in_leaf 类似) |
**参数调优优先级**:`num_leaves` > `max_depth` > `min_data_in_leaf` > `feature_fraction` > `lambda_l1/l2`
|
|
### objective 与 metric
#### objective — 目标函数
| 值 | 任务类型 | 说明 |
|----|---------|------|
| `regression` | 回归 | 默认,均方误差(MSE/L2) |
| `regression_l1` | 回归 | L1 损失(绝对值误差/MAE) |
| `huber` | 回归 | Huber 损失,结合 L1 和 L2 的优点 |
| `fair` | 回归 | Fair 损失,对异常值鲁棒 |
| `binary` | 二分类 | 二分类对数损失(交叉熵) |
| `multiclass` | 多分类 | softmax 多分类(需配合 `num_class`) |
| `lambdarank` | 排序 | LambdaRank 排序目标 |
##### 各目标函数详解
**1. regression(均方误差回归)**
损失函数:
$$L(y, \hat{y}) = \frac{1}{2}(y - \hat{y})^2$$
- 一阶导数(负梯度):$\frac{\partial L}{\partial \hat{y}} = \hat{y} - y$
- 二阶导数:$\frac{\partial^2 L}{\partial \hat{y}^2} = 1$
> 适用场景:房价预测、温度预测等连续值预测任务。对异常值较敏感(误差被平方放大)。
**2. regression_l1(绝对值误差回归)**
损失函数:
$$L(y, \hat{y}) = |y - \hat{y}|$$
- 一阶导数:$\frac{\partial L}{\partial \hat{y}} = \text{sign}(\hat{y} - y) = \begin{cases} +1 & \hat{y} > y \\ -1 & \hat{y} < y \end{cases}$
> 适用场景:数据中存在较多异常值时,L1 损失比 L2 更鲁棒。例如预测收入、销量等带有极端值的数据。
**3. huber(Huber 损失回归)**
损失函数:
$$L(y, \hat{y}) = \begin{cases} \frac{1}{2}(y - \hat{y})^2 & \text{if } |y - \hat{y}| \leq \delta \\ \delta \cdot |y - \hat{y}| - \frac{1}{2}\delta^2 & \text{if } |y - \hat{y}| > \delta \end{cases}$$
其中 $\delta$ 是阈值参数。当误差较小($\leq \delta$)时使用 MSE(保持平滑可导),误差较大($> \delta$)时使用 MAE(减少异常值影响)。
> 适用场景:数据中有异常值,但不想完全忽略它们的影响。兼顾回归精度和鲁棒性。
**4. binary(二分类)**
损失函数(交叉熵/对数损失):
$$L(y, p) = -y \log(p) - (1-y) \log(1-p)$$
其中 $p = \sigma(z) = \frac{1}{1+e^{-z}}$ 是 sigmoid 函数输出的概率,$y \in \{0, 1\}$。
一阶导数:$\frac{\partial L}{\partial z} = p - y$
> 适用场景:垃圾邮件识别、疾病诊断、欺诈检测等二分类任务。
```python
params = {
'objective': 'binary',
'metric': 'binary_logloss',
}
```
**5. multiclass(多分类)**
损失函数(softmax 交叉熵):
$$L(y, \hat{y}) = -\sum_{c=1}^{C} y_c \log(\hat{y}_c)$$
其中 $\hat{y}_c = \frac{e^{z_c}}{\sum_{j=1}^{C} e^{z_j}}$ 是 softmax 输出的第 $c$ 类概率。
> 适用场景:鸢尾花分类、手写数字识别、新闻分类等多分类任务。**必须**配合 `num_class` 参数指定类别数。
```python
params = {
'objective': 'multiclass',
'num_class': 3, # 必须指定类别数
'metric': 'multi_logloss',
}
```
**6. lambdarank(排序)**
使用 LambdaRank 方法,直接优化排序指标(如 NDCG)。不对损失函数直接求导,而是根据排序位置调整梯度的大小。
> 适用场景:搜索引擎排序、推荐系统、广告排序等学习排序(Learning to Rank)任务。
#### metric — 评估指标
##### 回归指标
**1. l1 / mae(平均绝对误差)**
$$\text{MAE} = \frac{1}{n} \sum_{i=1}^{n} |y_i - \hat{y}_i|$$
**计算过程**:
1. 计算每个样本的绝对误差 $|y_i - \hat{y}_i|$
2. 求所有样本的平均值
**示例**:真实值 `[3.0, 5.0, 2.5]`,预测值 `[2.5, 5.5, 2.0]`
- 绝对误差:`[0.5, 0.5, 0.5]`
- MAE = (0.5 + 0.5 + 0.5) / 3 = **0.5**
> 适用场景:通用回归评估。对所有误差一视同仁,不受异常值影响过大。
**2. l2 / mse(均方误差)**
$$\text{MSE} = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2$$
**计算过程**:
1. 计算每个样本的误差平方 $(y_i - \hat{y}_i)^2$
2. 求所有样本的平均值
**示例**:真实值 `[3.0, 5.0, 2.5]`,预测值 `[2.5, 5.5, 2.0]`
- 误差平方:`[0.25, 0.25, 0.25]`
- MSE = (0.25 + 0.25 + 0.25) / 3 = **0.25**
> 适用场景:需要放大惩罚大误差的场景。对异常值敏感。
**3. rmse(均方根误差)**
$$\text{RMSE} = \sqrt{\frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2} = \sqrt{\text{MSE}}$$
**计算过程**:
1. 先计算 MSE
2. 取平方根
**示例**:MSE = 0.25 → RMSE = √0.25 = **0.5**
> 适用场景:回归任务最常用指标。与原始数据同量纲,更直观。RMSE ≥ MAE,两者差距越大说明误差分布越不均匀。
**4. mape(平均绝对百分比误差)**
$$\text{MAPE} = \frac{100\%}{n} \sum_{i=1}^{n} \left|\frac{y_i - \hat{y}_i}{y_i}\right|$$
**示例**:真实值 `[100, 200, 50]`,预测值 `[90, 210, 55]`
- 百分比误差:`[10%, 5%, 10%]`
- MAPE = (10 + 5 + 10) / 3 = **8.33%**
> 适用场景:关注相对误差而非绝对误差的任务,如销量预测、财务预测。
##### 分类指标
**5. binary_logloss(二分类对数损失)**
$$\text{LogLoss} = -\frac{1}{n} \sum_{i=1}^{n} \left[y_i \log(p_i) + (1-y_i) \log(1-p_i)\right]$$
**计算过程**:
1. 对每个样本计算 $-y_i \log(p_i) - (1-y_i) \log(1-p_i)$
2. 求所有样本的平均值
**示例**:真实标签 `[1, 0, 1]`,预测概率 `[0.9, 0.2, 0.7]`
- 样本1:$-(1 \times \log(0.9) + 0) = -\log(0.9) \approx 0.105$
- 样本2:$-(0 + 1 \times \log(0.8)) = -\log(0.8) \approx 0.223$
- 样本3:$-(1 \times \log(0.7) + 0) = -\log(0.7) \approx 0.357$
- LogLoss = (0.105 + 0.223 + 0.357) / 3 ≈ **0.228**
> 适用场景:配合 `binary` 目标函数。值越小越好,完美预测时 LogLoss = 0。
**6. auc(ROC 曲线下面积)**
$$\text{AUC} = P(\text{score}(\text{正样本}) > \text{score}(\text{负样本}))$$
**计算过程**:
1. 将样本按预测概率从大到小排序
2. 对每个阈值计算 TPR(真正率)和 FPR(假正率)
3. 绘制 ROC 曲线
4. 用梯形法计算曲线下面积
**示例**:
| 样本 | 真实标签 | 预测概率 |
|------|---------|---------|
| A | 1 | 0.9 |
| B | 0 | 0.8 |
| C | 1 | 0.7 |
| D | 0 | 0.3 |
正负样本对比较(正样本分数是否大于负样本):
- A vs B:0.9 > 0.8 ✅ A vs D:0.9 > 0.3 ✅
- C vs B:0.7 < 0.8 ❌ C vs D:0.7 > 0.3 ✅
- AUC = 3/4 = **0.75**
> 适用场景:类别不平衡时的分类评估。AUC = 0.5 等同随机猜测,AUC = 1.0 为完美分类。
**7. multi_logloss(多分类对数损失)**
$$\text{MultiLogLoss} = -\frac{1}{n} \sum_{i=1}^{n} \sum_{c=1}^{C} y_{ic} \log(p_{ic})$$
其中 $y_{ic}$ 是 one-hot 编码后的标签值(只有真实类别为 1,其余为 0)。
**计算过程**:
1. 对每个样本,用 one-hot 编码真实标签
2. 只计算真实类别对应位置的 $-\log(p_{ic})$
3. 求所有样本的平均值
**示例**:3 分类,真实标签 `[0, 2, 1]`,预测概率分布:
- 样本1(类别0):`[0.8, 0.1, 0.1]` → $-\log(0.8) \approx 0.223$
- 样本2(类别2):`[0.1, 0.1, 0.8]` → $-\log(0.8) \approx 0.223$
- 样本3(类别1):`[0.1, 0.7, 0.2]` → $-\log(0.7) \approx 0.357$
- MultiLogLoss = (0.223 + 0.223 + 0.357) / 3 ≈ **0.268**
> 适用场景:配合 `multiclass` 目标函数,评估多分类模型的概率预测质量。
#### objective 与 metric 搭配建议
| objective | 推荐 metric | 说明 |
|-----------|------------|------|
| `regression` | `rmse`、`mae` | 回归任务通用 |
| `regression_l1` | `mae`、`rmse` | L1 回归 |
| `huber` | `mae`、`rmse` | Huber 回归 |
| `binary` | `binary_logloss`、`auc` | 二分类 |
| `multiclass` | `multi_logloss` | 多分类 |
```python
params = {
'objective': 'binary',
'metric': ['binary_logloss', 'auc'], # 可指定多个指标
}
```
|
|
### boosting_type — 提升类型
LightGBM 支持 4 种提升类型,通过 `boosting_type` 参数指定,默认为 `gbdt`。
---
#### 1. gbdt(Gradient Boosting Decision Tree)
**描述**:传统的梯度提升决策树,LightGBM 的默认提升类型。每轮训练一棵新树来拟合当前模型的负梯度(伪残差),最终预测为所有树的加权和。
**数学原理**:
$$F_m(x) = F_{m-1}(x) + \eta \cdot h_m(x)$$
其中:
- $F_m(x)$ 是第 $m$ 轮的集成模型预测
- $h_m(x)$ 是第 $m$ 棵决策树,拟合负梯度(伪残差)
- $\eta$ 是学习率(`learning_rate`),控制每棵树的贡献
- 伪残差:$r_{im} = -\left[\frac{\partial L(y_i, F(x_i))}{\partial F(x_i)}\right]_{F=F_{m-1}}$
**简单例子**:
假设预测房价,学习率 $\eta = 0.1$:
- 第1棵树:预测基础价格 $h_1 = 100$万
- 残差 = 实际价格 - 100万,如 `[+20, -10, +5]`
- 第2棵树:拟合残差,输出调整量 $h_2$
- 第3棵树:拟合新的残差 $h_3$
- 最终预测 = $0.1 \times h_1 + 0.1 \times h_2 + 0.1 \times h_3 + ...$
> **适用场景**:大多数任务的默认选择。回归、二分类、多分类等通用场景,需要快速训练且数据量较大时。适合作为基线模型。
---
#### 2. rf(Random Forest)
**描述**:基于随机森林的提升类型。与 GBDT 的串行纠错不同,RF 模式下每棵树独立训练,最终预测为所有树的**简单平均**。需要配合 `bagging_fraction` 和 `bagging_freq` 参数使用。
**数学原理**:
$$F(x) = \frac{1}{M}\sum_{m=1}^{M} h_m(x)$$
与 GBDT 的关键区别:
- GBDT(加权求和,串行纠错):$F(x) = \sum_{m=1}^{M} \eta \cdot h_m(x)$
- RF(简单平均,并行投票):$F(x) = \frac{1}{M}\sum_{m=1}^{M} h_m(x)$
**RF 模式必需参数**:
```
'bagging_fraction': 0.8, # 每棵树使用随机 80% 样本
'bagging_freq': 1, # 每轮都进行采样
'feature_fraction': 0.8 # 每棵树使用随机 80% 特征
```
**简单例子**:
假设预测客户是否会购买产品(3棵树投票):
- 树1:使用 {年龄, 收入, 地区} → 预测"买"
- 树2:使用 {学历, 职业, 收入} → 预测"不买"
- 树3:使用 {年龄, 地区, 学历} → 预测"买"
- 最终:投票结果 买:不买 = 2:1 → 预测**"买"**
> **适用场景**:希望减少过拟合风险时;模型可解释性要求较高时;数据噪声较大时。适合需要稳定预测的场景。
---
#### 3. dart(Dropouts meet Multiple Additive Regression Trees)
**描述**:将深度学习中的 **Dropout** 思想引入 GBDT。训练每棵新树时,随机"丢弃"(忽略)之前已生成的部分树,使新树不仅纠正最后一棵树的残差,还要弥补被丢弃树的影响。有效防止过拟合。
**数学原理**:
标准 GBDT 更新(保留所有历史树):
$$F_m(x) = \sum_{k=1}^{m-1} \eta \cdot h_k(x) + \eta \cdot h_m(x)$$
DART 更新(随机丢弃部分历史树):
$$F_m(x) = \sum_{k \in S_m} \frac{\eta}{|D_m|+1} \cdot h_k(x) + \eta \cdot h_m(x)$$
其中:
- $S_m$ 是未被丢弃的树的集合
- $D_m$ 是被丢弃的树的集合,$|D_m|$ 为被丢弃树的数量
- 每棵历史树以概率 `drop_rate` 被丢弃
- 归一化因子 $\frac{1}{|D_m|+1}$ 确保新树的贡献与被丢弃树的贡献均衡
**简单例子**:
假设已训练了 5 棵树,`drop_rate = 0.2`,现在要训练第 6 棵:
1. 随机丢弃树 2 和树 4(2/5 = 40% 被丢弃)
2. 当前模型 = 树1 + 树3 + 树5(临时忽略树2、树4)
3. 用残差训练第 6 棵新树
4. 最终模型:所有树重新归一化 → 每棵树的权重缩小为 $\frac{1}{3}$(丢弃数+1)
> **适用场景**:标准 GBDT 出现过拟合时;数据噪声较大时;竞赛中追求更高精度时。**注意**:训练速度比 gbdt 慢 1.5~2 倍,且无法使用早停(early_stopping)。
---
#### 4. goss(Gradient-based One-Side Sampling)
**描述**:基于梯度的单边采样,是 LightGBM 论文提出的核心优化方法。保留所有大梯度样本(预测误差大的),仅对小梯度样本进行随机采样,并放大其权重来补偿。
**数学原理**:
标准 GBDT 中,第 $j$ 个特征在分裂点 $d$ 的信息增益:
$$V_{j}(d) = \frac{(\sum_{x_i \in L} g_i)^2}{n_L} + \frac{(\sum_{x_i \in R} g_i)^2}{n_R}$$
GOSS 优化后的增益估计:
$$\tilde{V}_{j}(d) = \frac{(\sum_{x_i \in A_L} g_i + \frac{1-a}{b}\sum_{x_i \in B_L} g_i)^2}{\tilde{n}_L} + \frac{(\sum_{x_i \in A_R} g_i + \frac{1-a}{b}\sum_{x_i \in B_R} g_i)^2}{\tilde{n}_R}$$
其中:
- $A$ 是大梯度样本集合(全量保留),占比 $a$(如 top 20%)
- $B$ 是小梯度样本集合(随机采样),采样比例 $b$(如 10%)
- $\frac{1-a}{b}$ 是小梯度样本的**放大系数**,补偿采样损失
- $L, R$ 表示分裂后的左右子集
**简单例子**:
假设有 1000 个训练样本,$a = 0.2$,$b = 0.1$:
1. 计算所有样本梯度,按绝对值降序排列
2. 保留梯度最大的 200 个样本(top 20% = 集合 $A$)
3. 从剩余 800 个小梯度样本中随机抽取 80 个(10% = 集合 $B$)
4. 放大系数 = $(1 - 0.2) / 0.1 = 8$,即小梯度样本权重放大 8 倍
5. 用这 280 个样本(而非 1000 个)寻找最佳分裂点 → **速度提升约 3.5 倍**
> **适用场景**:数据量非常大时(>100万样本);训练速度是首要考虑因素;不想大幅损失模型精度。注意:goss 不支持 `bagging_fraction` 参数。
---
#### 四种提升类型对比
| 对比项 | gbdt | rf | dart | goss |
|--------|------|-----|------|------|
| **训练速度** | 快 | 快 | 慢(1.5~2×) | 最快 |
| **过拟合控制** | 一般 | 好(独立投票) | 最好(Dropout) | 一般 |
| **精度** | 高 | 中 | 高 | 接近 gbdt |
| **早停支持** | ✅ | ✅ | ❌ | ✅ |
| **适用数据量** | 中~大 | 中~大 | 中~大 | 大 |
| **特殊要求** | 无 | 需设 bagging 参数 | 无 | 不支持 bagging |
#### 选择建议
```python
# 通用场景(默认推荐)
'boosting_type': 'gbdt'
# 数据量大、追求速度
'boosting_type': 'goss'
# 过拟合严重
'boosting_type': 'dart'
# 希望更稳定、减少过拟合
'boosting_type': 'rf'
```
|
|
### 树结构参数
#### max_depth — 树的最大深度
```
max_depth, default = -1, type = int, <= 0 表示不限制
根节点为第1层,max_depth 不包含叶子节点
示例:
A depth=1:只有根节点A
/ \
B C depth=2:A为根,B、C为分裂节点
在 iris 数据集上,max_depth=1 就已有效,
max_depth=3 时日志会输出 "No further splits with positive gain"
说明数据集太简单,树的深度足够后无法找到正增益的分裂
```
#### num_leaves — 叶子节点数
```
num_leaves, default = 31, type = int
约束: 1 < num_leaves <= 131072
只是一棵树中叶子节点的个数
与特征数量没有直接关系,而是与模型复杂度和拟合能力有关
```
**设置建议**:
- 通常比特征数多一些(允许模型有足够的灵活性)
- 通过交叉验证确定最佳值
- 在 breast_cancer(30特征)上,num_leaves 从 4~40 准确率都为 0.96
树的棵数由 n_estimators / num_iterations 指定,两者完全独立
- `num_iterations` 是 LightGBM **原生参数名**,表示提升迭代的次数
- `n_estimators` 是 scikit-learn 风格的**别名**,两者指向同一个参数
- **迭代次数 = 树的棵数**:因为 LightGBM 是 GBDT 框架,每一轮迭代会训练一棵新的决策树来拟合上一轮的残差,所以迭代 100 次 = 构建 100 棵树
- 如果同时设置了两者,以先设置的为准,后设置的不会覆盖,实际使用中只设其中一个即可
单棵树的叶子节点个数 ✅ 每一棵基学习器(决策树)最多拥有的叶子节点数
每棵树的叶子节点个数 ✅ 同上,每棵树的叶子数上限都由此参数控制
关键点
LightGBM 使用的是 leaf-wise(按叶子生长) 策略,与 XGBoost 的 level-wise 不同
num_leaves 直接控制一棵树的最大叶子数,间接决定了树的深度上限:
理论最大深度 ≈ $2^{\log_2(numLeaves)}$(满二叉树时)
默认值通常是 31(即单棵树最多 31 个叶子节点)
设置规则:一般取 $num_leaves < 2^{max_depth}$,防止过拟合
#### max_bin — 特征直方图桶数
```
每个特征最多划分为多少个桶(默认 255)
实际桶数由 LightGBM 自动决定,通常少于 max_bin
通常先让模型自主决定,出现问题时再人工调整
```
|
|
### 采样与正则化参数
#### feature_fraction — 特征采样比例
```python
'feature_fraction': 0.9 # 每棵树随机选择 90% 的特征
```
- **随机选择,不考虑特征重要性**(不是按重要性排序取前 90%)
- 每棵树随机选不同子集 → 增加模型多样性 → 提高泛化能力
- 所有树共同构成最终模型,综合利用全部特征信息
#### bagging_fraction — 数据采样比例
```python
'bagging_fraction': 0.8, # 每棵树随机使用 80% 的数据
'bagging_freq': 5, # 每 5 轮执行一次 bagging
```
- 有放回随机抽样,增加模型多样性
- 需配合 `bagging_freq` 使用
#### lambda_l1 / lambda_l2 — 正则化
```python
'lambda_l1': 0.1, # L1 正则化(稀疏化权重,减少不必要特征)
'lambda_l2': 0.1, # L2 正则化(平滑权重,减少过拟合)
```
| 参数 | 作用 | 建议 |
|------|------|------|
| `lambda_l1` | 对权重做稀疏化处理,部分权重变为 0 | 从 0.01 或 0.1 开始尝试 |
| `lambda_l2` | 使权重更平滑稳定,防止过大 | 从 0.01 或 0.1 开始尝试 |
#### min_gain_to_split — 最小分裂增益
```
分裂增益低于该值则不再分裂
建议范围: 0~10
值越大模型越简单(可能欠拟合),值越小模型越复杂(可能过拟合)
```
#### min_child_samples / min_data_in_leaf
| 参数 | 说明 | 默认值 |
|------|------|--------|
| `min_child_samples` | 叶子节点最小样本个数 | 20 |
| `min_data_in_leaf` | 叶子节点最小数据量(样本数 × 特征维度) | 20 |
两者都用于防止过拟合:值越大树越简单,值越小树越复杂。
|
|
### 训练控制参数
#### num_threads — 线程数
```python
'num_threads': 4 # 使用 4 个线程,默认 0(使用 OpenMP 默认线程数)
```
利用 OpenMP 并行化多个操作,设置为 CPU 核心数可获得最佳性能。
#### verbose / verbose_eval — 日志控制
| 参数 | 说明 |
|------|------|
| `verbose` (params 中) | `-1`:静默,`0`:错误,`1`:信息 |
| `verbose_eval` (lgb.train 中) | `None`:不输出,`整数N`:每 N 轮输出一次 |
#### valid_sets — 验证数据集
`valid_sets` 用于在训练过程中监控模型在验证集上的表现。
**主要作用**:
| 作用 | 说明 |
|------|------|
| 监控模型表现 | 每轮计算验证集指标,观察是否过拟合 |
| 支持早停 | 配合 `early_stopping_rounds`,连续 N 轮不提升则停止 |
| 多数据集对比 | 可传入多个验证集 |
```python
gbm = lgb.train(
params,
train_data,
num_boost_round=1000,
valid_sets=[train_data, test_data], # 可传多个
valid_names=['train', 'valid'], # 日志中显示的名字
early_stopping_rounds=10, # 早停
verbose_eval=10 # 每10轮输出
)
# 输出示例:
# [10] train's binary_logloss: 0.321 valid's binary_logloss: 0.345
# [20] train's binary_logloss: 0.280 valid's binary_logloss: 0.320
# ...
# [56] early stopping, best iteration is [46]
```
> **注意**:没有 `verbose_eval` 看不到日志输出,但早停仍会生效。
|
|
### Dataset 参数
#### reference 参数
```python
train_data = lgb.Dataset(X_train, label=y_train)
test_data = lgb.Dataset(X_test, label=y_test, reference=train_data)
```
验证集引用训练集的原因:
| 作用 | 说明 |
|------|------|
| 分类特征处理 | 使用训练集中分类特征的取值信息 |
| 缺失值处理 | 根据训练集信息更合理地处理验证集缺失值 |
| 特征对齐 | 确保训练和验证使用相同的特征集和顺序 |
| 性能优化 | 内部优化的前提条件 |
> 对于 iris 等简单数据集,是否添加 reference 结果都一样(精度都是 1.0)。但在复杂场景下建议始终使用。
#### categorical_feature — 类别特征
```python
train_data = lgb.Dataset(X_train, label=y_train,
categorical_feature=['col1', 'col2'])
```
LightGBM 可以直接处理类别特征,无需独热编码。
#### force_col_wise — 强制列存储
```python
'force_col_wise': True # 内存不足时有助于模型训练
```
|
|
### Iris 多分类示例
```python
import numpy as np
import lightgbm as lgb
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 加载数据
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, test_size=0.3, random_state=42)
# 创建数据集
train_data = lgb.Dataset(X_train, label=y_train)
test_data = lgb.Dataset(X_test, label=y_test, reference=train_data)
# 参数设置
params = {
'objective': 'multiclass',
'num_class': 3,
'metric': 'multi_logloss',
'verbosity': -1
}
# 训练
gbm = lgb.train(params, train_data,
num_boost_round=100,
valid_sets=[test_data])
# 预测(返回概率矩阵)
y_pred = gbm.predict(X_test)
y_pred_classes = [int(np.argmax(line)) for line in y_pred]
# 评估
print(f'Accuracy: {accuracy_score(y_test, y_pred_classes):.4f}')
# Accuracy: 1.0000
```
**训练日志**:
```
[LightGBM] [Info] Total Bins 86
[LightGBM] [Info] Number of data points in the train set: 105, number of used features: 4
```
|
|
### LGBMClassifier 二分类示例
使用 sklearn 风格的 API,支持 `fit` / `predict` / `score`。
```python
import pandas as pd
from lightgbm import LGBMClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
# 加载乳腺癌数据集
data = load_breast_cancer()
X = pd.DataFrame(data.data, columns=data.feature_names)
y = pd.Series(data.target)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 初始化模型
model = LGBMClassifier(
boosting_type='gbdt',
objective='binary',
num_leaves=31,
max_depth=-1,
learning_rate=0.1,
n_estimators=100,
lambda_l1=0.1,
lambda_l2=0.1,
min_data_in_leaf=20,
bagging_fraction=0.8,
feature_fraction=0.9,
verbose=-1
)
model.fit(X_train, y_train)
print(f'Accuracy: {model.score(X_test, y_test):.4f}')
```
#### 原生 API 等价写法
```python
params = {
'boosting_type': 'gbdt',
'objective': 'binary',
'metric': 'binary_logloss',
'num_leaves': 31,
'learning_rate': 0.05,
'feature_fraction': 0.9,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'verbose': -1,
'lambda_l1': 0.1,
'lambda_l2': 0.1
}
train_data = lgb.Dataset(X_train, label=y_train)
val_data = lgb.Dataset(X_test, label=y_test, reference=train_data)
bst = lgb.train(params, train_data,
num_boost_round=100,
valid_sets=[train_data, val_data],
valid_names=['train', 'valid'])
y_pred = bst.predict(X_test, num_iteration=bst.best_iteration)
y_pred_binary = [1 if pred > 0.5 else 0 for pred in y_pred]
print(f'Accuracy: {accuracy_score(y_test, y_pred_binary):.4f}')
```
|
|
### 样本加权(class_weight)
处理类别不平衡问题,给少数类更高的权重。
```python
from sklearn.datasets import make_classification
from sklearn.metrics import accuracy_score
# 生成不平衡数据集(10% vs 90%)
X, y = make_classification(n_samples=1000, n_features=20,
n_classes=2, weights=[0.1, 0.9], random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 方式一:手动指定权重
class_weight = {0: 10, 1: 1}
model = lgb.LGBMClassifier(class_weight=class_weight,
num_leaves=31, learning_rate=0.1,
n_estimators=100, random_state=42)
model.fit(X_train, y_train)
print(f"Accuracy (手动权重): {model.score(X_test, y_test):.4f}")
# 方式二:自动平衡
model = lgb.LGBMClassifier(class_weight='balanced',
num_leaves=31, learning_rate=0.1,
n_estimators=100, random_state=42)
model.fit(X_train, y_train)
print(f"Accuracy (自动平衡): {model.score(X_test, y_test):.4f}")
# 方式三:不加权
model = lgb.LGBMClassifier(num_leaves=31, learning_rate=0.1,
n_estimators=100, random_state=42)
model.fit(X_train, y_train)
print(f"Accuracy (不加权): {model.score(X_test, y_test):.4f}")
```
> **注意**:加权不一定比不加权好,取决于数据分布。需要通过实验确定。
|
|
### 指明分类列(categorical_feature)
LightGBM 原生支持类别特征,无需独热编码。
```python
import lightgbm as lgb
# 指定哪些列是分类列
cat_features = ['cat_0', 'cat_1', 'cat_2']
# 创建数据集时指定分类特征
train_data = lgb.Dataset(X_train, label=y_train,
categorical_feature=cat_features)
test_data = lgb.Dataset(X_test, label=y_test,
reference=train_data)
# 训练
params = {
'objective': 'binary',
'metric': 'binary_logloss',
'boosting_type': 'gbdt',
'max_depth': 3,
'num_leaves': 3,
'max_bin': 3,
'min_gain_to_split': 0,
'min_data_in_leaf': 10,
'force_col_wise': True
}
lgb_model = lgb.train(params, train_data,
num_boost_round=1,
valid_sets=[test_data])
```
**LGBMClassifier 方式**:
```python
model = LGBMClassifier(categorical_feature=cat_features)
model.fit(X_train, y_train)
```
|
|
### LGBMClassifier 获取特征重要性
```python
import pandas as pd
from lightgbm import LGBMClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
# 数据准备
data = load_breast_cancer()
X = pd.DataFrame(data.data, columns=data.feature_names)
y = pd.Series(data.target)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 训练
model = LGBMClassifier(num_leaves=31, learning_rate=0.05, n_estimators=2)
model.fit(X_train, y_train)
# 获取特征重要性
importances = model.feature_importances_
feature_importances = pd.DataFrame({
'feature': X.columns,
'importance': importances
}).sort_values(by='importance', ascending=False)
print(feature_importances)
```
**输出示例**:
```
feature importance
0 mean radius 3
21 worst texture 3
27 worst concave points 2
13 area error 2
6 mean concavity 2
...
19 fractal dimension error 0
24 worst smoothness 0
```
- 值为 0 的特征:未被使用(对划分无贡献)
- 值越大:该特征对模型越重要
|
|
### lgb.train 获取特征重要性
```python
import lightgbm as lgb
import matplotlib.pyplot as plt
train_data = lgb.Dataset(X_train, label=y_train)
params = {
'objective': 'binary',
'metric': 'binary_logloss',
'boosting_type': 'gbdt'
}
model = lgb.train(params, train_data, num_boost_round=3)
# 获取特征重要性
importance = model.feature_importance()
feature_names = X.columns
# 可视化
important_features = pd.DataFrame({
'feature': feature_names,
'importance': importance
}).sort_values(by='importance', ascending=False)
plt.figure(figsize=(10, 6))
plt.barh(important_features['feature'], important_features['importance'])
plt.xlabel('Importance')
plt.title('Feature Importance - LightGBM')
plt.show()
```
**两种 API 的特征重要性方法对比**:
| API | 方法 | 返回值 |
|-----|------|--------|
| `LGBMClassifier` | `model.feature_importances_` | numpy 数组 |
| `lgb.train` | `model.feature_importance()` | numpy 数组 |
|
|
### 特征筛选与重训练
根据特征重要性筛选关键特征,用更少的特征重新训练。
```python
from sklearn.metrics import accuracy_score, roc_auc_score
# 选择前 N 个重要特征
top_n = 10
top_features = feature_importances.head(top_n)['feature'].tolist()
# 提取这些特征对应的训练集和测试集
X_train_selected = X_train[top_features]
X_test_selected = X_test[top_features]
# 重新训练
model_selected = LGBMClassifier(num_leaves=31, learning_rate=0.05, n_estimators=3)
model_selected.fit(X_train_selected, y_train)
# 评估
y_pred_selected = model_selected.predict(X_test_selected)
accuracy = accuracy_score(y_test, y_pred_selected)
auc = roc_auc_score(y_test, y_pred_selected)
print(f"Accuracy: {accuracy:.4f}, AUC: {auc:.4f}")
```
> **注意**:特征筛选后的模型性能不一定更好。如果重要特征的选取不当(如 n_estimators 太少导致重要性评估不准确),反而会降低性能。建议用较多轮次的交叉验证来评估特征重要性。
|
|
### GridSearchCV 网格搜索
使用交叉验证自动搜索最优参数组合。
```python
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, GridSearchCV
from lightgbm import LGBMClassifier
# 数据准备
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
data.data, data.target, test_size=0.2, random_state=42)
# 定义参数搜索空间
param_grid = {
'learning_rate': [0.05, 0.06, 0.07, 0.08, 0.09, 0.1],
'n_estimators': [500, 600, 700, 800],
'max_depth': range(2, 8)
}
# 网格搜索 + 交叉验证
grid_search = GridSearchCV(
LGBMClassifier(),
param_grid,
cv=5,
scoring='accuracy',
n_jobs=-1,
verbose=1
)
grid_search.fit(X_train, y_train)
print(f"最优参数: {grid_search.best_params_}")
print(f"最优分数: {grid_search.best_score_:.4f}")
# 使用最优模型
best_model = grid_search.best_estimator_
print(f"测试集准确率: {best_model.score(X_test, y_test):.4f}")
```
> **注意**:网格搜索遍历所有参数组合,参数空间大时计算量很大。建议先用粗粒度搜索,再在最优区间内细搜。
|
|
### 交叉验证 cv
使用 LightGBM 内置的 `lgb.cv` 确定最优迭代轮次。
```python
import lightgbm as lgb
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
data, label = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
data, label, test_size=0.2, random_state=42)
train_data = lgb.Dataset(X_train, label=y_train)
params = {
'objective': 'binary',
'metric': 'binary_logloss',
'num_leaves': 31,
'learning_rate': 0.05,
'verbose': -1
}
# 5折交叉验证
cv_results = lgb.cv(params, train_data,
num_boost_round=100,
nfold=5,
stratified=True,
seed=42)
# 找到最小 loss 对应的轮次
best_round = len(cv_results['valid binary_logloss-mean'])
print(f'最优轮次: {best_round}')
print(f'最小 loss: {cv_results["valid binary_logloss-mean"][-1]:.4f}')
# 使用最优轮次训练最终模型
model = lgb.train(params, train_data, num_boost_round=best_round)
```
**注意事项**:
- 损失函数下降不代表准确率一定上升
- 在乳腺癌数据集上,loss 在 100 轮内持续下降,但准确率在 20 轮后就不再提升
- 后面构建的 80 多棵树可能是多余的
- 最终还是要以准确率等业务指标为评判标准
|
|
### LightGBM 调参策略
#### 调参顺序
```
第1步:确定 objective 和 metric(由任务类型决定)
第2步:确定 num_boost_round(通过 cv 找到最优轮次)
- 先设较大的 learning_rate(0.1)和较大的轮次
- 使用 lgb.cv 找到最优轮次
第3步:调整树结构参数
- max_depth:从 3~10 尝试
- num_leaves:通常设为 2^max_depth 或更小
- min_data_in_leaf:根据样本量调整
第4步:调整采样参数
- feature_fraction:0.5~0.9
- bagging_fraction:0.5~0.9
- bagging_freq:3~5
第5步:调整正则化参数
- lambda_l1:0~1
- lambda_l2:0~1
- min_gain_to_split:0~0.1
第6步:降低 learning_rate,增大 num_boost_round
- 例如 learning_rate=0.01, num_boost_round=1000
```
#### 常见问题
| 问题 | 原因 | 解决方案 |
|------|------|---------|
| 训练慢 | 参数空间太大 | 减少搜索范围,或用 RandomizedSearchCV |
| 过拟合 | 树太深/轮次太多 | 减小 max_depth、num_leaves,增大 min_data_in_leaf |
| 欠拟合 | 模型太简单 | 增大 num_boost_round,减小正则化 |
| "No further splits" | 数据集太小/特征太少 | 减小 max_depth,或增大 min_gain_to_split |
|
|
### CatBoost 概述
CatBoost(**Cat**egorical **Boost**ing)由 Yandex 开发,专为处理**类别型特征**而设计的 GBDT 算法。
**核心特点**:
- **自动处理类别特征**:无需独热编码,通过 Ordered Target Encoding 技术处理
- **Ordered Boosting**:模拟时间序列方式训练,减少过拟合
- **对称树结构(Oblivious Trees)**:完全对称二叉树,预测速度快
- **内置缺失值处理**:自动将缺失值视为单独类别
- **小数据集表现优异**:尤其在含大量类别特征的数据上
**CatBoost vs LightGBM vs XGBoost**:
| 特性 | CatBoost | LightGBM | XGBoost |
|------|---------|----------|---------|
| 类别特征处理 | 自动 Ordered TS | 支持但需指定 | 需手动编码 |
| 树结构 | 对称二叉树 | Leaf-wise | Level-wise |
| 训练速度 | 中 | 最快 | 较慢 |
| 过拟合控制 | Ordered Boosting | GOSS/EFB | 正则化 |
| GPU 支持 | 好 | 好 | 好 |
|
|
### CatBoost 代码示例
```python
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from catboost import CatBoostClassifier, Pool
# 生成示例数据
np.random.seed(42)
data_size = 1000
X_num = np.random.rand(data_size, 5) # 数值特征
X_cat = np.random.randint(0, 10, size=(data_size, 3)) # 类别特征
y = np.random.randint(0, 2, size=data_size)
# 组合数据
X = np.hstack([X_num, X_cat])
feature_names = [f'num_{i}' for i in range(5)] + [f'cat_{i}' for i in range(3)]
df = pd.DataFrame(X, columns=feature_names)
df['target'] = y
# 将类别特征标记为 string 类型
for col in [c for c in df.columns if 'cat_' in c]:
df[col] = df[col].astype('string')
data = df.drop('target', axis=1)
label = df['target']
X_train, X_test, y_train, y_test = train_test_split(data, label, test_size=0.2, random_state=42)
# 指定分类特征的列索引
cat_features_idx = [i for i, col in enumerate(X_train.columns) if 'cat_' in col]
# 初始化 CatBoost 分类器
model = CatBoostClassifier(
iterations=100,
learning_rate=0.05,
depth=3,
loss_function='Logloss',
verbose=False
)
# 训练
model.fit(X_train, y_train,
cat_features=cat_features_idx,
eval_set=(X_test, y_test),
early_stopping_rounds=50)
# 预测
y_pred = model.predict(X_test)
y_pred_proba = model.predict_proba(X_test)[:, 1]
# 评估
accuracy = accuracy_score(y_test, y_pred)
print(f'Accuracy: {accuracy:.4f}')
```
|
|
### CatBoost 核心原理
#### 1. Ordered Target Encoding(有序目标编码)
对类别特征,使用目标编码(Target Encoding),但避免目标泄露:
```
1. 将样本随机排序
2. 对第 i 个样本,仅使用前 i-1 个样本的标签均值来编码其类别值
3. 避免使用"未来信息",防止过拟合
```
#### 2. Ordered Boosting(有序提升)
```
- 传统 GBDT:用当前模型的预测残差训练下一棵树 → 容易过拟合
- CatBoost:模拟"时间序列"方式,对每个样本使用其"历史"数据训练模型来预测它
- 类似 leave-one-out 思想,但更高效
```
#### 3. 对称树结构(Oblivious Trees)
```
- 每棵树是完全对称二叉树(同一层所有叶子用同一个特征和切分点)
- 预测时只需一次按位运算 → 极快
- 便于并行计算和 GPU 加速
```
#### 4. 特征组合
- 对高基数类别特征,自动进行特征组合生成新特征
- 新特征同样用 Ordered TS 编码
#### 目标泄露问题
目标泄露(Target Leakage)是指在编码类别特征时使用了包含当前样本标签在内的全局信息:
- **错误做法**:用全部数据的标签均值编码 → 信息泄露 → 过拟合
- **CatBoost 做法**:仅用前 i-1 个样本的标签均值编码第 i 个样本 → 无泄露
|
|
### XGBoost 代码示例
```python
import numpy as np
import xgboost as xgb
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix
# 数据准备
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
data.data, data.target, test_size=0.2, random_state=42)
# 初始化 XGBoost 分类器
model = xgb.XGBClassifier(
learning_rate=0.1,
n_estimators=100,
max_depth=5,
verbosity=0
)
# 训练
model.fit(X_train, y_train)
# 预测
y_pred = model.predict(X_test)
# 评估
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy:.4f}")
# Accuracy: 0.9561
# 混淆矩阵
print("Confusion Matrix:")
print(confusion_matrix(y_test, y_pred))
# [[40 3]
# [ 2 69]]
# 特征重要性
feature_importances = model.feature_importances_
sorted_indices = np.argsort(feature_importances)[::-1]
for idx in sorted_indices[:5]:
print(f"Feature {idx}: {feature_importances[idx]:.4f}")
```
|
|
### XGBoost 原理
XGBoost(eXtreme Gradient Boosting)是 GBDT 的高效实现。
#### 核心改进
| 改进点 | 说明 |
|--------|------|
| **二阶泰勒展开** | 使用损失函数的一阶和二阶导数,更精确地逼近最优解 |
| **正则化** | 目标函数中加入 L1/L2 正则项,控制模型复杂度 |
| **列采样** | 类似随机森林,支持特征级别的随机采样 |
| **缺失值处理** | 自动学习缺失值的分裂方向 |
| **并行化** | 特征粒度的并行,加速寻找最佳分裂点 |
#### 目标函数
$$Obj = \sum_{i=1}^{n} l(y_i, \hat{y}_i) + \sum_{k=1}^{K} \Omega(f_k)$$
其中正则项:
$$\Omega(f) = \gamma T + \frac{1}{2}\lambda \|\omega\|^2$$
- $T$ 为叶子节点数,$\omega$ 为叶子权重
- $\gamma$ 控制叶子数,$\lambda$ 控制权重大小
#### GBDT vs XGBoost
| 对比项 | GBDT | XGBoost |
|--------|------|---------|
| 优化方式 | 一阶导数(梯度下降) | 一阶+二阶导数(牛顿法) |
| 正则化 | 无 | L1/L2 正则项 |
| 采样 | 无 | 支持列采样和行采样 |
| 缺失值 | 需预处理 | 自动处理 |
| 并行 | 不支持 | 特征粒度并行 |
|
|
### XGBoost 常用参数
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `learning_rate` / `eta` | `0.3` | 学习率 |
| `n_estimators` | `100` | 树的数量 |
| `max_depth` | `6` | 树的最大深度 |
| `min_child_weight` | `1` | 叶子节点最小权重和 |
| `gamma` / `min_split_loss` | `0` | 分裂最小增益 |
| `subsample` | `1` | 数据采样比例 |
| `colsample_bytree` | `1` | 特征采样比例 |
| `reg_alpha` | `0` | L1 正则化 |
| `reg_lambda` | `1` | L2 正则化 |
| `objective` | `'binary:logistic'` | 目标函数 |
| `eval_metric` | 自动 | 评估指标 |
| `verbosity` | `1` | 日志级别(0~3) |
#### 网格搜索调参示例
```python
from sklearn.model_selection import GridSearchCV
param_grid = {
'max_depth': [3, 5, 7],
'learning_rate': [0.01, 0.05, 0.1],
'n_estimators': [50, 100, 200],
'reg_alpha': [0, 0.1, 0.5, 1.0],
'reg_lambda': [0, 0.5, 1.0, 2.0]
}
grid_search = GridSearchCV(
xgb.XGBClassifier(verbosity=0),
param_grid, cv=5, scoring='accuracy', n_jobs=-1
)
grid_search.fit(X_train, y_train)
print(f"最优参数: {grid_search.best_params_}")
print(f"最优分数: {grid_search.best_score_:.4f}")
```
|
|
### GBDT(Gradient Boosting Decision Tree)
GBDT 是所有这些框架的基础算法。
**核心思想**:通过梯度下降的近似方法,以负梯度作为残差来训练 CART 回归树。
**工作流程**:
```
1. 初始化模型(通常为常数值)
2. 计算损失函数的负梯度(伪残差)
3. 用伪残差拟合一棵 CART 回归树
4. 通过线性搜索确定每棵树的权重
5. 更新模型 = 当前模型 + 学习率 × 新树
6. 重复步骤2~5,直到满足停止条件
```
**特点**:
- 自动进行特征组合和选择
- 较高的预测精度
- 对异常值不敏感(鲁棒性好)
- 广泛应用于竞赛和工业场景
|
|
### LightGBM vs XGBoost vs CatBoost
| 对比项 | LightGBM | XGBoost | CatBoost |
|--------|---------|---------|----------|
| **开发方** | 微软 | 陈天奇等 | Yandex |
| **发布年份** | 2017 | 2014 | 2017 |
| **树生长策略** | Leaf-wise | Level-wise | 对称树 |
| **分裂算法** | 直方图 | 直方图(v0.3+) | 对称二叉 |
| **类别特征** | 支持需指定 | 需手动编码 | **自动处理** |
| **缺失值** | 自动处理 | 自动学习方向 | 自动处理 |
| **训练速度** | **最快** | 中 | 较慢 |
| **内存占用** | **最低** | 中 | 较高 |
| **准确率** | 高 | 高 | 高(小数据集更优) |
| **过拟合风险** | 需控制 | 需控制 | **最低** |
| **GPU支持** | 好 | 好 | 好 |
| **适用场景** | 大数据集、速度优先 | 通用 | 类别特征多、小数据集 |
|
|
### 如何选择
#### 选择建议
| 场景 | 推荐算法 | 原因 |
|------|---------|------|
| 数据量大(>10万) | **LightGBM** | 速度最快,内存占用最低 |
| 通用场景,不确定选哪个 | **LightGBM** | 综合性能最好 |
| 类别特征多 | **CatBoost** | 自动处理类别特征,无需编码 |
| 数据量小(<1万) | **CatBoost** | Ordered Boosting 防过拟合 |
| 需要高精度竞赛 | **三个都试** | 不同数据集最优算法不同 |
| 需要 sklearn 兼容 | **三个都支持** | 都有 sklearn 风格 API |
#### sklearn 的 GBDT
```python
from sklearn.ensemble import GradientBoostingClassifier
model = GradientBoostingClassifier(
n_estimators=100,
max_depth=3,
learning_rate=0.1
)
model.fit(X_train, y_train)
```
sklearn 的 GBDT 实现功能完整但速度较慢,适合小数据集或学习用途。生产环境推荐使用 LightGBM/XGBoost/CatBoost。
#### 学习路径
```
GBDT(基础理论)
├── XGBoost(二阶优化 + 正则化)
├── LightGBM(直方图 + GOSS + EFB → 速度最快)
└── CatBoost(Ordered Boosting + 类别特征 → 最稳健)
```
|