大家好~
今天和大家再来聊聊GBDT,好的模型总是需要反复的思考。
确实在这些提升树方面的模型,大家总是有问不完的问题、讨论不晚的知识点。
GBDT,全名是梯度提升决策树(Gradient Boosting Decision Tree),是一种常用的机器学习算法。我们可以把它分成两部分来理解:决策树和梯度提升。
1. 决策树(Decision Tree)——“做选择”
你可以把决策树想象成一种根据问题做选择的“问答游戏”。举个例子:
假设你想判断今天要不要带伞,我们可以用一个决策树来帮忙:
第一个问题:今天会下雨吗?
如果是:带伞。 如果不是:第二个问题,今天多云吗? 如果是:带伞。 如果不是:不带伞。
这个过程就像是根据一个一个条件做出判断,最后得到一个明确的结论。这就是决策树的基本思想:每一步根据某个特征的值进行判断,直到得到最终结果。
2. 梯度提升(Boosting)——“不断改进”
梯度提升的意思是,通过多个弱模型(比如多个小决策树)的叠加,不断改进预测的准确性。也就是说,我们不是一次就建一个完美的决策树,而是先建一个不太完美的决策树,然后逐步改进。
举个小例子:
假设我们想预测房子的价格,最开始我们建了一个简单的决策树模型,它的预测结果可能不够准确,有些房子价格偏高,有些偏低。接下来,我们就根据这个初始模型犯的“错误”来建第二棵树,让第二棵树专门修正第一棵树的错误。这样,不断地修正,最终的模型会越来越准确。
这个“不断修正”的过程,就是梯度提升的思想。每一步都在改进之前的预测结果,直到整个模型变得非常准确。
3. 那么 GBDT 是怎么工作的?
GBDT的工作流程可以简单理解为:
第一步:先建一个简单的决策树模型,用它做初步预测,比如预测房价。 第二步:计算这个模型预测的误差,看看哪些房子预测错了(比如价格太高或太低)。 第三步:基于这些误差,建第二棵决策树,让它专门修正这些错误。 重复上述步骤:不断重复这个过程,每次都用新的决策树来修正上一个模型的错误。 最终结果:所有这些决策树的预测结果加起来,就是最终的预测结果。
4. 举例子说明
假设我们在预测某个城市的房价,初始模型预测:
房子A的实际价格是300万,模型预测300万,没错! 房子B的实际价格是500万,但模型只预测了450万,偏低了50万。 房子C的实际价格是700万,但模型预测了800万,偏高了100万。
于是,我们根据这些预测误差(50万和100万),让第二棵决策树专门来修正它们。第二棵树可能会说:“房子B要再加50万,房子C要减100万。” 这样,总的预测结果会更加接近真实价格。
最终,GBDT 通过一次次的改进,逐渐提升模型的预测准确性。
总结来看,GBDT 是一种通过多个小决策树合作工作、不断改进预测结果的机器学习算法。它非常强大,特别是在处理一些复杂问题时,比如预测房价、用户行为等等。
在上面的简单解释的基础上,我们从原理和案例和大家好好聊聊,现在可以很好的接受了~
基础原理
GBDT 公式推导
1. 目标函数
GBDT 的目标是通过多个决策树模型来最小化预测误差,假设我们有一个训练数据集 ,目标是构建一个函数 来预测 。
每一步 的目标是使得损失函数 最小化:
其中, 是损失函数,通常选择均方误差(MSE):
2. 梯度提升
GBDT 的思想是用一个初始模型 开始,然后逐步修正预测误差。每一步我们需要拟合一个新模型来修正之前模型的残差。为了实现这一点,我们使用梯度下降法。
在第 步时,我们想找到一个新的模型 来修正前一轮的预测误差。于是,我们有:
其中, 是一个学习率系数, 是第 棵新树,它拟合的是损失函数对模型的梯度,即:
通过最小化这个梯度,我们可以不断更新模型,最终得到一个高精度的预测函数。
案例实现
接下来我将基于上述推导,在 Python 中手动实现 GBDT~
1. 数据集准备
我们构造一个虚拟的回归数据集,包含两个特征和一个目标变量,假设数据符合某个非线性关系。
import numpy as np
import matplotlib.pyplot as plt
# 生成虚拟数据集
np.random.seed(42)
n_samples = 1000
X = np.random.uniform(0, 1, (n_samples, 2))
y = 4 * X[:, 0] + 2 * np.sin(5 * X[:, 1]) + np.random.normal(0, 0.1, n_samples)
# 将数据可视化
plt.figure(figsize=(8, 6))
plt.scatter(X[:, 0], y, color='red', label='Feature 1 vs Target', alpha=0.7)
plt.scatter(X[:, 1], y, color='blue', label='Feature 2 vs Target', alpha=0.7)
plt.title("Feature vs Target Plot")
plt.xlabel("Feature Value")
plt.ylabel("Target Value")
plt.legend()
plt.show()
2. 手动实现 GBDT 算法
以下是实现一个简化的 GBDT 模型代码,该代码将基于决策树残差拟合多次迭代:
from sklearn.tree import DecisionTreeRegressor
class GradientBoostingRegressor:
def __init__(self, n_estimators=100, learning_rate=0.1, max_depth=3):
self.n_estimators = n_estimators
self.learning_rate = learning_rate
self.max_depth = max_depth
self.models = []
self.gammas = []
def fit(self, X, y):
# 初始化模型 F_0(x)
F_0 = np.mean(y)
self.models.append(F_0)
F_t = np.full_like(y, F_0)
for i in range(self.n_estimators):
# 计算残差 r_t = y - F_t(x)
residual = y - F_t
# 拟合残差的回归树 h_t(x)
tree = DecisionTreeRegressor(max_depth=self.max_depth)
tree.fit(X, residual)
# 预测新树的输出
h_t = tree.predict(X)
# 更新模型 F_{t+1}(x) = F_t(x) + γ * h_t(x)
gamma = self.learning_rate
F_t += gamma * h_t
# 保存模型
self.models.append(tree)
self.gammas.append(gamma)
def predict(self, X):
# 使用所有的树进行预测
F_t = np.full(X.shape[0], self.models[0])
for tree, gamma in zip(self.models[1:], self.gammas):
F_t += gamma * tree.predict(X)
return F_t
3. 训练模型并可视化
我们使用上面构建的虚拟数据集来训练这个 GBDT 模型,并可视化结果。
# 训练 GBDT 模型
gbdt = GradientBoostingRegressor(n_estimators=10, learning_rate=0.1, max_depth=3)
gbdt.fit(X, y)
# 预测结果
y_pred = gbdt.predict(X)
# 可视化拟合效果
plt.figure(figsize=(8, 6))
plt.scatter(X[:, 0], y, color='red', label='Actual', alpha=0.7)
plt.scatter(X[:, 0], y_pred, color='green', label='Predicted', alpha=0.7)
plt.title("Actual vs Predicted Values (Feature 1)")
plt.xlabel("Feature 1")
plt.ylabel("Target Value")
plt.legend()
plt.show()
plt.figure(figsize=(8, 6))
plt.scatter(X[:, 1], y, color='blue', label='Actual', alpha=0.7)
plt.scatter(X[:, 1], y_pred, color='orange', label='Predicted', alpha=0.7)
plt.title("Actual vs Predicted Values (Feature 2)")
plt.xlabel("Feature 2")
plt.ylabel("Target Value")
plt.legend()
plt.show()
# 可视化残差
residuals = y - y_pred
plt.figure(figsize=(8, 6))
plt.scatter(y_pred, residuals, color='purple', alpha=0.7)
plt.axhline(y=0, color='black', linestyle='--')
plt.title("Residuals vs Predicted")
plt.xlabel("Predicted Values")
plt.ylabel("Residuals")
plt.show()
# 可视化损失变化
train_loss = []
F_0 = np.mean(y)
F_t = np.full_like(y, F_0)
for i, (tree, gamma) in enumerate(zip(gbdt.models[1:], gbdt.gammas)):
F_t += gamma * tree.predict(X)
loss = np.mean((y - F_t) ** 2)
train_loss.append(loss)
plt.figure(figsize=(8, 6))
plt.plot(range(len(train_loss)), train_loss, marker='o', color='blue')
plt.title("Training Loss Over Iterations")
plt.xlabel("Iteration")
plt.ylabel("MSE Loss")
plt.show()
Feature vs Target Plot:展示了两个特征与目标值的关系,可以看到特征与目标值之间的某种非线性关系。
Actual vs Predicted (Feature 1) 和 Actual vs Predicted (Feature 2):这些图展示了模型预测结果与实际值的对比,观察拟合的效果。
Residuals vs Predicted:残差图展示了预测值与残差之间的关系,理想情况下残差应随机分布,没有明显的模式。
Training Loss Over Iterations:显示了随着模型迭代次数的增加,训练损失如何下降,表明模型的收敛情况。