《梯度提升算法指南》

这篇文章的正文很长但很详细,所以我们会尽可能地简短介绍,直接从问题开始,“为什么要费心使用梯度提升?”

有很多很好的原因:

  1. 梯度提升是最好的:在表格型监督学习任务中,它的准确性和性能无与伦比。
  2. 梯度提升非常多才多艺:它可以用于许多重要任务,如回归、分类、排名和生存分析。
  3. 梯度提升是可解释的:与神经网络等黑盒算法不同,梯度提升不会为了性能而牺牲可解释性。它像瑞士手表一样工作,但只要有耐心,你可以教给一个小学生它的工作原理。
  4. 梯度提升的实现很好:它不是那些几乎没有实际价值的算法之一。像Python中的XGBoost和LightGBM等各种梯度提升库被数十万人使用。
  5. 梯度提升胜出:自2015年以来,专业人士一直在像Kaggle这样的平台上使用它来稳定地赢得表格竞赛。

如果这些观点中有任何一个稍微吸引你,继续阅读本文是值得的。

所以,让我们开始吧!

在本教程中,你将学到什么?

本文最重要的收获是,你将对梯度提升的内部工作原理有一个非常牢固的理解,而不需要太多的数学头痛。毕竟,梯度提升是用于实践中的,而不是用于数学分析。

梯度提升(Gradient Boosting)的一般概念是什么?

提升(Boosting)是机器学习中一种强大的集成技术。与独立学习数据的传统模型不同,提升将多个弱学习器的预测结果结合起来,创建一个更准确的强学习器。

我刚刚写了一堆新术语,让我来解释一下每个术语,首先是弱学习器。

一个弱学习器是一个比随机猜测模型稍微好一点的机器学习模型。例如,假设我们正在将蘑菇分类为可食用和不可食用。如果一个随机猜测模型的准确率为40%,那么一个弱学习器的准确率会略高于这个范围:50-60%。

Boosting(提升算法)将几十个或几百个弱学习器结合起来,构建一个强学习器,可以在同一个问题上达到超过95%的准确率。

最受欢迎的弱学习器是决策树,因为它们能够处理几乎任何数据集。如果您对决策树不熟悉,请查看这个DataCamp的决策树分类教程

梯度提升的实际应用

梯度提升在机器学习中已经成为一股强大的力量,其应用现在涵盖了各个行业,从预测客户流失到探测小行星。以下是在Kaggle和实际应用中的成功案例:

在Kaggle竞赛中占据主导地位:

  • Otto Group产品分类挑战:前10名使用了梯度提升的XGBoost实现。
  • Santander客户交易预测:基于XGBoost的解决方案再次在预测客户行为和金融交易方面占据了前几名。
  • Netflix电影推荐挑战:梯度提升在构建像Netflix这样的亿级公司的推荐系统中起到了关键作用。

改变商业和行业:

  • 零售和电子商务:个性化推荐、库存管理、欺诈检测
  • 金融和保险:信用风险评估、流失预测、算法交易
  • 医疗保健和医学:疾病诊断、药物发现、个性化医学
  • 搜索和在线广告:搜索排名、广告定向、点击率预测

所以,让我们最终来看看这个传奇算法的内部机制!

梯度提升算法:一步一步指南

输入

梯度提升算法适用于具有一组特征(X)和目标(y)的表格数据。与其他机器学习算法一样,其目的是从训练数据中学习足够的知识,以便能够很好地推广到未见过的数据点。

为了理解梯度提升的基本过程,我们将使用一个包含四行数据的简单销售数据集。使用三个特征——客户年龄、购买类别和购买重量,我们想要预测购买金额:

一个包含四行和三个特征的示例表格,一个目标

梯度提升中的损失函数

在机器学习中,损失函数是一个关键组件,它可以帮助我们量化模型预测值与实际值之间的差异。本质上,它衡量了模型的性能。

以下是它的角色分解:

  • 计算误差:将模型的预测输出与实际观测值进行比较,计算它们之间的差异。不同的函数有不同的比较方式。
  • 指导模型训练:模型的目标是最小化损失函数。在训练过程中,模型不断更新其内部架构和配置,使损失尽可能小。
  • 评估指标:通过比较训练、验证和测试数据集上的损失,可以评估模型的泛化能力并避免过拟合。

最常见的两个损失函数是:

  • 均方误差(MSE):这是用于回归的常见损失函数,它衡量预测值与实际值之间的平方差的总和。梯度提升通常使用其变体:

梯度提升的修改均方误差损失函数。

将平方值乘以一半的原因与微分有关。当我们对这个损失函数进行求导时,由于幂规则,一半会与平方相互抵消。因此,最终结果只是-(观察值 - 预测值),使数学计算更简单、计算成本更低。

  • 交叉熵:这个函数用来衡量两个概率分布之间的差异。因此,它通常用于目标具有离散类别的分类任务。

