目录

五、模型集成

5.1 学习目标

5.2 内容简介

5.3 集成学习

5.3.1 Stacking 理论概述

5.3.2 Blending 理论概述

5.3.3 回归模型 (或分类概率) 融合代码示例

5.3.4 分类模型融合代码示例

5.3.5 一些其它方法代码示例

5.4 代码示范

5.4.1 准备工作

5.2.2 加权融合

5.4.3 Stacking 融合

5.5 经验总结


五、模型集成


5.1 学习目标

  • 学习模型集成与模型融合策略

5.2 内容简介

模型融合是比赛后期一个重要的环节,整体上存在如下几种方式:

1. 简单加权融合

  • 回归 (分类概率):算术平均融合 (Arithmetic mean),几何平均融合 (Geometric mean);
  • 分类:投票 (Voting)
  • 综合:排序融合 (Rank averaging),log 融合

2. Stacking / Blending

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

3. Boosting / Bagging(在 XGBoost, Adaboost, GBDT 中已经用到):

  • 多树的提升方法

相关资料


5.3 集成学习

集成学习 (Ensemble Learning),即分类器集成,构建多个学习器并通过某种集成方法组合它们来完成最终的学习任务,有时也被称为多分类器系统 (multi-classifier system)、基于委员会的学习 (committee-based learning) 等。一般结构是:先产生一组 “个体学习器”,再用某种策略将它们组合起来。组合策略主要有平均法、投票法和学习法等。集成学习主要用来提高模型(分类,预测,函数估计等)的性能,或用来降低模型选择不当的可能性。集成算法本身是一种监督学习算法,因为它可以被训练然后进行预测,组合的多个模型作为整体代表一个假设 (hypothesis)。

sklearn 官方文档 中,可以很容易找到各类集成算法 API 及其实现。

集成方法 本身并不是某种具体的方法或者是算法,只是一种训练机器学习模型的思路。它的含义只有一点,就是训练多个模型,然后将它们的结果汇聚在一起。集成方法将几种机器学习技术组合成一个预测模型的元算法,以达到 减小方差 (Bagging)偏差 (Boosting)改进预测 (Stacking) 的效果。当然,也有说法 将多个分类器组合的方法 称为 集成方法 元算法

理论上,学习器的集成会带来 三个方面的好处

  1. 首先,从 统计 的方面看,由于学习任务的假设空间往往很大,可能有多个假设在训练集上达到相同的性能,此时若使用单学习器,可能因误选而导致 泛化性能不佳,结合多个学习器则会减少这一风险;
  2. 其次,从 计算 的方面看,学习算法往往会陷入 局部极小,有的局部极小点所对应的泛化性能可能很糟糕,而通过多次运行之后进行结合,可降低陷入糟糕局部极小点的风险;
  3. 最后,从 表示 的方面看,某些学习任务的 真实假设可能不在当前学习算法所考虑的假设空间中,此时若使用单学习器则肯定无效,而通过结合多个学习器,由于相应的假设空间有所扩大,有可能学得更好的近似。

常见集成学习方法 有:

  • Bagging 又称 自助聚合法 (Bootstrap Aggregating),通常考虑的是 同质弱学习器相互独立地并行学习 这些弱学习器,并按照某种 确定性的平均过程 将它们组合起来。

更具体地,Bagging 通过 自助采样法 —— 有放回随机采样 的方式创建 K 个新数据集,因此每个新数据集都可能存在重复样本,也可能总有些样本不出现。但整体而言,各样本出现概率相同。之后,K 个新数据集分别同时并行训练 K 个任意可用模型。K 个模型得到的 K 个结果可采用 公平投票 (voting) 等方式聚合。例如,典型的 随机森林 (Random Forest, RF) 就采用了 少数服从多数的 投票原则。

  • Boosting 又称 提升法,通常考虑的也是 同质弱学习器。它以一种高度自适应的方法 串行顺序地学习 这些弱学习器(每个基础模型都依赖于前面的模型),并按照某种 确定性的策略 将它们组合起来。

