机器学习6:集成学习--boosting(AdaBoost)与GBDT

2019-08-08 15:38:23 浏览数 (1)

算法框架彼此融合产生新的算法:

1)Bagging 决策树 = 随机森林;

2)AdaBoost 决策树 = 提升树;

3)Gradient Boosting 决策树 =GBDT。

目录:

第一部分:Boosting与AdaBoost;

1,Boosting(AdaBoost)

2,Adboosting优缺点

3,Adaboost的应用场景

第二部分:GBDT;

1,GBDT算法原理

2,Shrinkage(缩减)

3,一个实例

4,GBDT应用场景

第三部分:代码实现;

1,adboosting实现

2,GBDT实现

GBDT相关知识模块:前向分布算法,负梯度拟合,损失函数,回归,二分类,多分类,正则化。

第一部分:Boosting与AdaBoost:

1,Boosting

Boosting是一簇可将弱学习器提升为强学习器的方法。这簇算法的工作机制相似:先从初始训练集训练出一个基学习器,再根据及学习器的表现对训练样本分布进行调整,使得先前基学习器做错的训练样本在后续受到更多关注,然后基于调整后的样本分布来训练下一个基学习器。如此重复进行,直至基学习器的数目达到事先指定的值T,最终将这T个基学习器进行加权结合。

从图中可以看出,Boosting算法的工作机制是首先从训练集用初始权重训练出一个弱学习器1,根据弱学习的学习误差率表现来更新训练样本的权重,使得之前弱学习器1学习误差率高的训练样本点的权重变高,使得这些误差率高的点在后面的弱学习器2中得到更多的重视。然后基于调整权重后的训练集来训练弱学习器2.,如此重复进行,直到弱学习器数达到事先指定的数目T,最终将这T个弱学习器通过集合策略进行整合,得到最终的强学习器。

Boosting系列算法里最著名算法主要有AdaBoost算法和提升树(boosting tree)系列算法。提升树系列算法里面应用最广泛的是梯度提升树(GradientBoosting Tree)。

下图是Adboosting的算法流程图:

下面我们举一个简单的例子来看看adaboost的实现过程:

图中,“ ”和“-”分别表示两种类别,在这个过程中,我们使用水平或者垂直的直线作为分类器,来进行分类。

第一步:

根据分类的正确率,得到一个新的样本分布D2­,一个子分类器h1 ;

其中划圈的样本表示被分错的。在右边的途中,比较大的“ ”表示对该样本做了加权。

第二步:

根据分类的正确率,得到一个新的样本分布D3,一个子分类器h2

第三步:

得到一个子分类器h3。

第四步:

  整合所有子分类器:

因此可以得到整合的结果,从结果中看,即使简单的分类器,组合起来也能获得很好的分类效果,在例子中所有的。

2,Adboosting优缺点:

Adaboost的主要优点有:

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

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

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

4)不容易发生过拟合

Adaboost的主要缺点有:

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

3,Adaboost的应用场景:

1)用于二分类或多分类的应用场景

2)用于做分类任务的baseline:

  无脑化,简单,不会overfitting,不用调分类器

3)用于特征选择(feature selection)

4)Boosting框架用于对badcase的修正:

只需要增加新的分类器,不需要变动原有分类器

  由于adaboost算法是一种实现简单,应用也很简单的算法。Adaboost算法通过组合弱分类器而得到强分类器,同时具有分类错误率上界随着训练增加而稳定下降,不会过拟合等的性质,应该说是一种很适合于在各种分类场景下应用的算法。

第二部分:GBDT:

1,GBDT算法原理:

GBDT也是集成学习Boosting家族的成员,但是却和传统的Adaboost有很大的不同。

GBDT(Gradient Boosting Decision Tree) 又叫 MART(Multiple Additive Regression Tree),是一种迭代的决策树算法,该算法由多棵决策树组成,所有树的结论累加起来做最终答案。它在被提出之初就和SVM一起被认为是泛化能力(generalization)较强的算法。

GBDT中的树都是回归树,不是分类树,这点对理解GBDT相当重要(尽管GBDT调整后也可用于分类但不代表GBDT的树是分类树)。

