GBDT 是一种强大的集成学习方法,广泛用于分类和回归问题。它属于提升(Boosting)算法的一种,通过多个弱学习器(通常是决策树)结合起来提高模型的准确性。
1. 二分类
对于 GBDT 应用到二分类的场景,其预测过程如下公式所示:
其中:
- \( \hat{y}(x) \) 表示模型对输入 \( x \) 的最终预测值
- \( \hat{y}_{0}(x) \) 是初始值,可以理解为初步的预测分数
- \( f_{m}(x) \) 是第 \( m \) 棵树的输出分数
- \( \eta \) 表示学习率,控制每棵树的贡献
- \( M \) 表示 GBDT 中回归决策树的数量
对于新的样本,首先为其计算属于 1 类别的一个初始分数(分数越大,属于 1 类别的可能性就越大)。初始的分数通常是训练集标签为 1 的样本比例。例如:假设训练集中 1 类别样本占比 30%,则新样本的初始概率为 0.3,将概率值转换为分数:
为什么需要这个公式?这是因为 0.3 是概率,而 GBDT 中每一个决策树输出的是分数(logit),通过上面公式就可以将概率转换为分数,反之,将上面分数送入到 Sigmoid 函数可以得到概率 0.3。
接下来,将待预测的样本送入到每一个子决策树中,得到一系列的分数,将这些分数累加起来,别忘了加上初始的分数,这样就得到了该样本属于 1 类别的预测分数。最后,将分数送入到 Sigmoid 函数得到该样本属于 1 类别的概率:
1.1 计算案例
假设:数据集中有 20 条数据, 70% 为 0 标签,30% 为 1 标签,接下来使用 GBDT 预测新样本:[0.49671415 -0.1382643 0.64768854]
的类别标签。
首先,计算该样本的初始分数:logit(0.3) = -0.8472978603872036
假设:经过训练我们得到 GBDT 中多个决策树分别如下,这里需要注意两点:
- 每棵决策树输出结果为分数,而不是概率。
- 另外下面的分类器是基于 log loss 损失函数,对于 GBDT 其他常用的损失函数是指数损失函数,这个整体的思想是一样的,只是在进行具体计算的时候有所不同。
接下来,我们将新样本输入到每个决策树,分别得到的每个决策树的输出分数:
- 第一棵树:3.33
- 第二棵树:2.67
- 第三棵树:2.28
假设:学习率为 0.1,将所有的分数加起来:-0.85 + (3.33 + 2.67 + 2.28) * 0.1 = -0.02
。这里补充一点:GBDT 中的学习率表示训练得到的每棵决策树对最终结果的贡献程度。
最后,使用 sigmoid 函数计算得到该样本属于 1 类别的概率:
即:sigmoid(-0.02) = 0.495
,则新样本属于 1 类别的概率为:0.495,属于 0 类别的概率为 0.505。由于 0 类别概率较高,最终新样本归类到 0 类别。
1.2 代码演示
from sklearn.ensemble import GradientBoostingClassifier from sklearn.datasets import make_classification import numpy as np import matplotlib.pyplot as plt from sklearn.tree import plot_tree # 生成随机数据 x, y = make_classification(n_samples=20, n_features=3, n_redundant=0, n_repeated=0, weights=[0.7, 0.3], random_state=42) # 训练分类模型 gbdt = GradientBoostingClassifier(n_estimators=3, criterion='squared_error', loss='log_loss') gbdt.fit(x, y) # 可视化 GBDT for estimator in gbdt.estimators_: plt.figure(figsize=(10, 10)) plot_tree(estimator[0], filled=True, rounded=True, fontsize=14, precision=2) plt.show() # 新数据预测 np.random.seed(42) new_x = np.random.randn(1, 3) print('输入的新样本数据:', new_x) # 直接计算类别概率 print('直接计算类别概率:', gbdt.predict_proba(new_x)[0]) # 分布计算类别概率 def test(): # 1. 初始得分 # 将初始概率转换为分数,该分数通过 sigmoid 函数可以得到其原始概率 total_score = np.log(0.3 / (1 - 0.3)) # 2. 累加每个树的得分 learning_rate = 0.1 for estimator in gbdt.estimators_: # 每棵树计算得分 score = estimator[0].predict(new_x) # 累加每棵树得分 total_score += score[0] * learning_rate # 3. 将得分输出概率 sigmoid = lambda x : 1 / (1 + np.exp(-x)) proba = sigmoid(total_score) print('分步计算类别概率:', np.array([1 - proba, proba])) if __name__ == '__main__': test()
程序输出结果:
输入的新样本数据: [[ 0.49671415 -0.1382643 0.64768854]] 直接计算类别概率: [0.50469609 0.49530391] 分步计算类别概率: [0.50469609 0.49530391]
2. 多分类
GBDT 在应用到多分类问题场景时,通常会使用 OVR 的方式。例如:数据有 3 个类别,那么会训练得到 3 个二分类的 GBDT 分类器,分别是:0 类别和其他、1 类别和其他、2 类别和其他。
当对新的样本进行预测时,分别送入到不同的 GBDT 二分类器中,得到属于每个类别的分数,最后使用 Softmax 得到该样本属于不同类别的概率。
这里需要注意的是,对于每个二分类的 GBDT 也是需要一个初始分数,这里根据每个类别数据的占比,使用 np.log 函数得到初始分数。这里没有使用前面二分类场景的 logit 函数,而是直接计算对数值的原因是,多分类使用 Softmax 激活函数,而二分类使用的是 Sigmoid 激活函数。
2.1 计算案例
假设:数据集中有 20 条数据, 50% 为 0 标签,30% 为 1 标签,20% 为 2 标签。接下来使用 GBDT 预测新样本:[0.49671415 -0.1382643 0.64768854]
的类别标签。
对于新样本它属于不同类别的分数的计算如下:
- 初始第 0 类别的分数:np.log(0.5) = -0.6931471805599453
- 初始第 1 类别的分数:np.log(0.3) = -1.2039728043259361
- 初始第 2 类别的分数:np.log(0.2) = -1.6094379124341003
由于是多分类,GBDT 得到的模型结构(3 个二分类 GBDT 分类器)如下:
基于 0 类别和其他类别数据训练的 GBDT,用于预测样本属于 0 类别的分数。
基于 1 类别和其他类别数据训练的 GBDT,用于预测样本属于 1 类别的分数。
基于 2 类别和其他类别数据训练的 GBDT,用于预测样本属于 2 类别的分数。
将待预测的样本分别输入到不同类别的 GBDT 中得到分数(学习率 0.1):
- 输入到 0 类别 GBDT 得到的分数总和:(1.33 + 1.21 + 1.11) * 0.1 = 0.365
- 输入到 1 类别 GBDT 得到的分数总和:(-0.73 – 0.69 – 0.66) * 0.1 = -0.208
- 输入到 2 类别 GBDT 得到的分数总和:(-0.83 – 0.81 – 0.79) * 0.1 = -0.243
再分别加上属于不同类别的初始分数:
- 属于 0 类别分数:-0.6931471805599453 + 0.365 = -0.3281471805599453
- 属于 1 类别分数:-1.2039728043259361 – 0.208 = -1.411972804325936
- 属于 2 类别分数:-1.6094379124341003 – 0.243 = -1.8524379124341004
最后使用 Softmax 得到输出不同类别的概率:
- 属于 0 类别的概率:0.64
- 属于 1 类别的概率:0.22
- 属于 2 类别的概率:0.14
最后将输入样本归类到 0 类别。
2.2 代码演示
from sklearn.ensemble import GradientBoostingClassifier from sklearn.datasets import make_classification import numpy as np import matplotlib.pyplot as plt from sklearn.tree import plot_tree # 生成随机数据 x, y = make_classification(n_samples=20, n_classes=3, n_features=3, n_redundant=0, n_informative=3, n_repeated=0, weights=[0.5, 0.3, 0.2], random_state=42) # 训练分类模型 gbdt = GradientBoostingClassifier(n_estimators=3, criterion='squared_error', loss='log_loss') gbdt.fit(x, y) # 可视化 GBDT for estimators in gbdt.estimators_: plt.figure(figsize=(50, 10)) for idx, estimator in enumerate(estimators): plt.subplot(1, 3, idx + 1) plot_tree(estimator, filled=True, rounded=True, fontsize=12, precision=2) plt.show() # 新数据预测 np.random.seed(42) new_x = np.random.randn(1, 3) print('输入的新样本数据:', new_x) # 直接计算类别概率 print('直接计算类别概率:', gbdt.predict_proba(new_x)[0]) # 分布计算类别概率 def test(): # 1. 初始得分 total_score = [np.log(0.5), np.log(0.3), np.log(0.2)] # 2. 累加每个树的得分 learning_rate = 0.1 for estimator in gbdt.estimators_: # 计算每一个类别的得分 for idx, est in enumerate(estimator): # 每棵树计算得分 score = estimator[idx].predict(new_x) # 累加每棵树得分 total_score[idx] += score * learning_rate # 3. 将得分输出概率 def sofmax(x): numerator = np.exp(x).reshape(-1) denominator = sum(numerator) return numerator / denominator print('分步计算类别概率:', sofmax(total_score)) if __name__ == '__main__': test()
程序输出结果:
输入的新样本数据: [[ 0.49671415 -0.1382643 0.64768854]] 直接计算类别概率: [0.64261892 0.21755397 0.13982711] 分步计算类别概率: [0.64261892 0.21755397 0.13982711]