更具体地,Boosting 与 Bagging 的样本采样逻辑一致,但其 K 个模型是串行而非并行训练的。每个模型在训练时都会基于先前模型的结果,并更加关注/重视被先前模型误判的样本。即各样本具有权值,错误判断率越高的样本拥有越大的权值。同时,每个模型依其分类能力的强弱会被赋予不同的权重,最后,对所有模型的结果进行 加权求和而非公平投票。该方法的机制使得模型存在训练效率的差异。一方面,Bagging 的各模型间完全独立,可采用分布式训练;另一方面,Boosting 中每个模型则依赖或受先前模型结果的影响,故通常采用串行训练。

  • Stacking 又称 堆叠,通常考虑的是 异质弱学习器并行地学习 这些弱学习器,并通过训练一个 元模型 将它们组合起来,根据不同的各个弱模型的预测结果,组合输出一个最终的预测结果。

更具体地,Stacking 作为比赛中常用的方法,其选择 K 种不同的分类器,通过 交叉验证 在训练集上训练和预测。保证各分类器模型都对所有训练样本预测出一个结果。那么,对于每一个训练样本,都能得到来自 K 种分类器模型的 K 个结果。之后,再创建第二层次的模型,其训练特征就是这 K 个结果。也就是说 Stacking 方法当中会用到 多层模型的结构,最后一层模型的训练特征是上层模型预测的结果。由模型自己去训练究竟哪一个模型的结果更值得采纳,以及如何组合模型之间的特长。

 简言之,Bagging 旨在 获得一个方差比其组成部分更小的集成模型,而 Boosting 和 Stacking 则致力于 生成偏差比其组成部分更低的强模型(即使方差也可以被减小


5.3.1 Stacking 理论概述

Stacking 学习几个不同的弱学习器,并通过训练一个元模型来组合这些弱模型的输出,作为最终的预测结果。

Stacking 与 Bagging、Boosting 相比,主要 差异 如下:

  1. Stacking 主要使用 异质弱学习器(不同模型),而 Bagging 和 Boosting 主要使用 同质弱学习器
  2. Stacking 训练一个 元模型 来组合基础模型,而 Bagging 和 Boosting 则 根据 确定性算法 组合弱学习器

因此,为构建 Stacking 模型,需定义至少两部分(通常是 2 层)模型,分别是:

  1. 多个需要拟合的 弱学习器模型
  2. 一个用于组合各弱学习器模型的 元模型

例如,对于分类问题,可选 KNN、Logistic Regression、SVM 等作为弱分类器,并采用学习神经网络 NN 作为元模型。然后,神经网络将会把三个弱学习器的输出作为输入,并返回基于该输入的最终预测。所以,假设要拟合由 L 个弱学习器组成的 Stacking 集成模型,须遵循以下 步骤

  • 将训练数据分为两组;
  • 选择 L 个弱学习器,用它们拟合第一组数据;
  • 使 L 个学习器中的每个学习器对第二组数据中的观测数据进行预测;
  • 在第二组数据上拟合元模型,使用弱学习器做出的预测作为输入。

在前面的步骤中,将数据集一分为二,因为对用于训练弱学习器的数据的预测与元模型的训练 不相关。因此,将数据集分成两部分的一个 明显缺点 是:只有一半的数据用于训练基础模型,另一半数据用于训练元模型。

为克服这种限制,可使用某种 K 折交叉训练 方法(类似于 K 折交叉验证的做法)。这样所有的观测数据都可用来训练元模型:对于任意观测数据,弱学习器的预测都是通过在 K-1折数据(不包含已考虑的观测数据)上训练这些弱学习器的实例来完成的。换言之,它在 K-1 折数据上训练,在剩下 1 折数据上预测。迭代地重复改过程,就可得到对任 1 折观测数据的预测结果。这样一来,就可为数据集中的每个观测数据生成相关的预测,然后使用所有这些预测结果训练元模型。

Stacking 从数据集中训练出初级学习器,然后 ”生成“ 一个新的数据集用于训练次级学习器。由于深度学习模型一般需要较长的训练周期,如果硬件设备不允许建议选取留出法,如果需要追求精度可以使用交叉验证的方法。加之为防止过拟合,可采用 K 折交叉验证 求解。假设采用 5 折交叉验证,每个模型都要做满 5 次训练和预测,对于每一次:

  • 从 80% 的数据训练得到一个模型 ht,然后预测训练集剩下的 20%,同时也要预测测试集。
  • 每次有 20% 的训练数据被预测,5 次后正好每个训练样本都被预测过了。
  • 每次都要预测测试集,因此最后测试集被预测 5 次,最终结果取 5 次的平均。

Stacking 的示例

Stacking 的算法

Stacking 的特点

  • 它可以帮你打败当前学术界性能最好的算法        
  • 有可能将集成的知识迁移到到简单的分类器上          
  • 自动化的大型集成策略可通过添加正则项有效的对抗过拟合,且无需太多的调参和特征选择,所以原则上 Stacking 非常适合 “懒人”        
  • 这是目前提升机器学习效果最好的方法,或者说是最效率的方法 Human Ensemble Learning

5.3.2 Blending 理论概述

Blending 与 Stacking 大致相同,只是 Blending 的主要区别在于训练集不是通过 K-Fold 的 CV 策略来获得预测值从而生成第二阶段模型的特征,而是建立一个 Holdout 集,例如10% 的训练数据,第二阶段的 stacker 模型就基于第一阶段模型对这 10% 训练数据的预测值进行拟合。说白了,就是把 Stacking 流程中的 K-Fold CV 改成 HoldOut CV。

Blending 的流程

  1. 将数据划分为训练集和测试集 (test_set),其中训练集需要再次划分为训练集 (train_set) 和验证集 (val_set);
  2. 创建第一层的多个弱学习器模型,其既可为同质,也可为异质;
  3. 使用 train_set 训练步骤 2 中的多个模型,然后用训练好的模型预测 val_set 和 test_set 得到 val_predict, test_predict1;
  4. 创建第二层的模型,使用 val_predict 作为训练集训练第二层的模型;
  5. 使用第二层训练好的模型对第二层测试集 test_predict1 进行预测,该结果为整个测试集的结果。

Blending 的优点

  1. 比 Stacking 简单(因为不用进行 K 次的交叉验证来获得 stacker features)
  2. 避开了一个信息泄露问题:generlizers 和 stacker 使用了不一样的数据集
  3. 在团队建模过程中,无需给队友分享自己的随机种子

Blending 的缺点

  1. 使用了很少的数据(是划分 hold-out 作为测试集,并非 cv)
  2. 可能会过拟合(其实大概率是第一点导致的)
  3. Stacking 使用多次的 CV 会比较稳健

5.3.3 回归模型 (或分类概率) 融合代码示例

1) 简单加权平均,结果直接融合

