LightGBM 概述
### 什么是 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 概述 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
### 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 系列对比
### 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 + 类别特征 → 最稳健) ```
参考