模型融合 python_模型融合及 python 实现

模型融合及 python 实现

“如果你没有什么好的思路的话,那么就模型融合吧!”

Stacking 利器(mlxtend 库):

在 KDD CUP、Kaggle、天池等数据挖掘比赛中,常常用到集成学习。使用了集成学习后,模型的效果往往有很大的进步。

本文将介绍常见的集成学习方法,包括但不限于:

集成学习为什么有效

Voting

Linear Blending

Stacking

Bagging

随机森林

集成学习

如果硬要把集成学习进一步分类,可以分为两类,一种是把强分类器进行强强联合,使得融合后的模型效果更强,称为模型融合。另一种是将弱分类器通过学习算法集成起来变为很强的分类器,称为机器学习元算法。

这里我们把用来进行融合的学习器称为个体学习器。

模型融合的代表有:投票法 (Voting)、线性混合 (Linear Blending)、Stacking。

而机器学习元算法又可以根据个体学习器之间是否存在依赖关系分为两类,称为 Bagging 和 Boosting:

Bagging: 个体学习器不存在依赖关系,可同时对样本随机采样并行化生成个体学习器。代表作为随机森林 (Random Forest)

Boosting: 个体学习器存在依赖关系, 基于前面模型的训练结果误差生成新的模型,必须串行化生成。代表的算法有:Adaboost、GBDT、XGBoost

模型融合

上面提到,模型融合是把强分类器进行强强联合,变得更强。

在进行模型融合的时候,也不是说随意的融合就能达到好的效果。进行融合时,所需的集成个体(就是用来集成的模型)应该好而不同。好指的是个体学习器的性能要好,不同指的是个体模型的类别不同。

这里举个西瓜书的例子,在介绍例子之前,首先提前介绍简单投票法,以分类问题为例,就是每个分类器对样例进行投票,哪个类别得到的票数最多的就是融合后模型的结果。

在上面的例子中,采用的就是简单的投票法。中间的图 b 各个模型输出都一样,因此没有什么效果。第三个图 c 每个分类器的精度只有 33%,融合后反而更糟。也就是说,想要模型融合有效果,个体学习器要有一定的准确率,并且要有多样性,学习器之间具有差异,即” 好而不同 “。

如何做到好而不同呢?可以由下面几个方面:

针对输入数据:使用采样的方法得到不同的样本(比如 bagging 方法采用自助法进行抽样)

针对特征:对特征进行抽样

针对算法本身:

个体学习器

来自不

同的模型集合

个体学习器

来自于同一个模型集合的

不同超参数

,例如学习率η不同

算法本身具有随机性,例如用不同的随机种子来得到不同的模型

针对输出:对输出表示进行操纵以增强多样性

如将多分类转化为多个二分类任务来训练单模型

将分类输出转化为回归输出等

投票和平均 Voting and Average

分类

对于分类任务来说,可以使用投票的方法:

简单投票法:

即各个分类器输出其预测的类别,取最高票对应的类别作为结果。若有多个类别都是最高票,那么随机选取一个。

加权投票法:

和上面的简单投票法类似,不过多了权重αi,这样可以区分分类器的重要程度,通常

此外,个体学习器可能产生不同的

的值,比如类标记和类概率。

类标记

,若 hi 将样本

x

预测为类别

取值为 1,否则为 0。使用类标记的投票亦称 “硬投票”。(其实就是多分类的输出),使用类标记的称为

硬投票

类概率

,即输出类别为

的概率。使用类概率的投票称为

软投票

对应 sklearn 中的 VotingClassifier 中 voting 参数设为 soft。

PS:使用类概率进行结合往往比直接基于类标记的效果好,即使分类器估计出的概率值一般都不太准确。

回归

对于回归任务来说,采用的为平均法 (Average):

简单平均:

加权平均:

首先我们回顾两个概念:

holdout 和交叉验证是两个常用的评估分类器预测准确率的技术,它们均是在给定数据集中随机取样划分数据。 holdout:将所给定的数据集随机划分成两个独立部分:一个作为训练数据集,而另一个作为测试数据集,通常训练数据集包含初始数据集中的三分之二的数据,而其余的三分之一则作为测试数据集的内容。利用训练集数据学习获得一个分类器,然后使用测试数据集对该分类器预测准确率进行评估,由于仅使用初始数据集中的一部分进行学习,因此对所得分类器预测准确性的估计应该是悲观的估计。 随机取样是 holdout 方法的一种变化,在随机取样方法中,重复利用 holdout 方法进行预测准确率估计 k 次,最后对这 k 次所获得的预测准确率求平均,以便获得最终的预测准确率。 k - 交叉验证:将初始数据集随机分为 k 个互不相交的子集,S1,S2,...,Sk, 每个子集大小基本相同。学习和测试分别进行 k 次,在第 i 次循环中,子集 Si 作为测试集,其他子集则合并到一起构成一个大训练数据集并通过学习获得相应的分类器,也就是第一次循环,使用 S2....Sk 作为训练数据集,S1 作为测试数据集;而在第二次循环时,使用 S1,S3,...,Sk 作为训练数据集,S2 作为测试数据集;如此下去等等。而对整个初始数据所得分类器的准确率估计则可用 k 次循环中所获得的正确分类数目之和除以初始数据集的大小来获得。在分层交叉验证中,将所划分的子集层次化以确保每个子集中的各类别分布与初始数据集中的类别分布基本相同。

Stacking

Stacking 相比 Linear Blending 来说,更加强大,然而也更容易过拟合。

Stacking 做法和 Linear Blending 类似,首先从数据集中训练出初级学习器,然后” 生成 “一个新的数据集用于训练次级学习器。为了防止过拟合,采用 K 折交叉验证法求解。

一个直观的图如下:

假设采用 5 折交叉验证,每个模型都要做满 5 次训练和预测,对于每一次:

从 80% 的数据训练得到一个模型 ht,然后预测训练集剩下的那 20%,同时也要预测测试集。

每次有 20% 的训练数据被预测,5 次后正好每个训练样本都被预测过了。

每次都要预测测试集,因此最后测试集被预测 5 次,最终结果取 5 次的平均。

stacking 例子:

对于每一轮的 5-fold,Model 1 都要做满 5 次的训练和预测。

Titanic 栗子:

Train Data 有 890 行。(请对应图中的上层部分)

每 1 次的 fold,都会生成 713 行 小 train, 178 行 小 test。我们用 Model 1 来训练 713 行的小 train,然后预测 178 行 小 test。预测的结果是长度为 178 的预测值。

这样的动作走 5 次! 长度为 178 的预测值 X 5 = 890 预测值,刚好和 Train data 长度吻合。这个 890 预测值是 Model 1 产生的,我们先存着,因为,一会让它将是第二层模型的训练来源。

重点:这一步产生的预测值我们可以转成 890 X 1 (890 行,1 列),记作 P1 (大写 P)

接着说 Test Data 有 418 行。(请对应图中的下层部分,对对对,绿绿的那些框框)

每 1 次的 fold,713 行 小 train 训练出来的 Model 1 要去预测我们全部的 Test Data(全部!因为 Test Data 没有加入 5-fold,所以每次都是全部!)。此时,Model 1 的预测结果是长度为 418 的预测值。

这样的动作走 5 次!我们可以得到一个 5 X 418 的预测值矩阵。然后我们根据行来就平均值,最后得到一个 1 X 418 的平均预测值。

重点:这一步产生的预测值我们可以转成 418 X 1 (418 行,1 列),记作 p1 (小写 p)

走到这里,你的第一层的 Model 1 完成了它的使命。

第一层还会有其他 Model 的,比如 Model 2,同样的走一遍, 我们有可以得到 890 X 1 (P2) 和 418 X 1 (p2) 列预测值。