import numpy as np
import pandas as pd
from sklearn import metrics

# 生成一些简单的样本数据,test_prei 代表第i个模型的预测值
test_pre1 = [1.2, 3.2, 2.1, 6.2]
test_pre2 = [0.9, 3.1, 2.0, 5.9]
test_pre3 = [1.1, 2.9, 2.2, 6.0]

# y_test_true 代表真实值
y_test_true = [1, 3, 2, 6] 

# 定义结果的加权平均函数
def Weighted_method(test_pre1, test_pre2, test_pre3, w=[1/3, 1/3, 1/3]):
    Weighted_result = w[0]*pd.Series(test_pre1) + w[1]*pd.Series(test_pre2) + w[2]*pd.Series(test_pre3)
    return Weighted_result

# 各模型的预测结果计算 MAE
print('Pred1 MAE: ', metrics.mean_absolute_error(y_test_true, test_pre1))
print('Pred2 MAE: ', metrics.mean_absolute_error(y_test_true, test_pre2))
print('Pred3 MAE: ', metrics.mean_absolute_error(y_test_true, test_pre3))

# 根据加权计算 MAE
w = [0.3, 0.4, 0.3] # 定义比重权值
Weighted_pre = Weighted_method(test_pre1, test_pre2, test_pre3, w)
print('Weighted_pre MAE: ', metrics.mean_absolute_error(y_test_true, Weighted_pre))

