机器学习笔记——集成学习EnsembleLearning+代码详解

机器学习笔记——集成学习EnsembleLearning

  • 一、集成学习初步了解
    • 1.1 集成学习基本思想
    • 1.2 集成学习分类
  • 二、AdaBoost
    • 2.1 AdaBoost基本思想
    • 2.2 AdaBoost代码详解
  • 三、GBM/GBDT
    • 3.1 基本思想简介
    • 3.2 GBDT代码详解
  • 四、Bagging & RandomForest
    • 4.1 Bagging 基本思想
    • 4.2 Bagging 代码详解

一、集成学习初步了解

1.1 集成学习基本思想

  • 集成学习(Ensemble Learning)通过构建并结合多个学习器来完成学习任务,有时也可以被称为多分类器系统或者是基于委员会的学习等等。
  • 集成学习基本由两部分组成,选择个体学习器种类并生成一定量的个体学习器,然后根据一定的规则将个体学习器结合得到性能更好的学习器。个体学习器通常是一个现有的学习算法从训练集中产生,比如C4.5决策树算法、CART算法、BP神经网络算法等都可以作为个体学习器。个体学习器如果种类相同的话,我们称为同质集成学习,如果种类不同的话我们称为异质集成学习
  • 往往集成学习的个体分类器都是“弱学习器”,何为弱学习器?弱学习器就是指泛化性能略优于随即猜测的学习器,比如说对于二分类问题,我们猜测正确的可能性为50%,而弱学习器就是正确率略高于50%或者说错误率略低于50%的分类器。总而言之就是有一点点作用,但性能太差。而集成学习的作用就在于集合多个弱分类器取长补短得到强分类器。
  • 而对于个体分类器的选择也是有严格标准的,要满足“准确性”与“多样性”。准确性的意思是该弱分类器要能给整体分类器带来一定的精度性能改善,而不是来拖集体的后退。多样性的意思是多个弱分类器之间的性能优势要表现在不同方面,这样才能取长补短。但事实上,因为多个弱分类器都是为了解决同一问题,因此不可能误差相互独立,个体分类器的准确性与多样性本身就存在冲突,因此如何产生“好而不同”的个体分类器就是集成学习研究的核心。

1.2 集成学习分类

  • 根据个体学习器的生成方式,目前的集成学习方法大致可以分为两大类。第一类是个体学习器间存在着强依赖关系、必须串行生成的序列化方法,代表算法为AdaboostGBM。第二类是可同时生成的并行化方法,代表算法是BaggingDropout随机森林Random Forest)。
  • 我们本篇博客详细讨论的是AdaboostGBM随机森林算法。

二、AdaBoost

2.1 AdaBoost基本思想

  • AdaBoost中Boost的含义是“提升”,我理解的提升在于两方面。第一方面是将多个弱分类器组合得到强分类器,实现由弱到强的提升。第二方面是AdaBoost得到多个个体分类器的方法是改变训练数据集的权重,提高误分类数据的权重降低正确分类数据的权重。
  • AdaBoost需要解决以下两个问题。第一个问题是AdaBoost如何改变训练数据集的权重,使用同一训练方法、训练模型得到多个不同的个体分类器。第二个问题AdaBoost如何将若个弱分类器组合得到强分类器。针对第一个问题,该算法将上一轮错误分类的数据权重增大,将正确分类的数据权值减小,权值的不同会在每次得到个体分类器后计算误差时发挥作用。针对第二个问题,该算法采用加权多数表决的方法,分类错误率低的个体分类器会得到相应较高的权重,而分类错误率较高的个体分类器会得到相应较低的权重,根据投票表决结果决定分类。
    机器学习笔记——集成学习EnsembleLearning+代码详解_第1张图片
  • 对上图做个简单的解释。我们根据初始的训练数据集,训练得到弱分类器1,再根据弱分类器1产生的误差反馈调整训练数据集的权重,再根据权重调整后的训练数据集训练得到弱分类器2…这样以此类推得到多个弱分类器组合得到最终的强分类器。
  • AdaBoost可以理解为是模型为加法模型,损失函数为指数函数,学习算法为前向分布算法的二类分类学习方法。

