本文始发于个人公众号:TechFlow,原创不易,求个关注

今天是机器学习专题的第25篇文章,我们一起来聊聊AdaBoost。

我们目前为止已经学过了好几个模型,光决策树的生成算法就有三种。但是我们每次进行分类的时候,每次都是采用一个模型进行训练和预测。我们日常在做一个决策的时候,往往会咨询好几个人,综合采纳他们的意见。那么有没有可能把这个思路照搬到机器学习领域当中,创建多个模型来综合得出结果呢?

这当然是可以的,这样的思路就叫做集成方法(ensemble method)。

集成方法

集成方法本身并不是某种具体的方法或者是算法,只是一种训练机器学习模型的思路。它的含义只有一点,就是训练多个模型,然后将它们的结果汇聚在一起。

根据这个思路,业内又衍生出了三种特定的方法,分别是Bagging、Boosting和Stacking。

Bagging

Bagging是bootstrap aggregating的缩写,我们从字面上很难理解它的含义。我们记住这个名字即可,在Bagging方法当中,我们会通过有放回随机采样的方式创建K个数据集。对于每一个数据集来说,可能有一些单个的样本重复出现,也可能有一些样本从没有出现过,但整体而言,每个样本出现的概率是相同的。

之后,我们用抽样出来的K个数据集训练K个模型,这里的模型没有做限制,我们可以使用任何机器学习方模型。K个模型自然会得到K个结果,那么我们采取民主投票的方式对这K个模型进行聚合。

举个例子说,假设K=25,在一个二分类问题当中。有10个模型预测结果是0,15个模型预测结果是1。那么最终整个模型的预测结果就是1,相当于K个模型民主投票,每个模型投票权一样。大名鼎鼎的随机森林就是采取的这种方式。

Boosting

Boosting的思路和Bagging非常相似,它们对于样本的采样逻辑是一致的。不同的是,在Boosting当中,这K个模型并不是同时训练的,而是串行训练的。每一个模型在训练的时候都会基于之前模型的结果,更加关注于被之前模型判断错误的样本。同样,样本也会有一个权值,错误判断率越大的样本拥有越大的权值。

并且每一个模型根据它能力的不同,会被赋予不同的权重,最后会对所有模型进行加权求和,而不是公平投票。由于这个机制,使得模型在训练的时候的效率也有差异。因为Bagging所有模型之间是完全独立的,我们是可以采取分布式训练的。而Boosting中每一个模型会依赖之前模型的效果,所以只能串行训练。

Stacking

Stacking是Kaggle比赛当中经常使用的方法,它的思路也非常简单。我们选择K种不同的模型,然后通过交叉验证的方式,在训练集上进行训练和预测。保证每个模型都对所有的训练样本产出一个预测结果。那么对于每一条训练样本,我们都能得到K个结果。

之后,我们再创建一个第二层的模型,它的训练特征就是这K个结果。也就是说Stacking方法当中会用到多层模型的结构,最后一层模型的训练特征是上层模型预测的结果。由模型自己去训练究竟哪一个模型的结果更值得采纳,以及如何组合模型之间的特长。

我们今天介绍的AdaBoost顾名思义,是一个经典的Boosting算法。

模型思路

AdaBoost的核心思路是通过使用Boosting的方法,通过一些弱分类器构建出强分类器来。

强分类器我们都很好理解,就是性能很强的模型,那么弱分类器应该怎么理解呢?模型的强弱其实是相对于随机结果来定义的,比随机结果越好的模型,它的性能越强。从这点出发,弱分类器也就是只比随机结果略强的分类器。我们的目的是通过设计样本和模型的权重,使得可以做出最佳决策,将这些弱分类器的结果综合出强分类器的效果来。

首先我们会给训练样本赋予一个权重,一开始的时候,每一条样本的权重均相等。根据训练样本训练出一个弱分类器并计算这个分类器的错误率。然后在同一个数据集上再次训练弱分类器,在第二次的训练当中,我们将会调整每个样本的权重。其中正确的样本权重会降低,错误的样本权重会升高