DT-Decision Tree决策树,GB是Gradient Boosting,是一种学习策略,GBDT的含义就是用Gradient Boosting的策略训练出来的DT模型。模型的结果是一组回归分类树组合(CART Tree Ensemble):T1……Tk。其中Tj学习的是之前j-1棵树预测结果的残差,这种思想就像准备考试前的复习,先做一遍习题册,然后把做错的题目挑出来,在做一次,然后把做错的题目挑出来在做一次,经过反复多轮训练,取得最好的成绩。

GBDT还内含了第三个概念:Shrinkage (算法的一个重要演进分枝,目前大部分源码都按该版本实现)。搞定这三个概念后就能明白GBDT是如何工作的,要继续理解它如何用于搜索排序则需要额外理解RankNet概念。

2,Shrinkage(缩减):

Shrinkage(缩减)的思想认为,每次走一小步逐渐逼近结果的效果,要比每次迈一大步很快逼近结果的方式更容易避免过拟合。即它不完全信任每一个棵残差树,它认为每棵树只学到了真理的一小部分,累加的时候只累加一小部分,通过多学几棵树弥补不足。

Shrinkage仍然以残差作为学习目标,但对于残差学习出来的结果,只累加一小部分(step*残差)逐步逼近目标,step一般都比较小,如0.01~0.001(注意该step非gradient的step),导致各个树的残差是渐变的而不是陡变的。直觉上这也很好理解,不像直接用残差一步修复误差,而是只修复一点点,其实就是把大步切成了很多小步。本质上,Shrinkage为每棵树设置了一个weight,累加时要乘以这个weight,但和Gradient并没有关系。这个weight就是step。就像Adaboost一样,Shrinkage能减少过拟合发生也是经验证明的,目前还没有看到从理论的证明。

因此,模型最后的输出,是一个样本在各个树中输出的结果的和:

3,一个实例:

下面以一个例子来直观GBDT算法:

上图很直观地体现了GradientBoosting的策略,而每一步的预测模型是回归树:Decision Tree决策树。

GBDT的核心就在于,每一棵树学的是之前所有树结论和的残差,这个残差就是一个加预测值后能得真实值的累加量。比如A的真实年龄是18岁,但第一棵树的预测年龄是12岁,差了6岁,即残差为6岁。那么在第二棵树里我们把A的年龄设为6岁去学习,如果第二棵树真的能把A分到6岁的叶子节点,那累加两棵树的结论就是A的真实年龄;如果第二棵树的结论是5岁,则A仍然存在1岁的残差,第三棵树里A的年龄就变成1岁,继续学。

4,应用场景:

GBDT几乎可用于所有回归问题(线性/非线性),相对logistic regression仅能用于线性回归,GBDT的适用面非常广。亦可用于二分类问题(设定阈值,大于阈值为正例,反之为负例)。

第三部分:代码实现;

1,Adboosting实现:

共分成4步:1,构造数据;2,构建adaboost模型,并训练模型;3,预测;4,可视化。

代码语言:javascript复制
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

from sklearn.ensemble import AdaBoostClassifier#adaboost引入方法
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import make_gaussian_quantiles#造数据

# 1,构造数据:
X1, y1 = make_gaussian_quantiles(cov=2.,
                                 n_samples=200, n_features=2,
                                 n_classes=2, random_state=1)#创建符合高斯分布的数据集
X2, y2 = make_gaussian_quantiles(mean=(3, 3), cov=1.5,
                                 n_samples=300, n_features=2,
                                 n_classes=2, random_state=1)

X = np.concatenate((X1, X2))
y = np.concatenate((y1, - y2   1))

# 2,构建adaboost模型,并训练模型:
bdt = AdaBoostClassifier(DecisionTreeClassifier(max_depth=2),
                         algorithm="SAMME.R",#可以不写
                         n_estimators=200)
