XGBoost(Extreme Gradient Boosting)全名叫极端梯度提升树,XGBoost是集成学习方法的王牌,在Kaggle数据挖掘比赛中,大部分获胜者用了XGBoost。
XGBoost在绝大多数的回归和分类问题上表现的十分顶尖,本节将较详细的介绍XGBoost的算法原理。
我们在前面已经知道,构建最优模型的一般方法是最小化训练数据的损失函数。
我们用字母 L表示损失,如下式:
其中,F是假设空间
假设空间是在已知属性和属性可能取值的情况下,对所有可能满足目标的情况的一种毫无遗漏的假设集合。
式(1.1)称为经验风险最小化,训练得到的模型复杂度较高。当训练数据较小时,模型很容易出现过拟合问题。
因此,为了降低模型的复杂度,常采用下式:
其中 J ( f ) J(f) J(f)为模型的复杂度,
式(2.1)称为结构风险最小化,结构风险最小化的模型往往对训练数据以及未知的测试数据都有较好的预测 。
应用:
目标函数,即损失函数,通过最小化损失函数来构建最优模型。
由前面可知, 损失函数应加上表示模型复杂度的正则项,且XGBoost对应的模型包含了多个CART树,因此,模型的目标函数为:
(3.1)式是正则化的损失函数;
其中 y i y_i yi是模型的实际输出结果, y i ‾ \overline{y_i} yi是模型的输出结果;
等式右边第一部分是模型的训练误差,第二部分是正则化项,这里的正则化项是K棵树的正则化项相加而来的。
上图为第K棵CART树,确定一棵CART树需要确定两部分,
第一部分就是树的结构,这个结构将输入样本映射到一个确定的叶子节点上,记为 f k ( x ) f_k(x) fk(x);
第二部分就是各个叶子节点的值, q ( x ) q(x) q(x)表示输出的叶子节点序号, w q ( x ) w_q(x) wq(x)表示对应叶子节点序号的叶子节点值。
由定义得:
XGBoost法对应的模型包含了多棵cart树,定义每棵树的复杂度:
其中T为叶子节点的个数,||w||为叶子节点向量的模 。 γ \gamma γ表示节点切分的难度,λ表示L2正则化系数。
假设我们要预测一家人对电子游戏的喜好程度,考虑到年轻和年老相比,年轻更可能喜欢电子游戏,以及男性和女性相比,男性更喜欢电子游戏,故先根据年龄大小区分小孩和大人,然后再通过性别区分开是男是女,逐一给各人在电子游戏喜好程度上打分,如下图所示:
就这样,训练出了2棵树tree1和tree2,类似之前gbdt的原理,两棵树的结论累加起来便是最终的结论,所以:
具体如下图所示:
如下例树的复杂度表示:
根据(3.1)式,共进行t次迭代的学习模型的目标函数为:
由前向分布算法可知,前t-1棵树的结构为常数
我们知道,泰勒公式的二阶导近似表示:
令 f t ( x i ) f_t(x_i) ft(xi)为 Δ x \Delta x Δx 则(3.5)式的二阶近似展开:
其中:
g i g_i gi和 h i h_i hi分别表示预测误差对当前模型的一阶导和二阶导;
l ( y i , y ^ ( t − 1 ) ) l(y_i,\hat{y}^{(t-1)}) l(yi,y^(t−1))表示前t-1棵树组成的学习模型的预测误差。
当前模型往预测误差减小的方向进行迭代。
忽略(3.8)式常数项,并结合(3.4)式,得:
通过(3.2)式简化(3.9)式:
(3.10)式第一部分是对所有训练样本集进行累加,
此时,所有样本都是映射为树的叶子节点,
所以,我们换种思维,从叶子节点出发,对所有的叶子节点进行累加,得:
G j G_j Gj表示映射为叶子节点 j 的所有输入样本的一阶导之和,同理, H j H_j Hj表示二阶导之和。
得:
对于第 t 棵CART树的某一个确定结构(可用q(x)表示),其叶子节点是相互独立的,
G j G_j Gj和 H j H_j Hj是确定量,因此,(3.12)可以看成是关于叶子节点w的一元二次函数 。
最小化(3.12)式,得:
把(3.13)带入到(3.12),得到最终的目标函数:
(3.14)也称为打分函数(scoring function),它是衡量树结构好坏的标准,
在实际训练过程中,当建立第 t 棵树时,XGBoost采用贪心法进行树结点的分裂:
从树深为0时开始:
对树中的每个叶子结点尝试进行分裂;
每次分裂后,原来的一个叶子结点继续分裂为左右两个子叶子结点,原叶子结点中的样本集将根据该结点的判断规则分散到左右两个叶子结点中;
新分裂一个结点后,我们需要检测这次分裂是否会给损失函数带来增益,增益的定义如下:
如果增益Gain>0,即分裂为两个叶子节点后,目标函数下降了,那么我们会考虑此次分裂的结果。
那么一直这样分裂,什么时候才会停止呢?
情况一:上节推导得到的打分函数是衡量树结构好坏的标准,因此,可用打分函数来选择最佳切分点。首先确定样本特征的所有切分点,对每一个确定的切分点进行切分,切分好坏的标准如下:
Gain表示单节点obj与切分后的两个节点的树obj之差,
遍历所有特征的切分点,找到最大Gain的切分点即是最佳分裂点,根据这种方法继续切分节点,得到CART树。
若 γ \gamma γ 值设置的过大,则Gain为负,表示不切分该节点,因为切分后的树结构变差了。
情况二:当树达到最大深度时,停止建树,因为树的深度太深容易出现过拟合,这里需要设置一个超参数max_depth。
情况三:当引入一次分裂后,重新计算新生成的左、右两个叶子结点的样本权重和。如果任一个叶子结点的样本权重低于某一个阈值,也会放弃此次分裂。这涉及到一个超参数:最小样本权重和,是指如果一个叶子节点包含的样本数量太少也会放弃分裂,防止树分的太细,这也是过拟合的一种措施。
官网链接:https://xgboost.readthedocs.io/en/latest/
pip3 install xgboost
XGBoost虽然被称为kaggle比赛神奇,但是,我们要想训练出不错的模型,必须要给参数传递合适的值。
XGBoost中封装了很多参数,主要由三种类型构成:通用参数(general parameters),Booster 参数(booster parameters) 和 学习目标参数(task parameters)
gbtree和dart使用基于树的模型(dart 主要多了 Dropout),而gblinear 使用线性函数.
silent [缺省值=0]
设置为0打印运行信息;设置为1静默模式,不打印
nthread [缺省值=设置为最大可能的线程数]
并行运行xgboost的线程数,输入的参数应该<=系统的CPU核心数,若是没有设置算法会检测将其设置为CPU的全部核心数
下面的两个参数不需要设置,使用默认的就好了
num_pbuffer [xgboost自动设置,不需要用户设置]
预测结果缓存大小,通常设置为训练实例的个数。该缓存用于保存最后boosting操作的预测结果。
num_feature [xgboost自动设置,不需要用户设置]
在boosting中使用特征的维度,设置为特征的最大维度
eta [缺省值=0.3,别名:learning_rate]
gamma [缺省值=0,别名: min_split_loss](分裂最小loss)
max_depth [缺省值=6]
min_child_weight [缺省值=1]
subsample [缺省值=1]
colsample_bytree [缺省值=1]
colsample_bylevel [缺省值=1]
lambda [缺省值=1,别名: reg_lambda]
alpha [缺省值=0,别名: reg_alpha]
scale_pos_weight[缺省值=1]
linear booster一般很少用到。
objective [缺省值=reg:linear]
“reg:linear” – 线性回归
“reg:logistic” – 逻辑回归
“binary:logistic” – 二分类逻辑回归,输出为概率
“multi:softmax” – 使用softmax的多分类器,返回预测的类别(不是概率)。在这种情况下,你还需要多设一个参数:num_class(类别数目)
“multi:softprob” – 和multi:softmax参数一样,但是返回的是每个数据属于各个类别的概率。
eval_metric [缺省值=通过目标函数选择]
可供选择的如下所示:
“rmse”: 均方根误差
“mae”: 平均绝对值误差
“logloss”: 负对数似然函数值
“error”: 二分类错误率。
其值通过错误分类数目与全部分类数目比值得到。对于预测,预测值大于0.5被认为是正类,其它归为负类。
“error@t”: 不同的划分阈值可以通过 ‘t’进行设置
“merror”: 多分类错误率,计算公式为(wrong cases)/(all cases)
“mlogloss”: 多分类log损失
“auc”: 曲线下的面积
seed [缺省值=0]
随机数的种子
设置它可以复现随机数据的结果,也可以用于调整参数
奥托集团是世界上最大的电子商务公司之一,在20多个国家设有子公司。该公司每天都在世界各地销售数百万种产品,所以对其产品根据性能合理的分类非常重要。
不过,在实际工作中,工作人员发现,许多相同的产品得到了不同的分类。本案例要求,你对奥拓集团的产品进行正确的分分类。尽可能的提供分类的准确性。
链接:https://www.kaggle.com/c/otto-group-product-classification-challenge/overview
1.数据获取
2.数据基本处理
3.模型训练
2.数据基本处理
2.1 截取部分数据
2.2 把标签纸转换为数字
2.3 分割数据(使用StratifiedShuffleSplit)
# 使用StratifiedShuffleSplit对数据集进行分割
from sklearn.model_selection import StratifiedShuffleSplit
sss = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=0)
for train_index, test_index in sss.split(X_resampled.values, y_resampled):
print(len(train_index))
print(len(test_index))
x_train = X_resampled.values[train_index]
x_val = X_resampled.values[test_index]
y_train = y_resampled[train_index]
y_val = y_resampled[test_index]
# 分割数据图形可视化
import seaborn as sns
sns.countplot(y_val)
plt.show()
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(x_train)
x_train_scaled = scaler.transform(x_train)
x_val_scaled = scaler.transform(x_val)
print(x_train_scaled.shape)
# (13888, 93)
from sklearn.decomposition import PCA
pca = PCA(n_components=0.9)
x_train_pca = pca.fit_transform(x_train_scaled)
x_val_pca = pca.transform(x_val_scaled)
print(x_train_pca.shape, x_val_pca.shape)
(13888, 65) (3473, 65)
从上面输出的数据可以看出,只选择65个元素,就可以表达出特征中90%的信息
# 降维数据可视化
plt.plot(np.cumsum(pca.explained_variance_ratio_))
plt.xlabel("元素数量")
plt.ylabel("可表达信息的百分占比")
plt.show()
3.模型训练
from xgboost import XGBClassifier
xgb = XGBClassifier()
xgb.fit(x_train_pca, y_train)
# 改变预测值的输出模式,让输出结果为百分占比,降低logloss值
y_pre_proba = xgb.predict_proba(x_val_pca)
# logloss进行模型评估
from sklearn.metrics import log_loss
log_loss(y_val, y_pre_proba, eps=1e-15, normalize=True)
xgb.get_params
3.2 模型调优
3.2.1 调优参数:
n_estimator,
scores_ne = []
n_estimators = [100,200,400,450,500,550,600,700]
for nes in n_estimators:
print("n_estimators:", nes)
xgb = XGBClassifier(max_depth=3,
learning_rate=0.1,
n_estimators=nes,
objective="multi:softprob",
n_jobs=-1,
nthread=4,
min_child_weight=1,
subsample=1,
colsample_bytree=1,
seed=42)
xgb.fit(x_train_pca, y_train)
y_pre = xgb.predict_proba(x_val_pca)
score = log_loss(y_val, y_pre)
scores_ne.append(score)
print("测试数据的logloss值为:{}".format(score))
# 数据变化可视化
plt.plot(n_estimators, scores_ne, "o-")
plt.ylabel("log_loss")
plt.xlabel("n_estimators")
print("n_estimators的最优值为:{}".format(n_estimators[np.argmin(scores_ne)]))
max_depth,
scores_md = []
max_depths = [1,3,5,6,7]
for md in max_depths: # 修改
xgb = XGBClassifier(max_depth=md, # 修改
learning_rate=0.1,
n_estimators=n_estimators[np.argmin(scores_ne)], # 修改
objective="multi:softprob",
n_jobs=-1,
nthread=4,
min_child_weight=1,
subsample=1,
colsample_bytree=1,
seed=42)
xgb.fit(x_train_pca, y_train)
y_pre = xgb.predict_proba(x_val_pca)
score = log_loss(y_val, y_pre)
scores_md.append(score) # 修改
print("测试数据的logloss值为:{}".format(log_loss(y_val, y_pre)))
# 数据变化可视化
plt.plot(max_depths, scores_md, "o-") # 修改
plt.ylabel("log_loss")
plt.xlabel("max_depths") # 修改
print("max_depths的最优值为:{}".format(max_depths[np.argmin(scores_md)])) # 修改
3.2.2 确定最后最优参数
xgb = XGBClassifier(learning_rate =0.1,
n_estimators=550,
max_depth=3,
min_child_weight=3,
subsample=0.7,
colsample_bytree=0.7,
nthread=4,
seed=42,
objective='multi:softprob')
xgb.fit(x_train_scaled, y_train)
y_pre = xgb.predict_proba(x_val_scaled)
print("测试数据的logloss值为 : {}".format(log_loss(y_val, y_pre, eps=1e-15, normalize=True)))
AdaBoost是一种提升树的方法,和三个臭皮匠,赛过诸葛亮的道理一样。
AdaBoost两个问题:
(1) 如何改变训练数据的权重或概率分布
(2) 如何将弱分类器组合成一个强分类器,亦即,每个分类器,前面的权重如何设置
GBDT和AdaBosst很类似,但是又有所不同。
因此可以说 。
缺点:
GBDT ->预排序方法(pre-sorted)
(1)空间消耗大。
(2) 时间上也有较大的开销。
(3) 对内存(cache)优化不友好。
常用的机器学习算法,例如神经网络等算法,都可以以mini-batch的方式训练,训练数据的大小不会受到内存限制。
而GBDT在每一次迭代的时候,都需要遍历整个训练数据多次。
如果把整个训练数据装进内存则会限制训练数据的大小;如果不装进内存,反复地读写训练数据又会消耗非常大的时间。
尤其面对工业级海量的数据,普通的GBDT算法是不能满足其需求的。
LightGBM提出的主要原因就是为了解决GBDT在海量数据遇到的问题,让GBDT可以更好更快地用于工业实践。
lightGBM是2017年1月,微软在GItHub上开源的一个新的梯度提升框架。
https://github.com/Microsoft/LightGBM
在开源之后,就被别人冠以“速度惊人”、“支持分布式”、“代码清晰易懂”、“占用内存小”等属性。
LightGBM主打的高效并行训练让其性能超越现有其他boosting工具。在Higgs数据集上的试验表明,LightGBM比XGBoost快将近10倍,内存占用率大约为XGBoost的1/6。
higgs数据集介绍:这是一个分类问题,用于区分产生希格斯玻色子的信号过程和不产生希格斯玻色子的信号过程。
lightGBM 主要基于以下方面优化,提升整体特特性:
具体解释见下,分节介绍。
直方图算法的基本思想是
Eg:
[0, 0.1) --> 0;
[0.1,0.3) --> 1;
…
使用直方图算法有很多优点。首先,最明显就是内存消耗的降低,直方图算法不仅不需要额外存储预排序的结果,而且可以只保存特征离散化后的值,而这个值一般用8位整型存储就足够了,内存消耗可以降低为原来的1/8。
然后在计算上的代价也大幅降低,预排序算法每遍历一个特征值就需要计算一次分裂的增益,而直方图算法只需要计算k次(k可以认为是常数),时间复杂度从O(#data#feature)优化到O(k#features)。
当然,Histogram算法并不是完美的。由于特征被离散化后,找到的并不是很精确的分割点,所以会对结果产生影响。但在不同的数据集上的结果表明,离散化的分割点对最终的精度影响并不是很大,甚至有时候会更好一点。原因是决策树本来就是弱模型,分割点是不是精确并不是太重要;较粗的分割点也有正则化的效果,可以有效地防止过拟合;即使单棵树的训练误差比精确分割的算法稍大,但在梯度提升(Gradient Boosting)的框架下没有太大的影响。
一个叶子的直方图可以由它的父亲节点的直方图与它兄弟的直方图做差得到。
通常构造直方图,需要遍历该叶子上的所有数据,但直方图做差仅需遍历直方图的k个桶。
利用这个方法,LightGBM可以在构造一个叶子的直方图后,可以用非常微小的代价得到它兄弟叶子的直方图,在速度上可以提升一倍。
含例题:https://blog.csdn.net/zhong_ddbb/article/details/106244036
Level-wise便利一次数据可以同时分裂同一层的叶子,容易进行多线程优化,也好控制模型复杂度,不容易过拟合。
Leaf-wise则是一种更为高效的策略,每次从当前所有叶子中,找到分裂增益最大的一个叶子,然后分裂,如此循环。
实际上大多数机器学习工具都无法直接支持类别特征,一般需要把类别特征,转化到多维的0/1特征,降低了空间和时间的效率。
而类别特征的使用是在实践中很常用的。基于这个考虑,LightGBM优化了对类别特征的支持,可以直接输入类别特征,不需要额外的0/1展开。并在决策树算法上增加了类别特征的决策规则。
在Expo数据集上的实验,相比0/1展开的方法,训练速度可以加速8倍,并且精度一致。目前来看,LightGBM是第一个直接支持类别特征的GBDT工具。
Expo数据集介绍:数据包含1987年10月至2008年4月美国境内所有商业航班的航班到达和离开的详细信息。这是一个庞大的数据集:总共有近1.2亿条记录。主要用于预测航班是否准时。
LightGBM还具有支持高效并行的优点。LightGBM原生支持并行学习,目前支持特征并行和数据并行的两种。
LightGBM针对这两种并行方法都做了优化:
pip3 install lightgbm
https://github.com/Microsoft/LightGBM/blob/master/docs/Installation-Guide.rst#macos
下表对应了 Faster Speed ,better accuracy ,over-fitting 三种目的时,可以调的参数
接下来,通过鸢尾花数据集对lightGBM的基本使用,做一个介绍。
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import mean_squared_error
import lightgbm as lgb
加载数据,对数据进行基本处理
# 加载数据
iris = load_iris()
data = iris.data
target = iris.target
X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=0.2)
模型训练
gbm = lgb.LGBMRegressor(objective='regression', learning_rate=0.05, n_estimators=20)
gbm.fit(X_train, y_train, eval_set=[(X_test, y_test)], eval_metric='l1', early_stopping_rounds=5)
gbm.score(X_test, y_test)
# 0.810605595102488
# 网格搜索,参数优化
estimator = lgb.LGBMRegressor(num_leaves=31)
param_grid = {
'learning_rate': [0.01, 0.1, 1],
'n_estimators': [20, 40]
}
gbm = GridSearchCV(estimator, param_grid, cv=4)
gbm.fit(X_train, y_train)
print('Best parameters found by grid search are:', gbm.best_params_)
# Best parameters found by grid search are: {'learning_rate': 0.1, 'n_estimators': 40}
模型调优训练
gbm = lgb.LGBMRegressor(num_leaves=31, learning_rate=0.1, n_estimators=40)
gbm.fit(X_train, y_train, eval_set=[(X_test, y_test)], eval_metric='l1', early_stopping_rounds=5)
gbm.score(X_test, y_test)
# 0.9536626296481988
https://download.csdn.net/download/mengxianglong123/85695674