2.2 AdaBoost代码详解

  • 本次介绍算法我想换一种方式,用实际的实现代码来介绍算法。辅助以必要的公式、表达式。

  • 首先我们初始化权值矩阵D、弱分类器列表G以及弱分类器对应权值列表alpha。权值矩阵D在每次循环中都会用于当前分类器误差计算以及根据误差修改权值矩阵。弱分类器列表G以及弱分类器对应权值列表alpha都是在循环中存储,在最后弱分类集合时遍历。

  • 弱分类器的个数M我们可以自己指定,M也决定了学习的循环次数。我们该实验的弱分类器确定为CART算法生成的分类决策树,我们可以使用sklearn中封装好的函数生成弱分类器,具体方法详见sklearn的指导手册。M的大小确实决定了集成学习的效果,但往往集成学习test_error曲线上升段和下降段都很短,中间有很长的一段平台期,也就意味着M从较小值开始增大时test_error急剧下降,而集成学习很难过拟合也就是M特别特别大时才会适得其反,大部分中间值的波动对集成学习效果影响很小,所以实际应用中并不需要很大的M便可以逼近最佳效果。
    机器学习笔记——集成学习EnsembleLearning+代码详解_第2张图片

  • 我们在这里默认X维度(100,2),y维度(100,1)。我们的损失矩阵error(100,1),y_pred与y进行比对其中预测正确的位置值为0也就是不产生误差,预测错误的位置值为1产生误差。e_m代表每个个体分类器的总体误差,计算方法为权值矩阵与损失矩阵对应位置元素相乘。

  • 我们得到每个个体分类器的总体误差率e_m后,就可以根据这个值计算该个体分类器在最后集成时候的系数alpha以及更新权值矩阵D。所以说集成个体分类器时的投票权重以及每次更新训练数据权重矩阵都与个体分类器的误差率有关。具体公式如下图所示:
    机器学习笔记——集成学习EnsembleLearning+代码详解_第3张图片
    下图可以看出当 G m ( x i ) = y i G_m(x_i)=y_i Gm(xi)=yi时也就是分类正确时, G m ( x i ) ∗ y i = 1 G_m(x_i)*y_i=1 Gm(xi)yi=1 0 < e − α m < 1 00<eαm<1故分类正确的样本权重被减小。相反分类错误的样本权重被增大。这样看来设计的权重表达式可以很好的实现我们动态调整权重的目的。
    在这里插入图片描述

  • 下图表征的是个体分类器误差率e_m与投票权重alpha_m之间的函数关系,可以发现误差率e_m越小其在最后投票过程中所占权重更大,误差率e_m越大其所占权重越小。表明我们的alpha系数表达式可以很好的表征e_m与alpha之间的关系。
    机器学习笔记——集成学习EnsembleLearning+代码详解_第4张图片

  • 最后遍历所有的个体分类器G以及投票权重alpha得出最终的投票结果即可。np.sign(score)函数的意思是,score大于0时返回1,等于0时返回0,小于0时返回-1。加权投票表决的含义就是将所有模型的输出乘上对应系数求和,再根据和值和门限确定分类。

机器学习笔记——集成学习EnsembleLearning+代码详解_第5张图片