#数据量大的时候,可以增加内部分类器的树深度,也可以不限制树深
#max_depth树深,数据量大的时候,一般范围在10——100之间
#数据量小的时候,一般可以设置树深度较小,或者n_estimators较小
#n_estimators 迭代次数或者最大弱分类器数:200次
#base_estimator:DecisionTreeClassifier 选择弱分类器,默认为CART树
#algorithm:SAMME 和SAMME.R 。运算规则,后者是优化算法,以概率调整权重,迭代速度快,
#需要能计算概率的分类器支持
#learning_rate:0<v<=1,默认为1,正则项 衰减指数
#loss:linear、‘square’exponential’。误差计算公式:一般用linear足够
bdt.fit(X, y)

plot_step = 0.02
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, plot_step),
                     np.arange(y_min, y_max, plot_step))

# 3,预测:
Z = bdt.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)  #设置维度

# 4,可视化:
plot_colors = "br"
class_names = "AB"

plt.figure(figsize=(10, 5), facecolor='w')

plt.subplot(121)  # 局部子图
plt.pcolormesh(xx, yy, Z, cmap=plt.cm.Paired) 
for i, n, c in zip(range(2), class_names, plot_colors):
    idx = np.where(y == i)
    plt.scatter(X[idx, 0], X[idx, 1],
                c=c, cmap=plt.cm.Paired,
                label=u"类别%s" % n)
plt.xlim(x_min, x_max)
plt.ylim(y_min, y_max)
plt.legend(loc='upper right')
plt.xlabel('x')
plt.ylabel('y')
plt.title(u'AdaBoost分类结果,正确率为:%.2f%%' % (bdt.score(X, y) * 100))

twoclass_output = bdt.decision_function(X)  # 获取决策函数的数值

plot_range = (twoclass_output.min(), twoclass_output.max())   # 获取范围
plt.subplot(122)
for i, n, c in zip(range(2), class_names, plot_colors):   #直方图

    plt.hist(twoclass_output[y == i],
             bins=20,
             range=plot_range,
             facecolor=c,
             label=u'类别 %s' % n,
             alpha=.5)
x1, x2, y1, y2 = plt.axis()
plt.axis((x1, x2, y1, y2 * 1.2))
plt.legend(loc='upper right')
plt.ylabel(u'样本数')
plt.xlabel(u'决策函数值')
plt.title(u'AdaBoost的决策值')

plt.tight_layout()
plt.subplots_adjust(wspace=0.35)
plt.show()

2,GBDT实现

共分成6步:1,加载数据;2,特征工程;3,构建一个调用模型的函数;4,训练模型;5,使用模型进行测试集样本预测; 6,保存预测结果。

代码语言:javascript复制
import pandas as pd
import numpy as np
from sklearn.ensemble import GradientBoostingClassifier
from sklearn import cross_validation, metrics
from sklearn.model_selection import GridSearchCV

import matplotlib.pylab as plt
%matplotlib inline

# 1,加载数据:
train = pd.read_csv("DataSets/Titanic/train.csv")
test = pd.read_csv("DataSets/Titanic/test.csv")
target='Survived' # Disbursed的值就是二元分类的输出
IDcol = 'PassengerId'

# 2,特征工程:
train = train.drop(['PassengerId','Name','Ticket','Cabin'],axis=1)   # 剔除无关数据
test = test.drop(['Name','Ticket','Cabin'], axis=1)
train["Embarked"] = train["Embarked"].fillna("S")

train['Fare'] = train['Fare'].astype(int)   #float 2 int
test.Fare.fillna(test.Fare.median(), inplace=True)   #插补缺失数据
test['Fare'] = test['Fare'].astype(int)

embark_dummies_train  = pd.get_dummies(train['Embarked'])     # pd.get_dummies()方法对离散数据重新编码,生成0-1矩阵
embark_dummies_test  = pd.get_dummies(test['Embarked'])
train = train.join(embark_dummies_train)   #合并矩阵
test = test.join(embark_dummies_test)
train.drop(['Embarked'], axis=1,inplace=True)   #删除原来的Embark列,inplace=Ture
test.drop(['Embarked'], axis=1,inplace=True)

average_age_titanic   = train["Age"].mean()   #Age 主要是对缺失的值进行处理填补
std_age_titanic       = train["Age"].std()
count_nan_age_titanic = train["Age"].isnull().sum()

