Datawhale 智慧海洋建设-Task5 模型融合
该部分学习如何进行模型的融合
内容介绍
1.简单加权融合:
回归(分类概率):算术平均融合(Arithmetic mean),几何平均融合(Geometric mean);
分类:投票(Voting)
2.boosting/bagging(在xgboost,Adaboost,GBDT中已经用到):
多树的提升方法
3.stacking/blending:
构建多层模型,并利用预测结果再拟合预测。
简单加权融合
平均法-Averaging
1.对于回归问题,一个简单直接的思路是取平均。将多个模型的回归结果取平均值作为最终预测结果,进而把多个弱分类器荣和城强分类器。
2.稍稍改进的方法是进行加权平均,权值可以用排序的方法确定,举个例子,比如A、B、C三种基本模型,模型效果进行排名,假设排名分别是1,2,3,那么给这三个模型赋予的权值分别是3/6、2/6、1/6。
3.平均法或加权平均法看似简单,其实后面的高级算法也可以说是基于此而产生的,Bagging或者Boosting都是一种把许多弱分类器这样融合成强分类器的思想。
4.Averaging也可以用于对分类问题的概率进行平均。
投票法-voting
1.对于一个二分类问题,有3个基础模型,现在我们可以在这些基学习器的基础上得到一个投票的分类器,把票数最多的类作为我们要预测的类别。
2.投票法有硬投票(hard voting)和软投票(soft voting)
3.硬投票: 对多个模型直接进行投票,不区分模型结果的相对重要度,最终投票数最多的类为最终被预测的类。
4.软投票:增加了设置权重的功能,可以为不同模型设置不同权重,进而区别模型不同的重要度。
stacking/blending
堆叠法-stacking
用初始训练数据学习出若干个基学习器后,将这几个学习器的预测结果作为新的训练集(第一层),来学习一个新的学习器(第二层)。
例子:
1.训练集数据大小为10000100,测试集大小为3000100。即训练集有10000条数据、100个特征;测试集有3000条数据、100个特征。该数据对应回归问题。
2.第一层使用三种算法-XGB、LGB、NN。第二层使用GBDT。
算法:
1.stacking 第一层
XGB算法
输入:使用训练集进行5-fold处理
处理:具体处理细节如下
1.使用1、2、3、4折作为训练集,训练一个XGB模型并预测第5折和测试集,将预测结果分别称为XGB-pred-tran5(shape 20001)和XGB-pred-test1(shape 30001).
2.使用1、2、3、5折作为训练集,训练一个XGB模型并预测第4折和测试集,将预测结果分别称为XGB-pred-tran4(shape 20001)和XGB-pred-test2(shape 30001).
3.使用1、2、4、5折作为训练集,训练一个XGB模型并预测第3折和测试集,将预测结果分别称为XGB-pred-tran3(shape 20001)和XGB-pred-test3(shape 30001).
4.使用1、3、4、5折作为训练集,训练一个XGB模型并预测第2折和测试集,将预测结果分别称为XGB-pred-tran2(shape 20001)和XGB-pred-test4(shape 30001).
5.使用2、3、4、5折作为训练集,训练一个XGB模型并预测第1折和测试集,将预测结果分别称为XGB-pred-tran1(shape 20001)和XGB-pred-test5(shape 30001).
输出:
1.将XGB分别对1、2、3、4、5折进行预测的结果合并,得到XGB-pred-tran(shape 100001)。并且根据5-fold的原理可以知道,与原数据可以形成对应关系。
2.将XGB-pred-test1 - 5 的结果使用Averaging的方法求平均值,最终得到XGB-pred-test(shape 30001)。
LGB算法
输入:与XGB算法一致
处理:与XGB算法一致。只需更改预测结果的命名即可,如LGB-pred-tran5和LGB-pred-test1
输出:
1.将LGB分别对1、2、3、4、5折进行预测的结果合并,得到LGB-pred-tran(shape 100001)。
2.将LGB-pred-test1 - 5 的结果使用Averaging的方法求平均值,最终得到LGB-pred-test(shape 30001)。
NN算法
输入:与XGB算法一致
处理:与XGB算法一致。只需更改预测结果的命名即可,如NN-pred-tran5和NN-pred-test1
输出:
1.将NN分别对1、2、3、4、5折进行预测的结果合并,得到NN-pred-tran(shape 100001)。
2.将NN-pred-test1 - 5 的结果使用Averaging的方法求平均值,最终得到NN-pred-test(shape 30001)。
2.stacking 第二层
1.训练集:将三个新特征 XGB-pred-tran、LGB-pred-tran、NN-pred-tran合并得到新的训练集(shape 100003)
2.测试集:将三个新测试集XGB-pred-test、LGB-pred-test、NN-pred-test合并得到新的测试集(shape 300003)
用新训练集和测试集构造第二层的预测器,即GBDT模型
混合法 - blending
Blending与Stacking大致相同,只是Blending的主要区别在于训练集不是通过K-Fold的CV策略来获得预测值从而生成第二阶段模型的特征,而是建立一个Holdout集。简单来说,Blending直接用不相交的数据集用于不同层的训练。
同样以上述数据集为例,构造一个两层的Blending模型。
首先将训练集划分为两部分(d1,d2),例如d1为4000条数据用于blending的第一层,d2是6000条数据用于blending的第二层。
第一层:用d1训练多个模型,将其对d2和test的预测结果作为第二层的New Features。例如同样适用上述三个模型,对d2生成60003的新特征数据;对test生成30003的新特征矩阵。
第二层:用d2的New Features和标签训练新的分类器,然后把test的New Features输入作为最终的测试集,对test预测出的结果就是最终的模型融合的值。
优缺点对比
Blending的优点在于:
比stacking简单(因为不用进行k次的交叉验证来获得stacker feature)
避开了一个信息泄露问题:generlizers和stacker使用了不一样的数据集
在团队建模过程中,不需要给队友分享自己的随机种子
缺点:
使用了很少的数据(是划分hold-out作为测试集,并非cv)
blender可能会过拟合(其实大概率是第一点导致的)
stacking使用多次的CV会比较稳健
代码
import pandas as pd
import numpy as np
from sklearn.metrics import classification_report, f1_score
from sklearn.model_selection import StratifiedKFold, KFold,train_test_split
def reduce_mem_usage(df):
start_mem = df.memory_usage().sum() / 1024**2
print('Memory usage of dataframe is {:.2f} MB'.format(start_mem))
for col in df.columns:
col_type = df[col].dtype
if col_type != object:
c_min = df[col].min()
c_max = df[col].max()
if str(col_type)[:3] == 'int':
if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
df[col] = df[col].astype(np.int8)
elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
df[col] = df[col].astype(np.int16)
elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
df[col] = df[col].astype(np.int32)
elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
df[col] = df[col].astype(np.int64)
else:
if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
df[col] = df[col].astype(np.float16)
elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
df[col] = df[col].astype(np.float32)
else:
df[col] = df[col].astype(np.float64)
else:
df[col] = df[col].astype('category')
end_mem = df.memory_usage().sum() / 1024**2
print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))
print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))
return df
all_df = pd.read_csv(r'E:\百度云下载\智慧海洋组队文件/group_df.csv',index_col=0)
all_df = reduce_mem_usage(all_df)
all_df = all_df.fillna(99)
all_df.head()
Memory usage of dataframe is 30.28 MB
Memory usage after optimization is: 7.59 MB
Decreased by 74.9%
ID | label | cnt | anchor_cnt | anchor_ratio | lat_min | lat_max | lat_mean | lat_median | lat_nunique | ... | w2v_20_mean | w2v_21_mean | w2v_22_mean | w2v_23_mean | w2v_24_mean | w2v_25_mean | w2v_26_mean | w2v_27_mean | w2v_28_mean | w2v_29_mean | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 2 | 414 | 372 | 0.898438 | 40.06250 | 40.28125 | 40.06250 | 40.06250 | 23 | ... | 5.183594 | 4.207031 | -2.089844 | -2.855469 | -1.686523 | 1.701172 | 1.500000 | -1.897461 | 1.875977 | 1.623047 |
1 | 1 | 2 | 385 | 187 | 0.485596 | 40.06250 | 40.21875 | 40.09375 | 40.06250 | 138 | ... | 1.413086 | 2.259766 | -0.436279 | -1.192383 | -0.682617 | 1.127930 | 1.210938 | -0.223389 | 1.656250 | 1.078125 |
2 | 10 | 2 | 397 | 60 | 0.151123 | 40.21875 | 40.59375 | 40.28125 | 40.21875 | 97 | ... | 4.050781 | 2.544922 | -0.881836 | -0.593750 | -0.080688 | 2.097656 | 0.287598 | -0.038818 | 1.786133 | 1.169922 |
3 | 100 | 2 | 411 | 67 | 0.162964 | 40.06250 | 40.53125 | 40.31250 | 40.34375 | 337 | ... | 0.841309 | 0.671875 | -0.587402 | -0.842773 | -0.349365 | 0.170898 | 0.420166 | 0.127808 | 0.514160 | 0.275635 |
4 | 1000 | 0 | 377 | 36 | 0.095520 | 41.12500 | 41.90625 | 41.75000 | 41.84375 | 257 | ... | 0.291992 | 0.578613 | -0.998047 | -0.379395 | -0.004711 | 0.518066 | 0.317871 | 0.060028 | 0.463379 | 0.733887 |
5 rows × 440 columns
all_df.shape
(9000, 440)
all_df['label'].value_counts()
2 4361
-1 2000
0 1621
1 1018
Name: label, dtype: int64
all_df中label为0/1/2的为训练集,一共有7000条;label为-1的为测试集,一共有2000条。
label为-1的测试集没有label,这部分数据用于模拟真实比赛提交数据。
train数据均有标签,我们将从中分出30%作为验证集,其余作为训练集。在验证集上比较模型性能优劣,模型性能均使用f1作为评分。
train = all_df[all_df['label'] != -1]
test = all_df[all_df['label'] == -1]
feats = [c for c in train.columns if c not in ['ID', 'label']]
X_train,X_val,y_train,y_val= train_test_split(train[feats],train['label'],test_size=0.2,random_state=0)
单模及加权融合
分别是用了一个三种不同的RF/LGB/LGB模型。事实上模型融合需要基础分类器之间存在差异,一般不会选用相同的分类器模型。
# 单模函数
def build_model_rf(X_train,y_train):
model = RandomForestClassifier(n_estimators = 100)
model.fit(X_train, y_train)
return model
def build_model_lgb(X_train,y_train):
model = lgb.LGBMClassifier(num_leaves=127,learning_rate = 0.1,n_estimators = 200)
model.fit(X_train, y_train)
return model
def build_model_lgb2(X_train,y_train):
model = lgb.LGBMClassifier(num_leaves=63,learning_rate = 0.05,n_estimators = 400)
model.fit(X_train, y_train)
return model
# 这里针对三个单模进行训练,其中subA_rf/lgb/nn都是可以提交的模型
# 单模没有进行调参,因此是弱分类器,效果可能不是很好。
print('predict rf ...')
model_rf = build_model_rf(X_train,y_train)
val_rf = model_rf.predict(X_val)
subA_rf = model_rf.predict(test[feats])
rf_f1_score = f1_score(y_val,val_rf,average='macro')
print(rf_f1_score)
print('predict lgb...')
model_lgb = build_model_lgb(X_train,y_train)
val_lgb = model_lgb.predict(X_val)
subA_lgb = model_lgb.predict(test[feats])
lgb_f1_score = f1_score(y_val,val_lgb,average='macro')
print(lgb_f1_score)
print('predict lgb 2...')
model_lgb2 = build_model_lgb2(X_train,y_train)
val_lgb2 = model_lgb2.predict(X_val)
subA_lgb2 = model_lgb2.predict(test[feats])
lgb2_f1_score = f1_score(y_val,val_lgb2,average='macro')
print(lgb2_f1_score)
predict rf ...
0.9094505093296462
predict lgb...
0.9277712865703224
predict lgb 2...
0.9296871289926031
voting_clf = VotingClassifier(estimators=[('rf',model_rf ),
('lgb',model_lgb),
('lgb2',model_lgb2 )],voting='hard')
voting_clf.fit(X_train,y_train)
val_voting = voting_clf.predict(X_val)
subA_voting = voting_clf.predict(test[feats])
voting_f1_score = f1_score(y_val,val_voting,average='macro')
print(voting_f1_score)
0.9285687669543128
Stacking融合
import warnings
warnings.filterwarnings('ignore')
_N_FOLDS = 5 # 采用5折交叉验证
kf = KFold(n_splits=_N_FOLDS, random_state=42,shuffle=True) # sklearn的交叉验证模块,用于划分数据
def get_oof(clf, X_train, y_train, X_test):
oof_train = np.zeros((X_train.shape[0], 1))
oof_test_skf = np.empty((_N_FOLDS, X_test.shape[0], 1))
for i, (train_index, test_index) in enumerate(kf.split(X_train)): # 交叉验证划分此时的训练集和验证集
kf_X_train = X_train.iloc[train_index,]
kf_y_train = y_train.iloc[train_index,]
kf_X_val = X_train.iloc[test_index,]
clf.fit(kf_X_train, kf_y_train)
oof_train[test_index] = clf.predict(kf_X_val).reshape(-1, 1)
oof_test_skf[i, :] = clf.predict(X_test).reshape(-1, 1)
oof_test = oof_test_skf.mean(axis=0) # 对每一则交叉验证的结果取平均
return oof_train, oof_test # 返回当前分类器对训练集和测试集的预测结果
# 将你的每个分类器都调用get_oof函数,并把它们的结果合并,就得到了新的训练和测试数据new_train,new_test
new_train, new_test = [], []
model1 = RandomForestClassifier(n_estimators = 100)
model2 = lgb.LGBMClassifier(num_leaves=127,learning_rate = 0.1,n_estimators = 200)
model3 = lgb.LGBMClassifier(num_leaves=63,learning_rate = 0.05,n_estimators = 400)
for clf in [model1, model2, model3]:
oof_train, oof_test = get_oof(clf, X_train, y_train, X_val)
new_train.append(oof_train)
new_test.append(oof_test)
new_train = np.concatenate(new_train, axis=1)
new_test = np.concatenate(new_test, axis=1)
# 用新的训练数据new_train作为新的模型的输入,stacking第二层
# 使用LogisticRegression作为第二层是为了防止模型过拟合
# 这里使用的模型还有待优化,因此模型融合效果并不是很好
clf = LogisticRegression()
clf.fit(new_train, y_train)
result = clf.predict(new_test)
stacking_f1_score = f1_score(y_val,result,average='macro')
print(stacking_f1_score)