可见,加权结果相较之前有所提升的,这被称为 简单加权平均。但要注意的是,加权系数的选取很重要,线下线上的结果差异也必须关注,否则可能导致不佳的设定。

此外,除了简单加权平均,还有诸如 mean 平均median 平均 等更多形式:

## 定义结果的加权平均函数
def Mean_method(test_pre1,test_pre2,test_pre3):
    Mean_result = pd.concat([pd.Series(test_pre1),pd.Series(test_pre2),pd.Series(test_pre3)],axis=1).mean(axis=1)
    return Mean_result

Mean_pre = Mean_method(test_pre1,test_pre2,test_pre3)
print('Mean_pre MAE:',metrics.mean_absolute_error(y_test_true, Mean_pre))

## 定义结果的加权平均函数
def Median_method(test_pre1,test_pre2,test_pre3):
    Median_result = pd.concat([pd.Series(test_pre1),pd.Series(test_pre2),pd.Series(test_pre3)],axis=1).median(axis=1)
    return Median_result

Median_pre = Median_method(test_pre1,test_pre2,test_pre3)
print('Median_pre MAE:',metrics.mean_absolute_error(y_test_true, Median_pre))

2) Stacking 融合 (回归)

from sklearn import linear_model

def Stacking_method(train_reg1, train_reg2, train_reg3, y_train_true, 
                    test_pre1, test_pre2, test_pre3, model_L2=linear_model.LinearRegression()):
    # 默认使用线性回归作为第二级学习器(元学习器)
    model_L2.fit(pd.concat([pd.Series(train_reg1), pd.Series(train_reg2), pd.Series(train_reg3)],axis=1).values, y_train_true)
    # 元学习器预测
    Stacking_result = model_L2.predict(pd.concat([pd.Series(test_pre1), pd.Series(test_pre2), pd.Series(test_pre3)],axis=1).values)
    return Stacking_result

# 生成一些简单的样本数据

# test_prei 代表第一级第i个模型的验证集预测值 —— 用于第二级模型训练
train_reg1 = [3.2, 8.2, 9.1, 5.2]
train_reg2 = [2.9, 8.1, 9.0, 4.9]
train_reg3 = [3.1, 7.9, 9.2, 5.0]

# y_train_true 代表验证集真实值
y_train_true = [3, 8, 9, 5] 

# test_prei 代表第一级第i个模型的测试集预测值 —— 用于第二级模型测试
test_pre1 = [1.2, 3.2, 2.1, 6.2]
test_pre2 = [0.9, 3.1, 2.0, 5.9]
test_pre3 = [1.1, 2.9, 2.2, 6.0]

# y_test_true 代表测试集真实值
y_test_true = [1, 3, 2, 6] 

# 各弱学习器模型的预测结果计算 MAE
print('Pred1 MAE: ', metrics.mean_absolute_error(y_test_true, test_pre1))
print('Pred2 MAE: ', metrics.mean_absolute_error(y_test_true, test_pre2))
print('Pred3 MAE: ', metrics.mean_absolute_error(y_test_true, test_pre3))

# 定义元学习器
model_L2 = linear_model.LinearRegression()
# 开始 stacking
Stacking_pre = Stacking_method(train_reg1, train_reg2, train_reg3, y_train_true,
                               test_pre1, test_pre2, test_pre3, model_L2)
# stacking 评价
print('Stacking_pre MAE: ', metrics.mean_absolute_error(y_test_true, Stacking_pre))

可见,模型结果取得了进一步提升。注意,第二层 Stacking 模型不宜选取得过于复杂,否则容易导致模型在训练集上过拟合,并在测试集泛化性能不佳,呈现线下线上差异过大的结果。


5.3.4 分类模型融合代码示例

# 导入依赖
import numpy as np
import lightgbm as lgb
from sklearn.datasets import make_blobs
from sklearn import datasets
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_moons
from sklearn.metrics import accuracy_score,roc_auc_score
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import StratifiedKFold

1) Voting 投票机制