average_age_test   = test["Age"].mean()
std_age_test       = test["Age"].std()
count_nan_age_test = test["Age"].isnull().sum()

rand_1 = np.random.randint(average_age_titanic - std_age_titanic, average_age_titanic   std_age_titanic, size = count_nan_age_titanic)
rand_2 = np.random.randint(average_age_test - std_age_test, average_age_test   std_age_test, size = count_nan_age_test)
train["Age"][np.isnan(train["Age"])] = rand_1    
test["Age"][np.isnan(test["Age"])] = rand_2
train['Age'] = train['Age'].astype(int)
test['Age']  = test['Age'].astype(int)

def get_person(passenger):  #Sex  考虑到小孩及妇女优先对待,分三类
    age,sex = passenger
    return "child" if age < 16 else sex

train['Person'] = train[['Age','Sex']].apply(get_person,axis=1)
test['Person']  = test[['Age','Sex']].apply(get_person,axis=1)

train.drop(['Sex'],axis=1,inplace=True)
test.drop(['Sex'],axis=1,inplace=True)

person_dummies_titanic  = pd.get_dummies(train['Person'])
person_dummies_titanic.columns = ['Child','Female','Male']

person_dummies_test  = pd.get_dummies(test['Person'])
person_dummies_test.columns = ['Child','Female','Male']

train = train.join(person_dummies_titanic)
test = test.join(person_dummies_test)
train.drop(['Person'],axis=1,inplace=True)
test.drop(['Person'],axis=1,inplace=True)

pclass_dummies_titanic  = pd.get_dummies(train['Pclass'])   #P class
pclass_dummies_titanic.columns = ['Class_1','Class_2','Class_3']

pclass_dummies_test  = pd.get_dummies(test['Pclass'])
pclass_dummies_test.columns = ['Class_1','Class_2','Class_3']

train.drop(['Pclass'],axis=1,inplace=True)
test.drop(['Pclass'],axis=1,inplace=True)

train = train.join(pclass_dummies_titanic)
test= test.join(pclass_dummies_test)

# 3,构建一个调用模型的函数:
def modelfit(alg, dtrain, predictors, performCV=True, printFeatureImportance=True, cv_folds=5):
    #Fit the algorithm on the data
    alg.fit(dtrain[predictors], dtrain['Survived'])

    #Predict training set:
    dtrain_predictions = alg.predict(dtrain[predictors])
    dtrain_predprob = alg.predict_proba(dtrain[predictors])[:,1]

    #Perform cross-validation:
    if performCV:
        cv_score = cross_validation.cross_val_score(alg, dtrain[predictors], dtrain['Survived'], cv=cv_folds, scoring='roc_auc')

    #Print model report:
    print ("nModel Report")
    print ("Accuracy : %.4g" % metrics.accuracy_score(dtrain['Survived'].values, dtrain_predictions))
    print ("AUC Score (Train): %f" % metrics.roc_auc_score(dtrain['Survived'], dtrain_predprob))

    if performCV:
        print ("CV Score : Mean - %.7g | Std - %.7g | Min - %.7g | Max - %.7g" % (np.mean(cv_score),np.std(cv_score),np.min(cv_score),np.max(cv_score)))

    #Print Feature Importance:
    if printFeatureImportance:
        feat_imp = pd.Series(alg.feature_importances_, predictors).sort_values(ascending=False)
        feat_imp.plot(kind='bar', title='Feature Importances')
        plt.ylabel('Feature Importance Score')

# 4,训练模型:
# 4.1,创建一个baseline model,不使用任何参数优化
predictors = [x for x in train.columns if x not in [target, IDcol]]
gbm0 = GradientBoostingClassifier(random_state=10)
modelfit(gbm0,train,predictors)