这样吧,假设你第一层有 3 个模型,这样你就会得到:

来自 5-fold 的预测值矩阵 890 X 3,(P1,P2, P3) 和 来自 Test Data 预测值矩阵 418 X 3, (p1, p2, p3)。

-----------------------------------------

到第二层了..................

来自 5-fold 的预测值矩阵 890 X 3 作为你的 Train Data,训练第二层的模型

来自 Test Data 预测值矩阵 418 X 3 就是你的 Test Data,用训练好的模型来预测他们吧。

回归问题,代码如下(get_oof 就是上图的过程):

_N_FOLDS = 5 # 采用5折交叉验证

kf = KFold(n_splits=_N_FOLDS, random_state=42) # sklearn的交叉验证模块,用于划分数据

def get_oof(clf, X_train, y_train, X_test):

# X_train: 1000 * 10

# y_train: 1 * 1000

# X_test : 500 * 10

oof_train = np.zeros((X_train.shape[0], 1)) # 1000 * 1 Stacking后训练数据的输出

oof_test_skf = np.empty((_N_FOLDS, X_test.shape[0], 1)) # 5 * 500 * 1,oof_test_skf[i]代表第i折交叉验证产生的模型对测试集预测结果

for i, (train_index, test_index) in enumerate(kf.split(X_train)): # 交叉验证划分此时的训练集和验证集

kf_X_train = X_train[train_index] # 800 * 10 训练集

kf_y_train = y_train[train_index] # 1 * 800 训练集对应的输出

kf_X_val = X_train[test_index] # 200 * 10 验证集

clf.fit(kf_X_train, kf_y_train) # 当前模型进行训练

oof_train[test_index] = clf.predict(kf_X_val).reshape(-1, 1) # 对当前验证集进行预测, 200 * 1

oof_test_skf[i, :] = clf.predict(X_test).reshape(-1, 1) # 对测试集预测 oof_test_skf[i, :] : 500 * 1

oof_test = oof_test_skf.mean(axis=0) # 对每一则交叉验证的结果取平均

return oof_train, oof_test # 返回当前分类器对训练集和测试集的预测结果

# 将数据换成你的数据

X_train = np.random.random((1000, 10)) # 1000 * 10

y_train = np.random.random_integers(0, 1, (1000,)) # 1000

X_test = np.random.random((500, 10)) # 500 * 10

# 将你的每个分类器都调用get_oof函数,并把它们的结果合并,就得到了新的训练和测试数据new_train,new_test

new_train, new_test = [], []

for clf in [LinearRegression(), RandomForestRegressor()]:

oof_train, oof_test = get_oof(clf, X_train, y_train, X_test)

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第二层

clf = RandomForestRegressor()

clf.fit(new_train, y_train)

clf.predict(new_test)

如果是分类问题,我们对测试集的结果就不能像回归问题一样直接取平均,而是分类器输出所有类别的概率,最后取平均。每个分类器都贡献了_N_CLASS(类别数) 的维度。

修改 get_oof 函数如下即可:

_N_CLASS = 2

def get_oof(clf, X_train, y_train, X_test):

# X_train: 1000 * 10

# y_train: 1 * 1000

# X_test : 500 * 10

oof_train = np.zeros((X_train.shape[0], _N_CLASS)) # 1000 * _N_CLASS

oof_test = np.empty((X_test.shape[0], _N_CLASS)) # 500 * _N_CLASS

for i, (train_index, test_index) in enumerate(kf.split(X_train)):

kf_X_train = X_train[train_index] # 800 * 10 交叉验证划分此时的训练集和验证集

kf_y_train = y_train[train_index] # 1 * 800

kf_X_test = X_train[test_index] # 200 * 10 验证集

clf.fit(kf_X_train, kf_y_train) # 当前模型进行训练

oof_train[test_index] = clf.predict_proba(kf_X_test) # 当前验证集进行概率预测, 200 * _N_CLASS