同样每一个分类器也会分配到一个权重值,权重越高说明它的话语权越大。这些是根据模型的错误率来计算的。错误率定义为:

这里的D表示数据集表示分类错误的集合,它也就等于错误分类的样本数除以总样本数。

有了错误率之后,我们可以根据下面这个公式得到

得到了之后,我们利用它对样本的权重进行更新,其中分类正确的权重更改为:

分类错误的样本权重更改为:

这样,我们所有的权重都更新完了,这也就完成了一轮迭代。AdaBoost会反复进行迭代和调整权重,直到训练错误率为0或者是弱分类器的数量达到阈值。

代码实现

首先,我们来获取数据,这里我们选择了sklearn数据集中的乳腺癌预测数据。和之前的例子一样,我们可以直接import进来使用,非常方便:

import numpy as np
import pandas as pd
from sklearn.datasets import load_breast_cancer breast = load_breast_cancer()
X, y = breast.data, breast.target
# reshape,将一维向量转成二维
y = y.reshape((-1, 1))

接着,我们将数据拆分成训练数据和测试数据,这个也是常规做法了,没有难度:

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=23)

在AdaBoost模型当中,我们选择的弱分类器是决策树的树桩。所谓的树桩就是树深为1的决策树。树深为1显然不论我们怎么选择阈值,都不会得到特别好的结果,但是由于我们依然会选择阈值和特征,所以结果也不会太差,至少要比随机选择要好。所以这就保证了,我们可以得到一个比随机选择效果略好一些的弱分类器,并且它的实现非常简单。

在我们实现模型之前,我们先来实现几个辅助函数。

def loss_error(y_pred, y, weight):
return weight.T.dot((y_pred != y_train)) def stump_classify(X, idx, threshold, comparator):
if comparator == 'lt':
return X[:, idx] <= threshold
else:
return X[:, idx] > threshold def get_thresholds(X, i):
min_val, max_val = X[:, i].min(), X[:, i].max()
return np.linspace(min_val, max_val, 10)

这三个函数应该都不难理解,第一个函数当中我们计算了模型的误差。由于我们每一个样本拥有一个自身的权重,所以我们对误差进行加权求和。第二个函数是树桩分类器的预测函数,逻辑非常简单,根据阈值比较大小。这里有两种情况,有可能小于阈值的样本是正例,也有可能大于阈值的样本是正例,所以我们还需要第三个参数记录这个信息。第三个函数是生成阈值的函数,由于我们并不需要树桩的性能特别好,所以我们也没有必要去遍历阈值的所有取值,简单地把特征的范围划分成10段即可。

接下来是单个树桩的生成函数,它等价于决策树当中选择特征进行数据拆分的函数,逻辑大同小异,只需要稍作修改即可。

def build_stump(X, y, weight):
m, n = X.shape
ret_stump, ret_pred = None, []
best_error = float('inf') # 枚举特征
for i in range(n):
# 枚举阈值
for j in get_thresholds(X, i):
# 枚举正例两种情况
for c in ['lt', 'gt']:
# 预测并且求误差
pred = stump_classify(X, i, j, c).reshape((-1, 1))
err = loss_error(pred, y, weight)
# 记录下最好的树桩
if err < best_error:
best_error, ret_pred = err, pred.copy()
ret_stump = {'idx': i, 'threshold': j, 'comparator': c}
return ret_stump, best_error, ret_pred

接下来要做的就是重复生成树桩的操作,计算,并且更新每一条样本的权重。整个过程也没有太多的难点,基本上就是照着实现公式:

def adaboost_train(X, y, num_stump):
stumps = []
m = X.shape[0]
# 样本权重初始化,一开始全部相等
weight = np.ones((y_train.shape[0], 1)) / y_train.shape[0]
# 生成num_stump个树桩
for i in range(num_stump):
best_stump, err, pred = build_stump(X, y, weight)
# 计算alpha
alpha = 0.5 * np.log((1.0 - err) / max(err, 1e-10))
best_stump['alpha'] = alpha
stumps.append(best_stump) # 更新每一条样本的权重
for j in range(m):
weight[j] = weight[j] * (np.exp(-alpha) if pred[j] == y[j] else np.exp(alpha))
weight = weight / weight.sum()
# 如果当前的准确率已经非常高,则退出
if err < 1e-8:
break
return stumps

树桩生成结束之后,最后就是预测的部分了。整个预测过程依然非常简单,就是一个加权求和的过程。这里要注意一下,我们在训练的时候为了突出错误预测的样本,让模型拥有更好的能力,维护了样本的权重。然而在预测的时候,我们是不知道预测样本的权重的,所以我们只需要对模型的结果进行加权即可。

def adaboost_classify(X, stumps):
m = X.shape[0]
pred = np.ones((m, 1))
alphs = 0.0
for i, stump in enumerate(stumps):
y_pred = stump_classify(X, stump['idx'], stump['threshold'], stump['comparator'])
# 根据alpha加权求和
pred = y_pred * stump['alpha']
alphs += stump['alpha']
pred /= alphs
# 根据0.5划分0和1类别
return np.sign(pred).reshape((-1, 1))

到这里,我们整个模型就实现完了,我们先来看下单个树桩在训练集上的表现:

可以看到准确率只有0.54,只是比随机预测略好一点点而已。

然而当我们综合了20个树桩的结果之后,在训练集上我们可以得到0.9的准确率。在预测集上,它的表现更好,准确率有接近0.95!

这是因为AdaBoost当中,每一个分类器都是弱分类器,它根本没有过拟合的能力,毕竟在训练集的表现都很差,这就保证了分类器学到的都是实在的泛化能力,在训练集上适用,在测试集上很大概率也适用。这也是集成方法最大的优点之一。

总结

集成方法可以说是机器学习领域一个非常重要的飞跃,集成方法的出现,让设计出一个强分类器这件事的难度大大降低,并且还保证了模型的效果。

因为在一些领域当中,设计一个强分类器可能非常困难,然而设计一个弱一些的分类器则简单得多,再加上模型本身性能很好,不容易陷入过拟合。使得在深度学习模型流行之前,集成方法广泛使用,几乎所有机器学习领域的比赛的冠军,都使用了集成学习。

集成学习当中具体的思想或许各有不同,但是核心的思路是一致的。我们理解了AdaBoost之后,再去学习其他的集成模型就要容易多了。

如果喜欢本文,可以的话,请点个关注,给我一点鼓励,也方便获取更多文章。

本文使用 mdnice 排版

机器学习——打开集成方法的大门,手把手带你实现AdaBoost模型的更多相关文章

  1. 机器学习之——集成算法,随机森林,Bootsing,Adaboost,Staking,GBDT,XGboost

    集成学习 集成算法 随机森林(前身是bagging或者随机抽样)(并行算法) 提升算法(Boosting算法) GBDT(迭代决策树) (串行算法) Adaboost (串行算法) Stacking ...

  2. 硬货 | 手把手带你构建视频分类模型(附Python演练))

    译者 | VK 来源 | Analytics Vidhya 概述 了解如何使用计算机视觉和深度学习技术处理视频数据 我们将在Python中构建自己的视频分类模型 这是一个非常实用的视频分类教程,所以准 ...

  3. 【机器学习实战】第7章 集成方法 ensemble method

    第7章 集成方法 ensemble method 集成方法: ensemble method(元算法: meta algorithm) 概述 概念:是对其他算法进行组合的一种形式. 通俗来说: 当做重 ...

  4. 【机器学习实战】第7章 集成方法(随机森林和 AdaBoost)

    第7章 集成方法 ensemble method 集成方法: ensemble method(元算法: meta algorithm) 概述 概念:是对其他算法进行组合的一种形式. 通俗来说: 当做重 ...

  5. [.Net] 手把手带你将自己打造的类库丢到 NuGet 上

    手把手带你将自己打造的类库丢到 NuGet 上 序 我们习惯了对项目右键点击“引用”,选择“管理NuGet 程序包”来下载第三方的类库,可曾想过有一天将自己的打造的类库放到 NuGet 上,让第三者下 ...

  6. 手把手带你做一个超炫酷loading成功动画view Android自定义view

    写在前面: 本篇可能是手把手自定义view系列最后一篇了,实际上我也是一周前才开始真正接触自定义view,通过这一周的练习,基本上已经熟练自定义view,能够应对一般的view需要,那么就以本篇来结尾 ...

  7. Android性能优化:手把手带你全面实现内存优化

      前言 在 Android开发中,性能优化策略十分重要 本文主要讲解性能优化中的内存优化,希望你们会喜欢 目录   1. 定义 优化处理 应用程序的内存使用.空间占用 2. 作用 避免因不正确使用内 ...

  8. Android:手把手带你深入剖析 Retrofit 2.0 源码

    前言 在Andrroid开发中,网络请求十分常用 而在Android网络请求库中,Retrofit是当下最热的一个网络请求库 今天,我将手把手带你深入剖析Retrofit v2.0的源码,希望你们会喜 ...

  9. Solr4.10.2集成Nutch1.9与自带UI界面使用

    Solr4.10.2集成Nutch1.9与自带UI界面使用 一.Solr4.10.2与Nutch1.9集成 环境:Solr4.10.2已经配置在Tomcat上 Solr的Tomcat配置详见Solr4 ...

随机推荐

  1. Unity中激活子物体

    void GetChildrenAndSetActive() { Transform[] imageTargetObjects = GetComponentsInChildren<Transfo ...

  2. Eclipse中java文件选中变量名,相同变量都变色显示 .

    第一步设置高亮显示的颜色: Window-->preferences-->General-->Editors-->Text Editors-->Annotations-- ...

  3. This的关键字的使用

    this: 1.可以用来修饰属性  方法 构造器 2.this理解为当前对象或当前正在创建的对象. 3.可以在构造器中通过this()形参的方式显示的调用本类中其他重载的指定的构造器 要求: 在构造器 ...

  4. 被缠上了,小王问我怎么在 Spring Boot 中使用 JDBC 连接 MySQL

    上次帮小王入了 Spring Boot 的门后,他觉得我这个人和蔼可亲.平易近人,于是隔天小王又微信我说:"二哥,快教教我,怎么在 Spring Boot 项目中使用 JDBC 连接 MyS ...

  5. keras常见问题

    问题:AttributeError: 'CRF' object has no attribute '_outbound_nodes' 解答:这个一般情况下是keras版本的问题,将其改为keras== ...

  6. Rocket - diplomacy - LazyModuleImpLike

    https://mp.weixin.qq.com/s/gDbUto1qd7uWbpnxovr5pg   介绍LazyModuleImpLike类的实现.     1. wrapper   LazyMo ...

  7. Java面向对象中this关键字详解 意义+实例讲解【hot】

    this关键字 >>>便于理解简单的定义 this关键字可以简单的理解为,谁调用this所在的方法,this就是谁. 类的构造函数与getter.setter方法常用到this关键字 ...

  8. Spring Boot笔记(一) springboot 集成 swagger-ui

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 1.添加依赖 <!--SpringBoot整合Swagger-ui--> <depen ...

  9. Java实现 蓝桥杯 传纸条

    题目描述 小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题.一次素质拓展活动中,班上同学安排做成一个mm行nn列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了.幸运 ...

  10. Java实现 LeetCode 598 范围求和 II(最小值相乘)

    598. 范围求和 II 给定一个初始元素全部为 0,大小为 m*n 的矩阵 M 以及在 M 上的一系列更新操作. 操作用二维数组表示,其中的每个操作用一个含有两个正整数 a 和 b 的数组表示,含义 ...