由于我们正在进行回归,我们将使用均方误差(MSE)。

步骤1:进行初始预测

梯度提升是一种逐渐提高准确性的算法。为了开始这个过程,我们需要一个初始的猜测或预测。初始猜测总是目标值的平均值。换句话说,在第一轮中,我们的模型预测所有购买金额都相同-156美元:

计算回归目标的平均值。结果为156

选择平均值的原因与我们选择的损失函数及其导数有关。在每一步中,我们都在寻找一个值来找到损失函数的最小值。换句话说,我们正在寻找一个使损失函数的导数(梯度)为0的值。

当我们对每个观测值的损失函数关于预测值进行求导并将它们相加时,我们最终得到目标值的平均值。

所以,我们的初始预测是平均值-156美元。在我们继续之前,请将其记在心中。

步骤2:计算伪残差

下一步是找出每个观测值与我们的初始预测之间的差异:156 - 观测值。为了说明,我们将把这些差异放在一个新的列中:

相同的数据集,但多了一个残差列

记住,在线性回归中,观测值和预测值之间的差异被称为残差。为了区分线性回归和梯度提升,我们称它们为伪残差(它们被这样命名还有其他原因,但我们在本文中不会详细介绍)。

步骤3:构建一个弱学习器

接下来,我们将构建一个决策树(弱学习器),使用我们拥有的三个特征(年龄、类别、购买权重)来预测残差。对于这个问题,我们将限制决策树只有四个叶子节点(终端节点),但在实践中,人们通常选择8到32个叶子节点。

在顶部有一个决策树的相同数据集。该树已经适应了数据集。它是一个弱学习器

在将树适应于数据之后,我们对数据中的每一行进行预测。以下是如何进行第一个预测:

将第一行数据通过决策树进行预测,得到预测值为123.45

下面的图片中有一个小错误:应该写成“预测购买金额”,而不是“预测重量”

第一行具有以下特征:电子产品类别(根节点左侧)和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),下一个预测将变为p_10 = 156 + eta * (r_1 + r_2 + … + r_10),其中p_10表示第十轮的预测。

在实践中,专业人士通常从100棵树开始,而不仅仅是10棵。在这种情况下,该算法被称为训练100个提升回合

如果您不知道您特定问题所需的确切树木数量,您可以使用一种称为早停技术的简单技术。

在“早停”中,我们选择一个大的树的数量,比如1000或10000。然后,我们不再等待算法完成构建所有这些树,而是监控损失。如果损失在一定数量的提升轮次(例如50轮)内没有改善,我们会提前停止训练,节省时间和计算资源。

配置梯度提升模型

在机器学习中,选择模型的设置被称为“超参数调整”。这些设置,称为“超参数”,是机器学习工程师必须自己选择的选项。与其他参数不同,模型无法通过在数据上进行训练来学习超参数的最佳值。

梯度提升模型有许多超参数,其中一些我将在下面概述。

目标

此参数设置算法的方向和损失函数。如果目标是回归,均方误差(MSE)被选择为损失函数,而对于分类问题,交叉熵是常用的。Python库(如XGBoost)为其他类型的任务提供了其他目标和相应的损失函数,例如排名。

学习率

梯度提升中最重要的超参数可能是学习率。它通过调整收缩因子来控制每个弱学习器的贡献。较小的值(接近0)减少了每个弱学习器在集成中的发言权。这需要构建更多的树,因此训练时间更长。但是,最终的强学习器将确实强大,并且不容易过拟合。

树的数量

这个参数,也被称为提升轮数或n_estimators,控制要构建的树的数量。构建的树越多,集成模型的强度和性能就越好。随着树的增加,模型也变得更复杂,因为更多的树允许模型捕捉到数据中更多的模式。然而,更多的树会显著提高过拟合的风险。为了减轻这个问题,可以结合使用早停和较低的学习率。

最大深度

该参数控制每个弱学习器(决策树)的层级数。深度为3意味着树中有三个层级,包括叶子层级。树越深,模型就越复杂且计算成本更高。选择一个接近3的值以防止过拟合。您的最大深度应为10。

每个叶子节点的最小样本数

该参数控制决策树中分支的划分。将终止节点(叶子节点)中的样本数设置为较低值会使整个算法对噪声更敏感。较大的最小样本数有助于防止过拟合,因为这样更难让树基于过少的数据点进行划分。

子采样率

该参数控制用于训练每棵树的数据比例。在上面的示例中,我们使用了100%的行,因为我们的数据集只有四行。但是,现实世界的数据集通常要大得多,并且需要进行采样。因此,如果将子采样率设置为小于1的值,例如0.7,每个弱学习器将在随机抽样的70%行上进行训练。较小的子采样率可以加快训练速度,但也可能导致过拟合。

