集成(ensemble)是合并多个机器学习模型来构建更强大的模型的方法。主要需要解决两个问题:

  • 如何获得个体学习器
  • 使用什么样的结合规则

下载.png

个体学习器

  个体学习器可以分为同质和异质两种:

  • 同质指的是不同个体学习器属于同一种类,比如都是决策树。
  • 异质指的是个体学习器属于不同种类,比如既有决策树又有SVM。

  目前来说,同质个体学习器的应用是最广泛的,一般我们常说的集成学习的方法都是指的同质个体学习器,而同质个体学习器使用最多的模型是CART决策树和神经网络。

  同质学习器按照个体学习器之间是否存在依赖关系可分为两类:

  • 存在强依赖关系:学习器串行生成,即新学习器是在上一个学习器基础上生成的,代表就是boosting系列
  • 不存在强依赖关系:学习器并行生成,各个学习器之间没有太大的依赖关系,代表就是bagging系列

集成策略

  • 平均值:针对回归问题,对多个个体学习器的结果计算平均值作为最终结果,可以使用算数平均值、加权平均值等。
  • 投票法:针对分类问题,对多个个体分类器的结果使用投票作为最终结果,可以使用少数服从多数、绝对多数、加权投票等。
  • 学习法:对个体学习器结果再使用一个学习器来处理,即stacking

Bagging

Bagging原理

  如下图所示,bagging的个体学习器使用的训练集由随机采样获得,分别训练后集成。

  随机采样最常用的方法是自助采样,其方法是在m个样本中有放回的随机采样m次以获得m个样本的训练集。其中,某个样本没有被使用的概率是$$P=(1-\frac1m)^m$$令m趋向于正无穷可得$$\lim _{m\rightarrow+\infty}P=\frac 1e$$即大约36.8%的数据不会被取到。

  bagging的结合策略是简单的投票法和算术平均值,由于Bagging算法每次都进行采样来训练模型,因此泛化能力很强,对于降低模型的方差很有作用。当然对于训练集的拟合程度就会差一些,也就是模型的偏倚会大一些。

下载 (1).png

随机森林(Random Forest)

  随机森林是使用cart决策树作为弱学习器的使用bagging方法的集成模型,但是对cart决策树进了改进以增强泛化能力。Cart树在每个节点进行二分时,选择的是最优的特征,即划分后基尼系数最小或方差最小的特征,决策树中的弱学习器在选择特征时,先随机选择k个特征,然后在这k个特征中选择最优特征。

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import warnings
warnings.filterwarnings('ignore')
#在two_moon数据集上构造5棵树组成的随机森林
import seaborn as sns
import mglearn

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split

X,y = make_moons(n_samples=100,noise=0.25,random_state=3)
X_train,X_test,y_train,y_test = train_test_split(X,y,stratify=y,random_state=42)

RFC = RandomForestClassifier(n_estimators=5,random_state=2).fit(X_train,y_train)

#将每棵树及总预测可视化
fig,axes = plt.subplots(2,3,figsize=(20,10))
for i,(ax,tree) in enumerate(zip(axes.ravel(),RFC.estimators_)):
    ax.set_title('Tree{}'.format(i))
    mglearn.plots.plot_tree_partition(X_train,y_train,tree,ax=ax)
mglearn.plots.plot_2d_separator(RFC,X_train,fill=True,ax=axes[-1,-1],alpha=.4)
axes[-1,-1].set_title('Random Forest')
mglearn.discrete_scatter(X_train[:,0],X_train[:,1],y_train)

output_12_1.png

  可以看出,5棵决策树结果完全不同,综合后得到随机森林。

#在乳腺癌数据上构建随机森林
from sklearn.datasets import load_breast_cancer

cancer = load_breast_cancer()
X_train,X_test,y_train,y_test=train_test_split(cancer.data,cancer.target,stratify=cancer.target,random_state=0)

RFC = RandomForestClassifier(n_estimators=100,max_features=5,oob_score=True,random_state=88).fit(X_train,y_train)
print('训练精度:{}'.format(RFC.score(X_train,y_train)))
print('测试精度:{}'.format(RFC.score(X_test,y_test)))
print('袋外分数:{}'.format(RFC.oob_score_))
训练精度:1.0
测试精度:0.958041958041958
袋外分数:0.9624413145539906

  随机森林的特征重要性:

fig = plt.figure(dpi=100)
plt.barh(range(len(RFC.feature_importances_)),RFC.feature_importances_)
plt.yticks(range(len(RFC.feature_importances_)),cancer.feature_names)
plt.xlabel("Feature Importance")
plt.show()

output_16_0.png

参数

Bagging框架参数

  • n_estimators:弱学习器个数,默认是100。一般来说n_estimators太小,容易欠拟合,n_estimators太大,计算量会太大,并且n_estimators到一定的数量后,再增大n_estimators获得的模型提升会很小,所以一般选择一个适中的数值。
  • oob_score:是否使用袋外数据(Out of bag,重采样剩下的数据)来评价模型,默认False。

决策树参数

  • max_features:随机选择特征的最大特征数,默认是auto,即使用N的算数平方根个特征,N为特征数。
  • max_depth:树的最大深度,默认是不限制,特征过多时容易过拟合。
  • min_samples_split:最小划分值,默认为2,节点样本数小于该数值不会继续划分,预剪枝方法。
  • min_samples_leaf: 最小叶片数据量,默认为1,小于该值则会被与兄弟节点合并,后剪枝方法。
  • min_weight_fraction_leaf:叶节点样本权重的最小值,默认为0,小于该值会被剪枝。
  • max_leaf_nodes:最大叶节点个数,默认None。
  • min_impurity_split:最小不纯度,小于该值会被剪枝。

  决策树参数中最重要的是前四个,后三个一般不会使用。

优点与缺点

优点

  • 训练可以高度并行化,对于大数据时代的大样本训练速度有优势。
  • 由于可以随机选择决策树节点划分特征,这样在样本特征维度很高的时候,仍然能高效的训练模型。
  • 在训练后,可以给出各个特征对于输出的重要性。
  • 由于采用了随机采样,训练出的模型的方差小,泛化能力强。
  • 相对于Boosting系列的Adaboost和GBDT, RF实现比较简单。
  • 对部分特征缺失不敏感。

缺点

  • 在某些噪音比较大的样本集上,RF模型容易陷入过拟合。
  • 取值划分比较多的特征容易对RF的决策产生更大的影响,从而影响拟合的模型的效果。

Boosting

  如下图所示,boosting的基本思想是,先训练出一个模型1,此时,每个样本的权重是相同的,都是样本个数的倒数,计算学习误差率e1,然后根据学习结果来更新样本权重,增加被错误分类的样本的权重,训练模型2,计算学习误差率e2。重复该过程以获得T个个体学习器,然后使用结合策略结合成强学习器。因此,boosting系列算法需要解决以下四个问题:

  • 如何计算学习误差率e?
  • 如何得到弱学习器权重系数α?
  • 如何更新样本权重D?
  • 使用何种结合策略?

Adaboost

  Adaboost回归有很多种方法,这里只介绍分类。

Adaboost分类原理

  Adaboost二分类中标签y取值为1,-1

  • 误差率e,即分类错误的比率:$$e_k = P(G_k(x_i) \neq y_i) = \sum\limits_{i=1}^{m}w_{ki}I(G_k(x_i) \neq y_i)$$
  • 学习器权重系数,可以看出,误差率e越大,权重系数越小:$$\alpha_k = \frac{1}{2}log\frac{1-e_k}{e_k}$$
  • 样本权重:$$w_{k+1,i} = \frac{w_{ki}}{Z_K}exp(-\alpha_ky_iG_k(x_i))$$其中$$\alpha_k = \frac{1}{2}log\frac{1-e_k}{e_k}$$
  • 集合策略,加权表决法:$$f(x) = sign(\sum\limits_{k=1}^{K}\alpha_kG_k(x))$$

Adaboost分类实现

Adaboost框架参数

  • base_estimator:弱学习器,默认为决策树,一般使用Cart树或神经网络。
  • n_estimators:最大迭代次数,默认50。
  • learning_rate: :学习率,默认为1,取值大于0小于1。
  • algorithm:scikit-learn实现了两种Adaboost分类算法,SAMME和SAMME.R。两者的主要区别是弱学习器权重的度量,SAMME使用了和我们的原理篇里二元分类Adaboost算法的扩展,即用对样本集分类效果作为弱学习器权重,而SAMME.R使用了对样本集分类的预测概率大小来作为弱学习器权重。由于SAMME.R使用了概率度量的连续值,迭代一般比SAMME快,因此AdaBoostClassifier的默认算法algorithm的值也是SAMME.R。我们一般使用默认的SAMME.R就够了,但是要注意的是使用了SAMME.R, 则弱分类学习器参数base_estimator必须限制使用支持概率预测的分类器。SAMME算法则没有这个限制。

Adaboost分类器调参较为简单,框架参数基本上只需要调节学习率和最大迭代次数。

  下面是sklearn实现分类,首先导入相关包并创建数据,数据是二维数据,分布如图所示:

from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import make_gaussian_quantiles

# 生成2维正态分布,生成的数据按分位数分为两类,500个样本,2个样本特征,协方差系数为2
X1, y1 = make_gaussian_quantiles(cov=2.0,n_samples=500, n_features=2,n_classes=2, random_state=1)
# 生成2维正态分布,生成的数据按分位数分为两类,400个样本,2个样本特征均值都为3,协方差系数为2
X2, y2 = make_gaussian_quantiles(mean=(3, 3), cov=1.5,n_samples=400, n_features=2, n_classes=2, random_state=1)
#将两组数据合成一组数据
X = np.concatenate((X1, X2))
y = np.concatenate((y1, - y2 + 1))
plt.scatter(X[:, 0], X[:, 1], marker='o', c=y)
plt.show()

output_30_0.png

#训练模型
ada = AdaBoostClassifier(DecisionTreeClassifier(max_depth=10,min_samples_leaf=5),n_estimators=600,learning_rate=0.7).fit(X,y)
print(ada.score(X,y))
#绘图
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
                     np.arange(y_min, y_max, 0.02))

Z = ada.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
cs = plt.contourf(xx, yy, Z, cmap=plt.cm.Paired)
plt.scatter(X[:, 0], X[:, 1], marker='o', c=y)
plt.show()
1.0

output_31_1.png

优点:

  • Adaboost作为分类器时,分类精度很高
  • 在Adaboost的框架下,可以使用各种回归分类模型来构建弱学习器,非常灵活。
  • 作为简单的二元分类器时,构造简单,结果可理解。
  • 不容易发生过拟合

缺点:

  • 对异常样本敏感,异常样本在迭代中可能会获得较高的权重,影响最终的强学习器的预测准确性

梯度提升树(Gradient Boosting Decision Tree,GBDT)

  梯度提升树的原理是,构造一系列决策树,每一棵树在前一棵基础上进行修正,以此来获得效果较好的决策树。举个例子,一个人150kg,先训练一个模型,该模型为第T个模型,用这个模型估计他的体重为140kg,偏差为10kg,然后再训练一个模型t,用于估计偏差,偏差的估计值为8kg,然后将这两个模型加起来作为T+1个模型,来估计体重。

回归树

  首先训练一个Cart树$$f_0(x) = \underbrace{arg\; min}_{c}\sum\limits_{i=1}^{m}L(y_i, c)$$其损失函数为$$L=\sum \frac12[f(x_i)-y_i]^2$$其负梯度为$$r_{ti} = -\bigg[\frac{\partial L(y_i, f(x_i)))}{\partial f(x_i)}\bigg]_{f(x) = f_{t-1}\;\; (x)}=y_i-f(x_i)$$计算每条数据的负梯度,得到数据集$$(x_i,r_{ti})\;\; (i=1,2,..m)$$负梯度刚好是实际值与模型拟合值之间的偏差,再训练一个模型用于拟合这个偏差$$c_{tj} = \underbrace{arg\; min}_{c}\sum\limits_{x_i \in R_{tj}} L(y_i,f_{t-1}(x_i) +c)$$然后更新学习器$$f_{t}(x) = f_{t-1}(x) + \sum\limits_{j=1}^{J}c_{tj}I(x \in R_{tj})$$
得到学习器t,重复此过程直到结束,最终预测值为$$f(x) = f_T(x) =f_0(x) + \sum\limits_{t=1}^{T}\sum\limits_{j=1}^{J}c_{tj}I(x \in R_{tj})$$

分类

  分类的原理与回归是类似的,只是损失函数不同,导致负梯度拟合不同,其他步骤相同。损失函数使用似然函数。

二元损失函数$$L(y, f(x)) = log(1+ exp(-yf(x)))$$$$r_{ti} = -\bigg[\frac{\partial L(y, f(x_i)))}{\partial f(x_i)}\bigg]_{f(x) = f_{t-1}\;\; (x)} = y_i/(1+exp(y_if(x_i)))$$多元损失函数$$L(y, f(x)) = - \sum\limits_{k=1}^{K}y_klog\;p_k(x)$$

正则化

  • 学习率:每个损失拟合值的系数,在0-1之间。
  • cart树剪枝等正则化。
  • 子采样:不放回的采样数据。

sklearn实现

  框架参数

  • n_estimators:学习器个数,默认100
  • learning_rate: 学习率,默认1,与学习器个数一同调参。
  • subsample:子采样比例,用于正则化,0-1之间,默认是1.
  • loss:损失函数。

  对于分类模型,有对数似然损失函数"deviance"和指数损失函数"exponential"两者输入选择。默认是对数似然损失函数"deviance"。一般来说,推荐使用默认的"deviance"。它对二元分离和多元分类各自都有比较好的优化。而指数损失函数等于把我们带到了Adaboost算法。

  对于回归模型,有均方差"ls", 绝对损失"lad", Huber损失"huber"和分位数损失“quantile”。默认是均方差"ls"。一般来说,如果数据的噪音点不多,用默认的均方差"ls"比较好。如果是噪音点较多,则推荐用抗噪音的损失函数"huber"。而如果我们需要对训练集进行分段预测的时候,则采用“quantile”。

  • alpha:这个参数只有GradientBoostingRegressor有,当我们使用Huber损失"huber"和分位数损失“quantile”时,需要指定分位数的值。默认是0.9,如果噪音点较多,可以适当降低这个分位数的值。

  弱学习器是Cart树,参数与cart树相同。

  下面是调参实践,首先训练一个默认模型:

#梯度提升树
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import roc_auc_score

gbdt=GradientBoostingClassifier(random_state=10).fit(X_train,y_train)
print('训练精度:{}'.format(gbdt.score(X_train,y_train)))
print('测试精度:{}'.format(gbdt.score(X_test,y_test)))
print('测试AUC:{}'.format(roc_auc_score(y_test,gbdt.predict_proba(X_test)[:,1])))
训练精度:1.0
测试精度:0.958041958041958
测试AUC:0.978511530398323

  然后令学习率为0.1,寻找最优的学习器个数,为80个。

from sklearn.model_selection import GridSearchCV

param_test1 = {'n_estimators':range(20,101,10)}
gsearch1 = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.1,
                                  max_depth=8, random_state=10), 
                       param_grid = param_test1, scoring='roc_auc',iid=False,cv=5).fit(X_train,y_train)
gsearch1.best_params_,gsearch1.best_score_
({'n_estimators': 80}, 0.9836974622979643)

  使用以上两参数,来网格搜索最大深度和最小叶片分割数以及最小叶片数据量。

param_test2 = {'max_depth':range(2,14,2),
               'min_samples_split':range(10,41,5),
              'min_samples_leaf':[5,41,5]}
gsearch2 = GridSearchCV(GradientBoostingClassifier(n_estimators=80,learning_rate=0.1,random_state=10),
                        param_test2,scoring='roc_auc',cv=5).fit(X_train,y_train)
gsearch2.best_params_,gsearch2.best_score_
({'max_depth': 10, 'min_samples_leaf': 5, 'min_samples_split': 15},
 0.9939034991321204)

  使用新模型参数预测:

gbdt_grided = GradientBoostingClassifier(max_depth=10,min_samples_leaf=5,min_samples_split=10,
                                         n_estimators=160,learning_rate=0.05,random_state=10).fit(X_train,y_train)
print('训练集AUC{}'.format(roc_auc_score(y_train,gbdt_grided.predict_proba(X_train)[:,1])))
print('测试集AUC{}'.format(roc_auc_score(y_test,gbdt_grided.predict_proba(X_test)[:,1])))
训练集AUC1.0
测试集AUC0.9865828092243186

小结

优点

  • 可以灵活处理各种类型的数据,包括连续值和离散值。
  • 在相对少的调参时间情况下,预测的准确率也可以比较高。这个是相对SVM来说的。
  • 使用一些健壮的损失函数,对异常值的鲁棒性非常强。比如 Huber损失函数和Quantile损失函数。

缺点

  • 由于弱学习器之间存在依赖关系,难以并行训练数据。不过可以通过自采样的SGBT来达到部分并行。

XGBoost

