ML-集成学习:AdaBoost、Bagging、随机森林、Stacking(mlxtend)、GBDT、XGBoost、LightGBM、CatBoost原理推导及实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jiang425776024/article/details/87885290

目录

1.名称简介

2.设计集成原则

3.集成学习算法分类

4.Boosting

4.1基本流程

4.2Adaboost实现

4.2.1.分类

4.2.2.回归

4.2.3Adaboost的正则化

4.2.4Adaboost小结

5.Bagging

5.1基本流程

6.随机深林Random Forest

6.1基本流程

7.Stacking

8.GBDT

8.1 GBDT回归

8.2GBDT分类

二分类

多分类

9.XGBoost

1)定义结构函数q(x)

 2)输出函数的叠加形式为:

3)第 t 轮的目标(损失)函数为:

4)结构评价函数 

5)确定最佳分裂准则

6)分裂流程

10.LightGBM

11.CatBoost


1.名称简介

集成学习:构建并结合多个学习器来完成学习任务;

基学习方法/强学习器:由一个现有算法的多个实例集成组合的方法。如n个决策树组成集成算法:随机森林。

基学习器/弱学习器:基学习方法中的个体学习器,如随机森林则的决策树算法;

组件学习器:不同算法共同集成中的个体学习器,如决策树和神经网络.....等多算法集成;

参考:https://www.cnblogs.com/pinard/p/6131423.html


2.设计集成原则

应该好而不同,如这图:

误差下降:

考虑二分类问题:y∈{-1,+1}和真实函数f,假设hi分类器的错误率都为\large \epsilon

则用T个基分类器用简单投票法,过半预测正确则集成结果预测正确,集成分类的结果:

假设基分类错误率相互独立,则集成器的错误率为:

结果显示随着基学习器个数T增大,错误率指数下降;但现实中基学习器是为同一个问题设计训练的,错误率不可能独立,基学习器的“准确性”与“多样性”基本矛盾不可同时最优,因此,如何设计好的“好而不同”个体学习器,是集成学习的核心。


3.集成学习算法分类

根据个体学习器生成方式:

1)个体学习器有强依赖,必须串行生成序列方法:Boosting(AdaBoost、GDBT、XGBoost)

2)不存在强依赖,可同时生成的并行方法:Bagging和 随机森林(Random Forest)


4.Boosting

4.1基本流程

基本流程:先从初始训练集训练一个基学习器 Gk;

再根据基学习器Gk的表现对训练样本分布做调整(先前基学习器识别错的训练样本在后续受到更多关注);

基于调整后的训练样本分布,训练出一个新的基学习器G(k+1);

反复进行,直到达到指定T个基学习器个数{G1,G2,...,GT},最终将这T个加权结合(G=a1*G1+a2*G2+...+aT*GT)

4.2Adaboost实现

Adaboost既可以用作分类,也可以用作回归。

4.2.1.分类

理论上可以选择任何一个分类或者回归学习器作为其基础学习器,不过需要支持样本权重

我们可以假设设计使用的基学习器是CART(https://blog.csdn.net/jiang425776024/article/details/87644983)分类决策树。

CART的优点就是可以分类和回归,这和AdaBoost一样,因此很适合做为其基础学习器,实际上,后面使用的sklearn对Adaboost的使用也是默认使用这个的。

1)定义样本权重

假设我们的训练集样本是:D={(x1,y1),...,(xm,ym)},为每个样本赋予一个权重wi,训练集在第 t 轮弱学习器的输出权重为:

初始分布权重都一样大为:

2)定义第 t 轮弱学习器的误差

   \large e_t = P(h_t(x_i) \neq y_i) = \sum\limits_{i=1}^{m}w_{ti}I(h_t(x_i) \neq y_i)

(还有另外一种,计算准确率,不过这样的话后面4)对样本权重的计算就改反了)

(也就是说,学习器如这里的CART,使用的时候还是不需要管其样本权重这个集成里面的概念的,其单独训练完成以后,样本权重最终使用的地方就在交叉验证等计算错误率的时候,乘上了权重,就是我们的弱学习器的误差了

3)定义第 t 轮学习器其本身的权重

(怎么来的后面解释推导)

\large \alpha_t = \frac{1}{2}log\frac{1-e_t}{e_t}

(可见,正确率/误差率,越小\large h_t的权重越大)

(可见,这里\large e_t>0.5的时候,如0.6:\large log\frac{0.4}{0.6}<0,是非法的,此时要么终止,要么重头/重新学习一个\large h_t

4)定义第 t轮学习器,权重分布D的更新方式

(怎么来的后面解释推导)

分错的f(x)ht(x)<0)权重越来越大,分对的(f(x)ht(x)>0)权重越来越小???因为这样得到的误差\large e_{k}就会越来越接近真实的误差,得到真实的误差,就能计算出更真实的加权值\large \alpha _{k},这样多个学习器加权累积起来,就可能抵消前面学习器的错误影响,集成出一个更真实的分类模型

(如果2)是基于计算准确率的,那么这里就要反一下,比如去掉里面的负号,那么分错的样本权重越小,对的越大)

5)最终的强学习器

Adaboost分类采用的是加权表决法,按照4.1那T轮,根据上面1)-4)的计算后,最终的强分类器为

\large f(x) = sign(\sum\limits_{t=1}^{T}\alpha_th_t(x))

即f(x)>0表示正例,....。看到这其实可以直接看结尾了,后面推导比较难。

6)对3)、4)的推导

这部分很需要数学知识,你也可以忽略。不影响使用。

对 3)的推导

思想,基于已知的样本权重(第一次设为均匀的1/m)分布\large D_{t}训练,产生第 t 个基分类器\large h_{t},进而计算错误率 \large \epsilon,先假定权重\large a_{t}已知,定义指数损失函数为:

因为上面最后的加法运算,左边加的e^(-at)*正确率,右边加的是e^(at)*错误率,所以,我们希望at能尽可最小化Dt分布下关于at*ht的指数损失函数,即,上面左右加起来后的结果趋于极小。因此转而对上面求导:

对 4)的推导

这个更恶心,可以不看。

在学得集成学习器Ht-1后,希望下一轮学到的基学习器能够纠正错误,理想的就是纠正所有错误,也就是最小化指数损失函数:

因此理想的下一个基学习器ht,为下式极大值对应的h:

构造出分布新的Dt:

加入当前集成学习期望(是常数,对argmax无影响),则ht可以变成D(x)分布到Dt(x)分布的转变

因为:

所以:

搞了这么多推算竟然得出这么个无关的结论,即,理想的下一个学习器是:期望真实值f(x)!=预测值h(x)尽可能小。不过,现在返回去,由上面的Dt分布,及关系E(XY)=E(X)E(Y),就能得到分布D的前后迭代更新形式了

解释一下其中符号,这些符号最终都是数字,能让分布转为一个概率:

 

所以,第4)步中的更新,只是把分布D写开了,写在一起就是上面那个更新形式。而第一次迭代的分布D为均匀的1/m。其它时候D中的m个样本对应的权重按照4)这个更新:

7)最终Adaboost分类流程

数据集:D={(x1,y1),...,(xm,ym)}

1)设置分布D0=1/m

2)for t=1,2,....,T(T个弱学习器,这个自己指定) do:

           ht=Algri(D,Dt)    //根据Dt样本权重分布,从数据集D中训练出弱模型,如CART

           epsilon_t=P(ht(x)!=f(x))   //上面的2)节形式计算第t轮弱学习器误差

            if epsilon_t>0.5 then break  //训练太差就不进一步学了,这样实际上得不到T个弱学习去,因此这种情况时,还可以从初始分布或者随机分布中学到ht。

            \large \alpha _{t}按照上面 3)节更新

            对分布Dt 中每个wti更新,按照4)节形式更新

3)end for

4) 按照5)节中形式加权累积起来作为集成模型输出。


分类方面的使用 

from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score

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

from sklearn import datasets

dataset = datasets.load_breast_cancer()
X = dataset.data
Y = dataset.target

seed = 1

# 10折交叉验证
kfold = KFold(n_splits=10, random_state=seed)
# 弱分类器:CART(criterion='gini'表示用CART),设置弱分类器maxdepth=3
dtree = DecisionTreeClassifier(criterion='gini', max_depth=3)

# dtree = dtree.fit(X, Y)
# result = cross_val_score(dtree, X, Y, cv=kfold)
# print(result.mean())

model = AdaBoostClassifier(base_estimator=dtree, n_estimators=100, random_state=seed)
result = cross_val_score(model, X, Y, cv=kfold)

print('10折交叉验证的分数:',result.mean())

#10折交叉验证的分数: 0.9666040100250626

4.2.2.回归

1)定义第 t 个弱学习器,训练集上的最大误差

\large E_t= max|y_i - h_t(x_i)|\;i=1,2...m

2)样本的误差

(相对误差)\large e_{ti}= \frac{|y_i - h_t(x_i)|}{E_t}、(平方误差)\large e_{ti}= \frac{(y_i - h_t(x_i))^2}{E_t^2}

(指数误差)\large e_{ti}= 1 - exp(\frac{-y_i + h_t(x_i))}{E_t})

3)第 t 个弱学习器的误差

\large e_t = \sum\limits_{i=1}^{m}w_{ti}e_{ti}

4)第t个学习器权重

因为回归问题误差是与真实值之间的差距而不是分类里面的是和否了,因此没必要像上面分类那样推导出权重,这里按照启发为:误差率/正确率。其实这并不固定的。

\large \alpha_t =\frac{e_t}{1-e_t}

(可见这里是错误率越高,\large h_t 权重越大。)

5)第 t 个弱学习器的第 i 个样本权重更新

\large w_{t+1,i}=\frac{w_{ti}}{Z_t}\alpha_t^{1-e_{ti}} ,Z_t=\sum_{i=1}^{m}w_{ti}\alpha_t^{1-e_{ti}}

6)最终集成学习器为

\large h_c(x)为T个\large a_th_t(x)乘积排列的中位数(不是固定值,每次根据x不同不一样),T为弱学习器的个数。

\large h(x)=\sum_{t=1}^{T}(ln\frac{1}{\alpha_t})h_c(x)

(可见,\large a_t越大,\large ln\frac{1}{\alpha_t}越小,此时作用在hc上的权值越小,而4)说了,错误率越大\large a_t越大,因此,错误率越大的作用在hc上越小)


回归方面的使用:

from sklearn.model_selection import KFold
from sklearn.model_selection import train_test_split
import numpy as np
from sklearn.ensemble import AdaBoostRegressor
from sklearn.tree import DecisionTreeRegressor
from sklearn.svm import LinearSVR
from matplotlib import pyplot as plt

from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure()
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
ax = Axes3D(fig)

seed = 1


def load_data_regression():
    # 构造X数据
    c=np.random.rand(220)
    X =np.array([c+2*i for i in range(2)]).T
    # 构造y数据, y = 1 * x_0 + 2 * x_1 + 3,后面打印参数会发现,是一致的
    y = np.dot(X, np.array([1, 2])) + 1

    return train_test_split(X, y, test_size=0.2, random_state=0)


X_train, X_test, y_train, y_test = load_data_regression()
# 10折交叉验证
kfold = KFold(n_splits=10, random_state=seed)
# 弱回归器:CART(criterion='gini'表示用CART),设置弱回归器maxdepth=3
dtree = DecisionTreeRegressor(max_depth=3)

# model = AdaBoostRegressor(base_estimator=LinearSVR(epsilon=0.01, C=100), n_estimators=200, random_state=seed)
model = AdaBoostRegressor(base_estimator=dtree, n_estimators=300, random_state=seed)
model.fit(X_train, y_train)

result = model.predict(X_test)
# # 计算准确率
cnt1 = 0
cnt2 = 0
for i in range(len(y_test)):
    if abs(result[i] - y_test[i]) < 0.1:
        cnt1 += 1
    else:
        cnt2 += 1

print("Accuracy: %.2f %% " % (100 * cnt1 / (cnt1 + cnt2)))

ax.scatter(X_train[:, 0], X_train[:, 1], y_train, marker='o')
ax.plot(X_test[:, 0], X_test[:, 1], result)
plt.show()

4.2.3Adaboost的正则化

为了防止Adaboost过拟合,我们通常也会加入正则化项,这个正则化项我们通常称为步长(learning rate)。定义为ν,对于前面的弱学习器的迭代:

\large f_{t}(x) = h_{t-1}(x) + \alpha_th_t(x)

加入正则化v,0<v<=1,较小的ν意味着我们需要更多的弱学习器的迭代次数。通常我们用步长和迭代最大次数一起来决定算法的拟合效果:

\large f_{t}(x) = h_{t-1}(x) + \nu\alpha_th_t(x)


4.2.4Adaboost小结

使用最广泛的Adaboost弱学习器是决策树和神经网络。对于决策树,Adaboost分类用了CART分类树,而Adaboost回归用了CART回归树

    这里对Adaboost算法的优缺点做一个总结。

    Adaboost的主要优点有:

    1)Adaboost作为分类器时,分类精度很高

    2)在Adaboost的框架下,可以使用各种回归分类模型来构建弱学习器,非常灵活。

    3)作为简单的二元分类器时,构造简单,结果可理解。

    4)不容易发生过拟合

    Adaboost的主要缺点有:

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


5.Bagging

Bagging算法是另外一类,可以并行计算,不像前面的AdaBoost,是一个依赖一个的串行的。这种集成算法数学性并不要求很高。简单易理解。

5.1基本流程

1)基于自助采样给定m个数据,有放回的采样m次,得到一个包含m个数据的数据集(有些会出现多次,有些不出现,但理论上只有63.2%的概率出现在集合中)。

2)自助采样T次,就得到了T个含m个数据的数据集。

3)T次采样每次采样完都训练出一个弱学习器,这样得到T个弱学习器了。

4)预测,对分类任务:使用投票多数表决了;回归:简单平均。


6.随机深林Random Forest

随机森林RF是Bagging的扩展变体,在其基础上,进一步在决策树上引入了随机性:传统是基于最优属性划分,RF先从属性集合中随机选出k个属性作为候选属性集,再从k个属性集中挑一个最佳属性(按照决策树属性划分的那些方式)作为划分点。显然k=1的时候就是每次都随机划分属性了,k=所有属性个数则就是传统决策树划分形式了。通常按照\large k=log_2d划分,其中d为所有属性数量。

(值得说明的是,随机森林通常优于Bagging,因为显然二者都是基于随机的,但是随机森林随机的更加彻底)

6.1基本流程

1)按照Bagging那样随机自助采样得到m个数据集

2)运用决策树算法,利用采样数据进行弱学习器学习,但是决策树每次划分的时候是在当且可选属性d中挑选k个(k<=d)然后再选择最优属性划分。

3)重复采样、学习T次,得到T个弱学习器,

4)和Bagging一样,对分类任务:使用投票多数表决了;回归:简单平均。


随机森林的使用:

from sklearn.model_selection import cross_val_score
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import ExtraTreesClassifier

from sklearn.tree import DecisionTreeClassifier

seed = 1
X, y = make_blobs(n_samples=1000, n_features=6, centers=50, random_state=seed)

# 普通决策树
clf = DecisionTreeClassifier(max_depth=None, min_samples_split=2, random_state=seed)
scores = cross_val_score(clf, X, y)
print(scores.mean())

# 随机森林
clf = RandomForestClassifier(n_estimators=10, max_depth=None, min_samples_split=2, random_state=seed)
scores = cross_val_score(clf, X, y)
print(scores.mean())

# RF随机森林的变种
clf = ExtraTreesClassifier(n_estimators=10, max_depth=None, min_samples_split=2, random_state=seed)
scores = cross_val_score(clf, X, y)
print(scores.mean())

7.Stacking

stacking产生方法是一种截然不同的组合多个模型的方法,它不像bagging和boosting是同质组合,而是组合不同的模型,具体的过程如下:

过程1-3 是训练出来个体学习器,也就是初级学习器,应该异质。

(过程7:每个数据xi经过T个初级学习器预测之后,会输出T个值\large z_i=[z_i_1,z_i_2,...,z_i_T],zi与xi的真正值yi组成新的数据D'=(zi,yi),这个数据会被用来在次级学习器中进行学习,产生m个这样的D',组成了次级学习器的训练集。

过程5-9是 使用训练出来的个体学习器来得预测的结果,当做次级学习器的训练集。

过程11 是用初级学习器预测的结果训练出次级学习器,得到我们最后训练的模型。

如果想要预测一个数据的输出,只需要把这条数据用初级学习器预测,然后将预测后的结果用次级学习器预测便可。


问题:这样的实现是有很大的缺陷的。在原始数据集D上面训练的模型,然后用这些模型在D上面再进行预测得到的次级训练集肯定是非常好的。会出现过拟合的现象。

改进:k折方式,初始训练集D划分为k份,每次选一份作为测试集\large D_j其余 \large D\D_j 作为训练集训练出弱学习器\large h_t^j,然后用测试集\large D_j中每一个测试样本经过T个学习器预测后,产生T个\large z_i=[z_i_1,z_i_2,...,z_i_T]的输出,作为次级学习器的输入。


使用:pip install mlxtend

初级学习器:高斯、KNN,RF

次级:逻辑回归

用于分类:

from sklearn import model_selection
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from mlxtend.classifier import StackingClassifier
import numpy as np

# 初级学习器
clf1 = KNeighborsClassifier(n_neighbors=1)
clf2 = RandomForestClassifier(random_state=1)
clf3 = GaussianNB()

# 次级学习器
lr = LogisticRegression()

# sclf = StackingClassifier(classifiers=[clf1, clf2, clf3],
#                           meta_classifier=lr)

#use_probas=True使用初级学习器的概率作为输入,没有的话直接使用其输出作为输入。
# average_probas=False,不使用初级的平均作为输入,拼接起来作为输入。
'''
classifier 1: [0.2, 0.5, 0.3]

classifier 2: [0.3, 0.4, 0.4]

   1) average = True : 

产生的meta-feature 为:[0.25, 0.45, 0.35]

   2) average = False:

产生的meta-feature为:[0.2, 0.5, 0.3, 0.3, 0.4, 0.4]
'''
sclf = StackingClassifier(classifiers=[clf1, clf2, clf3],
                          use_probas=True,
                          average_probas=False,
                          meta_classifier=lr)
# 3折交叉验证
print('3-fold cross validation:\n')

from sklearn import datasets

from sklearn import datasets

from sklearn.datasets.samples_generator import make_classification

# X为样本特征,Y为样本类别输出, 共1000个样本,每个样本2个特征,输出有3个类别,没有冗余特征,每个类别一个簇
X, y = make_classification(n_samples=1000, n_features=2, n_redundant=0,
                           n_clusters_per_class=1, n_classes=3)


for clf, label in zip([clf1, clf2, clf3, sclf],
                      ['KNN',
                       'Random Forest',
                       'Naive Bayes',
                       'StackingClassifier']):
    scores = model_selection.cross_val_score(clf, X, y,
                                             cv=3, scoring='accuracy')
    print("Accuracy: %0.2f (+/- %0.2f) [%s]"
          % (scores.mean(), scores.std(), label))

#
3-fold cross validation:

Accuracy: 0.96 (+/- 0.00) [KNN]
Accuracy: 0.97 (+/- 0.00) [Random Forest]
Accuracy: 0.95 (+/- 0.01) [Naive Bayes]
Accuracy: 0.97 (+/- 0.00) [StackingClassifier]

8.GBDT

GBDT也是迭代型的,可以用于分类和回归,弱学习器限定只能使用CART回归树模型,要注意的是这里的决策树是回归树,GBDT中的决策树是个弱模型,深度较小一般不会超过5,叶子节点的数量也不会超过10

8.1 GBDT回归

输入训练集样本:\large T=\{(x_,y_1),(x_2,y_2), ...(x_m,y_m)\},最大迭代次数T, 损失函数L。

输出是强学习器f(x)

1)假设前一轮学到的强学习器是:\large f_{t-1}(x)

如果是初始轮,就按照正常CART那样构建出来。\large f_0(x) = \underbrace{arg\; min}_{c}\sum\limits_{i=1}^{m}L(y_i, c)

2)定义损失为:\large L(y, f_{t-1}(x))

那么当前迭代学习的弱学习器,目的是希望找到一个CART回归树模型的弱学习器:\large h_t(x)让接下来的强学习器的损失:\large L(y, f_{t}(x)) =L(y, f_{t-1}(x)+ h_t(x))降到最低。

也就是希望找到这样一个新的CART回归决策树,让损失进一步下降

显然负梯度方向是函数下降方向,记 t 轮的第 i 个样本的损失函数的负梯度为:

\large r_{ti} = -\bigg[\frac{\partial L(y_i, f(x_i)))}{\partial f(x_i)}\bigg]_{f(x) = f_{t-1}\;\; (x)}

L可以这么选,GBDT通常是平方损失,也就是下1,因此对于\large x_i,其负梯度损失可以看作是\large y_i-f{(x_i)}

è¿éåå¾çæè¿°

3)构造出CART树

a)计算出每个样本的损失负梯度值(是个值),得到新的数据集(自变量及对应损失梯度值构成的集合):\large (x_i,r_{ti})\;\; (i=1,2,..m),就可以可以开始构造CART回归树了。

b)记叶子节点为:\large R_{tj}, j =1,2,..., J,J为叶子节点个数。假设叶子节点\large R_{tj}包含k个样本,我们希望这个叶子节点的输出能让节点内k个样的累积损失最小(因为每个节点都这样计算最佳c,整个树的最佳输出就找到了):

\large c_{tj} = \underbrace{arg\; min}_{c}\sum\limits_{x_i \in R_{tj}} L(y_i,f_{t-1}(x_i) +c)

c)根据CART构造规则,叶子节点的值为节点内数据的平均或中位数。但是现在叶子节点内部k个样本负梯度的均值记avgrtj是梯度均值。(问题是梯度只是个方向,梯度=0.1=100=无穷 >0,仅仅表示下一轮弱学习器应该往增大预测输出这个方向上去)(有些资料直接拿这个做输出也是可以的,就是学习率是1,然后执行了一次梯度下降嘛。但是不一定就是最佳输出,可以用一维线性搜索:https://blog.csdn.net/jiang425776024/article/details/87600422比如设置学习率因子为0.1,然后利用节点的那个平均负梯度值为梯度,\large c_0=0,c_i_+_1=c_i+0.1*avgrtj,.....直到n步后损失值收敛或不下降,这时累积找到的\large c_n作为最佳输出\large c_t_j=c_n

d)划分CART树就是正常那样方差最小划分,节点输出为基于节点负梯度均值为梯度下降方向,找到的最佳c为此节点的输出,这样,就能构造出新的CART树了。

4)得到CART决策树函数

\large h_t(x) = \sum\limits_{j=1}^{J}c_{tj}I(x \in R_{tj})

既,对于x做预测,其输出为x一路判断到叶子节点的那个区域的ctj。

5)得到新的强学习器

\large f_{t}(x) = f_{t-1}(x) + \sum\limits_{j=1}^{J}c_{tj}I(x \in R_{tj})

6)得到最终集成器

上面那样计算T次负梯度误差(残差),构造T个CART树,叠加起来就得到了最终的集成学习器:

\large 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})


 

8.2GBDT分类

二分类

输入训练集样本:\large T=\{(x_,y_1),(x_2,y_2), ...(x_m,y_m)\},最大迭代次数T, 损失函数L。

输出是强学习器f(x)

GBDT的分类算法从思想上和GBDT的回归算法没有区别,但是由于样本输出不是连续的值,而是离散的类别,导致我们无法直接从输出类别去拟合类别输出的误差。

为了解决这个问题,主要有两个方法,一个是用指数损失函数,此时GBDT退化为Adaboost算法。另一种方法是用类似于逻辑回归的对数似然损失函数的方法。也就是说,我们用的是类别的预测概率值和真实概率值的差来拟合损失。

1)这里记分类标签为-1,1,定义损失形式为:

(负二项对数似然)

\large L(y,f_t(x)))=log(1+exp(-2yf_t(x))),y \epsilon -1,1

其中初始时每个样本的预测输出为样本集中正负样本概率和的比:

\large f_0(x)=log\left(\frac{\sum_{i=1}^m y_i}{\sum_{i=1}^m(1-y_i)}\right)=log\left(\frac{num(p_1)}{num(p_-_1)}\right)

\large f_t(x)由后面累加CART树得到。

2)然后就能计算负梯度函数了:

\large r_{ti}=-[\frac{\partial L(y,f(x_{i}))}{\partial f(x_{i})}]_{f(x)=f_{t-1}(x)}=\frac{2y_{i}}{1+exp(2y_{i}f_{t-1}(x_{i}))}

3)叶子节点估计值:

\large c_{tj} = argmin_{c}\sum_{x_{i}\epsilon R_{tj}} log(1+exp(-2y_{i}(f_{t-1}(x_{i})+c)))

分类问题没有绝对意义上的步长和距离的概念,因此难以求解最佳值,所以改为近似求解:

\large c_{tj} = \sum\limits_{x_i \in R_{tj}}r_{ti}\bigg / \sum\limits_{x_i \in R_{tj}}|r_{ti}|(2 -|r_{ti}|)

4)这样,基于CART划分节点规则,基于上面对节点的输出,可以构造出CART回归树。

5)其它步骤与上面的回归一致。

\large f_{t}(x) = f_{t-1}(x) + \sum\limits_{j=1}^{J}c_{tj}I(x \in R_{tj})

\large 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})

6)最后预测形式

把上面得到的f(x),可以传入计算出概率:

\large P(y=1|x)=p= \frac{1}{1+e^{-2f(x)}}\large P(y=-1|x)=1-p= \frac{1}{1+e^{2f(x)}}

可以计算正负类的概率。


多分类

对于多分类任务,GDBT的做法是采用一对多的策略,也就是说,对每个类别训练T个分类器。假设有K个类别,那么训练完之后总共有T*K颗树。K个类别都拟合完第一颗树之后才开始拟合第二颗树,不允许先把某一个类别的M颗树学习完,再学习另外一个类别。

1)定义损失

使用多分类log损失作为损失函数

\large L(y, f(x)) = - \sum\limits_{k=1}^{K}y_klog\;p_k(x)  ,yk∈0,1.

2)定义概率p

对于K个类,初始第 0 次迭代,样本输出全部为0(论文就是这样的):\large f_{k,0}(x_i)=0,(K个都这样)

Softmax数值转化为相对概率,显然,初始时pk=第k类样本的占比,因为 fl(x)=0,e^0=1。

样本的第k个类的第t轮的概率求解,k=1,2,...,K:

\large p_{k,t}(x) = exp(f_{k,t}(x)) \bigg / \sum\limits_{l=1}^{K} exp(f_{l,t}(x))

3)xi第 t 轮的第k类 的负梯度误差

(多分类假设有K=5个类,已知xi是1类,那么yi=[0,1,0,0,0],yi1=1,其余为0,也就是说,每个样本要对K个类都计算出概率)

\large r_{tik} = -\bigg[\frac{\partial L(y_i, f(x_i)))}{\partial f(x_i)}\bigg]_{f_(x) = f_{k, t-1}\;\; (x)} = y_{ik} - p_{k, t-1}(x_i)

既为:第 i 个样本预测为 l 类的概率  -  第 t-1 轮第 i 个样本预测为l的概率

4)x的第t轮第j个叶子节点的第l类输出

\large c_{tjl} = \underbrace{arg\; min}_{c_{jl}}\sum\limits_{i=0}^{m}\sum\limits_{k=1}^{K} L(y_k, f_{l,t-1}(x) + \sum\limits_{j=0}^{J}c_{jl} I(x_i \in R_{tj}))

显然上式更加不易求最优,因此一般近似为

(每个数据xi会划分到K棵树的K个不同的叶子节点\large R_{tjl}内(j不一样),有K个不同的损失负梯度值\large r_{til},对应于xi的K个类的残差)

\large c_{tjl} = \frac{K-1}{K} \; \frac{\sum\limits_{x_i \in R_{tjl}}r_{til}}{\sum\limits_{x_i \in R_{til}}|r_{til}|(1-|r_{til}|)}

5)x的第 k类的第 t 轮输出

\large f_{k,t}(x) = f_{k,t-1}(x) + \sum\limits_{j=1}^{J}c_{tjk}I(x \in R_{tjk})

6)得到x的最终预测

T轮学完后,第k个类的输出函数为

\large f_k(x) = f_{k,T}(x) =f_{k,0}(x) + \sum\limits_{t=1}^{T}\sum\limits_{j=1}^{J}c_{tjk}I(x \in R_{tjk})

然后就可以计算出x的k类的概率:

\large p_{k}(x) = exp(f_{k}(x)) \bigg / \sum\limits_{l=1}^{K} exp(f_{l}(x))

组成x的预测:y=[p1(x),p2(x),...,pK(x)]


在sacikit-learn中,GradientBoostingClassifier为GBDT的分类类, 而GradientBoostingRegressor为GBDT的回归类

使用方法:https://www.cnblogs.com/pinard/p/6143927.html


9.XGBoost

Xgboost是GB算法的高效实现,xgboost中的基学习器除了可以是CART(gbtree)也可以是线性分类器(gblinear)。

xgboost在目标函数中显示的加上了正则化项,正则化项与树的叶子节点的数量T和叶子节点的值有关。

1)定义结构函数q(x)

其能返回x索引的叶子节点序号,Wq(x)则返回x索引序号的索引值。(就是给定x,返回其叶子节点的函数。T为叶子节点数。w为叶子节点输出组成的数组)

 2)输出函数的叠加形式为:

\begin{split}\hat{y}_i^{(0)} &= 0\\\hat{y}_i^{(1)} &= f_1(x_i) = \hat{y}_i^{(0)} + f_1(x_i)\\\hat{y}_i^{(2)} &= f_1(x_i) + f_2(x_i)= \hat{y}_i^{(1)} + f_2(x_i)\\&\dots\\\hat{y}_i^{(t)} &= \sum_{k=1}^t f_k(x_i)= \hat{y}_i^{(t-1)} + f_t(x_i)\end{split}

3)第 t 轮的目标(损失)函数为:

\begin{split}\text{Obj}^{(t)} & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t)}) + \sum_{i=1}^t\Omega(f_i) \\          & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t-1)} + f_t(x_i)) + \Omega(f_t) + constant\end{split}

其中预测损失函数,常用有:

平方损失:L(\theta) = \sum_i (y_i-\hat{y}_i)^2

Logistic损失:L(\theta) = \sum_i[ y_i\ln (1+e^{-\hat{y}_i}) + (1-y_i)\ln (1+e^{\hat{y}_i})]

 如果我们考虑使用平方误差作为损失函数,公式可改写为:

\begin{split}\text{Obj}^{(t)} & = \sum_{i=1}^n (y_i - (\hat{y}_i^{(t-1)} + f_t(x_i)))^2 + \sum_{i=1}^t\Omega(f_i) \\          & = \sum_{i=1}^n [2(\hat{y}_i^{(t-1)} - y_i)f_t(x_i) + f_t(x_i)^2] + \Omega(f_t) + constant\end{split}

我们可以采用如下的泰勒展开近似来定义一个近似的目标函数,方便我们进行这一步的计算:

f(x+\Delta  x)\simeq f(x)+f^{'}(x)\Delta x+\frac{1}{2} f^{''}(x)\Delta x^2

此时目标函数为: 

\text{Obj}^{(t)} = \sum_{i=1}^n [l(y_i, \hat{y}_i^{(t-1)}) + g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) + constant

其中g_i= \partial_{\hat{y}_i^{(t-1)}} l(y_i, \hat{y}_i^{(t-1)})h_i = \partial_{\hat{y}_i^{(t-1)}}^2 l(y_i, \hat{y}_i^{(t-1)})

如果移除掉常数项,我们会发现这个目标函数有一个非常明显的特点,它只依赖于每个数据点的在误差函数上的一阶导数和二阶导数

\sum_{i=1}^n [g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t)

把n个样本的累加转为遍历所有叶子节点:

记Ij为叶子节点 j 内样本集合:

则有: 

4)结构评价函数 

第j个叶子节点输出wj的偏导,求极小值

解得:

代入原函数,得到只与q有关的函数\large L_{t}(q)(什么东西?就是q(树结构)确定时,这个函数是确定的。问题是q不确定,所以后面转为反向计算其极小值时的最佳结构q,或者根据这个求得的值评价这个结构q优劣)类似于决策树寻找分裂时的不纯度指标:

假如结构q是这个样子,那么就能求出值了(认为是这个结构的分数。越小的时候这个结构越好): 

不过这里不符合直觉,既然是分数,就越大越好吧,所有通常改为计算 \large -L_{t}(q),记住,这里换了,此后评价按这个来,分数越大的结构越好

5)确定最佳分裂准则

XGBoost是一层层往下分的,先简单来说,最初只有根节点,1个叶子节点,包含整个的训练集样本,好了,这个时候怎么分裂为二变成3个节点,两个叶子节点呢?这个过程清楚了,其它所有节点都这样递归下去就行了。

先讲个例子:

a)原始树q是这样子,给定损失函数就能计算出一二阶导数,进而求得G,H,给定了\large \gamma,那么这个树q的分数就可以唯一确定了,计算\large -L_{t}(q)即可。

b)假设现在我们要对节点9进行分裂,假设我们通过某种划分把9中的数据划分成左右2个部分9L/9R了,记为节点12,13(划分方式,怎么划分为两个数据集,参考CART树的划分方式)。

c)那么现在我要比较两个树的分数了,减法嘛,小学生都知道,我们记减法后的结果为Gain,显然这是个数(Gain>0意味着划分后的结构分数更高,Gain=0意味着划分不划分都一样,Gain<0意味着划分后变差了):

(请详细看最后那步的结果,最终两颗树分数的相减结果,只与划分点9有关,这也很符合常识,因为这两颗树仅仅在9那里有区别,最后一步的第3项之所以能这样展开,是因为显然有这样的关系:\large set(9L)+set(9R)=set(9),H_{9L}+H_{9R}=H_9,G_{9L}^2+G_{9R}^2=G_{9}^2.

d)调参阀门 \large \gamma

显然,Gain>=<0,还由\large \gamma决定(\large \gamma=正无穷怎么划分都是<0的,\large \gamma=负无穷怎么划都是>0)显然这也是个炼丹术。称呼这个\large \gamma为新加入叶子节点引入的复杂度代价(因为每次划分后节点数增1,而XGBoost的损失评估考虑进去了模型复杂度),它的值越大,表示我们对切分后目标下降幅度要求越严

现在名正言顺的导出,很多资料没说为什么的Gain

代表左子树分数,代表右子树分数,代表不分割时我们可以获得的分数,\large \gamma为新加入叶子节点引入的复杂度代价。

我们希望划分后的这个Gain越大越好,因为这样就意味着划分后在的树结构qt+1比qt更好。通常节点划分后可能有很多个属性的很多个值的位置划分都能让这个分数Gain>0,显然,我们要取这些分数里面最大的,作为当前节点的划分

