算法框架彼此融合产生新的算法:
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)