XGB原理

  XGBoost是GBDT的一个高效实现,主要从下面三个方面做了优化:

  • 算法本身的优化:在算法的弱学习器模型选择上,对比GBDT只支持决策树,还可以直接很多其他的弱学习器。在算法的损失函数上,除了本身的损失,还加上了正则化部分。在算法的优化方式上,GBDT的损失函数只对误差部分做负梯度(一阶泰勒)展开,而XGBoost损失函数对误差部分做二阶泰勒展开。
  • 算法运行效率的优化:对每个弱学习器,比如决策树建立的过程做并行选择,找到合适的子树分裂特征和特征值。在并行选择之前,先对所有的特征的值进行排序分组,方便前面说的并行选择。对分组的特征,选择合适的分组大小,使用CPU缓存进行读取加速。将各个分组保存到多个硬盘以提高IO速度。
  • 对于缺失值的特征,通过枚举所有缺失值在当前节点是进入左子树还是右子树来决定缺失值的处理方式。算法本身加入了L1和L2正则化项,可以防止过拟合,泛化能力更强。

  Boosting加法模型可以表示为$$\hat y_i=\sum _{k=1}^Kf_k(x_i)$$其中K为弱学习器个数,整个模型的损失函数为$$L=\sum_{i=1}^n l(y_i,\hat y_i)$$n为样本容量,上一节所说的GBDT使用的是MSE作为损失函数,当然,也可以用其他损失函数,考虑正则化项,损失函数变为$$L=\sum _{i=1}^nl(y_i,\hat y_i)+\sum_{k=1}^K\Omega (f_k)$$由于$$\hat y_i^t=\hat y_i^{t-1}(x_i)+f_t(x_i)$$其中$f_t(x_i)$就是第t个新加入的模型,因此$$L=\sum _{i=1}^nl(y_i,\hat y_i^{t-1}+f_t)+\sum_{k=1}^K\Omega (f_k)$$

由泰勒定理,$$f(x)=\sum_{i=0}^n\frac {f^{(i)}(a)}{i!}(x-a)^i+R_{n+1}(x)$$其中$R_{n+1}(x)$为$x^n$的高阶无穷小,当n = 2时$$f(x+\delta x)=f(x)+f'(x)\delta x+\frac 12f''(x)\delta x^2$$

做如下替换$$\hat y_i^{t-1}=x,f_t=\delta x$$得$$L=\sum _{i=1}^n[l(y_i,\hat y_i^{t-1})+g_i\delta f_t+\frac 12h_i\delta f_t^2]+\sum_{k=1}^K\Omega (f_k)$$

其中$$g_i=\frac{\partial l(y_i,\hat y_i^{t-1})}{\partial\hat y_i^{t-1}} ,h_i=\frac{\partial^2 l(y_i,\hat y_i^{t-1})}{\partial[\hat y_i^{t-1}]^2}$$

由于已经训练完成第t-1个模型,因此,上一个模型的损失函数为常数,最终第t个模型的损失函数转化为$$\begin{split} L&\approx\sum_{i=1}^n(g_if_t+\frac12h_if_t^2)+\sum_{k=1}^K\Omega(f_k)\\&= \sum_{i=1}^n \left [ g_iw_{q(x_i)}+\frac12h_iw_{q(x_i)}^2 \right] + \gamma T + \frac12 \lambda \sum_{j=1}^T w_j^2 \\&= \sum_{j=1}^T \left[(\sum_{i \in I_j}g_i)w_j + \frac12(\sum_{i \in I_j}h_i + \lambda)w_j^2 \right]+ \gamma T\end{split}$$其中由于决策树同节点中的预测值相等,有$$f_t(x)=w_{q(x)}$$

令$$G_j=\sum_{i \in I_j}g_i,H_j=\sum_{i \in I_j}h_i$$则$$L = \sum_{j=1}^T \left[G_iw_j + \frac12(H_i + \lambda)w_j^2 \right] + \gamma T$$

令一阶偏导为0得$$w_j^*=-\frac{G_j}{H_j+\lambda}$$此时$$L = -\frac12 \sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T$$

  以上就是模型的损失函数,在构建决策树模型时,使用L代替MSE进行分叉。

import xgboost as xgb
from sklearn.datasets.samples_generator import make_classification
from sklearn.model_selection import GridSearchCV
# X为样本特征,y为样本类别输出, 共10000个样本,每个样本20个特征,输出有2个类别,没有冗余特征,每个类别一个簇
X, y = make_classification(n_samples=10000, n_features=20, n_redundant=0,
                             n_clusters_per_class=1, n_classes=2, flip_y=0.1)