这里需要提的是,显然这样计算最佳划分的过程是可以并行的,把节点j下所有样本xi的hi,gi算出来得到Gj和Hj,然后扫描一遍(遍历每个属性的每个属性值,离散属性时划分为此属性值的集合(左集)和此属性其它值的集合(右集)。连续的值,每次把低于这个属性的为一个集合(左集),大于等于此属性的为一个集合(右集)。计算时只需要关注左集划入划出的样本即可,划入GjL+=gi,划出GjL-=gi,最后GjR=Gj-GjL。)

6)分裂流程

上面只是介绍最核心的一些原理,具体xgboost在实现时还做了许多优化:

在寻找最佳分割点时,考虑传统的枚举每个特征的所有可能分割点的贪心法效率太低,xgboost实现了一种近似的算法。大致的思想是根据百分位法列举几个可能成为分割点的候选者,然后从候选者中根据上面求分割点的公式计算找出最佳的分割点。

xgboost考虑了训练数据为稀疏值的情况,可以为缺失值或者指定的值指定分支的默认方向,这能大大提升算法的效率,paper提到50倍。

特征列排序后以块的形式存储在内存中,在迭代中可以重复使用;虽然boosting算法迭代必须串行,但是在处理每个特征列时可以做到并行。

按照特征列方式存储能优化寻找最佳的分割点,但是当以行计算梯度数据时会导致内存的不连续访问,严重时会导致cache miss,降低算法效率。paper中提到,可先将数据收集到线程内部的buffer,然后再计算,提高算法的效率。

xgboost 还考虑了当数据量比较大,内存不够时怎么有效的使用磁盘,主要是结合多线程、数据压缩、分片的方法,尽可能的提高算法的效率。

使用教程:https://blog.csdn.net/HHTNAN/article/details/81079257#Scikitlearn_700

全部参数介绍:http://xgboost.apachecn.org/#/docs/15

回归使用:

import numpy as np
import xgboost as xgb
from xgboost import plot_importance
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn.model_selection import train_test_split

fig = plt.figure()
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
ax = Axes3D(fig)

# read in the iris data
# 构造X数据
X = np.random.randint(0, 7, (2000, 2))
# 构造y数据, y = 1 * x_0 + 2 * x_1 + 3,后面打印参数会发现,是一致的
y = np.dot(X, np.array([1, 2])) + 3

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

# 训练模型
model = xgb.XGBRegressor(max_depth=5, learning_rate=0.1, n_estimators=100, silent=True, objective='reg:linear')
model.fit(X_train, y_train)

# 对测试集进行预测
ans = model.predict(X_test)
print(ans)
ax.scatter(X_train[:, 0], X_train[:, 1], y_train, marker='o')
ax.plot(X_test[:, 0], X_test[:, 1], ans)

# # 计算准确率
cnt1 = 0
cnt2 = 0
for i in range(len(y_test)):
    if abs(ans[i] - y_test[i])<0.1:
        cnt1 += 1
    else:
        cnt2 += 1

print("Accuracy: %.2f %% " % (100 * cnt1 / (cnt1 + cnt2)))

# 显示重要特征
plot_importance(model)
plt.show()

 分类使用:

from sklearn.datasets import load_iris
import xgboost as xgb
from xgboost import plot_importance
from matplotlib import pyplot as plt
from sklearn.model_selection import train_test_split

# read in the iris data
iris = load_iris()

X = iris.data
y = iris.target

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

# 训练模型
model = xgb.XGBClassifier(max_depth=5, learning_rate=0.1, n_estimators=160, silent=True, objective='reg:logistic')
model.fit(X_train, y_train)

# 对测试集进行预测
ans = model.predict(X_test)

# 计算准确率
cnt1 = 0
cnt2 = 0
for i in range(len(y_test)):
    if ans[i] == y_test[i]:
        cnt1 += 1
    else:
        cnt2 += 1

print("Accuracy: %.2f %% " % (100 * cnt1 / (cnt1 + cnt2)))

# 显示重要特征
plot_importance(model)
plt.show()

10.LightGBM

LightGBM是个快速的,分布式的,高性能的基于决策树算法的梯度提升框架。可用于排序,分类,回归以及很多其他的机器学习任务中。

ç´æ¹å¾ç®æ³

LightGBM的改进更多是工程上的改进,可能准确率并不会比XGBoost高,但是速度快很多。

è¿éåå¾çæè¿°

这里不会详细说这些工程细节,可以参考别人的介绍https://blog.csdn.net/weixin_38569817/article/details/78808535

使用:

1、安装Anaconda3

2、pip install lightgbm

官网:https://lightgbm.readthedocs.io/en/latest/Python-Intro.html

LightGBM 的所有参数介绍:http://lightgbm.apachecn.org/#/docs/6

调参使用:https://blog.csdn.net/u012735708/article/details/83749703


11.CatBoost

 Yandex 的 CatBoost 号称是比 XGBoost 和 LightGBM 在算法准确率等方面表现更为优秀的算法。

使用方法:https://github.com/catboost/catboost/blob/master/catboost/tutorials/python_tutorial.ipynb

猜你喜欢

转载自blog.csdn.net/jiang425776024/article/details/87885290