突破最强算法模型,XGBoost!!
最近几天,对于XGBoost的讨论,也是久居不下,XGBoost的重要程度不言而喻。
现在工业界使用的场景特别多,那咱们针对其重要的方面进行一个详细的阐述~
简单来说,XGBoost 是一种非常非常强大的机器学习算法,全称叫做 Extreme Gradient Boosting,它是提升树(Boosting Trees)的一种实现。
通过实例理解XGBoost
下面我们用一个生活中的简单例子来帮助理解。
假设你是一个班级的班主任,你要根据学生的学习情况预测他们期末考试的成绩。你不想靠自己的直觉,所以你决定让一群老师帮你。
第一步:请一个老师来打分
你请来了第一位数学老师。数学老师根据学生们的平时表现打了一个分数(可能依据作业、上课表现等)。但这位老师的预测并不完美,有些学生的分数偏高,有些偏低。
第二步:再请一位老师来补充
你意识到第一位老师的打分有误差,于是你请第二位老师来专门纠正第一位老师的错误。比如,第一位老师给某个学生打了80分,但你觉得这个学生应该拿85分,那第二位老师就给这个学生增加5分,纠正这个误差。
第三步:继续请更多的老师
但是,第二位老师也不可能完美修正所有错误。于是你继续请第三位老师,专门纠正前面两位老师都没有修正好的部分。你可以不断地请新的老师,每个老师都根据之前所有老师的打分结果进行微调。
总的来说,每个新来的老师不会重新打分,而是根据之前的结果专注纠正误差。最终,所有老师的打分结果加在一起,就能得到一个越来越准确的成绩预测。
这就是Boosting的基本思路——逐步改进预测,每一步都在修正前面的错误。
那么 XGBoost 是如何做的呢?
- XGBoost 使用的是一种叫做“决策树”的模型。可以理解为,每个老师不是凭感觉打分,而是根据学生表现做出逻辑判断,比如“如果上课积极性高,成绩增加10分;如果作业完成度低,成绩减少5分”。
- 极端(Extreme):XGBoost 进行了很多优化,使得它比传统的提升树算法(比如经典的Gradient Boosting)运行得更快、性能更强、处理更复杂的数据。
- 加权修正:每次纠正时,XGBoost 会根据错误的大小来决定纠正的幅度。那些预测误差大的学生会得到更多的关注。
一个具体例子:
假设我们要预测小明的期末成绩:
- 第一棵树:根据小明的上课参与度、作业完成情况等,预测他能考 70分。
- 第二棵树:发现第一棵树的预测有误差,小明其实应该得 75分,于是第二棵树来修正这个错误,增加了 5分。
- 第三棵树:可能还有些小的误差,第三棵树会再进行微调,比如再给小明加 2分,最终得出小明的预测成绩是 77分。
最终,你会得到一个由多棵树组成的模型,每棵树都专注于改进前面树的错误,最后的结果是这几棵树的预测结果的累加。
下面,咱们从原理和案例,给大家进行一个细致的说明~
原理和案例
我们可以从其背后的 数学推理 开始,随后用一个具体案例展示如何从零实现 XGBoost,并且使用 Kaggle 数据集。
在实现时我们将不依赖已有的机器学习库,手动计算和实现所有过程,深入理解其背后原理。
XGBoost 的数学原理
XGBoost 的核心是基于提升树(Boosting Trees)的思想,而提升树算法的目的是通过一系列决策树的逐步改进来优化预测结果。
1. 基本思想:加法模型
2. 目标函数
3. 梯度提升(Gradient Boosting)
每一棵新树的目标是最小化之前模型的误差。为了达到这个目标,XGBoost 使用梯度下降的思想来优化树的结构和权重。
损失函数的二阶泰勒展开(利用一阶梯度和二阶导数)如下:
通过这个损失函数,我们可以逐步优化每棵树的结构。
4. 树的分裂
为了找到最佳的分裂点(即如何划分数据),XGBoost 通过计算每个分裂点的 增益 来选择最佳分裂。增益表示分裂后带来的误差减少量,增益公式为:
其中:
找到分裂点后,可以继续递归构建决策树,直到满足一定的停止条件。
实现 XGBoost 案例
数据集选择
我们将使用 Kaggle 上的经典 “House Prices” 数据集。这个数据集包含房屋的各种特征,目标是预测房价。
实现步骤
- 数据预处理:加载并清洗数据。
- 构建弱学习器(决策树):基于每次残差进行训练。
- 计算梯度和 Hessian:用来指导树的生长和分裂。
- 优化每棵树:通过增益公式选择最佳分裂点。
- 模型预测:累积所有树的预测结果。
- 绘制图形:可视化残差和模型预测效果。
代码实现
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 加载数据集
data = pd.read_csv('house_prices.csv') # 请确保你已经下载了Kaggle数据集
# 选择一些特征并处理缺失值
features = ['OverallQual', 'GrLivArea', 'GarageCars', 'GarageArea', 'TotalBsmtSF']
X = data[features].fillna(0).values
y = data['SalePrice'].values
# 定义损失函数的梯度和Hessian
def gradient(y_true, y_pred):
return y_pred - y_true
def hessian(y_true, y_pred):
return np.ones_like(y_true)
# 构建决策树类
class XGBoostTree:
def __init__(self, max_depth=3, lambda_reg=1):
self.max_depth = max_depth
self.lambda_reg = lambda_reg
self.tree = {}
def fit(self, X, g, h):
self.tree = self._build_tree(X, g, h, depth=0)
def _build_tree(self, X, g, h, depth):
if depth == self.max_depth or len(X) <= 1:
weight = -np.sum(g) / (np.sum(h) + self.lambda_reg)
return weight
best_gain = 0
best_split = None
for feature in range(X.shape[1]):
for threshold in np.unique(X[:, feature]):
left_idx = X[:, feature] <= threshold
right_idx = X[:, feature] > threshold
if len(X[left_idx]) == 0 or len(X[right_idx]) == 0:
continue
G_L, G_R = np.sum(g[left_idx]), np.sum(g[right_idx])
H_L, H_R = np.sum(h[left_idx]), np.sum(h[right_idx])
gain = 0.5 * ((G_L**2 / (H_L + self.lambda_reg)) +
(G_R**2 / (H_R + self.lambda_reg)) -
((G_L + G_R)**2 / (H_L + H_R + self.lambda_reg)))
if gain > best_gain:
best_gain = gain
best_split = (feature, threshold)
if best_split is None:
weight = -np.sum(g) / (np.sum(h) + self.lambda_reg)
return weight
feature, threshold = best_split
left_idx = X[:, feature] <= threshold
right_idx = X[:, feature] > threshold
return {
'feature': feature,
'threshold': threshold,
'left': self._build_tree(X[left_idx], g[left_idx], h[left_idx], depth + 1),
'right': self._build_tree(X[right_idx], g[right_idx], h[right_idx], depth + 1)
}
def predict(self, X):
return np.array([self._predict_one(row, self.tree) for row in X])
def _predict_one(self, x, tree):
if isinstance(tree, dict):
feature = tree['feature']
threshold = tree['threshold']
if x[feature] <= threshold:
return self._predict_one(x, tree['left'])
else:
return self._predict_one(x, tree['right'])
else:
return tree
# XGBoost模型类
class XGBoostModel:
def __init__(self, n_estimators=10, learning_rate=0.1, max_depth=3, lambda_reg=1):
self.n_estimators = n_estimators
self.learning_rate = learning_rate
self.max_depth = max_depth
self.lambda_reg = lambda_reg
self.trees = []
def fit(self, X, y):
y_pred = np.zeros_like(y, dtype=np.float64)
for i in range(self.n_estimators):
g = gradient(y, y_pred)
h = hessian(y, y_pred)
tree = XGBoostTree(max_depth=self.max_depth, lambda_reg=self.lambda_reg)
tree.fit(X, g, h)
update = tree.predict(X)
y_pred += self.learning_rate * update
self.trees.append(tree)
def predict(self, X):
y_pred = np.zeros(X.shape[0])
for tree in self.trees:
y_pred += self.learning_rate * tree.predict(X)
return y_pred
# 训练模型
model = XGBoostModel(n_estimators=50, learning_rate=0.1, max_depth=3, lambda_reg=1)
model.fit(X, y)
# 预测和可视化
y_pred = model.predict(X)
plt.figure(figsize=(10, 5))
# 可视化预测值与真实值的关系
plt.subplot(1, 2, 1)
plt.scatter(y, y_pred, color='blue', alpha=0.5)
plt.title('Predicted vs Actual Sale Prices')
plt.xlabel('Actual Sale Prices')
plt.ylabel('Predicted Sale Prices')
# 可视化残差
residuals = y - y_pred
plt.subplot(1, 2, 2)
plt.hist(residuals, bins=50, color='red', alpha=0.7)
plt.title('Residuals Distribution')
plt.xlabel('Residuals')
plt.ylabel('Frequency')
plt.show()
注意点:
- 梯度和 Hessian:我们定义了损失函数的梯度和 Hessian,作为树生长的依据。
- XGBoostTree 类:实现了单棵决策树的构建,通过增益公式选择最佳分裂点。
- XGBoostModel 类:逐步训练多个决策树,并累积其预测结果。
- 可视化:通过散点图显示预测值与真实值的对比,并通过直方图展示残差分布。
这个案例,我们手动实现了 XGBoost 模型的核心原理,并且基于 Kaggle 的房价预测数据集进行了模型训练和结果可视化。