X_train, X_test, y_train, y_test = train_test_split(X,y,stratify = y,random_state=1)

sklearn_xgb = xgb.XGBClassifier(learning_rate= 0.5, verbosity=1, objective='binary:logistic',random_state=1)
sklearn_xgb.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="error",
        eval_set=[(X_test, y_test)])
gsCv = GridSearchCV(sklearn_xgb,
                   {'max_depth': [4,5,6],
                    'n_estimators': [5,10,20]})
gsCv.fit(X_train,y_train)
print(gsCv.best_score_)
print(gsCv.best_params_)
[0]    validation_0-error:0.1248
Will train until validation_0-error hasn't improved in 10 rounds.
[1]    validation_0-error:0.116
[2]    validation_0-error:0.116
[3]    validation_0-error:0.116
[4]    validation_0-error:0.1164
[5]    validation_0-error:0.1164
[6]    validation_0-error:0.1164
[7]    validation_0-error:0.116
[8]    validation_0-error:0.1164
[9]    validation_0-error:0.1184
[10]    validation_0-error:0.118
[11]    validation_0-error:0.1172
Stopping. Best iteration:
[1]    validation_0-error:0.116

0.8838666666666667
{'max_depth': 5, 'n_estimators': 5}

XGB参数

XGBoost框架参数 

  对于XGBoost的框架参数,最重要的是3个参数: booster,n_estimators和objectve。

  • booster:XGBoost使用的弱学习器类型,可以是默认的gbtree, 也就是CART决策树,还可以是线性弱学习器gblinear以及DART。一般来说,使用gbtree就可以了,不需要调参。
  • n_estimators:决策树弱学习器的个数。n_estimators太小,容易欠拟合,n_estimators太大,模型会过于复杂,一般需要调参选择一个适中的数值。
  • objective:要解决的问题是分类还是回归,或其他问题,以及对应的损失函数。具体可以取的值很多,一般只关心在分类和回归的时候使用的参数。在回归问题objective一般使用reg:squarederror ,即MSE均方误差。二分类问题一般使用binary:logistic, 多分类问题一般使用multi:softmax。
XGBoost 弱学习器参数

  这里只讨论使用gbtree默认弱学习器的参数。要调参的参数主要是决策树的相关参数如下:

  • max_depth: 控制树结构的深度。
  • min_child_weight: 最小的子节点权重阈值,如果某个树节点的权重小于这个阈值,则不会再分裂子树,即这个树节点就是叶子节点。这里树节点的权重使用的是该节点所有样本的二阶导数的和,即XGBoost原理里面的Htj:$$H_{tj} = \sum\limits_{x_i \in R_{tj}}h_{ti}$$这个值需要网格搜索寻找最优值,在sklearn GBDT里面,没有完全对应的参数,不过min_samples_split从另一个角度起到了阈值限制。
  • gamma: XGBoost的决策树分裂所带来的损失减小阈值。也就是我们在尝试树结构分裂时,会尝试最大化下式:$$\max \frac{1}{2}\frac{G_L^2}{H_L + \lambda} + \frac{1}{2}\frac{G_R^2}{H_R+\lambda}-\frac{1}{2}\frac{(G_L+G_R)^2}{H_L+H_R+ \lambda} - \gamma$$这个最大化后的值需要大于gamma,才能继续分裂子树。
  • subsample: 子采样参数,这个也是不放回抽样,和sklearn GBDT的subsample作用一样。选择小于1的比例可以减少方差,即防止过拟合,但是会增加样本拟合的偏差,因此取值不能太低。初期可以取值1,如果发现过拟合后可以网格搜索调参找一个相对小一些的值。
  • colsample_bytree/colsample_bylevel/colsample_bynode: 这三个参数都是用于特征采样的,默认都是不做采样,即使用所有的特征建立决策树。colsample_bytree控制整棵树的特征采样比例,colsample_bylevel控制某一层的特征采样比例,而colsample_bynode控制某一个树节点的特征采样比例。比如我们一共64个特征,则假设colsample_bytree,colsample_bylevel和colsample_bynode都是0.5,则某一个树节点分裂时会随机采样8个特征来尝试分裂子树。
  • reg_alpha/reg_lambda: 这2个是XGBoost的正则化参数。reg_alpha是L1正则化系数,reg_lambda是L2正则化系数,在原理篇里我们讨论了XGBoost的正则化损失项部分:
XGBoost 其他参数

  XGBoost还有一些其他的参数需要注意,主要是learning_rate。
  learning_rate控制每个弱学习器的权重缩减系数,和sklearn GBDT的learning_rate类似,较小的learning_rate意味着我们需要更多的弱学习器的迭代次数。通常我们用步长和迭代最大次数一起来决定算法的拟合效果。所以这两个参数n_estimators和learning_rate要一起调参才有效果。当然也可以先固定一个learning_rate,然后调完n_estimators,再调完其他所有参数后,最后再来调learning_rate和n_estimators。

  此外,n_jobs控制算法的并发线程数, scale_pos_weight用于类别不平衡的时候,负例和正例的比例。类似于sklearn中的class_weight。importance_type则可以查询各个特征的重要性程度。可以选择“gain”, “weight”, “cover”, “total_gain” 或者 “total_cover”。最后可以通过调用booster的get_score方法获取对应的特征权重。“weight”通过特征被选中作为分裂特征的计数来计算重要性,“gain”和“total_gain”则通过分别计算特征被选中做分裂特征时带来的平均增益和总增益来计算重要性。“cover”和 “total_cover”通过计算特征被选中做分裂时的平均样本覆盖度和总体样本覆盖度来来计算重要性。

LightGBM

from lightgbm import LGBMRegressor
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import GridSearchCV
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.externals import joblib

# 加载数据
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 = LGBMRegressor(objective='regression', num_leaves=31, 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)

# 模型存储
joblib.dump(gbm, 'loan_model.pkl')
# 模型加载
gbm = joblib.load('loan_model.pkl')

# 模型预测
y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration_)

# 模型评估
print('The rmse of prediction is:', mean_squared_error(y_test, y_pred) ** 0.5)

# 特征重要度
print('Feature importances:', list(gbm.feature_importances_))

# 网格搜索,参数优化
estimator = LGBMRegressor(num_leaves=31)
param_grid = {
    'learning_rate': [0.01, 0.1, 1],
    'n_estimators': [20, 40]
}
gbm = GridSearchCV(estimator, param_grid)
gbm.fit(X_train, y_train)
print('Best parameters found by grid search are:', gbm.best_params_)
[1]    valid_0's l1: 0.770574    valid_0's l2: 0.729519
Training until validation scores don't improve for 5 rounds
[2]    valid_0's l1: 0.736515    valid_0's l2: 0.662428
[3]    valid_0's l1: 0.70474    valid_0's l2: 0.602743
[4]    valid_0's l1: 0.673927    valid_0's l2: 0.548122
[5]    valid_0's l1: 0.647149    valid_0's l2: 0.498853
[6]    valid_0's l1: 0.619693    valid_0's l2: 0.454836
[7]    valid_0's l1: 0.59361    valid_0's l2: 0.41521
[8]    valid_0's l1: 0.568332    valid_0's l2: 0.378919
[9]    valid_0's l1: 0.546353    valid_0's l2: 0.346133
[10]    valid_0's l1: 0.523806    valid_0's l2: 0.316952
[11]    valid_0's l1: 0.502036    valid_0's l2: 0.290314
[12]    valid_0's l1: 0.481625    valid_0's l2: 0.266628
[13]    valid_0's l1: 0.461931    valid_0's l2: 0.245002
[14]    valid_0's l1: 0.445287    valid_0's l2: 0.225741
[15]    valid_0's l1: 0.427082    valid_0's l2: 0.20802
[16]    valid_0's l1: 0.409383    valid_0's l2: 0.190996
[17]    valid_0's l1: 0.392466    valid_0's l2: 0.175393
[18]    valid_0's l1: 0.377264    valid_0's l2: 0.162742
[19]    valid_0's l1: 0.36181    valid_0's l2: 0.150029
[20]    valid_0's l1: 0.348036    valid_0's l2: 0.139806
Did not meet early stopping. Best iteration is:
[20]    valid_0's l1: 0.348036    valid_0's l2: 0.139806
The rmse of prediction is: 0.37390595890720224
Feature importances: [1, 9, 40, 18]
Best parameters found by grid search are: {'learning_rate': 0.1, 'n_estimators': 40}


HH丶丶
29 声望8 粉丝