def adaboost(X, y, M, Max_depth=None):
    """
    adaboost函数,使用CART作为弱分类器
    参数:
        X: 训练样本
        y: 样本标签, y = {-1, +1}
        M: 使用M个弱分类器
        Max_depth: 基学习器CART决策树的最大深度
    返回:
        F: 生成的模型
    """
    # 假设X(100,2) num_X=100,num_feature=2
    num_X, num_feature = X.shape
    
    ### START CODE HERE ###
    # 初始化训练数据的权值分布
    # 生成一个(100,1)的矩阵,值都为1/num_X
    D = (np.ones((num_X,1))/num_X).reshape(-1,1)
    G = [] #用于存放多个弱分类器,以待线性组合
    alpha = [] #用于存放每个弱分类器的alpha参数
    
    for m in range(M):
        # 使用具有权值分布D_m的训练数据集学习,得到基本分类器
        # 使用DecisionTreeClassifier,设置树深度为Max_depth
        G_m = DecisionTreeClassifier(max_depth = Max_depth)
        # 开始训练
        model_m = G_m.fit(X,y,sample_weight = D.flatten())#注意加上参数,样本权重sample_weight = D
        # 计算G_m在训练数据集上的分类误差率
        y_pred = model_m.predict(X).reshape(-1,1)
        error = np.where(y_pred == y,0,1) #返回矩阵,相等的地方为0不计算损失,不相等的地方为1计算损失
        e_m = np.sum(D * error)  #两个(100,1)的矩阵对应元素相乘,求和计算总体损失
        
        #以下两个判断是为了保证np.log内不为0
        if e_m == 0:
            break
        if e_m == 1:
            raise ValueError("e_m = {}".format(e_m))
            
        # 计算G_m的系数
        alpha_m = (1/2) * np.log((1-e_m)/e_m)
        # 更新训练数据集的权值分布
        temp = D * np.exp(-alpha_m * y * y_pred)#三个(100,1)的矩阵对应元素相乘
        D = temp / np.sum(temp)#更新权值矩阵
        # 保存G_m和其系数
        G.append(G_m)
        alpha.append(alpha_m)
    
    # 构建基本分类器的线性组合
    def F(X):
        num_G = len(G)
        score = 0
        for i in range(num_G):
            score += alpha[i] * G[i].predict(X).reshape(-1,1)
        return np.sign(score).reshape(-1,1)#返回投票表决产生的分类结果
        
    ### END CODE HERE ###
    return F

三、GBM/GBDT

3.1 基本思想简介

  • GBM:Gradient Boosting Machine。GBM和AdaBoost一样采用加法模型: H ( x ) = ∑ t = 1 T α t h t ( x ) H(x) = \sum_{t=1}^{T} \alpha_t h_t(x) H(x)=t=1Tαtht(x),但GBM拓展为可以采用其他任意损失 l l l(如前面介绍过的平方损失、交叉熵损失等)。GBM一般采用决策树(或回归树)作为基学习器,称为Gradient Boosting Decision Tree (GBDT),针对不同问题使用不同的损失函数,分类问题使用指数损失函数,回归问题使用平方误差损失函数。
  • 之前我们说过集成学习的关键在于如何设计个体分类器,使得个体分类器之间“好而不同”。AdaBoost与GBDT的基分类器都是决策树,不会通过更换基分类器的种类的方法去实现个体分类器的不同,那就只能在训练数据上下手。AdaBoost通过改变训练数据集的权重的方法来实现个体分类器的不同,而GBDT则通过改变训练数据y值方式来实现,也就是每次训练的是当前值与最优值之间的差距(余量)。
    机器学习笔记——集成学习EnsembleLearning+代码详解_第6张图片
    机器学习笔记——集成学习EnsembleLearning+代码详解_第7张图片

3.2 GBDT代码详解

  • 第一步为初始化弱学习器:
    f 0 ( x ) = arg ⁡ min ⁡ c ∑ i = 1 N L ( y i , c ) f_0(x) = \arg \underset{c}{\min}\sum_{i=1}^{N}L(y_i, c) f0(x)=argcmini=1NL(yi,c)
    GBDT算法的基学习器为回归CART树,因此所使用的损失函数为平方损失。我们带入该初始化的式子并对c求导就是得到c取值为所有训练样本标签值的均值。具体过程如下:
    机器学习笔记——集成学习EnsembleLearning+代码详解_第8张图片
  • 这会是一个比较容易混淆的点,GBDT的基学习器为回归树,使用平方损失构建每一个基学习器。而本题目基学习器集成起来是为了解决分类问题,因此余量公式的推导需要使用交叉熵损失。
    机器学习笔记——集成学习EnsembleLearning+代码详解_第9张图片
