Stacking是用新的模型(次学习器)去学习怎么组合那些基学习器,它的思想源自于Stacked Generalization(http://www.machine-learning.martinsewell.com/ensembles/stacking/Wolpert1992.pdf)这篇论文。如果把Bagging看作是多个基分类器的线性组合,那么Stacking就是多个基分类器的非线性组合。Stacking可以很灵活,它可以将学习器一层一层地堆砌起来,形成一个网状的结构,如下图:
举个更直观的例子,还是那两道加法题:
这里A和B可以看作是基学习器,C、D、E都是次学习器。
[if !supportLists]· [endif]
Stage1: A和B各自写出了答案。
[if !supportLists]· [endif]
[if !supportLists]· [endif]
Stage2: C和D偷看了A和B的答案,C认为A和B一样聪明,D认为A比B聪明一点。他们各自结合了A和B的答案后,给出了自己的答案。
[if !supportLists]· [endif]
[if !supportLists]· [endif]
Stage3: E偷看了C和D的答案,E认为D比C聪明,随后E也给出自己的答案作为最终答案。
[if !supportLists]· [endif]
在实现Stacking时,要注意的一点是,避免标签泄漏(Label Leak)。在训练次学习器时,需要上一层学习器对Train Data的测试结果作为特征。如果我们在Train Data上训练,然后在Train Data上预测,就会造成Label Leak。为了避免Label Leak,需要对每个学习器使用K-fold,将K个模型对Valid Set的预测结果拼起来,作为下一层学习器的输入。如下图:
由图可知,我们还需要对Test Data做预测。这里有两种选择,可以将K个模型对Test Data的预测结果求平均,也可以用所有的Train Data重新训练一个新模型来预测Test Data。所以在实现过程中,我们最好把每个学习器对Train Data和对Test Data的测试结果都保存下来,方便训练和预测。
对于Stacking还要注意一点,固定K-fold可以尽量避免Valid Set过拟合,也就是全局共用一份K-fold,如果是团队合作,组员之间也是共用一份K-fold。如果想具体了解为什么需要固定K-fold,
举个例子,假设训练数据一共有x1, x2, x3, x4, x5, x6这6个,并且使用3-fold,在Stage1的时候使用两个Model。
1.使用固定的k-fold的情况如下图:
先看Stage1:可以看到m1-m6和n1-n2是相同的k-fold预测出来的结果,m1,m2由x3,x4,x5,x6预测所得,所以m1,m2包含了x3,x4,x5,x6的信息,以此类推。
Stage2:假设我要用如图中所示的Train Set来预测Valid Set,那么Train Set包含了x1-x6的信息,而Valid Set包含了x3-x6的信息。
看到这里你会疑惑,这有什么用呢?别急,再来看看Stage1的两个Model各自用不同的k-fold:
请仔细观察Model2的k-fold,现在n1,n6包含了x2-x5的信息,以此类推。
关键在于Stage2,现在Train Set也是包含了x1-x6的信息,而Valid Set也包含了x1-x6的信息,这就是不固定kfold与固定kfold的区别。尽管从固定kfold的图看来,它也有可能出现一定程度的过拟合,但不固定kfold它对Valid Set的过拟合情况会更加严重,所以按照Nutastray说的,通过kfold可以避免人为造成的过拟合。
可能你会问,那如果我们同一层的Stage固定k-fold,而不同层之间不固定,会发生什么?答案是,情况也会比固定k-fold要糟糕,具体的话可以按照上图画一下。所以最终给出的建议是,做Stacking最好还是固定k-fold,如果是团队合作完成项目,那就组员之间共享一份k-fold。
下面给出我的代码:bb=data_train.iloc[:, 4:6749]#dd=data_train.iloc[:, 3:4]cc = bb.apply(lambda x: x.fillna(x.mean()), axis=0)x_data = preprocessing.minmax_scale(cc.iloc[:, :].values, feature_range=(-1,1))cc['tag'] = data_train.iloc[:, 3:4]test = dfp.iloc[:, 2:6747].apply(lambda x: x.fillna(x.mean()), axis=0)#test_data = preprocessing.minmax_scale(test.iloc[:, :].values, feature_range=(-1,1))Xtest = list(test.columns.values)[4:6745]x_data_output = dfp.iloc[:, 0:1].values#print(data_train.iloc[:, 3:4])predictors = list(cc.columns.values)[4:6745]alg1 = lgb.LGBMClassifier(boosting_type='gbdt', num_leaves=42, max_depth=-1, learning_rate=0.054, n_estimators=490, subsample_for_bin=200, objective='binary', class_weight=None, min_split_gain=0.0, min_child_weight=1, min_child_samples=21, subsample=0.72, subsample_freq=1, colsample_bytree=0.63, reg_alpha=6.18, reg_lambda=2.718, random_state=142857, n_jobs=-1, silent=True, importance_type='split')alg2 = XGBClassifier(n_estimators=60,max_depth=9,min_child_weight=2,gamma=0.9,subsample=0.8,learning_rate=0.02, colsample_bytree=0.8,objective='binary:logistic',nthread=-1,scale_pos_weight=1)alg3 = GradientBoostingClassifier(learning_rate=0.01, n_estimators=600, max_depth=7, min_samples_leaf=60, min_samples_split=1200, max_features=9, subsample=0.7, random_state=10)lr = LogisticRegression()pipe1 = make_pipeline(ColumnSelector(cols=predictors[4:2000]), lr)pipe2 = make_pipeline(ColumnSelector(cols=predictors[2000:4000]), alg2)pipe3 = make_pipeline(ColumnSelector(cols=predictors[4000:5000]), alg3)sclf = StackingClassifier(classifiers=[lr, pipe2, pipe3], meta_classifier=alg1)# Compute the accuracy score for all the cross validation folds. (much simpler than what we did before!)# kf=cross_validation.KFold(data_train.shape[0],n_folds=10,random_state=1)kf = model_selection.KFold(n_splits=120, shuffle=False, random_state=1)scores = model_selection.cross_val_score(sclf, cc[predictors], cc['tag'], cv=kf)print("scores.mean=", scores.mean())File = open("data/prob_stackingLXG.txt", "w", encoding=u'utf-8', errors='ignore')File.write("id"+"," + "prob" + "\n")classifier = sclf.fit(cc[predictors], cc['tag'])predictiontest = classifier.predict_proba(test[Xtest])[:, 1]for step in range(len(test)): File.write(str(x_data_output[step][0])+"," + str(predictiontest[step]) + "\n")end = time.time()stamp = end - startprint("耗时", stamp / 3600)#print(predictiontest)
大家有感兴趣的可以自己拿代码测试一下。我的github完整测试代码地址如下:https://github.com/crystal-tensor/-360