oof_test += clf.predict_proba(X_test) # 对测试集概率预测 oof_test_skf[i, :] , 500 * _N_CLASS

oof_test /= _N_FOLDS # 对每一则交叉验证的结果取平均

return oof_train, oof_test # 返回当前分类器对训练集和测试集的预测结果

上面的代码只做了两层,你想的话还可以在加几层,因此这个方法叫做 stacking,堆叠。。

线性混合 Linear Blending

前面提到过加权平均法,每个个体学习器的权重不再相等,看起来就像是对每个个体学习器做一个线性组合,这也是线性混合法名字的由来。那么最优的权重是什么呢?一个直接的想法就是最好的αi 使得 error 最小,即对应了优化问题:

这里有 T 个个体学习器,每个学习器用

表示,而αt 就是对应的权重。

这里我们首先用训练数据训练出所有的 h,然后再做线性回归求出

。注意到这里要求

,来个拉格朗日函数?其实不用,通常我们可以忽略这个条件。以二分类为例如果αi 小于 0,相当于把模型反过来用。(假如给你个错误率 99% 的模型,你反过来用正确率不就 99% 了么!)

如何得到

呢?这里我们将个体学习器称为初级学习器,用于结合的学习器称为次级学习器。首先从数据集中训练出初级学习器,然后” 生成 “一个新的数据集用于训练次级学习器。注意为了防止过拟合,我们需要在训练集上做训练得到初级学习器 ht,而在验证集上比较不同α的好坏。最终模型则在所有的数据上进行训练(数据量多可能使得模型效果更好)

步骤如下:

训练集

Dtrain 中训练得到

,并对

验证集

中的数据

做转换为新的数据集

,其中

用线性回归求解

最后,用所有的数据 D 求解得到

,组成特征变换向量

对于新数据 x,

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

Blending 的优点在于:

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

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

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

而缺点在于:

使用了很少的数据

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

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

mlxtend 库

一. StackingClassifier

下面我们介绍一款功能强大的 stacking 利器,mlxtend 库,它可以很快地完成对 sklearn 模型地 stacking。

主要有以下几种使用方法吧:

I. 最基本的使用方法,即使用前面分类器产生的特征输出作为最后总的 meta-classifier 的输入数据

from sklearn import datasets

iris = datasets.load_iris()

X, y = iris.data[:, 1:3], iris.target

from sklearn import model_selection

from sklearn.linear_model import LogisticRegression

from sklearn.neighbors import KNeighborsClassifier

from sklearn.naive_bayes import GaussianNB

from sklearn.ensemble import RandomForestClassifier

from mlxtend.classifier import StackingClassifier

import numpy as np

clf1 = KNeighborsClassifier(n_neighbors=1)

clf2 = RandomForestClassifier(random_state=1)

clf3 = GaussianNB()

lr = LogisticRegression()

sclf = StackingClassifier(classifiers=[clf1, clf2, clf3],

meta_classifier=lr)

print('3-fold cross validation:\n')

for clf, label in zip([clf1, clf2, clf3, sclf],

['KNN',

'Random Forest',

'Naive Bayes',

'StackingClassifier']):

scores = model_selection.cross_val_score(clf, X, y,

cv=3, scoring='accuracy')

print("Accuracy: %0.2f (+/- %0.2f) [%s]"

% (scores.mean(), scores.std(), label))

II. 另一种使用第一层基本分类器产生的类别概率值作为 meta-classfier 的输入,这种情况下需要将 StackingClassifier 的参数设置为 use_probas=True。如果将参数设置为 average_probas=True,那么这些基分类器对每一个类别产生的概率值会被平均,否则会拼接。

例如有两个基分类器产生的概率输出为:

classifier 1: [0.2, 0.5, 0.3]

classifier 2: [0.3, 0.4, 0.4]

1) average = True :

产生的meta-feature 为:[0.25, 0.45, 0.35]

2) average = False:

产生的meta-feature为:[0.2, 0.5, 0.3, 0.3, 0.4, 0.4]