def gbdt_classifier(X, y, M, Max_depth=5):
    """
    用于分类的GBDT函数
    参数:
        X: 训练样本
        y: 样本标签,y = {0, +1}
        M: 使用M个回归树
        Max_depth: 基学习器CART决策树的最大深度
    返回:
        F: 生成的模型
    """
    ### START CODE HERE ###
    y_pred = np.mean(y) #对应算法起始的fo
    Models = [] #用于存放所用个体分类器的列表
    
    for m in range(M):
        # 根据分类问题交叉熵损失计算余量r
        r = y - sigmoid(y_pred)
        # 使用DecisionTreeRegressor,设置树深度为5,random_state=0
        f_m = DecisionTreeRegressor(max_depth = Max_depth,random_state=0)
        # 开始训练
        model = f_m.fit(X,r)
        r_pred = model.predict(X).reshape(-1,1)
        y_pred = y_pred + r_pred #更新下次待学习的余量
        # 添加当前个体分类器
        Models.append(model)
    
    def F(X):
        num_X, _ = X.shape
        reg = np.zeros((num_X,1))
        
        # 累加所有模型的计算结果
        for model in Models:
            y_pred = model.predict(X)
            reg += y_pred.reshape(-1,1)
        # 分类问题需要使用sigmoid将计算值归一到0-1之间
        y_pred_gbdt = sigmoid(reg)
        # 以0.5为阈值,得到最终分类结果0或1
        one_position = y_pred_gbdt >= 0.5
        y_pred_gbdt[one_position] = 1
        y_pred_gbdt[~one_position] = 0
        
        return y_pred_gbdt
    
    ### END CODE HERE ###
    return F

四、Bagging & RandomForest

4.1 Bagging 基本思想

  • 先回顾一下AdaBoost与GBM是如何让基学习器“好而不同”的。AdaBoost每次循环动态调整训练数据集的权重,上一次分类错误的权重增大,分类正确的权重减小。GBM每次循环训练学习当前余量,相当于保持x不动修改了y。而Bagging则采取了随机放回采样的方式,利用自助采样(有放回的均匀抽样)得到T组训练样本集,分别利用这些训练样本集训练T个分类器(CART or SVM or others),最后进行投票集成。
  • 随机森林是Bagging的一个扩展变体,它充分利用“随机”的思想来增加各分类器的多样性。“随机”体现在两个方面:基于自助采样法来选择训练样本和随机选择特征(或属性)。随机选择特征是指,对基决策树的每个节点,先从该节点的属性集合中随机选择一个包含k个属性的子集,然后再从这个子集中选择一个最优属性用于划分。这里的参数k控制了随机性和引入程度,一般情况下,推荐值 k=log2d (假设有d个属性)。随机森林的弱分类器一般是CART。随机森林的特点是可高度并行化、继承了CART的优点和克服了完全生长树的缺点。
  • 直接采用bagging算法集成CART树就是Random Forest的实现函数。

4.2 Bagging 代码详解

def bagging(X, y, T, size, seed=0, max_depth=None):
    """
    Bagging算法,分类器为CART,用于二分类
    参数:
        X: 训练集
        y: 样本标签
        T: T组
        size: 每组训练集的大小
        seed: 随机种子
        max_depth: 基学习器CART决策树的最大深度
    返回:
        F: 生成的模型
    """
    classifiers = [] # 存放分类器的列表
    m, n = X.shape
    
    ### START CODE HERE ###
    np.random.seed(seed)
    for i in range(T):
        # 使用np.random.choice选择size个序号,注意replace参数的设置,以满足有放回的均匀抽样。
        index = np.random.choice(a=m,size=size,replace=True)
        X_group = X[index]
        y_group = y[index]
        # 使用tree.DecisionTreeClassifier,设置max_depth=max_depth, min_samples_split=2(生成完全树),random_state=0
        t = DecisionTreeClassifier(max_depth=max_depth,min_samples_split=2,random_state=0)
        # 开始训练
        t = t.fit(X_group,y_group)
        classifiers.append(t)
    
    def F(X):
        # 计算所有分类器的预测结果
        result = []
        for t in classifiers:
            result.append(t.predict(X).reshape(-1,1))
        # 把预测结果组成 num_X * T 的矩阵
        pred = np.array(result)
        # 计算"0"有多少投票
        vote_0 = np.sum(pred == 0)
        # 计算"1"有多少投票
        vote_1 = np.sum(pred == 1)
        # 选择投票数最多的一个标签
        pred = 0 if vote_0 > vote_1 else 1
        
        return pred     
    ### END CODE HERE ###
    return F

你可能感兴趣的:(机器学习,机器学习,人工智能)