投票法 (Voting),分为软投票和硬投票,其原理采用少数服从多数的思想。

  • 硬投票:对多个模型直接进行投票,最终投票数最多的类为最终被预测的类。
  • 软投票:和硬投票原理相同,但还可为不同模型设置不同权重,进而区别不同模型的重要程度。

通常,模型之间差异越大,融合效果越好,而这种特性不会受融合方式的影响。注意,这里所指模型之间的差异,并不是指正确率的差异,而是指 模型之间相关性的差异

''' 硬投票:对多个模型直接进行投票,不区分模型结果的相对重要度,最终投票数最多的类为最终被预测的类 '''

# 使用经典的鸢尾花数据集演示

# 数据加载
iris = datasets.load_iris()
x = iris.data
y = iris.target

# 留出法划分数据集
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.3)

# 构造分类器 (弱学习器)
clf1 = lgb.LGBMClassifier(learning_rate=0.1, n_estimators=150, max_depth=3, min_child_weight=2, subsample=0.7,
                     colsample_bytree=0.6, objective='binary:logistic')
clf2 = RandomForestClassifier(n_estimators=200, max_depth=10, min_samples_split=10,
                              min_samples_leaf=63,oob_score=True)
clf3 = SVC(C=0.1)

# 硬投票
eclf = VotingClassifier(estimators=[('lgb', clf1), ('rf', clf2), ('svc', clf3)], voting='hard')
for clf, label in zip([clf1, clf2, clf3, eclf], ['LGB', 'Random Forest', 'SVM', 'Ensemble']):
    scores = cross_val_score(clf, x, y, cv=5, scoring='accuracy')
    print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))

 2) 分类的 Stacking 融合

Stacking 是一种分层模型集成框架。以两层为例,第一层由多个基学习器组成,其输入为原始训练集,第二层的模型则是以第一层基学习器的输出作为训练集进行再训练,从而得到完整的 Stacking 模型。注意,Stacking 两层模型都使用了全部的训练数据。

''' 5-Fold Stacking '''

from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import ExtraTreesClassifier, GradientBoostingClassifier

# 创建训练的数据集
data_0 = iris.data
data = data_0[:100,:]

target_0 = iris.target
target = target_0[:100]

# 模型融合中使用到的各个单模型
clfs = [LogisticRegression(solver='lbfgs'),
        RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
        ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
        ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='entropy'),
        GradientBoostingClassifier(learning_rate=0.05, subsample=0.5, max_depth=6, n_estimators=5)]
 
# 切分一部分数据作为测试集
X, X_predict, y, y_predict = train_test_split(data, target, test_size=0.3, random_state=2020)

dataset_blend_train = np.zeros((X.shape[0], len(clfs)))
dataset_blend_test = np.zeros((X_predict.shape[0], len(clfs)))

# 5 折 Stacking
n_splits = 5
skf = StratifiedKFold(n_splits)
skf = skf.split(X, y)

for j, clf in enumerate(clfs):
    # 依次训练各个单模型
    dataset_blend_test_j = np.zeros((X_predict.shape[0], 5))
    for i, (train, test) in enumerate(skf):
        # 5-Fold交叉训练,使用第i个部分作为预测,剩余的部分来训练模型,获得其预测的输出作为第i部分的新特征。
        X_train, y_train, X_test, y_test = X[train], y[train], X[test], y[test]
        clf.fit(X_train, y_train)
        y_submission = clf.predict_proba(X_test)[:, 1]
        dataset_blend_train[test, j] = y_submission
        dataset_blend_test_j[:, i] = clf.predict_proba(X_predict)[:, 1]
    # 对于测试集,直接用这k个模型的预测值均值作为新的特征。
    dataset_blend_test[:, j] = dataset_blend_test_j.mean(1)
    print("val auc Score: %f" % roc_auc_score(y_predict, dataset_blend_test[:, j]))

clf = LogisticRegression(solver='lbfgs')
clf.fit(dataset_blend_train, y)
y_submission = clf.predict_proba(dataset_blend_test)[:, 1]