from sklearn import datasets

iris = datasets.load_iris()

X, y = iris.data[:, 1:3], iris.target

from sklearn import model_selection

from sklearn.linear_model import LogisticRegression

from sklearn.neighbors import KNeighborsClassifier

from sklearn.naive_bayes import GaussianNB

from sklearn.ensemble import RandomForestClassifier

from mlxtend.classifier import StackingClassifier

import numpy as np

clf1 = KNeighborsClassifier(n_neighbors=1)

clf2 = RandomForestClassifier(random_state=1)

clf3 = GaussianNB()

lr = LogisticRegression()

sclf = StackingClassifier(classifiers=[clf1, clf2, clf3],

use_probas=True,

average_probas=False,

meta_classifier=lr)

print('3-fold cross validation:\n')

for clf, label in zip([clf1, clf2, clf3, sclf],

['KNN',

'Random Forest',

'Naive Bayes',

'StackingClassifier']):

scores = model_selection.cross_val_score(clf, X, y,

cv=3, scoring='accuracy')

print("Accuracy: %0.2f (+/- %0.2f) [%s]"

% (scores.mean(), scores.std(), label))

III. 另外一种方法是对训练基中的特征维度进行操作的,这次不是给每一个基分类器全部的特征,而是给不同的基分类器分不同的特征,即比如基分类器 1 训练前半部分特征,基分类器 2 训练后半部分特征(可以通过 sklearn 的 pipelines 实现)。最终通过 StackingClassifier 组合起来。

from sklearn.datasets import load_iris

from mlxtend.classifier import StackingClassifier

from mlxtend.feature_selection import ColumnSelector

from sklearn.pipeline import make_pipeline

from sklearn.linear_model import LogisticRegression

iris = load_iris()

X = iris.data

y = iris.target

pipe1 = make_pipeline(ColumnSelector(cols=(0, 2)),

LogisticRegression())

pipe2 = make_pipeline(ColumnSelector(cols=(1, 2, 3)),

LogisticRegression())

sclf = StackingClassifier(classifiers=[pipe1, pipe2],

meta_classifier=LogisticRegression())

sclf.fit(X, y)

StackingClassifier 使用API及参数解析:

StackingClassifier(classifiers, meta_classifier, use_probas=False, average_probas=False, verbose=0, use_features_in_secondary=False)

参数:

classifiers : 基分类器,数组形式,[cl1, cl2, cl3]. 每个基分类器的属性被存储在类属性 self.clfs_.

meta_classifier : 目标分类器,即将前面分类器合起来的分类器

use_probas : bool (default: False) ,如果设置为True, 那么目标分类器的输入就是前面分类输出的类别概率值而不是类别标签

average_probas : bool (default: False),用来设置上一个参数当使用概率值输出的时候是否使用平均值。

verbose : int, optional (default=0)。用来控制使用过程中的日志输出,当 verbose = 0时,什么也不输出, verbose = 1,输出回归器的序号和名字。verbose = 2,输出详细的参数信息。verbose > 2, 自动将verbose设置为小于2的,verbose -2.

use_features_in_secondary : bool (default: False). 如果设置为True,那么最终的目标分类器就被基分类器产生的数据和最初的数据集同时训练。如果设置为False,最终的分类器只会使用基分类器产生的数据训练。

属性:

clfs_ : 每个基分类器的属性,list, shape 为 [n_classifiers]。

meta_clf_ : 最终目标分类器的属性

方法:

fit(X, y)

fit_transform(X, y=None, fit_params)

get_params(deep=True),如果是使用sklearn的GridSearch方法,那么返回分类器的各项参数。

predict(X)

predict_proba(X)

score(X, y, sample_weight=None), 对于给定数据集和给定label,返回评价accuracy

set_params(params),设置分类器的参数,params的设置方法和sklearn的格式一样

二. StackingRegressor

见:

你可能感兴趣的:(模型融合,python)