【算法竞赛学习】数字中国创新大赛智慧海洋建设-Task5模型融合

智慧海洋建设-Task5 模型融合

5.1 学习目标

学习融合策略

完成相应学习打卡任务

5.2 内容介绍

https://mlwave.com/kaggle-ensembling-guide/
https://github.com/MLWave/Kaggle-Ensemble-Guide

模型融合是比赛后期一个重要的环节,大体来说有如下的类型方式。

  1. 简单加权融合:

    • 回归(分类概率):算术平均融合(Arithmetic mean),几何平均融合(Geometric mean);
    • 分类:投票(Voting)
  2. boosting/bagging(在xgboost,Adaboost,GBDT中已经用到):

    • 多树的提升方法
  3. stacking/blending:

    • 构建多层模型,并利用预测结果再拟合预测。

5.3 相关理论介绍

5.3.1 简单加权融合

平均法-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. 软投票:增加了设置权重的功能,可以为不同模型设置不同权重,进而区别模型不同的重要度。

5.3.2 stacking/blending

堆叠法-stacking

基本思想:用初始训练数据学习出若干个基学习器后,将这几个学习器的预测结果作为新的训练集(第一层),来学习一个新的学习器(第二层)。

背景: 为了帮助大家理解模型的原理,我们先假定一下数据背景。

  1. 训练集数据大小为10000*100,测试集大小为3000*100。即训练集有10000条数据、100个特征;测试集有3000条数据、100个特征。该数据对应回归问题

  2. 第一层使用三种算法-XGB、LGB、NN。第二层使用GBDT。

算法解读

  1. stacking 第一层

  2. XGB算法 - 对应图中model 1部分

- 输入:使用训练集进行5-fold处理
- 处理:具体处理细节如下
    - 使用1、2、3、4折作为训练集,训练一个XGB模型并预测第5折和测试集,将预测结果分别称为**XGB-pred-tran5**(shape `2000*1`)和**XGB-pred-test1**(shape `3000*1`).
    - 使用1、2、3、5折作为训练集,训练一个XGB模型并预测第4折和测试集,将预测结果分别称为**XGB-pred-tran4**(shape `2000*1`)和**XGB-pred-test2**(shape `3000*1`).
    - 使用1、2、4、5折作为训练集,训练一个XGB模型并预测第3折和测试集,将预测结果分别称为**XGB-pred-tran3**(shape `2000*1`)和**XGB-pred-test3**(shape `3000*1`).
    - 使用1、3、4、5折作为训练集,训练一个XGB模型并预测第2折和测试集,将预测结果分别称为**XGB-pred-tran2**(shape `2000*1`)和**XGB-pred-test4**(shape `3000*1`).
    - 使用2、3、4、5折作为训练集,训练一个XGB模型并预测第1折和测试集,将预测结果分别称为**XGB-pred-tran1**(shape `2000*1`)和**XGB-pred-test5**(shape `3000*1`).
- 输出:
    - 将XGB分别对1、2、3、4、5折进行预测的结果合并,得到**XGB-pred-tran**(shape `10000*1`)。并且根据5-fold的原理可以知道,与原数据可以形成对应关系。因此在图中称为NEW FEATURE。
    - 将XGB-pred-test1 - 5 的结果使用Averaging的方法求平均值,最终得到**XGB-pred-test**(shape `3000*1`)。
  1. LGB算法 - 同样对应图中model 1部分
- 输入:与XGB算法一致
- 处理:与XGB算法一致。只需更改预测结果的命名即可,如**LGB-pred-tran5**和**LGB-pred-test1**
- 输出:
    - 将LGB分别对1、2、3、4、5折进行预测的结果合并,得到**LGB-pred-tran**(shape `10000*1`)。
    - 将LGB-pred-test1 - 5 的结果使用Averaging的方法求平均值,最终得到**LGB-pred-test**(shape `3000*1`)。
  1. NN算法 - 同样对应图中model 1部分
- 输入:与XGB算法一致
- 处理:与XGB算法一致。只需更改预测结果的命名即可,如**NN-pred-tran5**和**NN-pred-test1**
- 输出:
    - 将NN分别对1、2、3、4、5折进行预测的结果合并,得到**NN-pred-tran**(shape `10000*1`)。
    - 将NN-pred-test1 - 5 的结果使用Averaging的方法求平均值,最终得到**NN-pred-test**(shape `3000*1`)。
  1. stacking 第二层
  • 训练集:将三个新特征 XGB-pred-tranLGB-pred-tranNN-pred-tran合并得到新的训练集(shape 10000*3)
  • 测试集:将三个新测试集XGB-pred-testLGB-pred-testNN-pred-test合并得到新的测试集(shape 30000*3)
  • 用新训练集和测试集构造第二层的预测器,即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生成6000*3的新特征数据;对test生成3000*3的新特征矩阵。

第二层:用d2的New Features和标签训练新的分类器,然后把test的New Features输入作为最终的测试集,对test预测出的结果就是最终的模型融合的值。

优缺点对比

Blending的优点在于:

  1. 比stacking简单(因为不用进行k次的交叉验证来获得stacker feature)

  2. 避开了一个信息泄露问题:generlizers和stacker使用了不一样的数据集

  3. 在团队建模过程中,不需要给队友分享自己的随机种子

而缺点在于:

  1. 使用了很少的数据(是划分hold-out作为测试集,并非cv)

  2. blender可能会过拟合(其实大概率是第一点导致的)

  3. stacking使用多次的CV会比较稳健

5.4 代码实现

import pandas as pd
import numpy as np
import warnings
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns

warnings.filterwarnings('ignore')
%matplotlib inline

import itertools
import matplotlib.gridspec as gridspec
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB 
from sklearn.ensemble import RandomForestClassifier,RandomForestRegressor
from sklearn.linear_model import LogisticRegression
# from mlxtend.classifier import StackingClassifier
from sklearn.model_selection import cross_val_score, train_test_split
# from mlxtend.plotting import plot_learning_curves
# from mlxtend.plotting import plot_decision_regions

from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import train_test_split
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import train_test_split
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import VotingClassifier
import lightgbm as lgb
from sklearn.neural_network import MLPClassifier,MLPRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error

5.4.1 load data

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('data/group_df.csv',index_col=0)
all_df = reduce_mem_usage(all_df)
all_df = all_df.fillna(99)
Memory usage of dataframe is 30.28 MB
Memory usage after optimization is: 7.59 MB
Decreased by 74.9%
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条。

  1. label为-1的测试集没有label,这部分数据用于模拟真实比赛提交数据。

  2. 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']]

# 根据7:3划分训练集和测试集
X_train,X_val,y_train,y_val= train_test_split(train[feats],train['label'],test_size=0.3,random_state=0)

5.4.2 单模及加权融合

这里训练三个单模,分别是用了一个三种不同的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.8987051046527208
predict lgb...
0.9144414270113281
predict lgb 2...
0.9183965870229657
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.9142736444973326

5.4.3 Stacking融合

_N_FOLDS = 5  # 采用5折交叉验证
kf = KFold(n_splits=_N_FOLDS, random_state=42)  # 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)
0.8816601744239989

5.5 思考题

  1. 如何基于stacking改进出blending - stacking使用了foldCV,blending使用了holdout.

  2. stacking还可以进行哪些优化提升F1-score - 从第一层模型数量?模型差异性?角度出发

参考内容

https://blog.csdn.net/weixin_44585839/article/details/110148396

https://blog.csdn.net/weixin_39962758/article/details/111101263

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值