print("Val auc Score of Stacking: %f" % (roc_auc_score(y_predict, y_submission)))

 3) 分类的 Blending 融合

'''
Blending
'''
 
#创建训练的数据集
#创建训练的数据集
data_0 = iris.data
data = data_0[:100,:]

target_0 = iris.target
target = target_0[:100]
 
#模型融合中使用到的各个单模型
clfs = [LogisticRegression(solver='lbfgs'),
        RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
        RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='entropy'),
        ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
        #ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='entropy'),
        GradientBoostingClassifier(learning_rate=0.05, subsample=0.5, max_depth=6, n_estimators=5)]

#切分一部分数据作为测试集
X, X_predict, y, y_predict = train_test_split(data, target, test_size=0.3, random_state=2020)

#切分训练数据集为d1,d2两部分
X_d1, X_d2, y_d1, y_d2 = train_test_split(X, y, test_size=0.5, random_state=2020)
dataset_d1 = np.zeros((X_d2.shape[0], len(clfs)))
dataset_d2 = np.zeros((X_predict.shape[0], len(clfs)))
 
for j, clf in enumerate(clfs):
    #依次训练各个单模型
    clf.fit(X_d1, y_d1)
    y_submission = clf.predict_proba(X_d2)[:, 1]
    dataset_d1[:, j] = y_submission
    #对于测试集,直接用这k个模型的预测值作为新的特征。
    dataset_d2[:, j] = clf.predict_proba(X_predict)[:, 1]
    print("val auc Score: %f" % roc_auc_score(y_predict, dataset_d2[:, j]))

#融合使用的模型
clf = GradientBoostingClassifier(learning_rate=0.02, subsample=0.5, max_depth=6, n_estimators=30)
clf.fit(dataset_d1, y_d2)
y_submission = clf.predict_proba(dataset_d2)[:, 1]
print("Val auc Score of Blending: %f" % (roc_auc_score(y_predict, y_submission)))


5.3.5 一些其它方法代码示例

将特征放进模型中预测,并将预测结果变换并作为新的特征加入原有特征中再经过模型预测结果 (Stacking变化)(可以反复预测多次将结果加入最后的特征中)

def Ensemble_add_feature(train,test,target,clfs):
    
    # n_flods = 5
    # skf = list(StratifiedKFold(y, n_folds=n_flods))

    train_ = np.zeros((train.shape[0],len(clfs*2)))
    test_ = np.zeros((test.shape[0],len(clfs*2)))

    for j,clf in enumerate(clfs):
        '''依次训练各个单模型'''
        # print(j, clf)
        '''使用第1个部分作为预测,第2部分来训练模型,获得其预测的输出作为第2部分的新特征。'''
        # X_train, y_train, X_test, y_test = X[train], y[train], X[test], y[test]

        clf.fit(train,target)
        y_train = clf.predict(train)
        y_test = clf.predict(test)

        ## 新特征生成
        train_[:,j*2] = y_train**2
        test_[:,j*2] = y_test**2
        train_[:, j+1] = np.exp(y_train)
        test_[:, j+1] = np.exp(y_test)
        # print("val auc Score: %f" % r2_score(y_predict, dataset_d2[:, j]))
        print('Method ',j)

    train_ = pd.DataFrame(train_)
    test_ = pd.DataFrame(test_)
    return train_,test_
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression()

data_0 = iris.data
data = data_0[:100,:]

target_0 = iris.target
target = target_0[:100]

x_train,x_test,y_train,y_test=train_test_split(data,target,test_size=0.3)
x_train = pd.DataFrame(x_train) ; x_test = pd.DataFrame(x_test)

#模型融合中使用到的各个单模型
clfs = [LogisticRegression(),
        RandomForestClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
        ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='gini'),
        ExtraTreesClassifier(n_estimators=5, n_jobs=-1, criterion='entropy'),
        GradientBoostingClassifier(learning_rate=0.05, subsample=0.5, max_depth=6, n_estimators=5)]

New_train,New_test = Ensemble_add_feature(x_train,x_test,y_train,clfs)