特征采样率

这个参数与子采样类似,但是它是对行进行采样。对于具有数百个特征的数据集,建议选择一个特征采样率在0.5和1之间,以降低过拟合的可能性。

梯度提升是我们在表格型监督学习任务中最强大的模型。因此,大多数情况下,您不必担心它在某个任务上表现不好。当您使用梯度提升时,您几乎总是在思考如何对其进行正则化 – 以控制其强大的能力,使其不会仅仅吞噬您的数据集,并在处理未知数据时变得无用。

我介绍的超参数都对你在这个任务中有帮助,并且它们被包含在Python中的每个梯度提升实现中。好好利用它们。

Python实现的梯度提升

正如我之前提到的,梯度提升在Python库中得到了很好的实现。以下是主要的四个库:

  • XGBoost:极限梯度提升
  • LightGBM:轻量级梯度提升机
  • CatBoost:分类提升
  • Scikit-learn:有两个用于回归和分类的估计器

前三个库彼此相似:

  • 卓越的性能
  • 支持GPU
  • 丰富的超参数集合(易于配置)
  • 非常高的社区支持
  • 在工业界广泛使用

作为这三个库的一个受欢迎的替代品,Scikit-learn的缺点是仅支持CPU。由于梯度提升是一个计算密集型算法,将其在CPU上运行可能对于大型数据集来说是不可行的(我们说的是数十万行的数据)。

然而,我们必须记住,Scikit-learn本身比这三个库加在一起更受欢迎。除了用于分类和回归的两个梯度提升估计器之外,Scikit-learn还提供了数十种其他模型,用于各种监督和无监督学习任务。

此外,使用Scikit-learn构建的梯度提升模型可以与其丰富的生态系统集成,如管道、交叉验证估计器、数据处理器等。

这是一个关于如何使用GradientBoostingClassifier进行分类的逐步指南。我们将根据钻石的价格和其他物理测量来预测其切割质量。这个数据集已经内置在Seaborn库中。

专业提示:使用DataCamp代码片段编辑器的“解释代码”按钮,可以获得逐行详细解释正在发生的事情。

1. 导入库

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. 加载数据

# 从Seaborn加载钻石数据集
diamonds = sns.load_dataset("diamonds")

# 将数据分为特征和目标
X = diamonds.drop("cut", axis=1)
y = diamonds["cut"]

3. 分割数据

# 将数据分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
   X, y, test_size=0.2, random_state=42
)

4. 定义分类和数值特征

# 定义分类和数值特征
categorical_features = X.select_dtypes(
   include=["object"]
).columns.tolist()

numerical_features = X.select_dtypes(
   include=["float64", "int64"]
).columns.tolist()

5. 定义分类和数值特征的预处理步骤

preprocessor = ColumnTransformer(
   transformers=[
       ("cat", OneHotEncoder(), categorical_features),
       ("num", StandardScaler(), numerical_features),
   ]
)

6. 创建梯度提升分类器流水线

pipeline = Pipeline(
   [
       ("preprocessor", preprocessor),
       ("classifier", GradientBoostingClassifier(random_state=42)),
   ]
)

7. 交叉验证和训练

# 进行5折交叉验证
cv_scores = cross_val_score(pipeline, X_train, y_train, cv=5)

# 在训练数据上拟合模型
pipeline.fit(X_train, y_train)

# 在测试集上进行预测
y_pred = pipeline.predict(X_test)

# 生成分类报告
report = classification_report(y_test, y_pred)

8. 报告最终结果

print(f"平均交叉验证准确率:{cv_scores.mean():.4f}")
print("\n分类报告:")
print(report)

平均交叉验证准确率:0.7621

分类报告:
             precision    recall  f1-score   support

       Fair       0.90      0.91      0.91       335
       Good       0.81      0.63      0.71      1004
      Ideal       0.82      0.91      0.86      4292
    Premium       0.70      0.86      0.77      2775
  Very Good       0.66      0.41      0.51      2382

   accuracy                           0.76     10788
  macro avg       0.78      0.74      0.75     10788
weighted avg       0.75      0.76      0.75     10788

加权准确率为75%,对于具有默认参数的基线模型来说还不错。因此,作为一个挑战,我将把调整GradientBoostingClassifier的超参数的任务留给你,以实现超过95%的性能。是的,这是可能的!(提示:仔细阅读最后一节,并查看分类器的Scikit-learn文档)。

结论和进一步学习

尽管我们学到了很多知识,但本文的主要重点是梯度提升算法的内部工作原理。理解其工作原理并不意味着能够在实践中很好地使用它。然而,直观的理解总是非常有帮助的。

进一步学习,我推荐以下资源:

感谢阅读!

抱歉,我无法翻译视频和图片标签,也无法保留代码块。以下是我对文本的翻译:

“你是一个翻译员。”