免责声明:本文是通过网络收集并结合自身学习等途径合法获取,仅作为学习交流使用,其版权归出版社或者原创作者所有,并不对涉及的版权问题负责。若原创作者或者出版社认为侵权,请联系及时联系,我将立即删除文章,十分感谢!
注:来源刘顺祥《从零开始学Python数据分析与挖掘》,版权归原作者或出版社所有,仅供学习使用,不用于商业用途,如有侵权请留言联系删除,感谢合作。
12.1 提升树算法
本书的第7章介绍了有关多元线性回归模型的相关知识点,该模型的构造实质上是将输入特征X进行加权运算,即
。本节所介绍的提升树算法与线性回归模型的思想类似,所不同的是该算法实现了多棵基础决策树f(x)的加权运算,最具代表的提升树为AdaBoost算法,即:
其中,F(x)是由M棵基础决策树构成的最终提升树,Fm-1(x)表示经过m-1轮迭代后的提升树,αm为第m棵基础决策树所对应的权重,fm(x)为第m棵基础决策树。除此之外,每一棵基础决策树的生成并不像随机森林那样,而是基于前一棵基础决策树的分类结果对样本点设置不同的权重,如果在前一棵基础决策树中将某样本点预测错误,就会增大该样本点的权重,否则会相应降低样本点的权重,进而再构建下一棵基础决策树,更加关注权重大的样本点。
按照这个思想,AdaBoost算法需要解决三大难题,即样本点的权重wmi如何确定、基础决策树f(x)如何选择以及每一棵基础决策树所对应的权重αm如何计算。为了解决这三个问题,还需要从提升树AdaBoost算法的损失函数着手。
12.1.1 AdaBoost算法的损失函数
对于分类问题而言,通常提升树的损失函数会选择使用指数损失函数;对于预测性问题,通常会选择平方损失函数。这里不妨以二分类问题为例(正负例分别用+1和-1表示),详细解说关于提升树损失函数的推导和延伸。
如上损失函数所示,未知信息为系数αm和基础树fm(x),即假设已经得知m-1轮迭代后的提升树Fm-1(x)之后,如何基于该提升树进一步求解第m棵基础决策树和相应的系数。如果提升树Fm-1(x)还能够继续提升, 就说明损失函数还能够继续降低,换句话说,如果将所有训练样本点带入损失函数中,一定存在一个最佳的αm和fm(x),使得损失函数尽量最大化地降低,即:
上面的式子还可以改写为:
其中,pmi=exp[-yiFm-1(xi)],由于pmi与损失函数中的αm和fm(x)无关, 因此在求解最小化的问题时只需重点关注部分。
对于而言,当第m棵基础决策树能够准确预测时,yi与fm(xi)的乘积为1,否则为-1,于是exp(-yiαmfm(xi))的结果为exp(- αm)或exp(αm),对于某个固定的αm而言,损失函数中的和式仅仅是关于αm的式子。所以,要想求得损失函数的最小值,首先得找到最佳的fm(x),使得所有训练样本点xi带入fm(x)后,误判结果越少越好,即最佳的fm(x)可以表示为:
其中,f表示所有可用的基础决策树空间,fm(x)∗就是从f空间中寻找到的第m轮基础决策树,它能够使加权训练样本点的分类错误率最小,I(yi≠fm(x))表示当第m棵基础决策树预测结果与实际值不相等时返回1。下一步需要求解损失函数中的参数αm,为了求解的方便,首先将损失函数改写为下面的式子:
其中, 可以被拆分为两部分,一部分是预测正确的样本点,另一部分是预测错误的样本点,即:
然后基于上文中改写后的损失函数求解最佳的参数αm,能够使得损失函数取得最小值。对损失函数中的αm求导,并令导函数为0:
如上α ∗即为基础决策树的权重,其中,
,表示基础决策树m的错误率。
在求得第m轮基础决策树fm(x)以及对应的权重αm后,便可得到经m 次迭代后的提升树Fm(x)=Fm-1(x)+αm∗fm(xi)∗,再根据pmi=exp[-yiFm-1(xi)], 进而可以计算第m+1轮基础决策树中样本点的权重wmi:wm+1,i=wmiexp[-yiαm∗fm(xi)∗]
为了使样本权重单位化,需要将每一个wm+1,i与所有样本点的权重和做商处理,即:
实际上,就是第m轮基础决策树的总损失值,然后将每一个样本点对应的损失与总损失的比值用作样本点的权重。
12.1.2 AdaBoost算法的操作步骤
AdaBoost算法在解决分类问题时,它的核心就是不停地改变样本点的权重,并将每一轮的基础决策树通过权重的方式进行线性组合。该算法在迭代过程中需要进行如下四个步骤:
(1) 在第一轮基础决策树f1(x)的构建中,会设置每一个样本点的权重w1i均为1/N。
(2) 计算基础决策树fm(x)在训练数据集上的误判率。
(3) 计算基础决策树fm(x)所对应的权重 。
(4) 根据基础决策树fm(x)的预测结果,计算下一轮用于构建基础决策树的样本点权重w ∗,该权重可以写成:
在如上的几个步骤中,需要说明三点,第一是关于基础决策树误判率em与样本点权重之间的关系,通过公式可知,实际上误判率em就是错分样本点权重之和;第二是关于权重 αm∗与基础决策树误判率em之间的关系,只有当第m轮决策树的误判率小于等于0.5时,该基础决策树才有意义,即误判率em越小于0.5,对应的权重 αm∗越大,进而说明误判率越m小的基础树越重要;第三是关于样本点权重的计算,很显然,根据公式可知,在第m轮决策树中样本点预测错误时对应的权重是预测正确样本点权重的exp(2α ∗)倍,进而可以使下一轮的基础决策树更加关注错分类的样本点。
AdaBoost算法也可以处理回归问题,对于回归提升树来说,它的核心就是利用第m轮基础树的残差值拟合第m+1轮基础树。算法在迭代过程中需要进行如下四个步骤:
(1) 初始化一棵仅包含根节点的树,并寻找到一个常数能够使损失函数达到极小值。
(2) 计算第m轮基础树的残差值rmi=yi-fm(xi)。
(3) 将残差值rmi视为因变量,再利用自变量的值对其构造第m+1 轮基础树fm+1(x)。
(4) 重复步骤(2)和(3),得到多棵基础树,最终将这些基础树相加得到回归提升树。
12.1.3 AdaBoost算法的简单例子
为了使读者能够理解AdaBoost算法在运算过程中的几个步骤,这里不妨以一个分类问题为例(来源于李航老师的《统计学习方法》),并通过手动方式求得最佳提升树。如表12-1所示,对于一个一维的自变量和对应的因变量数据来说,如何构造AdaBoost强分类器,具体步骤如 下:
步骤一:构建基础树f1(x)
初始情况下,将每个样本点的权重w1i设置为1/10,并构造一个误分类率最低的f1(x):
步骤二:计算基础树f1(x)的错误率e1
步骤三:计算基础树f1(x)的权重 α1
步骤四:更新样本点的权重w1i
W1=(0.07143,0.07143,0.07143,0.07143,0.07143, 0.07143,0.16667,0.16667,0.16667,0.07143)
所以得到第一轮加权后的提升树F(x)=0.4236f1(x),故可以根据分类器的判断标准sign(0.4236f1(x))得到相应的预测结果,见表12-2。
其中,函数sign(z)表示当z>0时返回+1,否则返回-1。根据表12-2中的结果可知,当x取值为6、7、8时,对应的预测结果是错误的,样本点的权重相对也是最大的。所以在进入第二轮基础树的构建时,模型会更加关注这三个样本点。
步骤一:构建基础树f2(x)
由于此时样本点的权重已经不完全相同,故该轮基础树会更加关注于第一轮错分的样本点,根据数据可知,可以构造一个误分类率最低的f2(x):
步骤二:计算基础树f2(x)的错误率e2
步骤三:计算基础树f2(x)的权重 α2
步骤四:更新样本点的权重w2i
W2=(0.0455,0.0455,0.0455,0.1667,0.1667,0.1667,0.1060,0.1060,0.1060,0.0455)
需要注意的是,这里样本点权重的计算是基于W1和α2的结果得到的,所以看见的权重有三种不同的结果。根据两棵基础树,可以组合为一个新的提升树:F(x)=0.4236f1(x)+0.6496f2(x),进而依赖判断标准,得到相应的预测结果,见表12-3。
如上结果所示,对于两棵基础树的F(x)来说,当x取值为3、4、5时,提升树的预测结果是错误的。同理,经计算后的样本权重W2中也是这三个样本点对应的值最大。接下来,继续进入第三轮基础树的构建, 此时模型会根据样本点权重的大小给予不同的关注度。
步骤一:构建基础树f3(x)
步骤二:计算基础树f3(x)的错误率e3
步骤三:计算基础树f3(x)的权重 α3
步骤四:更新样本点的权重w3i
W3=(0.125,0.125,0.125,0.102,0.102, 0.102,0.065,0.065,0.065,0.125)
其中,样本权重W3的值依赖于W2和α3。进一步得到包含三棵基础树的提升树,它们与各种权重的线性组合可以表示为F(x)=0.4236f1(x)+0.6496f2(x)+0.7514f3(x),进而根据判断标准,得到如下的预测结果,见表12-4。
如表12-4的预测结果所示,经过三轮之后的提升过程,AdaBoost模型可以百分之百准确地预测样本点所属的类别。所以,基于该样本运用提升树算法,求得最佳的提升树模型为F(x)=0.4236f1(x)+0.6496f2(x)+0.7514f3(x)。
12.1.4 AdaBoost算法的应用
前面通过简单的案例讲解了有关AdaBoost算法在求解分类问题中所涉及的几个步骤,除此,该算法还可以解决预测性问题。在Python中可以非常方便地将其实现落地,读者只需导入sklearn的子模块ensemble, 并从中调入AdaBoostClassifier类或AdaBoostRegressor类,其中AdaBoostClassifier用于解决分类问题,AdaBoostRegressor则用于解决预测问题。有关这两个类的语法和参数含义如下:
base_estimator:用于指定提升算法所应用的基础分类器,默认为分类决策树(CART),也可以是其他基础分类器,但分类器必须支持带样本权重的学习,如神经网络。
n_estimators:用于指定基础分类器的数量,默认为50个,当模型在训练数据集中得到完美的拟合后,可以提前结束算法,不一定非得构建完指定个数的基础分类器。
learning_rate:用于指定模型迭代的学习率或步长,即对应的提升模型F(x)可以表示为F(x)=Fm-1(x)+υαmfm(x),其中的υ就是该参数的指定值,默认值为1;对于较小的学习率υ而言,则需要迭代更多次的基础分类器,通常情况下需要利用交叉验证法确定合理的基础分类器个数和学习率。
algorithm:用于指定AdaBoostClassifier分类器的算法,默认为'SAMME.R',也可以使用'SAMME';使用'SAMME.R'时,基础模型必须能够计算类别的概率值;一般而言,'SAMME.R'算法相于'SAMME'算法,收敛更快、误差更小、迭代数量更少。loss:用于指定AdaBoostRegressor回归提升树的损失函数,可以是'linear',表示使用线性损失函数;也可以是'square',表示使用平方损失函数;还可以是'exponential',表示使用指数损失函数; 该参数的默认值为'linear'。
random_state:用于指定随机数生成器的种子。
需要说明的是,不管是提升分类器还是提升回归器,如果基础分类器使用默认的CART决策树,都可以调整决策树的最大特征数、树的深度、内部节点的最少样本量和叶子节点的最少样本量等;对于回归提升树AdaBoostRegressor而言,在不同的损失函数中,第k个基础分类器样本点损失值的计算也不相同。
(1) 线性损失函数
(2) 平方损失函数
(3) 指数损失函数
其中,Ek表示第k个基础分类器在训练样本点上的最大误差,它的计算表达式可以写成Ek=max|yi-fk(xi)|。本节中关于提升树的应用实战将以信用卡违约数据为例,该数据集来源于UCI网站,一共包含30000条记录和25个变量,其中自变量包含客户的性别、受教育水平、年龄、婚姻状况、信用额度、6个月的历史还款状态、账单金额以及还款金额,因变量y表示用户在下个月的信用卡还款中是否存在违约的情况(1表示违约,0表示不违约)。首先绘制饼图,查看因变量中各类别的比例差异,代码如下:
见图12-1。
如图12-1所示,数据集中违约客户占比为22.1%,不违约客户占比为77.9%,总体来说,两个类别的比例不算失衡(一般而言,如果两个类别比例为9:1,则认为失衡;如果比例为99:1,则认为严重失衡)。接下来,基于这样的数据构建AdaBoost模型,代码如下:
如上结果所示,在调用AdaBoost类构建提升树算法时,使用了“类”中的默认参数值,返回的模型准确率为81.25%。并且预测客户违约(因变量y取1)的精准率为68%、覆盖率为32%;预测客户不违约(因变量y取0)的精准率为83%、覆盖率为96%。可以基于如上的预测结果,绘制算法在测试数据集上的ROC曲线,代码如下:
见图12-2。
如图12-2所示,ROC曲线下的面积为0.78,接近于0.8,可知AdaBoost算法在该数据集上的表现并不是特别突出。试问是否可以通过模型参数的调整改善模型的预测准确率呢?接下来通过交叉验证方法, 选择相对合理的参数值。在参数调优之前,基于如上的模型寻找影响客户是否违约的重要因素,进而做一次特征筛选,代码如下:
# 自变量的重要性排序importance = pd.Series(AdaBoost1.feature_importances_, index importance.sort_values().plot(kind = 'barh')plt.show()
见图12-3。
如图12-3所示,可以一目了然地发现重要的自变量,如客户在近三期的支付状态(PAY_0,PAY_1,PAY_2)、支付金额(PAY_AMT1、PAY_AMT2、PAY_AMT3)、账单金额(BILL_AMT1、BILL_AMT2、BILL_AMT3)和信用额度(LIMIT_BAL);而客户的性别、受教育水平、年龄、婚姻状况、更早期的支付状态、支付金额等并不是很重要。接下来就基于这些重要的自变量重新建模,并使用交叉验证方法获得最佳的参数组合,代码如 下:
如上结果所示,经过5重交叉验证的训练和测试后,对于基础模型CART决策树来说,最大的树深度选择为3层比较合理。需要说明的是, 在对AdaBoost算法做交叉验证时,有两层参数需要调优,一个是基础模型的参数,即DecisionTreeClassifier类;另一个是提升树模型的参数, 即AdaBoostClassifier类。在对基础模型调参时,参数字典params1中的键必须以“base_estimator ”开头,因为该参数是嵌在AdaBoostClassifier 类下的DecisionTreeClassifier类中。
如上是对基础模型CART决策树做的参数调优过程,还需要基于这个结果对AdaBoost算法进行参数调优,代码如下:
如上结果所示,经过5重交叉验证后,得知AdaBoost算法的最佳基础模型个数为300、学习率为0.01。到目前为止,参数调优过程就结束了(仅仅涉及基础模型CART决策树的深度、提升树中包含的基础模型个数和学习率)。如果读者还需探索其他更多的参数值,只需对如上代码稍做修改即可(修改params1和params2)。基于如上的调参结果重新构造AdaBoost模型,并检验算法在测试数据集上的预测效果,代码如下:
如上结果所示,经过调优后,模型在测试数据集上的预测准确率为81.6%,相比于默认参数的AdaBoost模型,准确率仅提高0.35%。
12.2 梯度提升树算法
梯度提升树算法实际上是提升算法的扩展版,在原始的提升算法 中,如果损失函数为平方损失或指数损失,求解损失函数的最小值问题会非常简单,但如果损失函数为更一般的函数(如绝对值损失函数或Huber损失函数等),目标值的求解就会相对复杂很多。为了解决这个问题,Freidman提出了梯度提升算法,即在第m轮基础模型中,利用损失函数的负梯度值作为该轮基础模型损失值的近似,并利用这个近似值构建下一轮基础模型。利用损失函数的负梯度值近似残差的计算就是梯度提升算法在提升算法上的扩展,这样的扩展使得目标函数的求解更为方便。GBDT算法属于梯度提升算法中的经典算法,接下来介绍有关该算法的具体步骤以及算法在预测和分类问题中的解决方案。
12.2.1 GBDT算法的操作步骤
GBDT算法同样可以解决分类问题和预测问题,算法在运行过程中都会执行如下几个步骤:
(1)初始化一棵仅包含根节点的树,并寻找到一个常数Const能够使损失函数达到极小值;
(2) 计算损失函数的负梯度值,用作残差的估计值,即:
(3) 利用数据集(xi,rmi)拟合下一轮基础模型,得到对应的J个叶子节点Rmj,j=1,2,…,J;计算每个叶子节点Rmj的最佳拟合值,用以估计残差rmi:
(4) 进而得到第m轮的基础模型fm(x),再结合前m-1轮的基础模型,得到最终的梯度提升模型:
如上几个步骤中,cmj表示第m个基础模型fm(x)在叶节点j上的预测 值;FM(x)表示由M个基础模型构成的梯度提升树,它是每一个基础模型在样本点xi处的输出值cmj之和。
与AdaBoost算法一样,GBDT算法也能够非常好地解决离散型因变量的分类和连续型因变量的预测。接下来按照上面介绍的5个步骤,分别讲解分类和预测问题在GBDT算法的实现过程。
12.2.2 GBDT分类算法
当因变量为离散的类别变量时,无法直接利用各个类别值拟合残差rmi(因为残差是连续的数值型)。为了解决这个问题,通常将GBDT算法的损失函数设置为指数损失函数或对数似然损失函数,进而可以实现残差的数值化。如果损失函数选择为指数损失函数,GBDT算法实际上退化为AdaBoost算法;如果损失函数选择为对数似然损失函数,GBDT 算法的残差类似于Logistic回归的对数似然损失。这里不妨以二分类问题为例,并选择对数似然损失函数,介绍GBDT分类算法的计算过程:
(1) 初始化一个弱分类器:
(2) 计算损失函数的负梯度值:
(3) 利用数据集(xi,rmi)拟合下一轮基础模型:
其中,
。
(4) 重复(2)和(3),并利用m个基础模型,构建梯度提升模型:
12.2.3 GBDT回归算法
如果因变量为连续的数值型变量,问题就会相对简单很多,因为输出的残差值本身就是数值型的。GBDT回归算法的损失函数就有比较多的选择了,例如平方损失函数、绝对值损失函数、Huber损失函数和分位数回归损失函数,这些损失函数都可以非常方便地进行一阶导函数的计算。这里不妨以平方损失函数为例,介绍GBDT回归算法的计算过 程:
(1) 初始化一个弱回归器:
(2) 计算损失函数的负梯度值:
(3) 利用数据集(xi,rmi)拟合下一轮基础模型:
其中,
(4) 重复(2)和(3),并利用m个基础模型,构建梯度提升模型:
12.2.4 GBDT算法的应用
在Python中同样可以非常方便地将GBDT算法落地,读者只需导入sklearn的子模块ensemble,并从中调入GradientBoostingClassifier类或GradientBoostingRegressor类,其中GradientBoostingClassifier用于解决分类问题,GradientBoostingRegressor则用于解决预测问题。有关这两个类的语法和参数含义如下:
loss:用于指定GBDT算法的损失函数,对于分类的GBDT,可以选择'deviance'和'exponential',分别表示对数似然损失函数和指数损失函数;对于预测的GBDT,可以选择'ls' 'lad' 'huber'和'quantile',分别表示平方损失函数、绝对值损失函数、Huber损失函数(前两种损失函数的结合,当误差较小时,使用平方损失,否则使用绝对值损失,误差大小的度量可使用alpha参数指定)和分位数回归损失函数(需通过alpha参数设定分位数)。
learning_rate:用于指定模型迭代的学习率或步长,即对应的梯度提升模型F(x)可以表示为FM(x)=FM-1(x)+υfm(x):,其中的υ就是该参数的指定值,默认值为0.1;对于较小的学习率υ而言,则需要迭代更多次的基础分类器,通常情况下需要利用交叉验证法确定合理的基础模型的个数和学习率。
n_estimators:用于指定基础模型的数量,默认为100个。subsample:用于指定构建基础模型所使用的抽样比例,默认为1,表示使用原始数据构建每一个基础模型;当抽样比例小于1 时,表示构建随机梯度提升树模型,通常会导致模型的方差降低,偏差提高。
criterion:用于指定分割质量的度量,默认为'friedman_mse',表示使用Friedman均方误差,还可以使用'mse'和'mae',分别表示均方误差和绝对误差。
min_samples_split:用于指定每个基础模型的根节点或中间节点能够继续分割的最小样本量,默认为2。
min_samples_leaf:用于指定每个基础模型的叶节点所包含的最小样本量,默认为1。
min_weight_fraction_leaf:用于指定每个基础模型叶节点最小的样本权重,默认为0,表示不考虑叶节点的样本权值。max_depth:用于指定每个基础模型所包含的最大深度,默认为3 层。
min_impurity_decrease:用于指定每个基础模型的节点是否继续分割的最小不纯度值,默认为0;如果不纯度超过指定的阈值, 则节点需要分割,否则不分割。
min_impurity_split:该参数同min_impurity_decrease参数,在sklearn的0.21版本及之后版本将删除。
init:用于指定初始的基础模型,用于执行初始的分类或预测。random_state:用于指定随机数生成器的种子,默认为None,表示使用默认的随机数生成器。
max_features:用于指定每个基础模型所包含的最多分割字段数,默认为None,表示分割时使用所有的字段;如果为具体的整数,则考虑使用对应的分割字段数;如果为0~1的浮点数,则考虑对应百分比的字段个数;如果为'sqrt',则表示最多考虑个字段,与指定'auto'效果一致;如果为'log2',则表示最多使用log2P个字段。其中,P表示数据集所有自变量的个数。verbose:用于指定GBDT算法在计算过程中是否输出日志信息, 默认为0,表示不输出。
alpha:当loss参数为'huber'或'quantile'时,该参数有效,分别用于指定误差的阈值和分位数。
max_leaf_nodes:用于指定每个基础模型最大的叶节点个数,默认为None,表示对叶节点个数不做任何限制。 warm_start:bool类型参数,表示是否使用上一轮的训练结果, 默认为False。
presort:bool类型参数,表示是否在构建基础模型时对数据进行预排序(用于快速寻找最佳分割点),默认为'auto'。当数据集比较密集时,该参数自动对数据集做预排序;当数据集比较稀疏 时,则无须预排序;对于稀疏数据来说,设置该参数为True时, 反而会提高模型的错误率。
本节的项目实战部分仍然使用上一节中所介绍的客户信用卡违约数据,并对比GBDT算法和AdaBoost算法在该数据集上的效果差异。首 先,利用交叉验证方法,测试GBDT算法各参数值的效果,并从中挑选出最佳的参数组合,代码如下:
如上结果所示,运用5重交叉验证方法对基础模型树的深度、基础模型个数以及提升树模型的学习率三个参数进行调优,得到的最佳组合值为5、100和0.05。而且验证后的最佳AUC值为0.77,进而利用这样的参数组合,对测试数据集进行预测,代码如下:
如上结果所示,GBDT模型在测试数据集上的预测效果与AdaBoost 算法基本一致,进而可以说明GBDT算法采用一阶导函数的值近似残差是合理的,并且这种近似功能也提升了AdaBoost算法求解目标函数时的便捷性。基于GBDT算法的预测结果,也可以绘制一幅ROC曲线图,代码如下:
见图12-4。
12.3 非平衡数据的处理
在实际应用中,读者可能会碰到一种比较头疼的问题,那就是分类问题中类别型的因变量可能存在严重的偏倚,即类别之间的比例严重失调。如欺诈问题中,欺诈类观测在样本集中毕竟占少数;客户流失问题中,忠实的客户往往也是占很少一部分;在某营销活动的响应问题中,真正参与活动的客户也同样只是少部分。
如果数据存在严重的不平衡,预测得出的结论往往也是有偏的,即分类结果会偏向于较多观测的类。对于这种问题该如何处理呢?最简单粗暴的办法就是构造1:1的数据,要么将多的那一类砍掉一部分(欠采样),要么将少的那一类进行Bootstrap抽样(过采样)。但这样做会存在问题,对于第一种方法,砍掉的数据会导致某些隐含信息的丢失;而第二种方法中,有放回的抽样形成的简单复制,又会使模型产生过拟 合。
为了解决数据的非平衡问题,2002年Chawla提出了SMOTE算法, 即合成少数过采样技术,它是基于随机过采样算法的一种改进方案。该技术是目前处理非平衡数据的常用手段,并受到学术界和工业界的一致认同,接下来简单描述一下该算法的理论思想。
SMOTE算法的基本思想就是对少数类别样本进行分析和模拟,并将人工模拟的新样本添加到数据集中,进而使原始数据中的类别不再严重失衡。该算法的模拟过程采用了KNN技术,模拟生成新样本的步骤如下:
(1) 采样最邻近算法,计算出每个少数类样本的K个近邻。
(2) 从K个近邻中随机挑选N个样本进行随机线性插值。
(3) 构造新的少数类样本。
(4) 将新样本与原数据合成,产生新的训练集。
为了使读者理解SMOTE算法实现新样本的模拟过程,可以参考图12-5 和人工新样本的生成过程。
如图12-5所示,实心圆点代表的样本数量要明显多于五角星代表的样本点,如果使用SMOTE算法模拟增加少类别的样本点,则需要经过如下几个步骤:
(1)利用第11章所介绍的KNN算法,选择离样本点x1最近的K个同类样本点(不妨最近邻为5)。
(2)从最近的K个同类样本点中,随机挑选M个样本点(不妨设M 为2),M的选择依赖于最终所希望的平衡率。
(3)对于每一个随机选中的样本点,构造新的样本点。新样本点的构造需要使用下方的公式:
xnew=xi+rand(0,1)×(xj-xi),j=1,2,…,M其中,xi表示少数类别中的一个样本点(如图12-5中五角星所代表的x1样本);xj表示从K近邻中随机挑选的样本点j;rand(0,1)表示生成0~1的随机数。
假设图12-5中样本点x1的观测值为(2,3,10,7),从图中的5个近邻随机挑选两个样本点,它们的观测值分别为(1,1,5,8)和(2,1,7,6),由此得到的两个新样本点为:
xnew1=(2,3,10,7)+0.3×((1,1,5,8)-(2,3,10,7))=(1.7,2.4,8.5,7.3)
xnew2=(2,3,10,7)+0.26×((2,1,7,6)-(2,3,10,7))=(2,2.48,9.22,6.74)
(4)重复步骤(1)、(2)和(3),通过迭代少数类别中的每一个样本xi,最终将原始的少数类别样本量扩大为理想的比例。
通过SMOTE算法实现过采样的技术并不是太难,读者可以根据上面的步骤自定义一个抽样函数。当然,读者也可以借助于imblearn模块,并利用其子模块over_sampling中的SMOTE“类”实现新样本的生 成。有关该“类”的语法和参数含义如下:
SMOTE(ratio='auto', random_state=None, k_neighbors=5, m_neig out_step=0.5, kind='regular', svm_estimator=None, n_jo
ratio:用于指定重抽样的比例,如果指定字符型的值,可以是'minority'(表示对少数类别的样本进行抽样)、'majority'(表示对多数类别的样本进行抽样)、'not minority'(表示采用欠采样法)、'all'(表示采用过采样方法),默认为'auto',等同于'all'和'not minority'。如果指定字典型的值,其中键为各个类别标签,值为类别下的样本量。
random_state:用于指定随机数生成器的种子,默认为None,表示使用默认的随机数生成器。
k_neighbors:指定近邻个数,默认为5个。
m_neighbors:指定从近邻样本中随机挑选的样本个数,默认为10个。
kind:用于指定SMOTE算法在生成新样本时所使用的选项,默认为'regular',表示对少数类别的样本进行随机采样,也可以是'borderline1' 'borderline2'和'svm'。
svm_estimator:用于指定SVM分类器,默认为sklearn.svm.SVC, 该参数的目的是利用支持向量机分类器生成支持向量,然后生成新的少数类别的样本。
n_jobs:用于指定SMOTE算法在过采样时所需的CPU数量,默认为1表示仅使用1个CPU运行算法,即不使用并行运算功能。
12.4 XGBoost算法
XGBoost是由传统的GBDT模型发展而来的,在上一节中,GBDT 模型在求解最优化问题时应用了一阶导技术,而XGBoost则使用损失函数的一阶和二阶导,更神奇的是用户可以自定义损失函数,只要损失函数可一阶和二阶求导。除此,XGBoost算法相比于GBDT算法还有其他优点,例如支持并行计算,大大提高算法的运行效率;XGBoost在损失函数中加入了正则项,用来控制模型的复杂度,进而可以防止模型的过拟合;XGBoost除了支持CART基础模型,还支持线性基础模型; XGBoost采用了随机森林的思想,对字段进行抽样,既可以防止过拟 合,也可以降低模型的计算量。既然XGBoost算法有这么多优点,接下来就详细研究一下该算法背后的理论知识。
12.4.1 XGBoost算法的损失函数
正如前文所说,提升算法的核心思想就是多个基础模型的线性组合,对于一棵含有t个基础模型的集成树来说,该集成树可以表示为:
其中, 表示经第t轮迭代后的模型预测值, 表示已知t-1个基础模型的预测值,ft(xi)表示第t个基础模型。按照如上的集成树,关键点就是第t个基础模型ft的选择。对于该问题,如前文提升算法中所提及的,只需要寻找一个能够使目标函数尽可能最大化降低的ft即可,故构造的目标函数如下:
其中,Ω(fj)为第j个基础模型的正则项,用于控制模型的复杂度。为了简单起见,不妨将损失函数L表示为平方损失,则如上的目标函数可以表示为
由于前t-1个基础模型是已知的,故的预测值也是已知的,同时前t-1个基础模型的复杂度也是已知的,故不妨将所有的已知项设为常数constant,则目标函数可以重新表达为:
其中, 项就是前t-1个基础模型所产生的残差,说明目标函数的选择与前t-1个基础模型的残差相关,这一点与GBDT是相同的。如上是假设损失函数为平方损失,对于更一般的损失函数来说,可以使用泰勒展开对损失函数值做近似估计。根据泰勒展开式:f(x+Δx)≈f(x)+f(x)'Δx+f(x)''Δx2其中,f(x)是一个具有二阶可导的函数,f(x)'为f(x)的一阶导函数,f(x)''为f(x)的二阶导函数,Δx为f(x)在某点处的变化量。假设令损失函数L为泰勒公式中的f,令损失函数中项为泰勒公式中的x,令损失函数中ft(xi)项为泰勒公式中的Δx,则目标函数Obj(t)可以近似表示为:
其中,gi和hi分别是损失函数 关于的一阶导函数值和二阶导函数值,即它们可以表示为:
所以,在求解目标函数Obj(t)的最优化问题时,需要用户指定一个可以计算一阶导和二阶导的损失函数,进而可知每个样本点所对应的值、gi值和hi值。这样一来,为求解关于ft(xi)的目标函数Obj(t),只需求解第t个基础模型ft所对应的正则项Ω(ft)即可,那Ω(ft)该如何求解呢?
12.4.2 损失函数的演变
假设基础模型ft由CART树构成,对于一棵树来说,它可以被拆分为结构部分q,以及叶子节点所对应的输出值w。可以利用这两部分反映树的复杂度,即复杂度由树的叶子节点个数(反映树的结构)和叶子节点输出值的平方构成:
其中,T表示叶子节点的个数, 表示输出值向量的平方。CART 树生长得越复杂,对应的T越大,Ω(ft)也越大。根据上面的复杂度方程,可以将目标函数Obj(t)改写为:
如上推导所示,由于是关于前t-1个基础模型的损失值, 它是一个已知量,故将其归纳至常数项constant中;wq(xi)表示第i个样本点的输入值xi所对应的输出值;i∈Ij表示每个叶子节点j中所包含的样本集合。在如上的推导过程中,最关键的地方是倒数第二行,非常巧妙地将样本点的和转换为叶子节点的和,从而降低了算法的运算量。对于目标函数Obj(t)而言,我们是希望求解它的最小值,故可以将推导结果中的常数项忽略掉,进而目标函数重新表示为:
其中,Gj=∑i∈Ijgi;Hj=∑i∈Ijhi。它们分别表示所有属于叶子节点j的样本点对应的gi之和以及hi之和。所以,最终是寻找一个合理的ft,使得式子尽可能大地减小。
由于构建XGBoost模型之前需要指定某个损失函数L(如平方损失、指数损失、Huber损失等),进而某种树结构q下的Gj和Hj是已知的。所以要想求得Obj(t)的最小化,需要对方程中的wj(每个叶子节点的
输出值)求偏导,并令导函数为0,即
所以,将wj的值导入到目标函数Obj(t)中,可得:
现在的问题是,树结构q该如何选择,即最佳的树结构q就对应了最佳的基础模型ft。最笨的方法就是测试不同分割字段和分割点下的树结构q,并计算它们所对应的J(ft)值,从而挑选出使J(ft)达到最小的树结构。很显然,这样枚举出所有的树结构q是非常不方便的,计算量也是非常大的,通常会选择贪心法,即在某个已有的可划分节点中加入一个分割,并通过计算分割前后的增益值决定是否剪枝。有关增益值的计算如下:
其中,GL和HL为某节点分割后对应的左支样本点的导函数值,GR 和HR为某节点分割后对应的右支样本点的导函数值。这里的增益值Gain 其实就是将某节点分割为另外两个节点后对应的目标值J(ft)的减少量。为了帮助读者理解这个增益的计算,可以参考图12-6。
其中,J1表示某个可分割节点在分割前的目标函数值,J2和J3则代表该节点按照某个变量x在a处的分割后对应的目标函数值。按照目标函数的公式,可以将这三个值表示为下方的式子:
根据增益值Gain的定义,可以计算得到J1-J2-J3所对应的值,即Gain。所以,在实际应用中,根据某个给定的增益阈值,对树的生长进行剪枝,当节点分割后产生的增益小于阈值时,剪掉该分割,否则允许分割。最终,根据增益值Gain来决定最佳树结构q的选择。
12.4.3 XGBoost算法的应用
XGBoost算法并不存在于sklearn模块中,读者需要另行下载xgboost 模块。作者在初次下载并调用该模块时产生了错误,考虑到读者也可能出现类似的错误,这里简单描述一下该模块的下载和安装过程(以64位的Window系统为例):
从 微 软 官 网 (https://www.microsoft.com/zh- CN/download/details.aspx?id=13523)下载vcredist_x64.exe文件, 并安装到电脑中。
从 Python 扩 展 库 的 平 台 (https://www.lfd.uci.edu/~ gohlke/pythonlibs/)下载对应版本(如Python 3.6版本)的xgboost 模块。
将下载的模块解压到某个磁盘路径中,在cmd窗口下,锁定到该解压文件所在的路径,并执行python setup.py install命令。
不出意外的话,xgboost模块就可以成功地安装在Python中,通过import将其激活导入,读者只需调用XGBClassifier类和XGBRegressor类即可。其中XGBClassifier用于解决分类问题,XGBRegressor则用于解决预测问题。有关这两个类的语法和参数含义如下:
max_depth:用于指定每个基础模型所包含的最大深度,默认为3层。
learning_rate:用于指定模型迭代的学习率或步长,默认为0.1, 即对应的梯度提升模型FT(x)可以表示为FT(x)=FT-1(x)+υft(x):,其中的υ就是该参数的指定值,默认值为1;对于较小的学习率υ而言,则需要迭代更多次的基础分类器,通常情况下需要利用交叉验证法确定合理的基础模型的个数和学习率。
n_estimators:用于指定基础模型的数量,默认为100个。silent:bool类型参数,是否输出算法运行过程中的日志信息,默认为True。
objective:用于指定目标函数中的损失函数类型,对于分类型的XGBoost算法,默认的损失函数为二分类的Logistic损失(模型返回概率值),也可以是'multi:softmax',表示用于处理多分类的损失函数(模型返回类别值),还可以是'multi:softprob',与'multi:softmax'相同,所不同的是模型返回各类别对应的概率值;对于预测型的XGBoost算法,默认的损失函数为线性回归损失。
booster:用于指定基础模型的类型,默认为'gbtree',即CART模型,也可以是'gblinear',表示基础模型为线性模型。
n_jobs:用于指定XGBoost算法在并行计算时所需的CPU数量, 默认为1表示仅使用1个CPU运行算法,即不使用并行运算功能。nthread:用于指定XGBoost算法在运行时所使用的线程数,默认为None,表示使用计算机最大可能的线程数。
gamma:用于指定节点分割所需的最小损失函数下降值,即增益值Gain的阈值,默认为0。
min_child_weight:用于指定叶子节点中各样本点二阶导之和的最小值,即Hj的最小值,默认为1,该参数的值越小,模型越容易过拟合。
max_delta_step:用于指定模型在更新过程中的步长,如果为0, 表示没有约束;如果取值为某个较小的正数,就会导致模型更加保守。
subsample:用于指定构建基础模型所使用的抽样比例,默认为1,表示使用原始数据构建每一个基础模型;当抽样比例小于1 时,表示构建随机梯度提升树模型,通常会导致模型的方差降低,偏差提高。
colsample_bytree:用于指定每个基础模型所需的采样字段比例, 默认为1,表示使用原始数据的所有字段。
colsample_bylevel:用于指定每个基础模型在节点分割时所需的采样字段比例,默认为1,表示使用原始数据的所有字段。reg_alpha:用于指定L1正则项的系数,默认为0。 reg_lambda:用于指定L2正则项的系数,默认为1。
scale_pos_weight:当各类别样本的比例十分不平衡时,通过设定该参数设定为一个正值,可以使算法更快收敛。
base_score:用于指定所有样本的初始化预测得分,默认为0.5。random_state:用于指定随机数生成器的种子,默认为0,表示使用默认的随机数生成器。
seed:同random_state参数。
missing:用于指定缺失值的表示方法,默认为None,表示NaN即为默认值。
本节的应用实战部分将以信用卡欺诈数据为例,该数据集来源于Kaggle网站,一共包含284807条记录和25个变量,其中因变量Class表示用户在交易中是否发生欺诈行为(1表示欺诈交易,0表示正常交
易)。由于数据中涉及敏感信息,已将原始数据做了主成分分析(PCA)处理,一共包含28个主成分。此外,原始数据中仅包含两个变量没有做PCA处理,即“Time”和“Amount”,分别表示交易时间间隔和交易金额。首先,需要探索一下因变量Class中各类别的比例差异,查看是否存在不平衡状态,代码如下:
见图12-7。
如图12-7所示,在284 807条信用卡交易中,欺诈交易仅占0.17%, 两个类别的比例存在严重的不平衡现象。对于这样的数据,如果直接拿来建模,效果一定会非常差,因为模型的准确率会偏向于多数类别的样本。换句话说,即使不建模,对于这样的二元问题,正确猜测某条交易为正常交易的概率值都是99.83%,而正确猜测交易为欺诈的概率几乎为0。试问是否可以通过建模手段,提高欺诈交易的预测准确率,这里不妨使用XGBoost算法对数据建模。建模之前,需要将不平衡数据通过SMOTE算法转换为相对平衡的数据,代码如下:
如上代码所示,首先将数据集拆分为训练集和测试集,并运用训练数据集构建XGBoost模型,其中测试数据集占30%的比重。由于训练数据集中因变量Class对应的类别存在严重的不平衡,即打印结果中欺诈交易占0.175%、正常交易占99.825%,所以需要使用SMOTE算法对其做平衡处理。如上结果所示,经SMOTE算法重抽样后,两个类别的比例达到平衡。接下来,利用重抽样后的数据构建XGBoost模型,代码如下:
如上结果所示,经过重抽样之后计算的模型在测试数据集上的表现非常优秀,模型的预测准确率超过99%,而且模型对欺诈交易的覆盖率高达88%(即正确预测为欺诈的交易量/实际为欺诈的交易量),对正常交易的覆盖率高达99%。如上的模型结果是基于默认参数的计算,读者可以进一步利用交叉验证方法获得更佳的参数组合,进而可以提升模型的预测效果。接下来,可以运用ROC曲线验证模型在测试数据集上的表现,代码如下:
见图12-8。
如图12-8所示,ROC曲线下的面积高达0.98,接近于1,说明XGBoost算法在该数据集上的拟合效果非常优秀。为了体现SMOTE算法在非平衡数据上的价值,这里不妨利用XGBoost算法直接在非平衡数据上重新建模,并比较重抽样前后模型在测试数据集上的预测效果,代码如下:
如上结果所示,对于非平衡数据而言,利用XGBoost算法对其建 模,产生的预测准确率非常高,几乎为100%,要比平衡数据构建的模型所得的准确率高出近1%。但是,由于数据的不平衡性,导致该模型预测的结果是有偏的,对正常交易的预测覆盖率为100%,而对欺诈交易的预测覆盖率不足75%。再对比平衡数据构建的模型,虽然正常交易的预测覆盖率下降1%,但是促使欺诈交易的预测覆盖率提升了近15%, 这样的提升是有必要的,降低了欺诈交易所产生的损失。
同理,也基于非平衡数据构造的模型,绘制其在测试数据集上的ROC曲线,并通过比对AUC的值,比对前后两个模型的好坏,代码如下:
见图12-9。
如图12-9所示,尽管AUC的值也是非常高的,但相对于平衡数据所构建的模型,AUC值要小0.1,进而验证了利用SMOTE算法实现数据的平衡是有必要的。通过平衡数据,可以获得更加稳定和更具泛化能力的模型。
12.5 本章小结
本章介绍了几种有别于第10章中的随机森林集成算法,它们分别是提升算法AdaBoost、梯度提升算法GBDT和升级版的梯度提升算法XGBoost,内容中包含这几种集成算法的理论思想、基础模型的构建过程以及相应的应用实战。此外,也介绍了非平衡数据的处理技术SMOTE算法,并通过验证发现,该技术可以增强模型的稳定性和泛化能力。
AdaBoost算法在解决分类问题时,是通过改变样本点的权重大小, 并将各个基础模型按权重实现线性组合,最终得到拟合数据的提升树; 在解决预测性问题时,每一轮基础模型都是拟合上一轮模型所形成的残差,最终将各个基础模型的预测值相加。不管是分类提升树还是回归提升树,都是将各个基础模型以串联形式构成最终的提升树。在回归提升树中,如果损失函数使用的是平方损失或指数损失,目标函数的求解会相对简单,为了能够使提升树适用于更多类型的损失函数,便诞生了梯度提升树(如GBDT算法),即利用损失函数的导函数作为残差的近似值,方便了运算也提高了提升树的灵活性。不管是AdaBoost算法还是GBDT算法,在构建目标函数时都没有加入反映模型复杂度的正则项, 而XGBoost算法则实现了正则项的加入,进而可以防止模型的过拟合, 并在求解最优化问题时利用了损失函数的一阶导和二阶导。相比于GBDT算法,XGBoost算法具有更多的优势,如支持并行计算、支持线性的基础模型、支持建模字段的随机选择等。
为了使读者掌握有关本章内容所涉及的函数和“方法”,这里将其重新梳理一下,以便读者查阅和记忆: