DM07-Ensemble组合技术

一、组合技术

组合技术即通过聚集多个分类器的预测来提高分类准确率。
两个思路:
思路1:构建多个独立的估计器,然后取它们的预测结果的平均。一般来说组合之后的估计器是会比单个估计器要好的,因为它的方差减小了。[Bagging 方法 , 随机森林 , …]
思路2:结合多个弱模型,使集成的模型更加强大;基估计器是依次构建的,并且每一个基估计器都尝试去减少组合估计器的偏差。[AdaBoost , 梯度提升树 , …]
组合技术大概也可以这样【6】:
这里写图片描述

二、Bootstrap

可能还会遇到另外一个名词—-Bootstrap,这个被叫做组合方式。来自【4】的一个图,
这里写图片描述
Bootstrap

具体的方法是:
(1)采用重复抽样的方法每次从n个原始样本中抽取m个样本(m自己设定)
(2)对于m个样本计算统计量
(3)重复步骤(1)(2)N次(N一般大于1000),这样就可以算出N个统计量
(4)计算这N个统计量的方差
比如说,我现在要对一些未知样本做分类,分类算法选取一种,比如SVM。我要估计的总体参数是准确率(accuracy)。对于n个原始样本,从步骤(1)开始,每次对抽取出的样本用SVM训练出一个模型,然后用这个模型对未知样本做分类,得到一个准确率。重复N次,可以得到N个准确率,然后对计算出的N个准确率做方差。
我在考虑为什么要计算这N个统计量的方差而不是期望或者均值。方差表示的是一组数据与其平均水平的偏离程度,如果计算的方差值在一定范围之内,则表示这些数据的波动不是很大,那么他们的均值就可以用来估计总体的参数,而如果方差很大,这些样本统计量波动很大,那么总体的参数估计就不太准确?

按这样理解,Bootstrap是概率统计层面的一个术语了。

三、Bagging

装袋(bagging):默认为理解为[还可以有不放回的抽取或特征子空间啥的]根据均匀概率分布从数据集中重复抽样(有放回),每个自助样本集和原数据集一样大,每个自助样本集含有原数据集大约63%的数据。训练k个分类器,测试样本被指派到得票最高的类。[多分类器,有放开抽样重复学习]
因为 bagging 方法可以减小过拟合,所以通常在强分类器和复杂模型上使用时表现的很好(例如,完全决策树,fully developed decision trees),相比之下 boosting 方法则在弱模型上表现更好(例如,浅层决策树,shallow decision trees);
sklearn中,对于Bagging的描述【5】:

bagging 方法会在原始训练集的随机子集上构建一类黑盒估计器的多个实例,然后把这些估计器的预测结果结合起来形成最终的预测结果。
该方法通过在构建模型的过程中引入随机性,来减少基估计器的方差(例如,决策树)。 在多数情况下,bagging
方法提供了一种非常简单的方式来对单一模型进行改进,而无需修改背后的算法。 因为 bagging
方法可以减小过拟合,所以通常在强分类器和复杂模型上使用时表现的很好(例如,完全决策树,fully developed decision
trees),相比之下 boosting 方法则在弱模型上表现更好(例如,浅层决策树,shallow decision trees)。

这里写图片描述
参考【4】中形式化说明一下:

Baggingboostrap aggregation的缩写,是一种根据均匀概率分布从数据集中重复抽样(有放回的)的技术。子训练样本集的大小和原始数据集相同。在构造每一个子分类器的训练样本时,由于是对原始数据集的有放回抽样,因此同一个训练样本集中可能出现多次同一个样本数据。
步骤是:
这里写图片描述

在Sklearn中使用,BaggingClassifier: Bagging分类器组合&&BaggingRegressor: Bagging回归器组合;
在sklearn的Bagging中,参数max_samples 和 max_features 控制着子集的大小(对于样例和特征);
bootstrap 和 bootstrap_features 控制着样例和特征的抽取是有放回还是无放回的;
当使用样本子集时,通过设置 oob_score=True ,可以使用袋外(out-of-bag)样本来评估泛化精度;
可以有放回的多次训练,有一个例子,可以对比单个分类器与Bagging组合后的对比;
误差可以进行分解的,可以分解为bias, variance, noise.【7】

# coding=utf-8
import matplotlib.pyplot as plt
import numpy as np
from sklearn.ensemble import BaggingRegressor
from sklearn.tree import DecisionTreeRegressor

# Settings
# repeat表示进行多少次抽样
n_repeat = 50  # Number of iterations for computing expectations
# 训练样本集的大小
n_train = 50  # Size of the training set
# 测试样本集的大小
n_test = 1000  # Size of the test set
# 增加噪音参数
noise = 0.1  # Standard deviation of the noise
np.random.seed(0)

# 两个分类器的对比
estimators = [("Tree", DecisionTreeRegressor()),
              ("Bagging(Tree)", BaggingRegressor(DecisionTreeRegressor()))]

n_estimators = len(estimators)


# 生成数据
def f(x):
    x = x.ravel()
    return 1.2 * (np.exp(-x ** 2) + 1.5 * np.exp(-(x - 2) ** 2))


def generate(n_samples, noise, n_repeat=1):
    X = np.random.rand(n_samples) * 10 - 5
    X = np.sort(X)

    if n_repeat == 1:
        y = f(X) + np.random.normal(0.0, noise, n_samples)
    else:
        y = np.zeros((n_samples, n_repeat))
        for i in range(n_repeat):
            y[:, i] = f(X) + np.random.normal(0.0, noise, n_samples)
    X = X.reshape((n_samples, 1))
    return X, y


X_train = []
y_train = []
# 构造多次抽样的数据集
for i in range(n_repeat):
    X, y = generate(n_samples=n_train, noise=noise)
    X_train.append(X)
    y_train.append(y)
# 生成测试数据
X_test, y_test = generate(n_samples=n_test, noise=noise, n_repeat=n_repeat)
# 遍历分类器
for n, (name, estimator) in enumerate(estimators):
    # 计算预测数据
    y_predict = np.zeros((n_test, n_repeat))
    for i in range(n_repeat):
        estimator.fit(X_train[i], y_train[i])
        y_predict[:, i] = estimator.predict(X_test)

    # Bias^2 + Variance + Noise decomposition of the mean squared error
    y_error = np.zeros(n_test)
    for i in range(n_repeat):
        for j in range(n_repeat):
            y_error += (y_test[:, j] - y_predict[:, i]) ** 2
    y_error /= (n_repeat * n_repeat)
    y_noise = np.var(y_test, axis=1)
    y_bias = (f(X_test) - np.mean(y_predict, axis=1)) ** 2
    y_var = np.var(y_predict, axis=1)


    print("{0}: {1:.4f} (error) = {2:.4f} (bias^2) "
          " + {3:.4f} (var) + {4:.4f} (noise)".format(name,
                                                      np.mean(y_error),
                                                      np.mean(y_bias),
                                                      np.mean(y_var),
                                                      np.mean(y_noise)))

    # Plot figures
    plt.subplot(2, n_estimators, n + 1)
    plt.plot(X_test, f(X_test), "b", label="$f(x)$")
    plt.plot(X_train[0], y_train[0], ".b", label="LS ~ $y = f(x)+noise$")

    for i in range(n_repeat):
        if i == 0:
            plt.plot(X_test, y_predict[:, i], "r", label="$\^y(x)$")
        else:
            plt.plot(X_test, y_predict[:, i], "r", alpha=0.05)

    plt.plot(X_test, np.mean(y_predict, axis=1), "c",
             label="$\mathbb{E}_{LS} \^y(x)$")

    plt.xlim([-5, 5])
    plt.title(name)

    if n == 0:
        plt.legend(loc="upper left", prop={"size": 11})

    plt.subplot(2, n_estimators, n_estimators + n + 1)
    plt.plot(X_test, y_error, "r", label="$error(x)$")
    plt.plot(X_test, y_bias, "b", label="$bias^2(x)$"),
    plt.plot(X_test, y_var, "g", label="$variance(x)$"),
    plt.plot(X_test, y_noise, "c", label="$noise(x)$")

    plt.xlim([-5, 5])
    plt.ylim([0, 0.1])

    if n == 0:
        plt.legend(loc="upper left", prop={"size": 11})

plt.show()

这里写图片描述

四、随机森林(RF)

RandomTreeClassifier:随机森林分类器组合&&RandomTreeRegressor: 随机森林回归器组合
在随机森林中(参见 RandomForestClassifier 和 RandomForestRegressor 类), 集成模型中的每棵树构建时的样本都是由训练集经过有放回抽样得来的(例如,自助采样法-bootstrap sample,这里采用西瓜书中的译法)。 另外,在构建树的过程中进行结点分割时,选择的分割点不再是所有特征中最佳分割点,而是特征的一个随机子集中的最佳分割点。 由于这种随机性,森林的偏差通常会有略微的增大(相对于单个非随机树的偏差),但是由于取了平均,其方差也会减小,通常能够补偿偏差的增加,从而产生一个总体上更好的模型。
与原始文献 [B2001] 不同的是,scikit-learn 的实现是取每个分类器预测概率的平均,而不是让每个分类器对类别进行投票
这里里面涉及到OOB,这个与N-Fold有交叉验证有些相近了,随机森林自己带验证,不用交叉验证。
经过选择特征子集与多次抽样来计算多颗树的模型。

五、极限随机树

ExtraTreeClassifier:ExtraTree分类器组合&&ExtraTreeRegressor: ExtraTree回归器组合
在极限随机树中(参见 ExtraTreesClassifier 和 ExtraTreesRegressor 类), 计算分割点方法中的随机性进一步增强。 在随机森林中,使用的特征是候选特征的随机子集;不同于寻找最具有区分度的阈值, 这里的阈值是针对每个候选特征随机生成的,并且选择这些随机生成的阈值中的最佳者作为分割规则。 这种做法通常能够减少一点模型的方差,代价则是略微地增大偏差。
n_estimators:森林里树的数量,通常数量越大,效果越好,但是计算时间也会随之增加.当树的数量超过一个临界值之后,算法的效果并不会很显著地变好。
max_features:分割节点时考虑的特征的随机子集的大小。 这个值越低,方差减小得越多,但是偏差的增大也越多。
根据经验,回归问题中使用 max_features = n_features , 分类问题使用 max_features = sqrt(n_features (其中 n_features 是特征的个数)是比较好的默认值。 max_depth = None 和 min_samples_split = 2 结合通常会有不错的效果(即生成完全的树)
这些(默认)值通常不是最佳的,同时还可能消耗大量的内存,最佳参数值应由交叉验证获得。
一段来自【3】的描述:

ET或Extra-Trees(Extremely randomized
trees,极端随机树)是由PierreGeurts等人于2006年提出。该算法与随机森林算法十分相似,都是由许多决策树构成。但该算法与随机森林有两点主要的区别:
1、随机森林应用的是Bagging模型,而ET是使用所有的训练样本得到每棵决策树,也就是每棵决策树应用的是相同的全部训练样本;
2、随机森林是在一个随机子集内得到最佳分叉属性,而ET是完全随机的得到分叉值,从而实现对决策树进行分叉的。
对于第2点的不同,我们再做详细的介绍。我们仅以二叉树为例,当特征属性是类别的形式时,随机选择具有某些类别的样本为左分支,而把具有其他类别的样本作为右分支;当特征属性是数值的形式时,随机选择一个处于该特征属性的最大值和最小值之间的任意数,当样本的该特征属性值大于该值时,作为左分支,当小于该值时,作为右分支。这样就实现了在该特征属性下把样本随机分配到两个分支上的目的。然后计算此时的分叉值(如果特征属性是类别的形式,可以应用基尼指数;如果特征属性是数值的形式,可以应用均方误差)。遍历节点内的所有特征属性,按上述方法得到所有特征属性的分叉值,我们选择分叉值最大的那种形式实现对该节点的分叉。从上面的介绍可以看出,这种方法比随机森林的随机性更强。
对于某棵决策树,由于它的最佳分叉属性是随机选择的,因此用它的预测结果往往是不准确的,但多棵决策树组合在一起,就可以达到很好的预测效果。
当ET构建好了以后,我们也可以应用全部的训练样本来得到该ET的预测误差。这是因为尽管构建决策树和预测应用的是同一个训练样本集,但由于最佳分叉属性是随机选择的,所以我们仍然会得到完全不同的预测结果,用该预测结果就可以与样本的真实响应值比较,从而得到预测误差。如果与随机森林相类比的话,在ET中,全部训练样本都是OOB样本,所以计算ET的预测误差,也就是计算这个OOB误差。
在这里,我们仅仅介绍了ET算法与随机森林的不同之处,ET算法的其他内容(如预测、OOB误差的计算)与随机森林是完全相同的,具体内容请看关于随机森林的介绍。

这里写图片描述
RF与ET,可以作特征的重要性计算,特征对目标变量预测的相对重要性可以通过(树中的决策节点的)特征使用的相对顺序(即深度)来进行评估。 决策树顶部使用的特征对更大一部分输入样本的最终预测决策做出贡献;因此,可以使用接受每个特征对最终预测的贡献的样本比例来评估该 特征的相对重要性
通过对多个随机树中的 预期贡献率 (expected activity rates) 取平均,可以减少这种估计的 方差 ,并将其用于特征选择。:
http://sklearn.apachecn.org/cn/0.19.0/auto_examples/ensemble/plot_forest_importances.html#sphx-glr-auto-examples-ensemble-plot-forest-importances-py

六、Boostin

提升(boosting):通过给样本设置不同的权值,每轮迭代调整权值。不同的提升算法之间的差别,一般是(1)如何更新样本的权值,(2)如何组合每个分类器的预测。
其中Adaboost中,样本权值是增加那些被错误分类的样本的权值,分类器C_i的重要性依赖于它的错误率。
Boosting主要关注降低偏差,因此Boosting能基于泛化性能相当弱的学习器构建出很强的集成;Bagging主要关注降低方差,因此它在不剪枝的决策树、神经网络等学习器上效用更为明显。
偏差指的是算法的期望预测与真实预测之间的偏差程度,反应了模型本身的拟合能力;方差度量了同等大小的训练集的变动导致学习性能的变化,刻画了数据扰动所导致的影响。

七、AdaBoost

AdaBoostClassifier: AdaBoost分类器组合&&AdaBoostRegressor: AdaBoost回归器组合
AdaBoost算法思想,由多个弱分类构建成一个强分类,这个多分类集成强分类器,主要弱分类器的分类错误率低于0.5,AdaBoost就可以让一堆分类器达到100%的准确率。
初始化时,将所有弱学习器的权重都设置为1/N,因此第一次迭代仅仅是通过原始数据训练出一个弱学习器。在接下来的 连续迭代中,样本的权重逐个地被修改,学习算法也因此要重新应用这些已经修改的权重。在给定的一个迭代中, 那些在上一轮迭代中被预测为错误结果的样本的权重将会被增加,而那些被预测为正确结果的样本的权 重将会被降低。随着迭代次数的增加,那些难以预测的样例的影响将会越来越大,每一个随后的弱学习器都将 会被强迫更加关注那些在之前被错误预测的样例.
在权重修改那一步,对于分类错的,原来权重会乘以大于1的d_i, 让这个样本继续增大,否则是除以d_i,更的权重在评估下一个分类器错误率时会起作用。这样,后面的分类会弥补前面的分类器的不足。所以,把这些分类联合起来就是一个强大的分类器了。
对于d_i的取值,最最本质与最直观的出发点是这样思考的:当f_i(x)分类器进行训练后,错误率为e,这个e小于0.5的。这个e是基于w_i的向量计算出来的,在修改权重w_i+1时,希望f_i(x)计算出来的错误率为0.5,即是说f_i(x)分类器,对于目前的分类是很差的,要找多一个分类器来解决这个问题,接着进行下一下分类器的计算,即f_i+1(x)是计算,此时这个w_i+1,就是刚假设f_i(x)错误率为0.5计算出来的值。
一个来自sklearn的AdaBoost例子,采用一个stump的弱分类器来分类,曲线表示1到500个分类器组合的结果。每一次迭代的测试误差能够通过 staged_predict 方法获取,该方法返回一个生成器,用来产生每一 个迭代的预测结果。这个方法在Gradient Tree Boosting的模型中也会有反映。

# coding=utf-8
import matplotlib.pyplot as plt
import numpy as np
from sklearn import datasets
from sklearn.ensemble import AdaBoostClassifier
from sklearn.metrics import zero_one_loss
from sklearn.tree import DecisionTreeClassifier

n_estimators = 500
# A learning rate of 1. may not be optimal for both SAMME and SAMME.R
learning_rate = 1.

X, y = datasets.make_hastie_10_2(n_samples=12000, random_state=1)

X_test, y_test = X[2000:], y[2000:]
X_train, y_train = X[:2000], y[:2000]

# 一刀切分类器,这个分类器是非常弱的,只用一条线去把点分开
dt_stump = DecisionTreeClassifier(max_depth=1, min_samples_leaf=1)
dt_stump.fit(X_train, y_train)
dt_stump_err = 1.0 - dt_stump.score(X_test, y_test)

# 决策树
dt = DecisionTreeClassifier(max_depth=9, min_samples_leaf=1)
dt.fit(X_train, y_train)
dt_err = 1.0 - dt.score(X_test, y_test)

# 离散型的分类器[这里用到”一刀切“]
ada_discrete = AdaBoostClassifier(
    base_estimator=dt_stump,
    learning_rate=learning_rate,
    n_estimators=n_estimators,
    algorithm="SAMME")
ada_discrete.fit(X_train, y_train)

##  连续型的分类器[”一刀切“]
ada_real = AdaBoostClassifier(
    base_estimator=dt_stump,
    learning_rate=learning_rate,
    n_estimators=n_estimators,
    algorithm="SAMME.R")
ada_real.fit(X_train, y_train)

fig = plt.figure()
ax = fig.add_subplot(111)

# 两条参考线
ax.plot([1, n_estimators], [dt_stump_err] * 2, 'k-',
        label='Decision Stump Error')
ax.plot([1, n_estimators], [dt_err] * 2, 'k--',
        label='Decision Tree Error')

# 可获取所有的分类器
# for estimator in ada_real.estimators_:
#     print(estimator)

# ------------------------
# 计算每步过程的错误率
ada_discrete_err = np.zeros((n_estimators,))
for i, y_pred in enumerate(ada_discrete.staged_predict(X_test)):
    ada_discrete_err[i] = zero_one_loss(y_pred, y_test)

ada_discrete_err_train = np.zeros((n_estimators,))
for i, y_pred in enumerate(ada_discrete.staged_predict(X_train)):
    ada_discrete_err_train[i] = zero_one_loss(y_pred, y_train)

ada_real_err = np.zeros((n_estimators,))
for i, y_pred in enumerate(ada_real.staged_predict(X_test)):
    ada_real_err[i] = zero_one_loss(y_pred, y_test)

ada_real_err_train = np.zeros((n_estimators,))
for i, y_pred in enumerate(ada_real.staged_predict(X_train)):
    ada_real_err_train[i] = zero_one_loss(y_pred, y_train)
# ------------------------

# 显示出来begin
ax.plot(np.arange(n_estimators) + 1, ada_discrete_err,
        label='Discrete AdaBoost Test Error',
        color='red')
ax.plot(np.arange(n_estimators) + 1, ada_discrete_err_train,
        label='Discrete AdaBoost Train Error',
        color='blue')
ax.plot(np.arange(n_estimators) + 1, ada_real_err,
        label='Real AdaBoost Test Error',
        color='orange')
ax.plot(np.arange(n_estimators) + 1, ada_real_err_train,
        label='Real AdaBoost Train Error',
        color='green')
# 显示出来end
ax.set_ylim((0.0, 0.5))
ax.set_xlabel('n_estimators')
ax.set_ylabel('error rate')

leg = ax.legend(loc='upper right', fancybox=True)
leg.get_frame().set_alpha(0.7)

plt.show()

这里写图片描述
连续型算法表现得比较好,离散的分类器还有待提高。

八、Gradient Tree Boosting(梯度树提升)

可参见GBDT推导与xgboost推导【10】。
GradientBoostingClassifier:GradientBoosting分类器组合&&GradientBoostingRegressor: GradientBoosting回归器组合
Gradient Tree Boosting 或梯度提升回归树(GBRT)是对于任意的可微损失函数的提升算法的泛化。 GBRT 是一个准确高效的现有程序, 它既能用于分类问题也可以用于回归问题。
弱学习器(例如:回归树)的数量由参数 n_estimators 来控制;每个树的大小可以通过由参数 max_depth 设置树的深度,或者由参数 max_leaf_nodes 设置叶子节点数目来控制。 learning_rate 是一个在 (0,1] 之间的超参数,这个参数通过 shrinkage(缩减步长) 来控制过拟合。GradientBoostingRegressor 和 GradientBoostingClassifier 都支持设置参数 warm_start=True ,这样设置允许我们在已经训练的模型上面添加更多的估计器。
8.1 GBRT模型
Gradient Tree Boosting其实是一个模型框架,这个模型是具有可加性,把弱的函数组合成强的分类器:
这里写图片描述
上面的损失函数,Sklearn支持的情况如下所示:

8.2 Loss Functions(损失函数)

以下是目前支持的损失函数,具体损失函数可以通过参数 loss 指定:
回归 (Regression)

  • Least squares ( ‘ls’ ): 由于其优越的计算性能,该损失函数成为回归算法中的自然选择。 初始模型 (校对者注:即损失函数的初始值,下同) 通过目标值的均值给出。
  • Least absolute deviation ( ‘lad’ ): 回归中具有鲁棒性的损失函数,初始模型通过目 标值的中值给出。
  • Huber ( ‘huber’ ): 回归中另一个具有鲁棒性的损失函数,它是最小二乘和最小绝对偏差两者的结合. 其利用 alpha 来控制模型对于异常点的敏感度.
  • Quantile ( ‘quantile’ ): 分位数回归损失函数.用 0 < alpha < 1 来指定分位数这个损 失函数可以用来产生预测间隔。

分类 (Classification)

  • Binomial deviance (‘deviance’): 对于二分类问题(提供概率估计)即负的二项 log 似然损失函数。模型以 log 的比值比来初始化。
  • Multinomial deviance (‘deviance’): 对于多分类问题的负的多项log似然损失函数具有 n_classes 个互斥的类。提供概率估计。 初始模型由每个类的先验概率给出.在每一次迭代中 n_classes 回归树被构建,这使得 GBRT 在处理多类别数据集时相当低效。
  • Exponential loss (‘exponential’): 与 AdaBoostClassifier 具有相同的损失函数。与 ‘deviance’ 相比,对被错误标记的样本的鲁棒性较差,仅用于在二分类问题。

另外,还有总结成这样的。
这里写图片描述

可以看到,AdaBoost是GD的一种,损失函数采用指数损失的一种。

8.3 Regularization(正则化)
8.3.1 收缩率 (Shrinkage)

这里写图片描述
在训练一定数量的弱分类器时,参数 learning_rate 和参数 n_estimators 之间有很强的制约关系。 较小的 learning_rate 需要大量的弱分类器才能维持训练误差的稳定。经验表明数值较小的 learning_rate 将会得到更好的测试误差。 [HTF2009] 推荐把 learning_rate 设置为一个较小的常数 (例如: learning_rate <= 0.1 )同时通过提前停止策略来选择合适的 n_estimators . 有关 learning_rate 和 n_estimators 更详细的讨论可以参考 [R2007].
[F2001] (1, 2, 3) J. Friedman, “Greedy Function Approximation: A Gradient Boosting Machine”, The Annals of Statistics, Vol. 29, No. 5, 2001.
[HTF2009] Hastie, R. Tibshirani and J. Friedman, “Elements of Statistical Learning Ed. 2”, Springer, 2009.
[R2007] Ridgeway, “Generalized Boosted Models: A guide to the gbm package”, 2007

8.3.2 子采样 (Subsampling)

[F1999] 提出了随机梯度提升,这种方法将梯度提升(gradient boosting)和 bootstrap averaging(bagging) 相结合。在每次迭代中,基分类器是通过抽取所有可利用训练集中一小部分的 subsample 训练得到的子样本采用无放回的方式采样。 subsample 参数的值一般设置为 0.5 。
[F1999] Friedman, “Stochastic Gradient Boosting”, 1999

8.3.3 运用收缩率与子采样来提升模型

我们可以明显看到指定收缩率比没有收缩拥有更好的表现。而将子采样和收缩率相结合能进一步的提高模型的准确率。相反,使用子采样而不使用收缩的结果十分糟糕。
例子的原理来自T. Hastie, R. Tibshirani and J. Friedman, “Elements of Statistical Learning Ed. 2”, Springer, 2009.代码如下【8】:

# coding=utf-8
# T. Hastie, R. Tibshirani and J. Friedman, “Elements of Statistical Learning Ed. 2”, Springer, 2009
import numpy as np
import matplotlib.pyplot as plt

from sklearn import ensemble
from sklearn import datasets

X, y = datasets.make_hastie_10_2(n_samples=12000, random_state=1)
X = X.astype(np.float32)

# map labels from {-1, 1} to {0, 1}
labels, y = np.unique(y, return_inverse=True)

X_train, X_test = X[:2000], X[2000:]
y_train, y_test = y[:2000], y[2000:]

original_params = {'n_estimators': 1000, 'max_leaf_nodes': 4, 'max_depth': None, 'random_state': 2,
                   'min_samples_split': 5}

plt.figure()

for label, color, setting in [('No shrinkage', 'orange',
                               {'learning_rate': 1.0, 'subsample': 1.0}),
                              ('learning_rate=0.1', 'turquoise',
                               {'learning_rate': 0.1, 'subsample': 1.0}),
                              ('subsample=0.5', 'blue',
                               {'learning_rate': 1.0, 'subsample': 0.5}),
                              ('learning_rate=0.1, subsample=0.5', 'gray',
                               {'learning_rate': 0.1, 'subsample': 0.5}),
                              ('learning_rate=0.1, max_features=2', 'magenta',
                               {'learning_rate': 0.1, 'max_features': 2})]:
    params = dict(original_params)
    params.update(setting)

    clf = ensemble.GradientBoostingClassifier(**params)
    clf.fit(X_train, y_train)

    # compute test set deviance
    test_deviance = np.zeros((params['n_estimators'],), dtype=np.float64)

    for i, y_pred in enumerate(clf.staged_decision_function(X_test)):
        # clf.loss_ assumes that y_test[i] in {0, 1}
        test_deviance[i] = clf.loss_(y_test, y_pred)

    plt.plot((np.arange(test_deviance.shape[0]) + 1)[::5], test_deviance[::5],
             '-', color=color, label=label)

plt.legend(loc='upper left')
plt.xlabel('Boosting Iterations')
plt.ylabel('Test Set Deviance')

plt.show()

这里写图片描述
另一个减少方差的策略是特征子采样,这种方法类似于 RandomForestClassifier 中的随机分割。子采样的特征数可以通过参数 max_features 来控制。[采用一个较小的 max_features 值能大大缩减模型的训练时间。]

OOB:随机梯度提升允许计算测试偏差的袋外估计值(Out-of-bag),方法是计算那些不在自助采样之内的样本偏差的改进。这个改进保存在属性 oob_improvement_ 中 oob_improvement_[i] 如果将第 i 步添加到当前预测中,则可以改善 OOB 样本的损失。袋外估计可以使用在模型选择中,例如决定最优迭代次数。 OOB 估计通常都很悲观,因此我们推荐使用交叉验证来代替它,而当交叉验证太耗时时我们就只能使用 OOB 了。

8.4 Interpretation(解释性)

对于数据挖掘,很多时候需要回答一些问题,这个目标的造成是一些什么因素形成的。之前的LR等线性模型会容易回答这个问题,因为每个维度,会给上一个权重,这样就得到了一个每个维度的权重解释。另外,通过简单地可视化树结构可以很容易地解释单个决策树,然而对于梯度提升模型来说,一般拥有数百棵/种回归树,将每一棵树都可视化来解释整个模型是很困难的。幸运的是,有很多关于总结和解释梯度提升模型的技术。
GT在这里也提供了一个这样的功能。

8.4.1 Feature importance(特征重要性)

对于一个训练好的梯度提升模型,其特征重要性分数可以通过属性 feature_importances_ 查看。

8.4.2 Partial dependence(部分依赖)

部分依赖图(PDP)展示了目标响应和一系列目标特征的依赖关系,同时边缘化了其他所有特征值(候选特征)。 直觉上,我们可以将部分依赖解释为作为目标特征函数 [2] 的预期目标响应 [1] 。
模型 partial_dependence 提供了一个便捷的函数 plot_partial_dependence 来产生单向或双向部分依赖图。在下图的例子中我们展示如何创建一个部分依赖的网格图:特征值介于 0 和 1 的两个单向依赖 PDPs 和一个在两个特征间的双向 PDPs:

from sklearn.datasets import make_hastie_10_2
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.ensemble.partial_dependence import plot_partial_dependence

X, y = make_hastie_10_2(random_state=0)
clf = GradientBoostingClassifier(n_estimators=100, learning_rate=1.0,
    max_depth=1, random_state=0).fit(X, y)
features = [0, 1, (0, 1)]
fig, axs = plot_partial_dependence(clf, X, features) 
对于多类别的模型,你需要通过 label 参数设置类别标签来创建 PDPs:
from sklearn.datasets import load_iris
iris = load_iris()
mc_clf = GradientBoostingClassifier(n_estimators=10,
    max_depth=1).fit(iris.data, iris.target)
features = [3, 2, (3, 2)]
fig, axs = plot_partial_dependence(mc_clf, X, features, label=0) 

如果你需要部分依赖函数的原始值而不是图,你可以调用 partial_dependence 函数:

九、XgBoost

● 传统GBDT以CART作为基分类器,xgboost还支持线性分类器,这个时候xgboost相当于带L1和L2正则化项的逻辑斯蒂回归(分类问题)或者线性回归(回归问题)。
● 传统GBDT在优化时只用到一阶导数信息,xgboost则对代价函数进行了二阶泰勒展开,同时用到了一阶和二阶导数。顺便提一下,xgboost工具支持自定义代价函数,只要函数可一阶和二阶求导。
● xgboost在代价函数里加入了正则项,用于控制模型的复杂度。正则项里包含了树的叶子节点个数、每个叶子节点上输出的score的L2模的平方和。从Bias-variance tradeoff角度来讲,正则项降低了模型的variance,使学习出来的模型更加简单,防止过拟合,这也是xgboost优于传统GBDT的一个特性。
● Shrinkage(缩减),相当于学习速率(xgboost中的eta)。xgboost在进行完一次迭代后,会将叶子节点的权重乘上该系数,主要是为了削弱每棵树的影响,让后面有更大的学习空间。实际应用中,一般把eta设置得小一点,然后迭代次数设置得大一点。(补充:传统GBDT的实现也有学习速率)
● 列抽样(column subsampling)。xgboost借鉴了随机森林的做法,支持列抽样,不仅能降低过拟合,还能减少计算,这也是xgboost异于传统gbdt的一个特性。
● 对缺失值的处理。对于特征的值有缺失的样本,xgboost可以自动学习出它的分裂方向。
● xgboost工具支持并行。boosting不是一种串行的结构吗?怎么并行的?注意xgboost的并行不是tree粒度的并行,xgboost也是一次迭代完才能进行下一次迭代的(第t次迭代的代价函数里包含了前面t-1次迭代的预测值)。xgboost的并行是在特征粒度上的。我们知道,决策树的学习最耗时的一个步骤就是对特征的值进行排序(因为要确定最佳分割点),xgboost在训练之前,预先对数据进行了排序,然后保存为block结构,后面的迭代中重复地使用这个结构,大大减小计算量。这个block结构也使得并行成为了可能,在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行。
● 可并行的近似直方图算法。树节点在进行分裂时,我们需要计算每个特征的每个分割点对应的增益,即用贪心法枚举所有可能的分割点。当数据无法一次载入内存或者在分布式情况下,贪心算法效率就会变得很低,所以xgboost还提出了一种可并行的近似直方图算法,用于高效地生成候选的分割点。
参考:http://xgboost.readthedocs.io/en/latest/
论文:XGBoost: A Scalable Tree Boosting System, https://arxiv.org/abs/1603.02754

十、Voting Classifier(投票分类器)

VotingClassifier (投票分类器)的原理是结合了多个不同的机器学习分类器,并且采用多数表决(majority vote)(硬投票) 或者平均预测概率(软投票)的方式来预测分类标签。 这样的分类器可以用于一组同样表现良好的模型,以便平衡它们各自的弱点。
10.1 多数类标签 (又称为 多数/硬投票)
在多数投票中,对于每个特定样本的预测类别标签是所有单独分类器预测的类别标签中票数占据多数(模式)的类别标签。
例如,如果给定样本的预测是

  • classifier 1 -> class 1
  • classifier 2 -> class 1
  • classifier 3 -> class 2

类别 1 占据多数,通过 voting=’hard’ 参数设置投票分类器为多数表决方式,会得到该样本的预测结果是类别 1 。
在平局的情况下,投票分类器(VotingClassifier)将根据升序排序顺序选择类标签。 例如,场景如下:

  • classifier 1 -> class 2
  • classifier 2 -> class 1

这种情况下, class 1 将会被指定为该样本的类标签。
例子:

# coding=utf-8
from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.naive_bayes import GaussianNB

iris = datasets.load_iris()
X, y = iris.data[:, 1:3], iris.target

clf1 = LogisticRegression(random_state=1)
clf2 = RandomForestClassifier(random_state=1)
clf3 = GaussianNB()
eclf = VotingClassifier(estimators=[('lr', clf1), ('rf', clf2), ('gnb', clf3)], voting='hard')

for clf, label in zip([clf1, clf2, clf3, eclf], ['Logistic Regression', 'Random Forest', 'naive Bayes', 'Ensemble']):
    scores = cross_val_score(clf, X, y, cv=5, scoring='accuracy')
    print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))