clf = LogisticRegression()
# clf = GradientBoostingClassifier(learning_rate=0.02, subsample=0.5, max_depth=6, n_estimators=30)
clf.fit(New_train, y_train)
y_emb = clf.predict_proba(New_test)[:, 1]

print("Val auc Score of stacking: %f" % (roc_auc_score(y_test, y_emb)))


5.4 代码示范


5.4.1 准备工作

准备工作进行内容有:

  1. 导入数据集并进行简单的预处理
  2. 将数据集划分成训练集和验证集
  3. 构建单模:Random Forest,LGB,NN
  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 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
import lightgbm as lgb
from sklearn.neural_network import MLPClassifier,MLPRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error

定义内存量化压缩函数:

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

导入数据集、分离特征并优化内存: 

train = pd.read_csv('./data/train.csv')
test = pd.read_csv('./data/testA.csv')

# 简单预处理
train_list = []
for items in train.values:
    train_list.append([items[0]] + [float(i) for i in items[1].split(',')] + [items[2]])
    
test_list = []
for items in test.values:
    test_list.append([items[0]] + [float(i) for i in items[1].split(',')])

train = pd.DataFrame(np.array(train_list))
test = pd.DataFrame(np.array(test_list))

# id列不算入特征
features = ['s_'+str(i) for i in range(len(train_list[0])-2)] 
train.columns = ['id'] + features + ['label']
test.columns = ['id'] + features

train = reduce_mem_usage(train)
test = reduce_mem_usage(test)

# 数据与标签分离
X_train = train.drop(['id','label'], axis=1)
y_train = train['label']

# 测试集
X_test = test.drop(['id'], axis=1)

# 第一次运行可以先用一个subdata,这样速度会快些
X_train = X_train.iloc[:50000,:20]
y_train = y_train.iloc[:50000]
X_test = X_test.iloc[:,:20]

# 8:2 划分训练集和测试集
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.2)
X_train.shape, X_val.shape, y_train.shape, y_val.shape

# 单模函数
def build_model_rf(X_train,y_train):
    model = RandomForestRegressor(n_estimators = 100)
    model.fit(X_train, y_train)
    return model


def build_model_lgb(X_train,y_train):
    model = lgb.LGBMRegressor(num_leaves=63,learning_rate=0.1,n_estimators=100)
    model.fit(X_train, y_train)
    return model


def build_model_nn(X_train,y_train):
    model = MLPRegressor(alpha=1e-05, hidden_layer_sizes=(5, 2), random_state=1,solver='lbfgs')
    model.fit(X_train, y_train)
    return model

注意啦,上述示范内容使用的是用于回归预测的 回归器 (Regressor),而非本任务实际需要的 分类器 (Classifier),但加权融合和 Stacking 的基本思路还是相近的。

# 这里针对三个单模进行训练,其中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(X_test)
print('predict lgb...')
model_lgb = build_model_lgb(X_train,y_train)
val_lgb = model_lgb.predict(X_val)
subA_lgb = model_lgb.predict(X_test)
print('predict NN...')
model_nn = build_model_nn(X_train,y_train)
val_nn = model_nn.predict(X_val)
subA_nn = model_nn.predict(X_test)

5.2.2 加权融合

首先我们尝试加权融合模型:

  • 如果没有给权重矩阵,就是均值融合模型
  • 权重矩阵可以进行自定义,这里我们是用三个单模进行融合。如果有更多需要更改矩阵 size
# 加权融合模型,如果w没有变,就是均值融合
def Weighted_method(test_pre1,test_pre2,test_pre3,w=[1/3,1/3,1/3]):
    Weighted_result = w[0]*pd.Series(test_pre1)+w[1]*pd.Series(test_pre2)+w[2]*pd.Series(test_pre3)
    return Weighted_result

# 初始权重,可以进行自定义,这里我们随便设置一个权重
w = [0.2, 0.3, 0.5]

val_pre = Weighted_method(val_rf,val_lgb,val_nn,w)
MAE_Weighted = mean_absolute_error(y_val,val_pre)
print('MAE of Weighted of val:',MAE_Weighted)

 这里单独展示一下将多个单模预测结果融合成融和模型结果