# 4.2,调参
predictors = [x for x in train.columns if x not in [target, IDcol]]
# 4.2.1
param_test1 = {'n_estimators':range(20,101,10),'learning_rate':[0.1,0.12,0.14,0.16]}  #首先从长和迭代次数开始进行网格搜索
gsearch1 = GridSearchCV(estimator = GradientBoostingClassifier(min_samples_split=10,min_samples_leaf=50,max_depth=8,max_features='sqrt',subsample=0.8,random_state=10), 
param_grid = param_test1, scoring='roc_auc',n_jobs=4,iid=False, cv=5)
gsearch1.fit(train[predictors],train[target])
gsearch1.grid_scores_, gsearch1.best_params_, gsearch1.best_score_
# 4.2.2
param_test2 = {'max_depth':range(2,16,2), 'min_samples_split':range(5,31,5)}   #上述步骤确定了最佳步长0.14,最佳迭代次数90;接下来对决策树进行调参,ma_depth和min_sampls_split
gsearch2 = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.14, n_estimators=90, max_features='sqrt', subsample=0.8, random_state=10), 
param_grid = param_test2, scoring='roc_auc',n_jobs=4,iid=False, cv=5)
gsearch2.fit(train[predictors],train[target])
gsearch2.grid_scores_, gsearch2.best_params_, gsearch2.best_score_
# 4.2.3
param_test3 = { 'min_samples_leaf':range(50,101,10)}   #上述步骤确定了最佳'max_depth': 4, 'min_samples_split': 25,深度小可能是由于数据量较小所致;接下来选择叶结点最小样本数
gsearch3 = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.14, n_estimators=90,max_depth=4,min_samples_split=25,max_features='sqrt', subsample=0.8, random_state=10), 
param_grid = param_test3, scoring='roc_auc',n_jobs=4,iid=False, cv=5)
gsearch3.fit(train[predictors],train[target])
gsearch3.grid_scores_, gsearch3.best_params_, gsearch3.best_score_
# 4.2.4
param_test4 = {'max_features':range(1,14,1)}   #上述步骤确定'min_samples_leaf': 70;接下来选择最大特征数
gsearch4 = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.14, n_estimators=90,max_depth=4,min_samples_leaf=70, min_samples_split=25,  subsample=0.8, random_state=10),
param_grid = param_test4, scoring='roc_auc',n_jobs=4,iid=False, cv=5)
gsearch4.fit(train[predictors],train[target])
gsearch4.grid_scores_, gsearch4.best_params_, gsearch4.best_score_
# 4.2.5
param_test5 = {'subsample':[0.6,0.7,0.75,0.8,0.85,0.9]}   # 上述步骤中最大特征数为3 感觉不符合实际,所以还是选择默认情况
gsearch5 = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.14, n_estimators=90,max_depth=4,min_samples_split=25, min_samples_leaf=70, random_state=10,max_features="sqrt"),
param_grid = param_test5, scoring='roc_auc',n_jobs=4,iid=False, cv=5)
gsearch5.fit(train[predictors],train[target])
gsearch5.grid_scores_, gsearch5.best_params_, gsearch5.best_score_


# 5,使用模型进行测试集样本预测:
Y_pred = gsearch5.predict(test.drop('PassengerId',axis=1))

predictors = [x for x in train.columns if x not in [target, IDcol]]
gbm_tuned_1 = GradientBoostingClassifier(learning_rate=0.07, n_estimators=180,max_depth=4, min_samples_split=25,min_samples_leaf=70, subsample=0.8, random_state=10, max_features="sqrt")
modelfit(gbm_tuned_1, train, predictors)

predictors = [x for x in train.columns if x not in [target, IDcol]]
gbm_tuned_1 = GradientBoostingClassifier(learning_rate=0.07, n_estimators=140,max_depth=3, min_samples_split=10,min_samples_leaf=30, subsample=0.85, random_state=10, max_features="sqrt")
modelfit(gbm_tuned_1, train, predictors)

# 6,保存预测结果
my_submission =pd.DataFrame({'PassengerId': test['PassengerId'].as_matrix(),'Survived': Y_pred.astype(np.int32)})
my_submission.to_csv("DataSets/Titanic/my_submission2.csv",index=False)

Y_pred = gbm0.predict(test.drop('PassengerId',axis=1))  # 看一下不做优化的预测情况,以作对比

my_submission =pd.DataFrame({'PassengerId': test['PassengerId'].as_matrix(),'Survived': Y_pred.astype(np.int32)})
my_submission.to_csv("DataSets/Titanic/my_submission2.csv",index=False)

0 人点赞