运行结果:

Accuracy: 0.90 (+/- 0.05) [Logistic Regression]
Accuracy: 0.93 (+/- 0.05) [Random Forest]
Accuracy: 0.91 (+/- 0.04) [naive Bayes]
Accuracy: 0.95 (+/- 0.05) [Ensemble]

由结果来看,组合后的结果,比前面几个单独运行的准确率会高a。

10.2 加权平均概率 (软投票)

与多数投票(硬投票)相比,软投票将类别标签返回为预测概率之和的 argmax 。
具体的权重可以通过权重参数 weights 分配给每个分类器。当提供权重参数 weights 时,收集每个分类器的预测分类概率, 乘以分类器权重并取平均值。然后将具有最高平均概率的类别标签确定为最终类别标签。
为了用一个简单的例子来说明这一点,假设我们有 3 个分类器和一个 3 类分类问题,我们给所有分类器赋予相等的权重:w1 = 1,w2 = 1,w3 = 1 。
样本的加权平均概率计算如下:

分类器类别 1类别 2类别 3
分类器 1w1 * 0.2w1 * 0.5w1 * 0.3
分类器 2w2 * 0.6w2 * 0.3w2 * 0.1
分类器 3w3 * 0.3w3 * 0.4w3 * 0.3

加权平均的结果 0.37 0.4 0.23
这里可以看出,预测的类标签是 2,因为它具有最大的平均概率.
例子:

# coding=utf-8
print(__doc__)

from itertools import product

import numpy as np
import matplotlib.pyplot as plt

from sklearn import datasets
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.ensemble import VotingClassifier

# Loading some example data
iris = datasets.load_iris()
X = iris.data[:, [0, 2]]
y = iris.target

# Training classifiers
clf1 = DecisionTreeClassifier(max_depth=4)
clf2 = KNeighborsClassifier(n_neighbors=7)
clf3 = SVC(kernel='rbf', probability=True)
eclf = VotingClassifier(estimators=[('dt', clf1), ('knn', clf2),
                                    ('svc', clf3)],
                        voting='soft', weights=[2, 1, 2])

clf1.fit(X, y)
clf2.fit(X, y)
clf3.fit(X, y)
eclf.fit(X, y)

# Plotting decision regions
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
                     np.arange(y_min, y_max, 0.1))

f, axarr = plt.subplots(2, 2, sharex='col', sharey='row', figsize=(10, 8))

for idx, clf, tt in zip(product([0, 1], [0, 1]),
                        [clf1, clf2, clf3, eclf],
                        ['Decision Tree (depth=4)', 'KNN (k=7)',
                         'Kernel SVM', 'Soft Voting']):
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)

    axarr[idx[0], idx[1]].contourf(xx, yy, Z, alpha=0.4)
    axarr[idx[0], idx[1]].scatter(X[:, 0], X[:, 1], c=y,
                                  s=20, edgecolor='k')
    axarr[idx[0], idx[1]].set_title(tt)