## 预测数据部分
subA = Weighted_method(subA_rf,subA_lgb,subA_nn,w)

## 生成提交文件
sub = pd.DataFrame()
sub['SaleID'] = X_test.index
sub['price'] = subA
sub.to_csv('./sub_Weighted.csv',index=False)
subA


5.4.3 Stacking 融合

## Stacking

## 第一层
train_rf_pred = model_rf.predict(X_train)
train_lgb_pred = model_lgb.predict(X_train)
train_nn_pred = model_nn.predict(X_train)

stacking_X_train = pd.DataFrame()
stacking_X_train['Method_1'] = train_rf_pred
stacking_X_train['Method_2'] = train_lgb_pred
stacking_X_train['Method_3'] = train_nn_pred

stacking_X_val = pd.DataFrame()
stacking_X_val['Method_1'] = val_rf
stacking_X_val['Method_2'] = val_lgb
stacking_X_val['Method_3'] = val_nn

stacking_X_test = pd.DataFrame()
stacking_X_test['Method_1'] = subA_rf
stacking_X_test['Method_2'] = subA_lgb
stacking_X_test['Method_3'] = subA_nn
stacking_X_test.head()

# 第二层是用 random forest
model_lr_stacking = build_model_rf(stacking_X_train, y_train)

# 训练集
train_pre_Stacking = model_lr_stacking.predict(stacking_X_train)
print('MAE of stacking: ',mean_absolute_error(y_train, train_pre_Stacking))

# 验证集
val_pre_Stacking = model_lr_stacking.predict(stacking_X_val)
print('MAE of stacking: ', mean_absolute_error(y_val, val_pre_Stacking))

# 测试集
print('Predict stacking...')
subA_Stacking = model_lr_stacking.predict(stacking_X_test)

以上就是基本的 Stacking 流程了,可见,其基本过程大致就是记录第一层的多个单模在验证集和测试集上的预测结果,并用于第二层元学习器的训练和测试。从某种程度上看, Stacking 可视为一种 表示学习 (Representation Learning),即一种 模型从原始数据中自动抽取有效特征的过程,最耳熟能详的例子莫过于 深度学习 了。表示学习中,如影随形的问题就是 过拟合。如果说 Stacking 的有效性主要来自于表示学习特征提取取,那么同样会面临着过拟合风险。为此,一个降低过拟合风险的要求是:第二层分类器应较简单。在特征提取的过程中,已经使用了复杂的非线性变换,因此在输出层就不需要复杂的分类器了。好比神经网络的激活函数或输出层,都是相对简单的函数 —— 无需复杂函数且能控制复杂度。


5.5 经验总结

总之,模型融合是数据挖掘比赛后期上分的主要方式,尤其是进行队伍合并后,模型融合有很多优势,例如:

  1. 结果层面的融合:这种是最常见的融合方法,其可行的融合方法也有很多,比如根据结果的得分进行加权融合,还可以做 log、exp 处理等。在做结果融合时。有一个很重要的条件是模型结果的得分要比较近似但结果的差异要比较大,这样的结果融合往往有比较好的效果提升。如果不满足这个条件带来的效果很低,甚至是负效果。

  2. 特征层面的融合:这个层面叫融合并不准确,主要是队伍合并后,大家可相互学习特征工程。若用用同种模型训练,可以把特征切分给不同的模型,然后在后面进行模型或结果融合有时也能产生较好的效果。

  3. 模型层面的融合:模型层面的融合可能就涉及模型的堆叠和设计,比如加 Stacking,部分模型的结果作为特征输入等,这些就需要多实验和思考了,基于模型层面的融合最好不同模型类型要有一定的差异,用同种模型不同的参数的收益一般是比较小的。 


为期 15 天的组队学习至此告一段落啦,感谢各路前辈和同学的帮助,也欣慰自己的努力和坚持。事实上,养成坏习惯很容易,养成好习惯却很难,希望这是一个良好的开端 ~  ^ o ^ 在学习的过程中,我也发现自己许多的不足,接下来要闭关修炼强化一下啦~

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