import pandas as pd
import seaborn as sns
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import classification_report
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler技术支持
2. 加载数据
# Load the diamonds dataset from Seaborn
diamonds = sns.load_dataset("diamonds")# Split data into features and target
X = diamonds.drop("cut", axis=1)
y = diamonds["cut"]技术支持
3. 划分数据集
# Split data into train and test sets
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42)技术支持
4. 指定类别与数值特征
# Define categorical and numerical features
categorical_features = X.select_dtypes(
include=["object"]).columns.tolist()
numerical_features = X.select_dtypes(
include=["float64","int64"]).columns.tolist()技术支持
# Perform 5-fold cross-validation
cv_scores = cross_val_score(pipeline, X_train, y_train, cv=5)# Fit the model on the training data
pipeline.fit(X_train, y_train)# Predict on the test set
y_pred = pipeline.predict(X_test)# Generate classification report
report = classification_report(y_test, y_pred)技术支持
8. 输出最终结果
print(f"Mean Cross-Validation Accuracy: {cv_scores.mean():.4f}")print("\nClassification Report:")print(report)
Mean Cross-Validation Accuracy:0.7621
Classification Report:
precision recall f1-score support
Fair 0.900.910.91335
Good 0.810.630.711004
Ideal 0.820.910.864292
Premium 0.700.860.772775
Very Good 0.660.410.512382
accuracy 0.7610788
macro avg 0.780.740.7510788
weighted avg 0.750.760.7510788技术支持
本文主体内容较长但信息详尽,因此我们将尽量简短开场,直接从一个问题切入:为什么还要费心研究梯度提升?
理由有很多:
如果这些点哪怕只触动了您的一点兴趣,都值得继续读下去。
那就开始吧!
本教程您将学到什么?
本文最重要的目标,是让您在不被繁琐数学困扰的情况下,牢牢掌握梯度提升的内部工作机制。毕竟,梯度提升更偏重实践应用,而非纯数学推导。
梯度提升到底是什么?
Boosting(提升)是机器学习中一种强大的集成方法。与彼此独立学习的数据驱动模型不同,提升通过组合多个弱学习器的预测,得到一个更准确的强学习器。
上面有几个新术语,先从“弱学习器”讲起。
弱学习器指性能仅略优于随机猜测的模型。举个例子:我们要把蘑菇分成可食与不可食。若随机猜测的准确率是 40%,那么弱学习器大概在 50%-60% 左右。
提升会将几十、上百个这样的弱学习器组合起来,构建出在同一问题上精度可超过 95% 的强学习器。
最常见的弱学习器是决策树,因为它几乎能胜任任何数据集。如果您对决策树还不熟悉,可以参考这篇 DataCamp 的决策树分类教程。
梯度提升的真实世界应用
梯度提升在机器学习领域已占据主导地位,其应用覆盖众多行业,从预测客户流失到小行星探测。下面简要看看它在 Kaggle 与真实业务中的成功案例:
称霸 Kaggle 竞赛:
赋能商业与产业:
现在,让我们一窥这个“传奇算法”的内部吧!
梯度提升算法:分步讲解
输入
梯度提升适用于表格数据,包含一组特征(X)和一个目标(y)。与其他机器学习算法一样,目标是在训练数据上学到足够的模式,从而对未见样本具有良好的泛化能力。
为理解其内部流程,我们用一个只有四行记录的简单销售数据集。基于三项特征——顾客年龄、购买品类、购买重量,我们要预测购买金额:
梯度提升中的损失函数
在机器学习中,损失函数是用来量化模型预测与真实值差异的关键组件。简言之,它衡量模型表现如何。
其作用可以分解为:
最常用的两类损失函数:
平方项前的二分之一与求导有关。对该损失函数求导时,因幂次法则,二分之一会与平方相消。最终结果就是
-(Observed- Predicted),从而简化了计算,降低了计算开销。由于我们在做回归,所以采用 MSE。
步骤 1:做一个初始预测
梯度提升会逐步提升精度。要启动这个过程,我们先需要一个初始猜测或预测。初始猜测总是目标值的平均数。换言之,第一轮我们的模型认为所有购买金额都一样——156 美元:
选择均值与损失函数及其导数有关。每一步我们都在寻找使损失函数达到最小的值,也就是让损失函数的导数(梯度)为 0 的值。
当我们对每个观测值对应的损失函数关于预测值求导并求和后,结果正好是目标的平均数。
因此,我们的初始预测就是平均数——156 美元。记住它,后面还会用到。
步骤 2:计算伪残差
接下来,计算每个观测值与初始预测之间的差:
156 - Observed。为了演示,我们将这些差放在新的一列里:还记得在线性回归中,观测值与预测值之差称为残差。为与线性回归区分,这里称为伪残差(其命名还有其他原因,本文不展开)。
步骤 3:构建一个弱学习器
现在,用三项特征(年龄、品类、购买重量)训练一棵预测残差的决策树(弱学习器)。本文将树的叶子(终端节点)数量限定为 4;实际应用中通常在 8 到 32 之间。
树拟合完成后,我们对数据中的每一行进行预测。下面以第一行为例:
下面配图里有个小错误:应当写“预测的购买金额”,而不是“预测的重量”
第一行样本的特征是:品类为电子产品(根节点左侧),顾客年龄小于 30(子节点左侧)。因此落到叶子上的值为 -32.55。做最终预测时,我们将 -32.55 加到初始预测上,结果恰好与观测值相同——123.45 美元!
既然已经完美预测,为什么还要继续建树?因为此时我们对训练数据严重过拟合。我们希望模型具备泛化能力。为缓解这个问题,梯度提升引入了名为学习率的参数。
学习率是 0 到 1 之间的一个倍乘因子,用来缩放每个弱学习器的预测(关于学习率的细节见下文)。若我们取一个任意的学习率 0.1,那么预测就会变成 152.75,而不是完美的 123.45。
再来预测第二行:
将样本过一遍树后,得到 146.08 的预测。以此类推,我们为四行样本得到四个预测:152.75、146.08、174.945、150.2。先把它们加成新的一列:
接下来,用购买金额减去新预测,得到新的伪残差。将其加入表格,并删除最后两列:
如您所见,新的伪残差更小了,说明损失在下降。
步骤 4:迭代
接下来重复步骤 3,也就是继续构建更多弱学习器。需要记住的一点是:要不断把每棵树的残差按顺序叠加到初始预测上,生成下一次的预测。
例如,我们训练 10 棵树,并将每棵树的残差记为 r_i(1 <= i <= 10),那么第 10 轮的预测为
p_10 = 156 + eta * (r_1 + r_2 + ... + r_10),其中p_10表示第十轮的预测。实际中,工程师常常从 100 棵树起步,而不止 10 棵。这时我们称算法训练了 100 轮提升轮次。
如果您不清楚具体需要多少棵树,可以使用早停(early stopping)。
早停的做法是:先把树的数量设得很大,比如 1000 或 10000。然后不必等所有树都训练完,实时监控损失;若损失在若干个提升轮次(如 50 轮)内不再改进,就提前停止训练,节省时间与算力。
配置梯度提升模型
在机器学习中,为模型选择设置的过程称为“超参数调优”。这些设置即“超参数”,需要工程师自行选择。与模型参数不同,超参数无法通过训练自动学得最优值。
梯度提升模型有不少超参数,下面列出其中一些。
Objective(目标)
该参数决定算法的任务方向与损失函数。若任务是回归,则损失函数选 MSE;若是分类,则使用交叉熵。Python 库(如 XGBoost)还提供了其他任务类型(如排序)对应的目标与损失函数。
学习率
学习率也许是梯度提升中最重要的超参数。它通过调节收缩因子来控制每个弱学习器的贡献。更小的取值(趋近 0)意味着每个弱学习器在集成中的“话语权”更低,需要构建更多的树、花更长的训练时间。但最终得到的强学习器更强大,也更不易过拟合。
树的数量
也称提升轮次或
n_es timators,控制要构建多少棵树。树越多,集成越强、性能越好,同时复杂度也更高,因更多的树能捕捉更多数据模式。然而,树更多也显著增加过拟合风险。为缓解此问题,结合早停与较低学习率使用。最大深度
控制每棵弱学习器(决策树)的层数。最大深度为 3 表示有三层(包含叶子层)。树越深,模型越复杂、计算开销越大。为防止过拟合,建议取值靠近 3;最大不应超过 10。
叶节点最小样本数
控制决策树分裂的约束。若叶节点的最小样本数设得太小,算法会对噪声更敏感。较大的最小样本数能抑制过拟合,避免基于过少样本做出分裂。
子样本率(行采样)
控制训练每棵树时使用的数据比例。上面的示例中只有四行,我们用了 100% 的样本。真实数据往往更大,通常需要采样。若将子样本率设为小于 1 的值,如 0.7,则每个弱学习器在随机抽取的 70% 行上训练。更小的子样本率可能加快训练,但也可能导致过拟合。
特征采样率(列采样)
此参数与子样本率类似,但作用于列(特征)而非行。对于拥有数百特征的数据集,建议在 0.5 到 1 之间选择特征采样率,以降低过拟合风险。
对于表格型监督学习任务,梯度提升几乎是我们最强的模型选择。多数情况下,您无需担心它“不够好”;反而常常需要花时间思考如何对其正则化——也就是如何收敛其强大能力,避免“吞掉”数据而在未见样本上失效。
上面介绍的这些超参数都能帮助您做到这一点,并且在 Python 的各类梯度提升实现中均已提供。请善加利用。
Python 中的梯度提升实现
前文提到,Python 生态中的梯度提升实现非常成熟。主要有四个:
前三个库的共同点:
一个常用的替代方案是 Scikit-learn,但其劣势是仅支持 CPU。由于梯度提升计算量较大,对于几十万行的大数据集,仅用 CPU 可能不可行。
不过也要看到,Scikit-learn 本身的人气超过前三者之和。除两类用于分类与回归的梯度提升估计器外,Scikit-learn 还为各种监督/无监督任务提供了海量模型。
此外,使用 Scikit-learn 构建的梯度提升模型还能无缝融入其丰富生态,如管道、交叉验证估计器、数据预处理器等。
下面是使用 GradientBoostingClassifier 进行分类的分步指南。我们将基于价格与其他物理测量指标来预测钻石的切工质量。该数据集内置于 Seaborn 库。
专业提示:使用 DataCamp 代码片段编辑器的“Explain code”按钮,可获得逐行的详细解释。
1. 导入库
2. 加载数据
3. 划分数据集
4. 指定类别与数值特征
5. 为类别与数值特征定义预处理步骤
6. 创建梯度提升分类器管道
7. 交叉验证与训练
8. 输出最终结果
加权准确率为 75%,对于使用默认参数的基线模型来说并不差。作为挑战,留给您去调优 GradientBoostingClassifier 的超参数,把性能提升到 95% 以上。没错,做得到!(提示:仔细阅读最后一节,并参考Scikit-learn 分类器文档)。
结语与进阶学习
尽管我们已经学到了很多,但本文的重点仍在于梯度提升算法的内部机制。理解其工作原理并不等于能在实践中用得好,不过直观理解始终是巨大的助力。
若要继续深入,推荐如下资源:
感谢您的阅读!