plt.show()

这里写图片描述

十一、参考

【1】 Cer_ml; 简书; https://www.jianshu.com/p/516f009c0875
【2】 http://sklearn.apachecn.org/cn/0.19.0/modules/ensemble.html#bagging-meta-estimator-bagging
【3】http://blog.csdn.net/zhaocj/article/details/51648966
【4】http://blog.csdn.net/zjsghww/article/details/51591009
【5】http://sklearn.apachecn.org/cn/0.19.0/modules/ensemble.html#bagging-meta-estimator-bagging
【6】https://www.jianshu.com/p/516f009c0875
【7】http://sklearn.apachecn.org/cn/0.19.0/auto_examples/ensemble/plot_bias_variance.html#sphx-glr-auto-examples-ensemble-plot-bias-variance-py
【8】 http://sklearn.apachecn.org/cn/0.19.0/auto_examples/ensemble/plot_gradient_boosting_regularization.html#id1
【9】https://www.zhihu.com/question/41354392/answer/98658997
【10】 http://blog.csdn.net/china1000/article/details/51106856
【11】 XGBoost 与 Boosted Tree http://www.52cs.org/?p=429
【12】 XGBoost: A Scalable Tree Boosting System, https://arxiv.org/abs/1603.02754

by happyprince, http://blog.csdn.net/ld326/article/details/79367190

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值