NYC's Blog - 集成学习 http://niyanchun.com/tag/%E9%9B%86%E6%88%90%E5%AD%A6%E4%B9%A0/ 集成学习介绍(4)——GBDT&XGBoost http://niyanchun.com/gbdt-and-xgboost.html 2022-03-26T10:20:00+08:00 GBDT概述GBDT全称Gradient Boosting Decision Trees,即梯度提升回归树。GBDT可以拆分2部分:GB+DT。GB是一种通用思想或者算法,GBDT只是众多GBM(Gradient Boosting Machine)里面的一种。所以先来看GB。Boosting前面已经介绍过了,就是用多个弱学习器顺序迭代生成一个强学习器,GB里面的重点在于每次迭代的时候是拟合残差。所谓残差(residual)就是真实值和预测值的差值: $residual_i = y_i-f(x_i)$,它表示了数据模型中所不可能刻画的部分。所以GB的思想大致可以描述如下:初始:先有一个初始的弱学习器,然后用这个学习器去训练样本上面进行预测,得到每个样本的残差。第一轮:用训练样本作为属性,第一轮得到的残差作为target(而不是原始样本的target),训练得到第2个弱学习器。然后用这个弱学习器对样本进行预测得到预测值,并计算与真实值的残差,作为一下轮样本的target。第N轮:重复前面的步骤。每次变化的是使用上一轮计算的残差作为本轮样本的target。N次迭代后得到最终的强学习器。所以可以看到GB其实就是不断的迭代拟合残差。但至此并没有看到任何关于Gradient的影子。接着往下看:假设现在有样本集 $(x_1,y_1),(x_2,y_2),...(x_n,y_n)$ ,然后用一个模型$f(x)$去拟合这些数据。如果是回归模型的话,我们一般使用均方误差作为损失函数,即:$L(\theta) = \frac{1}{2}\sum_{i=0}^n(y_i-f(x_i))^2$然后最优化算法求解模型的参数$\theta$。常用的最优化求解方法之一就是梯度下降(gradient descent)。对损失函数求梯度得到:$\nabla L(\theta) = \sum_{i=0}^n{(f(x_i)-y_i)}$至此,可以看到我们前面定义的残差就是这里的负梯度:$residual_i = y_i-f(x_i) = -(f(xi)-y_i)$。也就是说前面对残差的拟合,其实就是对负梯度的拟合,而根据残差来更新集成后的模型实际就是根据负梯度来更新。这样来看,梯度提升方法就变成了广义上的梯度下降。这就是GB中Gradient的部分。需要注意的是:尽管这里残差和负梯度的值完全一样,但二者代表的含义却是不一样的:负梯度指向的是单个模型参数更新的方向,残差(即梯度提升)则表示了集成模型下一个模型的拟合目标。梯度的不断下降可以让模型的参数逐渐收敛到最优参数上,而残差的不断拟合则让集成之后的模型越来越解决真实数据的生成机制。换言之,梯度下降是参数空间的优化:针对单个模型的参数优化,找到最优的一组参数;梯度提升是函数空间的优化:用多个模型的组合来逼近问题的解,每个模型都是一个函数。有了从残差转换到梯度的思路以后,又可以再继续改进,不再局限于残差,而是可以从损失函数的负梯度角度去构造更丰富的提升方法,即构造更多的损失函数。因为基于残差的损失函数有一个明显的缺点就是对异常值比较敏感。看下面的例子:上面的例子中,5*是一个异常点。很明显,按照Boosting的思路,后续模型会对这个值关注过多,这不是一个好现象。所以一般回归类的损失函数会使用绝对损失(absolute loss)或者huber损失函数(huber loss)来代替平方损失函数:如果GB中的弱学习器使用决策树,就是GBDT了。GBDT中一般使用CART决策树。这便是GBDT算法,整体还是Boosting的思路,但具体到细节又和AdaBoost有明显的差别:AdaBoost中每个弱学习器的短板通过权重的加强得以凸显,而梯度提升中则将权重换成了梯度。在解决分类问题时,GBDT 中的负梯度可以表示成样本归属于每个类别的真实概率和上一轮预测概率的差值,这个差值会被回归树拟合,拟合值与上一轮预测概率求和就是这一轮的输出。算法下图来自Wikipedia:https://en.wikipedia.org/wiki/Gradient_boosting初始化$F_0(x)$,表示第0棵树的预测值。那怎么初始化呢?取决于所选择的损失函数:均方误差(mean square error,MSE)时:$F_0(x)=\bar y,\overline y$ 是样本真实值的平均值平均绝对误差(mean absolute error,MAE):$F_0(x)=median(y)$,即真实值的中位数Logit时(分类问题): $F_0(x)=\frac{1}{2}*log\frac{\sum{y_i}}{\sum(1-y_i)}, y_i\in{0, 1}$假如进行M轮迭代(即M个弱分类器),则对于第m轮($m=1,2,...,M$):计算上一轮模型的残差(即负梯度)$r_{im}$,即用真实值减去上一轮模型的预测值将计算出来的残差作为本轮的target,得到本轮的训练数据:$(x_1, r_{1m}),(x_2, r_{2m}),...,(x_n, r_{nm})$。然后训练得到本轮的弱分类器是$h_m(x)$。然后就是求解本轮弱分类器的权重$\gamma_m$了。迭代到本轮为止得到的强分类器为:$F_m(x)=F_{m-1}(x)+\gamma_mh_m(x)$,所以求解$\gamma_m$的方式就是:$\gamma_m = \mathop{\arg\min}\limits_{\theta}\sum_{i=1}^n{L(y_i,F_m(x_i))} = \mathop{\arg\min}\limits_{\theta}\sum_{i=1}^n{L(y_i,F_{m-1}(x_i)+\gamma h_m(x_i))}$得到$\gamma_m$之后,本轮迭代之后得到模型就是:$F_m(x)=F_{m-1}(x)+\gamma_mh_m(x)$。得到最终的模型:$F_M(x)$。再总结一下关键点:初始的时候先找一个常量模型$F_0$,计算初始的残差。之所以说是常量模型,是因为一般这个是直接计算的所有样本观测值(target)的“均值”(如果是分类问题的话,一般使用logit),然后样本观测值减去计算出来的“均值”就得到了初始的残差。然后就开始Boosting的迭代,每轮迭代的样本属性就是原始训练样本的属性,但样本观测值不是原始的观测值,而是计算出来的残差。第一轮使用初始算出的残差;第二轮使用第一轮模型预测的到的残差,...,第m轮使用第m-1轮模型预测得到的残差,以此类推。因为Boosting模型得到的最终强学习器形式为:$F_{strong\ learner}(x) = \sum_{i=1}^M{\gamma_i*h_i(x)} +const_{F_0}$其中的$const_{F_0}$就是初始计算的常量“均值”,所以每轮除了使用样本$(x_1, r_{1m}),(x_2, r_{2m}),...,(x_n, r_{nm})$ 训练得到本轮的弱分类器以外,还需要计算本轮弱分类器在最终强分类器中所占的权重$\gamma$,计算方式就是上面计算$\gamma_m$的方法。需要注意的是每轮训练时的target使用的是残差,而不是原始的target,所以该弱分类器模型后面预测出来的值也是残差,而不是原始样本的target。也即所以上面公式中的$\sum_{i=1}^M{\gamma_i*h_i(x)}$累加起来其实是M个弱分类器预测的“残差和”,需要加上最初始$F_0$算出来的“平均值”才是最终的预测值。另外还需要说明一个概念:学习率(Learning Rate),背景是这样的:测试表明每次沿正确的方向前进一小步可以获得更好的预测性能,所以设计了一个超参learning rate,用来控制每轮模型的影响。一般会设置的比较小(比如0.1),以此来让模型使用更多的弱分类器。英文版:It’s been shown through experimentation that taking small incremental steps towards the solution achieves a comparable bias with a lower overall vatiance (a lower variance leads to better accuracy on samples outside of the training data). Thus, to prevent overfitting, we introduce a hyperparameter called learning rate. When we make a prediction, each residual is multiplied by the learning rate. This forces us to use more decision trees, each taking a small step towards the final solution.上面的算法流程里面没有给出来,但实际迭代的时候还会引入一个学习率$\nu$,完整的迭代公式是这样的:$F_m(x)=F_{m-1}(x)+\nu*\gamma_mH_m(x)$一幅图表示就是(来自:How XGBoost Works):整个介绍起来比较抽象,可参考下面例子中的第一个:Gradient Boosting Decision Tree Algorithm Explained来感受一下整个流程。例子Gradient Boosting Decision Tree Algorithm Explained: 该例是一个使用GBDT解决回归的问题。见:gbdt-regression-demo.ipynb(参考自: Gradient Boosting Decision Tree Algorithm Explained)。GBDT算法原理以及实例理解Gradient Boosting In Classification: Not a Black Box Anymore!,是一个二分类例子。XGBoost概述XGBoost(eXtreme Gradient Boosting)是对GBDT的优化和工程化的实现。优化可分为算法优化和工程实现方面的优化:Algorithmic Enhancements:Regularization: It penalizes more complex models through both LASSO (L1) and Ridge (L2) regularization to prevent overfitting.Sparsity Awareness: XGBoost naturally admits sparse features for inputs by automatically ‘learning’ best missing value depending on training loss and handles different types of sparsity patterns in the data more efficiently.Weighted Quantile Sketch: XGBoost employs the distributed weighted Quantile Sketch algorithm to effectively find the optimal split points among weighted datasets.Cross-validation: The algorithm comes with built-in cross-validation method at each iteration, taking away the need to explicitly program this search and to specify the exact number of boosting iterations required in a single run.System Optimization:Parallelization: XGBoost approaches the process of sequential tree building using parallelized implementation. This is possible due to the interchangeable nature of loops used for building base learners; the outer loop that enumerates the leaf nodes of a tree, and the second inner loop that calculates the features. This nesting of loops limits parallelization because without completing the inner loop (more computationally demanding of the two), the outer loop cannot be started. Therefore, to improve run time, the order of loops is interchanged using initialization through a global scan of all instances and sorting using parallel threads. This switch improves algorithmic performance by offsetting any parallelization overheads in computation.Tree Pruning: The stopping criterion for tree splitting within GBM framework is greedy in nature and depends on the negative loss criterion at the point of split. XGBoost uses ‘max_depth’ parameter as specified instead of criterion first, and starts pruning trees backward. This ‘depth-first’ approach improves computational performance significantly.Hardware Optimization: This algorithm has been designed to make efficient use of hardware resources. This is accomplished by cache awareness by allocating internal buffers in each thread to store gradient statistics. Further enhancements such as ‘out-of-core’ computing optimize available disk space while handling big data-frames that do not fit into memory.以上引用自:XGBoost Algorithm: Long May She Reign!算法从算法角度来说,XGBoost主要对GBDT的目标函数进行了优化。GBDT的目标函数:$Obj_{gbdt} = \sum_{i=1}^N{L(f_m(x_i), y_i)}=\sum_{i=1}^N{L(f_{m-1}(x_i) + h_m(x_i), y_i)}$而XGBoost对目标函数的优化有两方面:加了一个正则项;使用泰勒公式展开。先看正则项:$Obj_{xgboost} = \sum_{i=1}^N{L(f_m(x_i), y_i)} + \sum_{j=1}^m{\Omega f(x_j)}=\sum_{i=1}^N{L(f_{m-1}(x_i) + h_m(x_i), y_i) + \sum_{j=1}^m{\Omega f(x_j)}}$加正则项就是Shrinkage的思想,可以防止过拟合、降低方差,获取更好的泛化效果。这个正则项的具体公式为:$\Omega f(x_i) = \gamma T + \frac{1}{2}\lambda||\omega||^2$其中$T$为树$f$的叶节点个数,$\omega$为所有叶节点输出回归值构成的向量,$\gamma,\lambda$为超参数。再看泰勒公式:更细的部分可参考下面2篇文章:机器学习|XGBoost详解(CSDN)XGBooost详解(知乎)总结梯度提升算法(Gradient Boosting Machine,GBM)其实是一类算法,GBDT只是其中一种,而XGBoost则是GBDT的一个优化版本(或者说是GBDT算法的一个具体的实现),这个优化不仅体现在算法层面,也包括很多工程实现方面的优化,比如并行、内存使用量少等。同类的还有微软开源的LightGBM,号称速度更快,内存使用更低,网上有很多对比评测文章,有兴趣的可以看看。 集成学习介绍(3)——Random Forest http://niyanchun.com/random-forest.html 2022-03-20T21:16:00+08:00 随机森林是一个非常直观,理解起来也比较容易的Bagging算法。前面我们介绍过决策树,其最大的一个缺点就是容易过拟合。随机森林则是由若干决策树组成的模型,其思想就是“三个臭皮匠顶个诸葛亮”。比如下图,就是由9个决策树组成的一个随机森林,其中6个决策树预测值为1,三个预测为0 ,所以最终预测值取多数方:1。如果是回归问题,一般取所有决策树预测结果的均值。理解随机森林的关键点在于理解“相关度低甚至不相关的多个决策树组合在一起的效果好于其中任何一个决策树”。这里拿一个例子做论证(注:此例来自第一个参考文章),做一个游戏:使用一个均匀分布的随机数产生器产生一个数字,如果这个数字大于等于40,则算你赢,可以获得一些钱;如果小于40,则算你输,你需要给对方同样数额的钱。现在有三种玩法供选择:玩法1(Game1):玩100次,每次的筹码是1元。玩法2(Game2):玩10次,每次筹码是10元。玩法3(Game3):玩1次,筹码是100.你会怎么选哪一个?我们计算一下赢钱的期望值:$E_1 = (0.6*1+0.4*(-1))*100=20$$E_2=(0.6*10+0.4*(-10))*100=20$$E_3=(0.6*100+0.4*(-100))=20$三种选择赢钱的期望值是一样的,那到底该如何选?我们做一个模拟:每种情况都模拟10000次,代码如下:import numpy as np import matplotlib.pyplot as plt import seaborn as sns sns.set_theme() # Game 1 simulations = 10000 # number of Monte Carlo Simulations games = 100 # number of times the game is played threshold = 40 # threshold where if greater than or equal to you win bet = 1 # dollar bet for the game # outer loop is Monte Carlo sims and inner loop is games played sim_results_1 = [] for sim in range(simulations): result = [] for g in range(games): number = int(np.random.uniform()*100) # get a random number to see who wins if number >= threshold: result.append(bet) else: result.append(-bet) sim_results_1.append(sum(result)) # sim_results_1 stores results for Game 1 print('Game 1 Mean: ', round(np.mean(sim_results_1), 2)) print('Game 1 Prob Positive: ', round(sum([1 for i in sim_results_1 if i>0])/simulations, 2)) print('\n') # Game 2 (structure of code is same as above) simulations = 10000 games = 10 threshold = 40 bet = 10 sim_results_2 = [] for sim in range(simulations): result = [] for g in range(games): number = int(np.random.uniform()*100) if number >= threshold: result.append(bet) else: result.append(-bet) sim_results_2.append(sum(result)) print('Game 2 Mean: ', round(np.mean(sim_results_2), 2)) print('Game 2 Prob Positive: ', round(sum([1 for i in sim_results_2 if i>0])/simulations, 2)) print('\n') # Game 3 (structure of code is same as above) simulations = 10000 games = 1 threshold = 40 bet = 100 sim_results_3 = [] for sim in range(simulations): result = [] for g in range(games): number = int(np.random.uniform()*100) if number >= threshold: result.append(bet) else: result.append(-bet) sim_results_3.append(sum(result)) print('Game 3 Mean: ', round(np.mean(sim_results_3), 2)) print('Game 3 Prob Positive: ', round(sum([1 for i in sim_results_3 if i>0])/simulations, 2)) # Histogram that shows the distribution of the Monte Carlo Results for 2 spending levels fig, ax = plt.subplots(figsize=(8,6)) sns.distplot(sim_results_1, kde=False, bins=60, label='Play 100 Times') sns.distplot(sim_results_2, kde=False, bins=60, label='Play 10 Times', color='orange') sns.distplot(sim_results_3, kde=False, bins=60, label='Play 1 Time', color='pink') ax.set_xlabel('Money Won by You', fontsize=16) ax.set_ylabel('Frequency',fontsize=16) plt.legend() plt.tight_layout() plt.savefig(fname='game_hist', dpi=150) plt.show()模拟的输出以分布图如下:# 代码输出 Game 1 Mean: 20.01 Game 1 Prob Positive: 0.97 Game 2 Mean: 20.2 Game 2 Prob Positive: 0.63 Game 3 Mean: 21.08 Game 3 Prob Positive: 0.61那可以看到赢钱的均值和即我们先前计算的期望值是一致的,三种玩法都接近20(Game x Mean),但赢钱的概率却相差很大,玩法1是97%,玩法2是63%,玩法3是61%。当然模拟次数再多一些,还有有一些变化。随机森林的思想和这个是一样的,里面包含的决策树的个数就是这里玩的次数。另外,随机森林还有一个非常关键的限定条件:各个决策树之间不相关或者关联度很低。类比到上面的游戏中,我们的假设是产生随机数的算法是遵从均匀分布的,也就是产生1~100之间的数字的概率是完全相等的。如果不是,那上面第一种玩法1最优的结论就不一定成立了。而随机森林实现各个决策树之间不相关或者关联度很低是通过“两个随机”实现的:数据随机:即每次只选取原始数据集的一个子集作为一个决策树的训练集,一般就是自助采样法(Bootstrap)。这是最核心的随机,主要是去除或者削弱各独立模型之间的相关性。属性随机:属性随机化的好处在于让每个单独的基学习器不会过分关注在训练集中具有高度预测性或者描述性的特征。通过两个随机,可以降低最终生成的模型的方差。而且通过这种方式生成的树一般也无需剪枝。Wikipedia上面的描述是这样的:Each decision tree in the forest considers a random subset of features when forming questions and only has access to a random set of the training data points. This increases diversity in the forest leading to more robust overall predictions and the name ‘random forest.’随机森林还有一个变种:Extra Trees。也称Extremely Randomized Trees,一般翻译为极限树,是随机森林的一个变种,进行了更彻底的随机,主要有两点:训练各个决策树的时候使用全部数据集,而非一个采样的数据子集;在选择决策树的拆分点的时候,均匀随机选择一个特征,而不是像随机森林那样根据信息增益或者基尼不纯度进行选择。极限树的两个改动点在大部分情况下会进一步的降低方差,但可能会稍微增大一些偏差。总结一下,随机森林的思路就是“群众的眼睛是雪亮的”,通过使用多个决策树组成一个“委员会”来进行预测或者回归,这些“群众”就是集成算法中的“若分类器”,他们相互之间没有关联度或者关联度低,而且一般在某一个点表现还可以。这点是通过“两个随机”去实现的,也是随机森林最核心的地方。References:Understanding Random ForestWikipedia:Random Forest 集成学习介绍(2)——AdaBoost http://niyanchun.com/adaboost.html 2022-03-12T11:17:00+08:00 概述AdaBoost是Adaptive Boosting的缩写,即自适应提升法,是最成功的Boosting算法。具体算法如下:Step1: Initialise the dataset and assign equal weight to each of the data point. Step2: Provide this as input to the model and identify the wrongly classified data points. Step3: Increase the weight of the wrongly classified data points. Step4: if (got required results) Goto step 5 else Goto step 2 Step5: End即初始的时候,赋予每个训练样本相同的权重;然后每次迭代后,增加分类错误样本的权重(数据集还是原来的数据集,只不过各个样本的权重变了),使得下一轮迭代时更加关注这些样本。AdaBoost一般选用决策树桩(decision stump)作为弱学习器。所谓stump是指由一个决策节点和两个叶子节点组成的二叉树:下图是一个二分类问题的学习过程示例,包含了三轮迭代:算法AdaBoost的算法描述如下:下面进行解释。“输入”(Given)部分:输入训练数据集包含m个样本,其中$x_i$是属性,取值范围属于集合$X$(实数集的子集),$y_i$是标签,取值是-1或+1.“初始化”(Initialize)部分:$D$表示带权重的样本(有时也用$w$表示),$D_i$表示第$i$个权重的样本。刚开始的时候,所有样本权重相同为$D_i = 1/m$.然后开始循环:使用权值分布为$D_t$的数据集,训练得到一个弱分类器$h_t$计算$h_t$在训练数据上面的误差$\varepsilon_t$根据训练误差$\varepsilon_t$计算出权重调整系数$\alpha_t$更新所有训练样本的权重,用于下一次迭代当训练误差比较低(比如错误率为0)或者弱分类器数目达到预先的设置值(超参控制),就停止循环最终的分类器就是各个各个弱分类器的加权求和,如果是分类问题,就再加上符号函数$sign$(输入数据大于0,输出1;等于0,输出0;小于0,输出-1).如何计算训练误差$\varepsilon_t$?公式如下:$$ \varepsilon_t = \frac {\sum_{i=1}^N\omega_iI(y_i \neq h_t(x_i))}{\sum_{i=1}^N\omega_i} $$其中$I(y_i \neq h_j(x_i))$的含义是如果$y_i \neq h_j(x_i)$ 成立,则返回1,否则返回0。下面看个具体的计算例子:有4个样本的权重分别为:0.5,0.2,0.1,0.2,弱分类器预测的值分别为:1,1,-1,-1实际的真实值为:-1,1,-1,1则$I$的值分别为:1,0,0,1于是有$$ \varepsilon_t = \frac{0.5*1+0.2*0+0.1*0+0.2*1}{0.5+0.2+0.1+0.2} = 0.7 $$有时也称$\varepsilon_t$为“total error”表示的是“所有错误分类的样本权重之和”,其实和上面的公式是一致的,因为权重一般是做过归一化的,所以分母里面的所有权重之和其实为1;而且分子中分类正确的样本的$I$值为0,也就是上面的公式可以简化为:$$ \varepsilon_t = \frac {\sum_{i=1}^N\omega_iI(y_i \neq h_t(x_i))}{\sum_{i=1}^N\omega_i} \\ = \sum_{i=1}^N\omega_iI(y_i \neq h_t(x_i)) = 错误分类样本的权重之和 = total\ error $$ 那权重调整系数$\alpha_t$的作用是什么呢?看个具体的例子。假设现在有3个$\varepsilon$值:0.3、0.5、0.7。则对应的$\alpha$的值如下:$$ \alpha(\varepsilon=0.3) = \frac{1}{2}*ln\frac{1-0.3}{0.3} = 0.42365 \\ \alpha(\varepsilon=0.5) = \frac{1}{2}*ln\frac{1-0.5}{0.5} = 0 \\ \alpha(\varepsilon=0.7) = \frac{1}{2}*ln\frac{1-0.7}{0.7} = -0.42365 \\ $$可以看到,当弱分类器的准确率为0.5时,其权重为0;准确率大于0.5(即错误率小于0.5)时,权重大于0;准确率小于0.5(即错误率大于0.5)时,权重小于0。所以,错误率$\varepsilon$越小,$\alpha$越大,即当前模型的表现越好,在最终的生成器中占的权重就越大。所以$\alpha$也称为“amount of say”、“Performance”、“Importance”,都指的是当前分类器在最终分类器中的权重。另外注意计算的时候有时使用自然对数($ln=log_e$),有时使用常用对数($log_{10}$)。再来看最重要的部分:样本权重更新。在Boosting的迭代中,我们每次要找的是错误率比较低的弱分类器。为了方便我们沿用上面$\alpha(\varepsilon=0.3)$的计算结果,看下分类错误和正确时计算出来的权重值:当在样本$i$上预测正确(即$y_i = h_t(i)$),训练误差为0.3时,计算的新的权重如下:当在样本$i$上预测错误(即$y_i \neq h_t(i)$),训练误差为0.3时,计算的新的权重如下:可以看到当弱分类器在某个样本上分类正确的时候,该样本的权重会降低;否则就会提升,符合理论预期。最后提一下$Z_t$,这是一个规范化系数(normalization factor ),是为了让新计算出来的权重代表一个真正的分布,一般就是归一化。比如上面的例子所有样本的权重更新后加起来后做一下归一化。上面的公式可以简化一下:对于分类正确的样本:$D_{t+1}(i)=\frac{D_t*e^{-\alpha_t}}{Z_t}$对于分类错误的样本:$D_{t+1}(i)=\frac{D_t*e^{\alpha_t}}{Z_t}$最后一个问题,增加了预测错误的样本的权重之后,如何在下一轮迭代中更关注他们呢?实质是通过权重影响每一轮迭代数据集的选择来实现:比如我们有一个包含N个样本的数据集,每个样本都有一个权重(第一轮时权重相同,均为1/N),然后每一轮选择本轮使用的$m(m \le N)$个样本时,是根据权重随机采样的。也就是有些样本可能会被选择多次,有的可能一次也不会被选中。特别是当样本的权重被更新后,权重大的样本就更容易被选中,甚至选中多次了。这样这些样本自然会对后面的弱分类器产生比较多的影响。比如Pandas的sample方法就可以实现在一个数据集上面按照权重采样。另外,还有一种“Bucket”的方式,思路如下:原来有N个样本,每次选N个样本进行弱学习器的训练。第一轮大家权重一样,第二轮的时候,根据权重先将原数据集划分“Bucket”,权重高的样本会占据多个bucket。而每个bucket被选中的几率是一样的,所以权重高的样本就可能被多选几次。也就是第二轮的时候虽然还是N个样本,但某些权重低的可能没有被选,某些权重高的样本可能被选了多次。比如下面的例子(N=5,ref:AdaBoost Algorithm – A Complete Guide for Beginners):第一轮,所有样本权重一样,可以理解为每个样本就是一个bucket:第一轮结束的时候,各个样本的权重已经发生了变化(假设第4条数据分错了,它的权重提升了):如上图,按照权重重新划分了数据集的bucket,分错的第2个样本占了比较多的bucket。所以在为第二轮迭代选择样本的时候,第4条被选中的概率就会比较大。比如算法随机的5个bucket是:0.38,0.26,0.98,0.40,0.55。那选出的样本就是下图:可以看到,样本4在第二轮出现了3次,这样后面的分类器必然会对该样本倾斜。上面的第一种情况其实是后面bucket这种方式的一种特殊情况。所以,需要注意的是,每轮迭代改变了样本的权重后,对下一轮的影响是体现在挑选数据集的时候。当数据集选好后,这些数据集又会被赋予相同的权重,开始新一轮的迭代。后续不断按此方式迭代,直到错误率降到某个阈值或者达到预设的迭代次数。例子下面看两个例子,第一个偏理论,第二个偏实现,都有助于理解整个算法的流程和内在逻辑。A Mathematical Explanation of AdaBoost in 5 Minutes该例引用自:A Mathematical Explanation of AdaBoost in 5 Minutes。如下图:有一个包含6个样本的数据集,共3个属性:x1,x2,x3,输出为Y。其中T代表True,F代表False。Step1:初始化,给每个样本赋予相同的权重,即1/6:Step2:使用上面的数据生成弱学习器stump:分别计算各个属性的基尼不纯度(gini impurity)。$$ Gini\ Impurity = 1- Pr_{true}^2-Pr_{false}^2 $$这里以$x_2$属性为例进行计算:$$ Total \ Impurity(x_2) = 0.375*(\frac{1+3}{6})+0*(\frac{2+0}{6}) = 0.25 $$同理可以计算出$x_1$和$x_3$的不纯度为:$$ Total\ Impurity(x_1)=(1-(\frac{2}{2+2})^2-(\frac{2}{2+2})^2)*(\frac{2+2}{6}) \\ + (1-(\frac{1}{1+0})^2-(\frac{0}{1+0})^2)*(\frac{1+0}{6}) \\ = 0.33 \\ \\ Total\ Impurity(x_3)=(1-(\frac{0}{0+1})^2-(\frac{1}{0+1})^2)*(\frac{0+1}{6}) \\ + (1-(\frac{3}{3+2})^2-(\frac{2}{3+2})^2)*(\frac{3+2}{6}) \\ = 0.4 $$可以看到$x_2$的基尼不纯度最低,所以使用它生成第一个stump。Step3:计算“amount of say”。使用x2属性生成的stump会将第1个样本分错,其它都正确。这样的话total error就是1/6。所以:$$ Amount\ of\ say = \frac{1}{2}*log{\frac{1-\frac{1}{6}}{\frac{1}{6}}} = 0.35 $$Step4:计算下一个弱分类器(stump)的样本权重。分类错误的样本新权重 $w_{incorrect} = \frac{1}{6}* e^{0.35} = 0.24$分类正确的样本新权重 $w_{correct} = \frac{1}{6}* e^{-0.35} = 0.12$然后归一化得到下一个stump(即下一轮迭代)的样本及权重,如下图:这样第一轮就结束了。第二轮迭代开始之前,要先根据样本权重选取训练数据,比如选择的样本可能如下(可以看到第一轮分类错误的样本这次被选了3次):然后,将选中的样本权重初始化为相等的值,继续重复前面的过程:直到训练误差足够小,或者弱分类器个数达到限制,则迭代终止。对所有弱分类器加权求和得到最终的强分类器。Implementing the AdaBoost Algorithm From Scratch上面的例子比较偏理论性,这个例子则是具体到代码实现层面的,限于篇幅和格式,就不在文章里面贴了。具体可以参见我的Github: implement-adaboost-from-scratch.ipynb。里面会根据上面的算法实现一个AdaBoost,并且最终和scikit-learn的AdaBoostClassifier做对比。​该例参考自:Implementing the AdaBoost Algorithm From Scratch总结AdaBoost算法的思想还是比较简单的,算法也容易理解,主要需要理解下面几个关键点:如何计算训练误差$\varepsilon_t$理解权重调整系数$\alpha_t$的作用样本权重是如何更新的,又是如何影响到下一轮迭代的掌握了这些,在做模型的调优以及一些超参设置上也就可以游刃有余了。 集成学习介绍(1)——Boosting&Bagging http://niyanchun.com/boosting-and-bagging.html 2022-02-26T11:31:00+08:00 最近准备整理一下之前关于集成学习的学习笔记,写一个关于集成学习的系列文章,毕竟目前用的比较多的机器学习算法基本都属于集成学习,整理一下,也算温习一下。有些笔记时间比较久了,里面的一些引用来源找不到了,所以有些引用可能附不全,敬请谅解。目前确定的几篇包括:集成学习介绍(1)——Boosting && Bagging集成学习介绍(2)——AdaBoost集成学习介绍(3)——Random Forests集成学习介绍(4)——GBDT集成学习介绍(5)——XGBoost后续待定...后面可能会根据时间补充一下其它一些现在也比较流行的算法,比如LightGBM。本文是第一篇。集成学习(ensemble learning)是将多个基学习器(base learners)进行集成,得到比每个单独基学习器更优的强学习器(strong learner)的方法。每个用于集成的基学习器都是弱学习器(weak learner),即性能只比随机猜测好一点点或只在某些方面表现好一点的学习器(classifiers that produce prediction that is slightly better than random guessing)。​那如何保证多个弱学习器集成在一起会变的更好,而不是更差呢?即如何实现“1+1>2”的效果呢?这对弱学习器提出了一些要求:弱学习器的性能要有一定的保证,至少在某个方面要表现比较好。弱学习器的性能要有一定的差异,即擅长点要有所区分,这样组合后才能集百家之所长。根据训练数据使用方法的不同,集成学习可以分为三种:提升法(Boosting):各弱学习器之间存在强依赖关系而必须串行生成的序列化方法,各个弱学习器在最终的强学习器中权重不同;装袋法(Bagging):各弱学习器之间不存在强依赖关系因而可以同时生成的并行化方法,各个弱学习器在最终的强学习器中权重相同;堆叠法(Stacking):融合提升法和装袋法的一种集成。注意:集成学习属于机器学习,但它只是一种训练思路,而不是某种具体的算法。一般把他划归到元学习(meta learning):关于学习的学习。Boosting/Bagging的所使用的弱学习器一般是同类型的。Boosting提升法的是通过改变训练数据的权重(或概率分布)来训练不同的弱分类器,然后组合为强分类器。下面是两个示意图:Boosting的重点在于取新模型之长补旧模型之短来降低偏差(bias),尽可能获得无偏估计。BaggingBagging是Bootstrap Aggregation的缩写。Bootstrap也称为自助法,是一种有放回抽样方法。Bagging的的基本思想是对训练数据进行有放回抽样,每次抽样数据就训练一个模型,最终在这些模型上面取平均。具体算法如下:Step 1: Multiple subsets are created from the original data set with equal tuples, selecting observations with replacement.Step 2: A base model is created on each of these subsets.Step 3: Each model is learned in parallel from each training set and independent of each other.Step 4: The final predictions are determined by combining the predictions from all the models.下面是一个示意图:Bagging可以降低模型算法的方差,但并没有降低偏差的效果,所以也就没法提升预测的准确性,所以在选择弱分类器时要尽量选择偏差小的。​为什么Bagging能降低模型的方差?因为“如果对N个相互独立且方差相同的高斯分布取平均值,新分布的方差就会变成原始方差的 $1/N$”。Bagging采用独立有放回抽样得到N份数据,并训练得到N个模型,预测的时候最终会取这N个结果的平均(分类的话是取多数),这样就可以降低方差。对比下面对Boosting和Bagging做了一些对比: BoostingBagging样本选择每一轮训练集不变,但权重根据上一轮进行调整从原始数据集中通过独立、有放回抽样获得样例权重根据错误率不断调整,错误率越大的样本权重越大使用均匀抽样,各个样本权重相同模型权重每个弱分类器都有相应的权重,分类误差越小的分类器权重越高各个模型(弱分类器)权重相同并行计算各个分类器串行生成,因为后面的分类器需要前一轮的结果各个分类器并行生成目标主要为了减小偏差主要为了减小方差,并且解决过拟合问题适用场景分类器比较稳定(即方差比较小)和简单(即偏差比较大)分类器不稳定(即方差比较大),且偏差比较小的代表算法AdaBoost、GBDT、XGBoost随机森林Stacking除了提升法和装袋法之外,另一种知名度较低的集成方法是堆叠法。堆叠法(stacking)也叫堆叠泛化(stacked generalization),是层次化的集成方法,其思想和神经网络类似,只不过神经网络堆叠的对象是神经元和隐藏层,而集成方法堆叠的是同构或者异构的基学习器。​堆叠法先要用自助采样生成不同的数据子集,用数据子集训练第一层中不同的基学习器。第一层基学习器的输出再被送到第二层的元分类器(meta classifier)中作为输入,用来训练元分类器的参数。​堆叠法的思想和前两种方法有所不同。无论是提升法还是装袋法,其重点都落在单个模型的生成方式上,也就是如何训练出合适的基学习器,基学习器的形式一般是统一的。而堆叠法的重点在于如何将不同的基学习器的结果组合起来,研究的对象是让所有基学习器共同发挥出最大效果的组合策略。某种意义上说,堆叠法的训练数据不是原始的训练数据集,而是不同基学习器在训练数据集上的结果,起到的是模型平均(model averaging)的作用,提升法和装袋法都可以看成它的特例。正因如此,堆叠法除了被视为集成方法外,还可以看成是模型选择的一个手段。以上这段摘自极客时间《机器学习40讲专栏》​关于偏差和方差最后补充介绍一下偏差和方差这两个重要的概念。一个机器学习模型的误差可分为两类:经验误差(empirical error):又称训练误差(training error),指模型在训练集上面的误差;泛化误差(generalization error):模型在测试集上面的误差。其中泛化误差又可以分为三部分:偏差(bias):表示算法预测值与真实值之间的偏离程度,刻画的是模型的欠拟合(under-fitting)特性。偏差大,说明模型欠拟合。方差(variance):表示数据扰动对预测性能的影响,刻画的是模型过拟合(over-fitting)特性。方差大,说明模型过拟合。噪声(noise):表示在当前学习任务上所能达到的最小泛化误差,刻画的是任务本身的难度。用公式可表示为:$$ generalization\_error = bias^2 + variance + noise $$一般很难做到同时将偏差和方差都降到很低,只能在二者之间做权衡。下图中,靶心的红色是是预测正确的值,越往外,预测结果越差。四个图分别描述了误差和方差的高低情况:上图来自:Understanding the Bias-Variance Tradeoff一文,关于偏差和方差的更多细节,也可以参考这篇文章。