点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达

自从陈天奇于2015年提出XGBoost以来,该模型就一直在各大数据竞赛中当作大杀器被频繁祭出。速度快、效果好是XGBoost的最大优点。XGBoost与GBDT同出一脉,都属于boosting集成学习算法,但XGBoost相较于GBDT要青出于蓝而胜于蓝。 XGBoost的全程为eXtreme Gradient Boosting,即极端梯度提升树。要深入理解整个XGBoost模型系统,建议还是要认真研读陈天奇的 XGBoost: A Scalable Tree Boosting System 论文,深入损失函数的推导,这样才能更好的掌握XGBoost。本文仅对模型最重要的部分,即XGBoost损失函数的数学推导过程和结点分裂的增益计算方式进行阐述。 既然XGBoost整体上仍然属于GBDT系统,那么XGBoost也一定是由多个基模型组成的一个加法模型,所以XGBoost可表示为:
其中, 为损失函数的正则化项,表示全部 棵树的复杂度之和,旨在防止模型过拟合。 XGBoost来自于GBDT,同样也适用前向分步算法,以第 步的模型为例,模型对第 个样本 的预测值为: 其中, 是由第 步的模型给出的预测值,作为一个已知常数存在, 是第 步树模型的预测值。所以,目标函数可以改写为: 同时也可以对正则化项进行拆分,由于前 棵树的结构已经确定,因此前 棵树的复杂度之和也可以表示为常数:
然后我们使用二阶泰勒公式(这里需要大家回顾微积分相关知识),可以将损失函数改写为: 其中, 为损失函数的一阶导, 为损失函数的二阶导,需要注意的是这里是对 求导。XGBoost相较于GBDT而言用到了二阶导数信息,所以如果要自定义损失函数,首要的要求是其二阶可导。 将该二阶泰勒展开式带入前述推导的XGBoost损失函数中,可得损失函数的近似表达式: 所以只需要求出每一步损失函数的一阶导和二阶导的值,然后最优化目标函数,就可以得到每一步的 ,最后根据加法模型得到一个boosting模型。 然后重新定义一棵决策树,其包括两个部分:叶子结点的权重向量 和实例(样本)到叶子结点的映射关系 (本质是树的分支结构); 再来看定义模型复杂度的正则化项。模型复杂度 可由单棵树的叶子结点数 和叶子权重 ,具体来说损失函数的复杂度由所有树的叶子结点数和叶子权重所决定。数学表达如下式所示: 然后我们对所有叶子结点进行重新归组。将属于第 个叶子结点的所有样本 划入到一个叶子结点的样本集合中,即: ,从而XGBoost的目标函数可以改写为:- :叶子结点 所包含样本的一阶偏导数累加之和,是一个常量;
- :叶子结点 所包含样本的二阶偏导数累加之和,是一个常量;
将 和 带入前述XGBoost损失函数,可得最终的损失函数表达式为: 由前述推导可知, 和 相对于第 棵树来说是可以计算出来的。所以该式就是一个只包含一个变量叶子结点权重 的一元二次函数,可根据最值公式求其最值点。当相互独立的每棵树的叶子结点都达到最优值时,整个损失函数也相应的达到最优。 当树结构固定的情况下,对上式求导并令其为0,可得最优点和最优值为: 以上就是XGBoost完整的损失函数推导过程。基本框架仍然是GBDT框架,但XGBoost的最大特色是用到了损失函数的二阶导数信息,目的就在于能够更加准确的逼近真实损失。
下图是XGBoost论文中给出的叶子结点权重计算:
根据二阶导信息把XGBoost的优化推到了极为逼近真实损失的状态,其结点分裂方式就跟CART树的结点分裂方式本质上并没有太大区别,但信息增益的计算方式有所不同。 假设模型在某一节点完成特征分裂,分裂前的目标函数可以写为: 如果增益Gain>0,即分裂为两个叶子节点后,目标函数下降了,则考虑此次分裂的结果。实际处理时需要遍历所有特征寻找最佳分裂特征。 以上就是XGBoost模型核心数学推导部分。完整的XGBoost模型还有很多工程上的细节,这里不做过多阐述,各位读者最好把XGBoost论文认真研读一遍。完整的XGBoost推导示意图如下所示。
有了GBDT的算法实现经验,XGBoost实现起来就并没有太多困难了,大多数底层代码都较为类似,主要是在信息增益计算、叶子得分计算和损失函数的二阶导信息上做一些变动。同样先列出代码框架:

底层的决策树结点和树模型定义基本差别不大,具体这里不再列出,可参考第15讲GBDT实现方式。主要是继承底层的树结点和树来定义XGBoost单棵树和XGBoost树模型拟合方式。
class XGBoostTree(Tree):
def _split
(self, y):
col = int(np.shape(y)[1]/2)
y, y_pred = y[:, :col], y[:, col:]
return y, y_pred
def _gain(self, y, y_pred):
Gradient = np.power((y * self.loss.gradient(y, y_pred)).sum(), 2)
Hessian = self.loss.hess(y, y_pred).sum()
return 0.5 * (Gradient / Hessian)
def _gain_by_taylor(self, y, y1, y2):
y, y_pred = self._split(y)
y1, y1_pred = self._split(y1)
y2, y2_pred = self._split(y2)
true_gain = self._gain(y1, y1_pred)
false_gain = self._gain(y2, y2_pred)
gain = self._gain(y, y_pred)
return true_gain + false_gain - gain
def _approximate_update(self, y):
y, y_pred = self._split(y)
gradient = np.sum(y * self.loss.gradient(y, y_pred), axis=0)
hessian = np.sum(self.loss.hess(y, y_pred), axis=0)
update_approximation = gradient / hessian
return update_approximation
def fit(self, X, y):
self._impurity_calculation = self._gain_by_taylor
self._leaf_value_calculation = self._approximate_update
super(XGBoostTree, self).fit(X, y)
class XGBoost(object):
def __init__(self, n_estimators=200, learning_rate=0.001, min_samples_split=2,
min_impurity=1e-7, max_depth=2):
self.n_estimators = n_estimators
self.learning_rate = learning_rate
self.min_samples_split = min_samples_split
self.min_impurity = min_impurity
self.max_depth = max_depth
self.loss = LogisticLoss()
self.estimators = []
for _ in range(n_estimators):
tree = XGBoostTree(
min_samples_split=self
.min_samples_split,
min_impurity=min_impurity,
max_depth=self.max_depth,
loss=self.loss)
self.estimators.append(tree)
def fit(self, X, y):
y = to_categorical(y)
y_pred = np.zeros(np.shape(y))
for i in range(self.n_estimators):
tree = self.trees[i]
y_and_pred = np.concatenate((y, y_pred), axis=1)
tree.fit(X, y_and_pred)
update_pred = tree.predict(X)
y_pred -= np.multiply(self.learning_rate, update_pred)
def predict(self, X):
y_pred = None
for tree in self.estimators:
update_pred = tree.predict(X)
if y_pred is None:
y_pred = np.zeros_like(update_pred)
y_pred -= np.multiply(self.learning_rate, update_pred)
y_pred = np.exp(y_pred) / np.sum(np.exp(y_pred), axis=1, keepdims=True)
y_pred = np.argmax(y_pred, axis=1)
return y_pred
from sklearn import datasets
data = datasets.load_iris()
X = data.data
y = data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4, seed=2)
clf = XGBoost()
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print ("Accuracy:", accuracy)
Accuracy: 0.9666666666666667
XGBoost也提供了原生的模型库可供我们调用。pip安装xgboost即可:import xgboost as xgb
from xgboost import plot_importance
from matplotlib import pyplot as plt
params = {
'booster': 'gbtree',
'objective': 'multi:softmax',
'num_class': 3,
'gamma': 0.1,
'max_depth': 2,
'lambda': 2,
'subsample': 0.7,
'colsample_bytree': 0.7,
'min_child_weight': 3,
'silent': 1,
'eta': 0.001,
'seed': 1000,
'nthread': 4,
}
plst = params.items()
dtrain = xgb.DMatrix(X_train, y_train)
num_rounds = 200
model = xgb.train(plst, dtrain, num_rounds)
dtest = xgb.DMatrix(X_test)
y_pred = model.predict(dtest)
accuracy = accuracy_score(y_test, y_pred)
print ("Accuracy:", accuracy)
plot_importance(model)
plt.show();
Accuracy: 0.9166666666666666
下载1:OpenCV-Contrib扩展模块中文版教程
在「小白学视觉」公众号后台回复:扩展模块中文教程,即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。在「小白学视觉」公众号后台回复:Python视觉实战项目,即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。在「小白学视觉」公众号后台回复:OpenCV实战项目20讲,即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。交流群
欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

