特征选择+++分裂大法好

1,xgboost分布式预测

2,xgboost特征选取代码

 

==================================================================================================================================================================================

作者:Porzy
链接:https://www.zhihu.com/question/23194489/answer/75555668
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 

 

(XGBOOST输出是标签呀,亲爱的,有标签是有监督学习,无标签是无监督学习

有监督学习(Supervised Learning)

先来问题化地解释一下有监督学习:你有一些问题和他们的答案,你要做的有监督学习就是学习这些已经知道答案的问题。然后你就具备了经验了,这就是学习的成果。然后在你接受到一个新的不知道答案的问题的时候,你可以根据学习得到的经验,得出这个新问题的答案。(试想一下高考不正是这样,好的学习器就能有更强的做题能力,考好的分数,上好的大学.....)。

我们有一个样本数据集,如果对于每一个单一的数据根据它的特征向量我们要去判断它的标签(算法的输出值),那么就是有监督学习。通俗的说,有监督学习就是比无监督学习多了一个可以表达这个数据特质的标签。

我们再来看有监督学习,分为两个大类:

1.回归分析(Regression Analysis):回归分析,其数据集是给定一个函数和它的一些坐标点,然后通过回归分析的算法,来估计原函数的模型,求出一个最符合这些已知数据集的函数解析式。然后它就可以用来预估其它未知输出的数据了,你输入一个自变量它就会根据这个模型解析式输出一个因变量,这些自变量就是特征向量,因变量就是标签。 而且标签的值是建立在连续范围的。 2.分类(Classification):其数据集,由特征向量和它们的标签组成,当你学习了这些数据之后,给你一个只知道特征向量不知道标签的数据,让你求它的标签是哪一个?其和回归的主要区别就是输出结果是离散的还是连续的。

无监督学习(Unsupervised Learning):

“Because we don't give it the answer, it's unsupervised learning”。

还是先来问题化地解释一下无监督学习:我们有一些问题,但是不知道答案,我们要做的无监督学习就是按照他们的性质把他们自动地分成很多组,每组的问题是具有类似性质的(比如数学问题会聚集在一组,英语问题会聚集在一组,物理........)。

所有数据只有特征向量没有标签,但是可以发现这些数据呈现出聚群的结构,本质是一个相似的类型的会聚集在一起。把这些没有标签的数据分成一个一个组合,就是聚类(Clustering)。比如Google新闻,每天会搜集大量的新闻,然后把它们全部聚类,就会自动分成几十个不同的组(比如娱乐,科技,政治......),每个组内新闻都具有相似的内容结构。

无监督学习还有一个典型的例子就是鸡尾酒会问题(声音的分离),在这个酒会上有两种声音,被两个不同的麦克风在不同的地方接收到,而可以利用无监督学习来分离这两种不同的声音。注意到这里是无监督学习的原因是,事先并不知道这些声音中有哪些种类(这里的种类就是标签的意思)。

而且鸡尾酒问题的代码实现只要一行,如下:

==================================================================================================================================================================================

监督学习和无监督学习区别

2018年08月24日 21:42:02 付石头_stone 阅读数:839

前言

机器学习分为:监督学习,无监督学习,半监督学习(也可以用hinton所说的强化学习)等。

在这里,主要理解一下监督学习和无监督学习。

监督学习(supervised learning)

从给定的训练数据集中学习出一个函数(模型参数),当新的数据到来时,可以根据这个函数预测结果。监督学习的训练集要求包括输入输出,也可以说是特征和目标。训练集中的目标是由人标注的。监督学习就是最常见的分类(注意和聚类区分)问题,通过已有的训练样本(即已知数据及其对应的输出)去训练得到一个最优模型(这个模型属于某个函数的集合,最优表示某个评价准则下是最佳的),再利用这个模型将所有的输入映射为相应的输出,对输出进行简单的判断从而实现分类的目的。也就具有了对未知数据分类的能力。监督学习的目标往往是让计算机去学习我们已经创建好的分类系统(模型)。

监督学习是训练神经网络和决策树的常见技术。这两种技术高度依赖事先确定的分类系统给出的信息,对于神经网络,分类系统利用信息判断网络的错误,然后不断调整网络参数。对于决策树,分类系统用它来判断哪些属性提供了最多的信息。

常见的有监督学习算法:回归分析和统计分类。最典型的算法是KNN和SVM。

有监督学习最常见的就是:regression&classification

Regression:Y是实数vector。回归问题,就是拟合(x,y)的一条曲线,使得价值函数(costfunction) L最小

   

Classification:Y是一个有穷数(finitenumber),可以看做类标号,分类问题首先要给定有lable的数据训练分类器,故属于有监督学习过程。分类过程中cost function l(X,Y)是X属于类Y的概率的负对数。

其中fi(X)=P(Y=i/X)。

 

无监督学习(unsupervised learning)

输入数据没有被标记,也没有确定的结果。样本数据类别未知,需要根据样本间的相似性对样本集进行分类(聚类,clustering)试图使类内差距最小化,类间差距最大化。通俗点将就是实际应用中,不少情况下无法预先知道样本的标签,也就是说没有训练样本对应的类别,因而只能从原先没有样本标签的样本集开始学习分类器设计。

非监督学习目标不是告诉计算机怎么做,而是让它(计算机)自己去学习怎样做事情。非监督学习有两种思路。第一种思路是在指导Agent时不为其指定明确分类,而是在成功时,采用某种形式的激励制度。需要注意的是,这类训练通常会置于决策问题的框架里,因为它的目标不是为了产生一个分类系统,而是做出最大回报的决定,这种思路很好的概括了现实世界,agent可以对正确的行为做出激励,而对错误行为做出惩罚。

无监督学习的方法分为两大类:

(1)    一类为基于概率密度函数估计的直接方法:指设法找到各类别在特征空间的分布参数,再进行分类。

(2)    另一类是称为基于样本间相似性度量的简洁聚类方法:其原理是设法定出不同类别的核心或初始内核,然后依据样本与核心之间的相似性度量将样本聚集成不同的类别。

利用聚类结果,可以提取数据集中隐藏信息,对未来数据进行分类和预测。应用于数据挖掘,模式识别,图像处理等。

    PCA和很多deep learning算法都属于无监督学习。 

两者的不同点

1.      有监督学习方法必须要有训练集与测试样本。在训练集中找规律,而对测试样本使用这种规律。而非监督学习没有训练集,只有一组数据,在该组数据集内寻找规律。

2.      有监督学习的方法就是识别事物,识别的结果表现在给待识别数据加上了标签。因此训练样本集必须由带标签的样本组成。而非监督学习方法只有要分析的数据集的本身,预先没有什么标签。如果发现数据集呈现某种聚集性,则可按自然的聚集性分类,但不予以某种预先分类标签对上号为目的。

3.      非监督学习方法在寻找数据集中的规律性,这种规律性并不一定要达到划分数据集的目的,也就是说不一定要“分类”。

这一点是比有监督学习方法的用途要广。    譬如分析一堆数据的主分量,或分析数据集有什么特点都可以归于非监督学习方法的范畴。

4.      用非监督学习方法分析数据集的主分量与用K-L变换计算数据集的主分量又有区别。后者从方法上讲不是学习方法。因此用K-L变换找主分量不属于无监督学习方法,即方法上不是。而通过学习逐渐找到规律性这体现了学习方法这一点。在人工神经元网络中寻找主分量的方法属于无监督学习方法。 

何时采用哪种方法

  简单的方法就是从定义入手,有训练样本则考虑采用监督学习方法;无训练样本,则一定不能用监督学习方法。但是,现实问题中,即使没有训练样本,我们也能够凭借自己的双眼,从待分类的数据中,人工标注一些样本,并把它们作为训练样本,这样的话,可以把条件改善,用监督学习方法来做。对于不同的场景,正负样本的分布如果会存在偏移(可能大的偏移,可能比较小),这样的话,监督学习的效果可能就不如用非监督学习了。

 

==================================================================================================================================================================================

检测评价函数(intersection-over-union, IOU)

2018年01月01日 00:57:23 __Mr-k 阅读数:188

(在*有监督学习中,数据是有标注的,以(x, t)的形式出现,其中x是输入数据,t是标注.正确的t标注是ground truth, 错误的标记则不是。)

在目标检测的评价体系中,有一个参数叫做 IoU ,简单来讲就是模型产生的目标窗口和原来标记窗口的交叠率。具体我们可以简单的理解为:即检测结果(Detection Result)与 Ground Truth 的交集比上它们的并集,即为检测的准确率 IoU :

IoU=(DR⋂GT)/(DR⋃GT)

 

如下图所示:GT =GroundTruth; DR = DetectionResult; 

红色边框框起来的是:    GT⋂DR      是GT和DR的交集

绿色框框起来的是:        GT⋃DR      是GT和DR的并集

当然最理想的情况就是 DR与 GT完全重合,即IoU=1

 

 

==================================================================================================================================================================================

xgboost 如何自定义评价函数

2018年12月01日 15:53:11 notplaid 阅读数:31

def tpr_weight_funtion(y_true,y_predict):
    d = pd.DataFrame()
    d['prob'] = list(y_predict)
    d['y'] = list(y_true)
    d = d.sort_values(['prob'], ascending=[0])
    y = d.y
    PosAll = pd.Series(y).value_counts()[1]
    NegAll = pd.Series(y).value_counts()[0]
    pCumsum = d['y'].cumsum()
    nCumsum = np.arange(len(y)) - pCumsum + 1
    pCumsumPer = pCumsum / PosAll
    nCumsumPer = nCumsum / NegAll
    TR1 = pCumsumPer[abs(nCumsumPer-0.001).idxmin()]
    TR2 = pCumsumPer[abs(nCumsumPer-0.005).idxmin()]
    TR3 = pCumsumPer[abs(nCumsumPer-0.01).idxmin()]
    return 0.4 * TR1 + 0.3 * TR2 + 0.3 * TR3
def myFeval(preds, xgbtrain):
    label = xgbtrain.get_label()
    score=tpr_weight_funtion(label, preds)
    return 'myFeval',score

 model = xgb.train(params, xgb_train_data,num_boost_round=2000,evals= [(xgb_train_data, 'train'),(xgb_val_data, 'valid')],early_stopping_rounds=100,verbose_eval=500,feval = myFeval)  

在末尾加上一句 feval = myFeval

 

==================================================================================================================================================================================

XGBoost自定义评价函数(feval)

2018年01月12日 16:19:44 manmantj 阅读数:2009

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014712516/article/details/79045402

最近参加了一个机器学习比赛,里面的评价函数比较特别,是这样的:
评价函数

其实也并不特别,就是在均方误差(MSE)的基础上再乘了一个0.5
但是众所周知,XGBoost中的评价函数(eval_metric)只提供了一下几种选值:

rmse 均方根误差
mae 平均绝对误差
logloss 负对数似然函数值
error 二分类错误率
merror 多分类错误率
mlogloss 多分类logloss损失函数
auc 曲线下面积

所以,为了在XGBoost训练时能够能准确,只能自己定义一个评价函数了:

def myFeval(preds, xgbtrain):
    label = xgbtrain.get_label()
    score = mean_squared_error(label,preds)*0.5
    return 'myFeval',score

这个函数有两个输入,需要注意的是第二个输入xgbtrain,这个输入是和你定义的xgb的训练集的命名有关的,我在这里的命名是这样的:

xgbtrain = xgb.DMatrix(X_train,y_train)
  • 1

所以,我的函数的第二个输入是xgbtrain

然后函数返回两个值,一个是这个评价函数的名字,是一个字符串形式,另一个就是你自己定的评价函数的值。函数自定义完毕之后如何应用呢?

model = xgb.train(params, xgbtrain, num_rounds, watchlist, early_stopping_rounds=25,feval = myFeval)

在xgb.train中加上一个参数feval = myFeval就ok了

最后的运行结果:
运行结果

参考网址:
http://blog.csdn.net/lujiandong1/article/details/52791117

==================================================================================================================================================================================

DMLC深盟分布式深度机器学习开源平台解析

发表于2015-05-21 14:42| 22088次阅读| 来源《程序员》电子刊| 0 条评论| 作者李沐 陈天奇 王敏捷 余凯 张峥

DMLC深盟机器学习分布式深度学习xgboostcxxnetMinerva参数服务器

摘要:本文由DMLC分布式深度机器学习开源项目(中文名深盟)开发者联合撰写,介绍DMLC已有的xgboost、cxxnet、Minerva、Parameter Server和Rabit等组件主要解决的问题、实现方式及其性能表现,并简要说明项目的近期规划。

【编者按】算法速度、系统性能以及易用性的瓶颈,制约着目前机器学习的普及应用,DMLC分布式深度机器学习开源项目(中文名深盟)的诞生,正是要降低分布式机器学习的门槛。本文由深盟项目开发者联合撰写,将深入介绍深盟项目当前已有的xgboost、cxxnet、Minerva、Parameter Server和Rabit等组件主要解决的问题、实现方式及其性能表现,并简要说明项目的近期规划。文章将被收录到《程序员》电子刊(2015.06A)人工智能实践专题,以下为全文内容:

 

机器学习能从数据中学习。通常数据越多,能学习到的模型就越好。在数据获得越来越便利的今天,机器学习应用无论在广度上还是在深度上都有了显著进步。虽然近年来计算能力得到了大幅提高,但它仍然远远不及数据的增长和机器学习模型的复杂化。因此,机器学习算法速度和系统性能是目前工业界和学术界共同关心的热点。

高性能和易用性的开源系统能对机器学习应用的其极大的推动作用。但我们发现目前兼具这两个特点的开源系统并不多,而且分散在各处。因此我们联合数个已有且被广泛使用的C++分布式机器学习系统的开发者,希望通过一个统一的组织来推动开源项目。我们为这个项目取名DMLC: Deep Machine Learning in Common,也可以认为是Distributed Machine Learning in C++。中文名为深盟。代码将统一发布在 https://github.com/dmlc

这个项目将来自工业界和学术界的几组开发人员拉到了一起,希望能提供更优质和更容易使用的分布式机器学习系统,同时也希望吸引更多的开发者参与进来。本文将介绍深盟项目目前已有的几个部件,并简要说明项目的近期规划。

 

xgboost: 速度快效果好的Boosting模型

 

在数据建模中,当我们有数个连续值特征时,Boosting分类器是最常用的非线性分类器。它将成百上千个分类准确率较低的树模型组合起来,成为一个准确率很高的模型。这个模型会不断地迭代,每次迭代就生成一颗新的树。然而,在数据集较大较复杂的时候,我们可能需要几千次迭代运算,这将造成巨大的计算瓶颈。

xgboost正是为了解决这个瓶颈而提出。单机它采用多线程来加速树的构建,并依赖深盟的另一个部件rabbit来进行分布式计算。为了方便使用,xgboost提供了 Python和R语言接口。例如在R中进行完整的训练和测试:

 

require(xgboost)
data(agaricus.train, package='xgboost')
data(agaricus.test, package='xgboost')
train<- agaricus.train
test<- agaricus.test
bst<- xgboost(data = train$data, label = train$label, max.depth = 2, eta = 1, nround = 100, objective = "binary:logistic")
pred<- predict(bst, test$data)

 

由于其高效的C++实现,xgboost在性能上超过了最常用使用的R包gbm和Python包sklearn。例如在Kaggle的希格斯子竞赛数据上,单线程xgboost比其他两个包均要快出50%,在多线程上xgboost更是有接近线性的性能提升。由于其性能和使用便利性,xgboost已经在Kaggle竞赛中被广泛使用,并已经有队伍成功借助其拿到了第一名,如图1所示。

 

 

图1 xgboost和另外两个常用包的性能对比

 

CXXNET:极致的C++深度学习库

 

cxxnet是一个并行的深度神经网络计算库,它继承了xgboost的简洁和极速的基因,并开始被越来越多人使用。例如Happy Lantern Festival团队借助Cxxnet在近期的Kaggle数据科学竞赛中获得了第二名。在技术上,cxxnet有如下两个亮点。

灵活的公式支持和极致的C++模板编程

 

追求速度极致的开发者通常使用C++来实现深度神经网络。但往往需要给每个神经网络的层和更新公式编写独立的CUDA kernel。很多以C++为核心的代码之所以没有向matlab/numpy那样支持非常灵活的张量计算,是因为因为运算符重载和临时空间的分配会带来效率的降低。

然而,cxxnet利用深盟的mshadow提供了类似matlab/numpy的编程体验,但同时保留了C++性能的高效性。其背后的核心思想是expression template,它通过模板编程技术将开发者写的公式自动展开成优化过的代码,避免重载操作符等带来的额外数据拷贝和系统消耗。另外,mshadow通过模板使得非常方便的讲代码切换到CPU还是GPU运行。

通用的分布式解决方案

 

在分布式深度神经网络中,我们既要处理一台机器多GPU卡,和多台机器多GPU卡的情况。然而后者的延迟和带宽远差于前者,因此需要对这种两个情形做不同的技术考虑。cxxnet采用mshadow-ps这样一个统一的参数共享接口,并利用接下来将要介绍Parameter Server实现了一个异步的通讯接口。其通过单机多卡和多机多卡采用不同的数据一致性模型来达到算法速度和系统性能的最佳平衡。

我们在单机4块GTX 980显卡的环境下测试了流行的图片物体识别数据集ImageNet和神经网络配置AlexNet。在单卡上,cxxnet能够处理244张图片每秒,而在4卡上可以提供3.7倍的加速。性能超过另一个流行深度学习计算库Caffe (均使用CUDA 6.5,未使用cuDNN加速)。

在多机情况下,我们使用Amazon EC2的GPU实例来测试性能。由于优秀的异步通信,cxxnet打满了机器的物理带宽,并提供了几乎是线性的加速比,如图2所示。

 

图2 cxxnet在Amazon EC2上的加速比

cxxnet的另外一些特性:

 

  1. 轻量而齐全的框架:推荐环境下仅需要CUDA、OpenCV、MKL或BLAS即可编译。
  2. cuDNN支持:Nvidia原生卷积支持,可加速计算30%。
  3. 及时更新的最新技术:及时跟进学术界的动态,例如现在已经支持MSRA的ParametricRelu和Google的Batch Normalization。
  4. Caffe模型转换:支持将训练好的Caffe模型直接转化为cxxnet模型。

 

 

Minerva: 高效灵活的并行深度学习引擎

 

不同于cxxnet追求极致速度和易用性,Minerva则提供了一个高效灵活的平台让开发者快速实现一个高度定制化的深度神经网络。

Minerva在系统设计上使用分层的设计原则,将“算的快”这一对于系统底层的需求和“好用”这一对于系统接口的需求隔离开来,如图3所示。在接口上,我们提供类似numpy的用户接口,力图做到友好并且能充分利用Python和numpy社区已有的算法库。在底层上,我们采用数据流(Dataflow)计算引擎。其天然的并行性能够高效地同时地利用多GPU进行计算。Minerva通过惰性求值(Lazy Evaluation),将类numpy接口和数据流引擎结合起来,使得Minerva能够既“好用”又“算得快”。

 

图 3 Minerva的分层设计

惰性求值

 

Minerva通过自己实现的ndarray类型来支持常用的矩阵和多维向量操作。在命名和参数格式上都尽量和numpy保持一致。Minerva同时支持读取Caffe的配置文件并进行完整的训练。Minerva提供了两个函数与numpy进行对接。from_numpy函数和to_numpy函数能够在numpy的ndarray与Minerva的类型之间互相转换。因此,将Minerva和numpy混合使用将变得非常方便。

数据流引擎和多GPU计算

 

从Mapreduce到Spark到Naiad,数据流引擎一直是分布式系统领域研究的热点。数据流引擎的特点是记录任务和任务之间的依赖关系,然后根据依赖关系对任务进行调度。没有依赖的任务则可以并行执行,因此数据流引擎具有天然的并行性。在Minerva中,我们利用数据流的思想将深度学习算法分布到多GPU上进行计算。每一个ndarray运算在Minerva中就是一个任务,Minerva自身的调度器会根据依赖关系进行执行。用户可以指定每个任务在哪块卡上计算。因此如果两个任务之间没有依赖并且被分配到不同GPU上,那这两个任务将能够并行执行。同时,由于数据流调度是完全异步的,多卡间的数据通信也可以和其他任务并行执行。由于这样的设计,Minerva在多卡上能够做到接近线性加速比。此外,利用深盟的Parameter Server,Minerva可以轻松将数据流拓展到多机上,从而实现多卡多机的分布式训练。

图4  Minerva和Caffe在单卡和多卡上训练GoogLeNet的比较

 

表1 Minerva在不同网络模型和不同GPU数目上的训练速度

 

数据流引擎和多GPU计算

 

Minerva采用惰性求值的方式将类numpy接口和数据流引擎结合起来。每次用户调用Minerva的ndarray运算,系统并不立即执行这一运算,而是将这一运算作为任务,异步地交给底层数据流调度器进行调度。之后,用户的线程将继续进行执行,并不会阻塞。这一做法带来了许多好处:

 

  • 在数据规模较大的机器学习任务中,文件I/O总是比较繁重的。而惰性求值使得用户线程进行I/O的同时,系统底层能同时进行计算。
  • 由于用户线程非常轻量,因此能将更多的任务交给系统底层。其中相互没有依赖的任务则能并行运算。
  • 用户能够在接口上非常轻松地指定每个GPU上的计算任务。Minerva提供了set_device接口,其作用是在下一次set_device调用前的运算都将会在指定的GPU上进行执行。由于所有的运算都是惰性求值的,因此两次set_device后的运算可以几乎同时进行调度,从而达到多卡的并行。

 

 

Parameter Server: 一小时训练600T数据

深盟的组件参数服务器(Parameter Server)对前述的应用提供分布式的系统支持。在大规模机器学习应用里,训练数据和模型参数均可大到单台机器无法处理。参数服务器的概念正是为解决此类问题而提出的。如图5所示,参数以分布式形式存储在一组服务节点中,训练数据则被划分到不同的计算节点上。这两组节点之间数据通信可归纳为发送(push)和获取(pull)两种。例如,一个计算节点既可以把自己计算得到的结果发送到所有服务节点上,也可以从服务节点上获取新模型参数。在实际部署时,通常有多组计算节点执行不同的任务,甚至是更新同样一组模型参数。

 

 

图5 参数服务器架构

在技术上,参数服务器主要解决如下两个分布式系统的技术难点。

降低网络通信开销

 

在分布式系统中,机器通过网络通信来共同完成任务。但不论是按照延时还是按照带宽,网络通信速度都是本地内存读写的数十或数百分之一。解决网络通信瓶颈是设计分布式系统的关键。

异步

在一般的机器学习算法中,计算节点的每一轮迭代可以划分成CPU繁忙和网络繁忙这两个阶段。前者通常是在计算梯度部分,后者则是在传输梯度数据和模型参数部分。串行执行这两个阶段将导致CPU和网络总有一个处于空闲状态。我们可以通过异步执行来提升资源利用率。例如,当前一轮迭代的CPU繁忙阶段完成时,可直接开始进行下一轮的CPU繁忙阶段,而不是等到前一轮的网络繁忙阶段完成。这里我们隐藏了网络通信开销,从而将CPU的使用率最大化。但由于没有等待前一轮更新的模型被取回,会导致这个计算节点的模型参数与服务节点处最新的参数不一致,由此可能会影响算法效率。

灵活的数据一致性模型

数据不一致性需要考虑提高算法效率和发挥系统性能之间的平衡。最好的平衡点取决于很多因素,例如CPU计算能力、网络带宽和算法的特性。我们发现很难有某个一致性模型能适合所有的机器学习问题。为此,参数服务器提供了一个灵活的方式用于表达一致性模型。

首先执行程序被划分为多个任务。一个任务类似于一个远程过程调用(Remote Procedure Call, RPC),可以是一个发送或一个获取,或者任意一个用户定义的函数,例如一轮迭代。任务之间可以并行执行,也可以加入依赖关系的控制逻辑,来串行执行,以确保数据的一致性。所有这些任务和依赖关系组成一个有向无环图,从而定义一个数据一致性模型,如图6所示。

 

图6 使用有向无环图来定义数据一致性模型

如图7所示,我们可以在相邻任务之间加入依赖关系的控制逻辑,得到顺序一致性模型,或者不引入任何依赖关系的逻辑控制,得到最终一致性模型。在这两个极端模型之间是受限延时模型。这里一个任务可以和最近的数个任务并行执行,但必须等待超过最大延时的未完成任务的完成。我们通过使用最大允许的延时来控制机器在此之前的数据不一致性。

 

图7 不同数据一致性下运行时间

图8展示了在广告点击预测中(细节描述见后文),不同的一致性模型下得到同样精度参数模型所花费的时间。当使用顺序一致性模型时(0延时),一半的运行时间花费在等待上。当我们逐渐放松数据一致性要求,可以看到计算时间随着最大允许的延时缓慢上升,这是由于数据一致性减慢了算法的收敛速度,但由于能有效地隐藏网络通信开销,从而明显降低了等待时间。在这个实验里,最佳平衡点是最大延时为8。

选择性通信

任务之间的依赖关系可以控制任务间的数据一致性。而在一个任务内,我们可以通过自定义过滤器来细粒度地控制数据一致性。这是因为一个节点通常在一个任务内有数百或者更多对的关键字和值(key, value)需要通信传输,过滤器对这些关键字和值进行选择性的通信。例如我们可以将较上次同步改变值小于某个特定阈值的关键字和值过滤掉。再如,我们设计了一个基于算法最优条件的KKT过滤器,它可过滤掉对参数影响弱的梯度。我们在实际中使用了这个过滤器,可以过滤掉至少95%的梯度值,从而节约了大量带宽。

缓冲与压缩

我们为参数服务器设计了基于区段的发送和获取通信接口,既能灵活地满足机器学习算法的通信需求,又尽可能地进行批量通信。在训练过程中,通常是值发生变化,而关键字不变。因此可以让发送和接收双方缓冲关键字,避免重复发送。此外,考虑到算法或者自定义过滤器的特性,这些通信所传输的数值里可能存在大量“0”,因此可以利用数据压缩有效减少通信量。

容灾

 

大规模机器学习任务通常需要大量机器且耗时长,运行过程中容易发生机器故障或被其他优先级高的任务抢占资源。为此,我们收集了一个数据中心中3个月内所有的机器学习任务。根据“机器数×用时”的值,我们将任务分成大中小三类,并发现小任务(100机器时)的平均失败率是6.5%;中任务(1000机器时)的失败率超过了13%;而对于大任务(1万机器时),每4个中至少有1个会执行失败。因此机器学习系统必须具备容灾功能。

参数服务器中服务节点和计算节点采用不同的容灾策略。对于计算节点,可以采用重启任务,丢弃失败节点,或者其他与算法相关的策略。而服务节点维护的是全局参数,若数据丢失和下线会严重影响应用的运行,因此对其数据一致性和恢复时效性要求更高。

参数服务器中服务节点的容灾采用的是一致性哈希和链备份。服务节点在存储模型参数时,通过一致性哈希协议维护一段或者数段参数。这个协议用于确保当有服务节点发生变化时,只有维护相邻参数段的服务节点会受到影响。每个服务节点维护的参数同时会在数个其他服务节点上备份。当一个服务节点收到来自计算节点的数据时,它会先将此数据备份到其备份节点上,然后再通知计算节点操作完成。中间的任何失败都会导致这次发送失败,但不会造成数据的不一致。

链备份适用于任何机器学习算法,但会使网络通信量成倍增长,从而可能形成性能瓶颈。对于某些算法,我们可以采用先聚合再备份的策略来减少通信。例如,在梯度下降算法里,每个服务节点先聚合来自所有计算节点的梯度,之后再更新模型参数,因此可以只备份聚合后的梯度而非来自每个计算节点的梯度。聚合可以有效减少备份所需通信量,但聚合会使得通信的延迟增加。不过这可以通过前面描述的异步执行来有效地隐藏。

在实现聚合链备份时,我们可以使用向量钟(vector clock)来记录收到了哪些节点的数据。向量钟允许我们准确定位未完成的节点,从而对节点变更带来的影响进行最小化。由于参数服务器的通信接口是基于区段发送的,所有区段内的关键字可以共享同一个向量钟来压缩其存储开销。

 

图8 三个系统在训练得到同样精度的模型时所各花费的时间

参数服务器不仅为深盟其他组件提供分布式支持,也可以直接在上面开发应用。例如,我们实现了一个分块的Proximal Gradient算法来解决稀疏的Logistic Regression,这是最常用的一个线性模型,被大量的使用在点击预测等分类问题中。

为了测试算法性能,我们采集了636TB真实广告点击数据,其中含有1700亿样本和650亿特征,并使用1000台机器共1.6万核来进行训练。我们使用两个服务产品的私有系统(均基于参数服务器架构)作为基线。图8展示的是这3个系统为了达到同样精度的模型所花费的时间。系统A使用了类梯度下降的算法(L-BFGS),但由于使用连续一致性模型,有30%的时间花费在等待上。系统B则使用了分块坐标下降算法,由于比系统A使用的算法更加有效,因此用时比系统A少。但系统B也使用连续一致性模型,并且所需全局同步次数要比系统A更多,所以系统B的等待时间增加到了50%以上。我们在参数服务器实现了与系统B同样的算法,但将一致性模型放松至受限延时一致性模型并应用了KKT过滤。与系统B相比,参数服务器需要略多的计算时间,但其等待时间大幅降低。由于网络开销是这个算法的主要瓶颈,放松的一致性模型使得参数服务器的总体用时只是系统B的一半。

Rabit:灵活可靠的同步通信

除了Parameter Server提供的异步通信之外,以GBDT和L-BFGS为代表的许多机器学习算法依然适合采用同步通信 (BSP)的方式进行交互。深盟的第二个通信框架Rabit提供了这一选择。

传统的同步通信机器学习程序往往采用MPI的Allreduce进行计算,但是因为MPI提供的接口过于复杂使得它并不容易提供容灾支持。Rabit简化了MPI的设计,抽取出机器学习最需要的Allreduce和Broadcast操作并加入了容灾的支持,使得基于分布式BSP的机器学习算法可以在部分节点出错或丢失的情况下快速恢复计算,完成剩下的任务。目前的GBDT算法xgboost就是基于Rabit提供的接口。同时,Rabit具有非常强的可移植性,目前支持在MPI、Hadoop Yarn和SunGrid Engine等各个平台下直接执行。异步的Parameter Server 接口加上同步的Rabit接口基本涵盖了各种分布式机器学习算法需要的通信需求,使得我们可以很快地实现各种高效的分布式机器学习算法。

 

未来规划

 

深盟目前已有的组件覆盖三类最常用的机器学习算法,包括被广泛用于排序的GBDT,用于点击预测的稀疏线性模型,以及目前的研究热点深度学习。未来深盟将致力于将实现和测试更多常用的机器学习算法,目前有数个算法正在开发中。另一方面,我们将更好的融合目前的组件,提供更加一致性的用户体验。例如我们将对cxxnet和Minerva结合使得其既满足对性能的苛刻要求,又能提供灵活的开发环境。

深盟另一个正在开发中的组件叫做虫洞,它将大幅降低安装和部署分布式机器学习应用的门槛。具体来说,虫洞将对所有组件提供一致的数据流支持,无论数据是以任何格式存在网络共享磁盘,无论HDFS还是Amazon S3。此外,它还提供统一脚本来编译和运行所有组件。使得用户既可以在方便的本地集群运行深盟的任何一个分布式组件,又可以将任务提交到任何一个包括Amazon EC2、Microsfot Azure和Google Compute Engine在内的云计算平台,并提供自动的容灾管理。

这个项目最大的愿望就是能将分布式机器学习的门槛降低,使得更多个人和机构能够享受大数据带来的便利。同时也希望能多的开发者能加入,联合大家的力量一起把这个事情做好。(责编/周建丁)

=========================================================================================

=================================================================================================================================================================================

xgboost 分布式部署教程

xgboost是一个非常优秀的用于梯度提升学习开源工具。在多个数值算法和非数值算法的优化下(XGBoost: A Scalable Tree Boosting System),速度非常惊人。经测试用spark10小时才能train出GBDT(Gradient Boosting Decision Tree)的数据量,xgboost 使用一半的集群资源只需要10分钟。出于种种原因,我在hadoop环境上部署xgboost花了一个多月的时间,期间在xgboost issues 上提了很多问题,也替作者找过bug。今天特地写篇部署教程,方便有需要的同行。
注:

    本教程是在集群 gcc -v < 4.8 libhdfs native目标代码不可用的情况下部署xgboost,因此可以cover掉部署过程中遇到的绝大多数问题。
    由于未对当前的代码进行测试,本教程使用特定版本的代码。
    本教程将运行xgboost依赖的文件都放到xgboost-packages目录下,再次部署只需scp -r xgboost-packages 到${HOME}目录下

获取特定版本的xgboost

    从github上git clonedmlc-core, rabit, xgboost
    git clone --recursive https://github.com/dmlc/xgboost
    进入xgboost目录,检出版本76c320e9f0db7cf4aed73593ddcb4e0be0673810
    git checkout 76c320e9f0db7cf4aed73593ddcb4e0be0673810
    进入dmlc-core目录,检出版本706f4d477a48fc75cb46b226ea007fbac862f9c2
    git checkout 706f4d477a48fc75cb46b226ea007fbac862f9c2
    进入 rabit 目录,检出版本112d866dc92354304c0891500374fe40cdf13a50
    git checkout 112d866dc92354304c0891500374fe40cdf13a50
    在${HOME}创建xgboost-packages目录,将xgboost拷贝到xgboost-package目录下

  mkdir xgboost-package
  cp -r xgboost xgboost-packages/

安装编译依赖的包
安装gcc-4.8.0

    下载gcc源码包并解压
    tar -jxvf gcc-4.8.2.tar.bz2
    下载编译所需依赖库

  cd gcc-4.8.2
  ./contrib/download_prerequisites
  # 建立一个目录供编译出的文件存放
  cd ..

    建立编译输出目录
    mkdir gcc-build-4.8.2
    进入此目录,执行以下命令,生成makefile文件(安装到${HOME}目录下)

cd  gcc-build-4.8.2
../gcc-4.8.2/configure --enable-checking=release --enable-languages=c,c++ --disable-multilib --prefix=${HOME}

    编译
    make -j21
    安装
    make install
    修改变量切换默认gcc版本

PATH=$HOME/bin:$PATH
cp -r ~/lib64 ~/xgboost-packages

 

安装cmake

    下载cmake-3.5.2
    安装:

tar -zxf cmake-3.5.2.tar.gz
cd cmake-3.5.2
./bootstrap --prefix=${HOME}
gmake
make -j21
make install

 

下载编译libhdfs*

    下载hadoop-common-cdh5-2.6.0_5.5.0
    编译

unzip hadoop-common-cdh5-2.6.0_5.5.0.zip
cd hadoop-common-cdh5-2.6.0_5.5.0/hadoop-hdfs-project/hadoop-hdfs/src
cmake -DGENERATED_JAVAH=/opt/jdk1.8.0_60 -DJAVA_HOME=/opt/jdk1.8.0_60
make
# 拷贝编译好的目标文件到xgboost-packages中
cp -r /target/usr/local/lib ${HOME}/xgboost-packages/libhdfs

 

安装xgboost

    编译

cd ${HOME}/xgboost-packages/xgboost
cp make/config.mk ./
# 更改config.mk 使用HDFS配置
# whether use HDFS support during compile
USE_HDFS = 1
HADOOP_HOME = /usr/lib/hadoop
HDFS_LIB_PATH = $(HOME)/xgboost-packages/libhdfs
#编译
make -j22

 

    修改部分代码(env python 版本>2.7不用修改)

# 更改dmlc_yarn.py首行
#!/usr/bin/python2.7
# 更改run_hdfs_prog.py首行
#!/usr/bin/python2.7

 

    测试

# 添加必要参数
cd ${HOME}/xgboost-packages/xgboost/demo/distributed-training
echo -e "booster = gbtree\nobjective = binary:logistic\nsave_period = 0\neval_train = 1" > mushroom.hadoop.conf
# 测试代码 run_yarn.sh
# 添加必要参数
cd ${HOME}/xgboost-packages/xgboost/demo/distributed-training
echo -e "booster = gbtree\nobjective = binary:logistic\nsave_period = 0\neval_train = 1" > mushroom.hadoop.conf
# 测试代码 run_yarn.sh
#!/bin/bash
if [ "$#" -lt 2 ];
then
        echo "Usage: <nworkers> <nthreads>"
        exit -1
fi

# put the local training file to HDFS
DATA_DIR="/user/`whoami`/xgboost-dist-test"
#hadoop fs -test -d ${DATA_DIR} && hadoop fs -rm -r ${DATA_DIR}
#hadoop fs -mkdir ${DATA_DIR}
#hadoop fs -put ../data/agaricus.txt.train ${DATA_DIR}
#hadoop fs -put ../data/agaricus.txt.test ${DATA_DIR}

# necessary env
export LD_LIBRARY_PATH=${HOME}/xgboost-packages/lib64:$JAVA_HOME/jre/lib/amd64/server:/${HOME}/xgboost-packages/libhdfs:$LD_LIBRARY_PATH
export HADOOP_HOME=/usr/lib/hadoop
export HADOOP_COMMON_HOME=$HADOOP_HOME
export HADOOP_HDFS_HOME=/usr/lib/hadoop-hdfs
export HADOOP_MAPRED_HOME=/usr/lib/hadoop-yarn
export HADOOP_YARN_HOME=$HADOOP_MAPRED_HOME
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop

# running rabit, pass address in hdfs
../../dmlc-core/tracker/dmlc_yarn.py  -n $1 --vcores $2\
    --ship-libcxx ${HOME}/xgboost-packages/lib64 \
    -q root.machinelearning \
    -f ${HOME}/xgboost-packages/libhdfs/libhdfs.so.0.0.0 \
    ../../xgboost mushroom.hadoop.conf nthread=$2 \
    data=hdfs://ss-hadoop${DATA_DIR}/agaricus.txt.train \
    eval[test]=hdfs://ss-hadoop${DATA_DIR}/agaricus.txt.test \
    eta=1.0 \
    max_depth=3 \
    num_round=3 \
    model_out=hdfs://ss-hadoop/tmp/mushroom.final.model

# get the final model file
hadoop fs -get /tmp/mushroom.final.model final.model

# use dmlc-core/yarn/run_hdfs_prog.py to setup approperiate env

# output prediction task=pred
#../../xgboost.dmlc mushroom.hadoop.conf task=pred model_in=final.model test:data=../data/agaricus.txt.test
#../../dmlc-core/yarn/run_hdfs_prog.py ../../xgboost mushroom.hadoop.conf task=pred model_in=final.model test:data=../data/agaricus.txt.test
# print the boosters of final.model in dump.raw.txt
#../../xgboost.dmlc mushroom.hadoop.conf task=dump model_in=final.model name_dump=dump.raw.txt
#../../dmlc-core/yarn/run_hdfs_prog.py ../../xgboost mushroom.hadoop.conf task=dump model_in=final.model name_dump=dump.raw.txt
# use the feature map in printing for better visualization
#../../xgboost.dmlc mushroom.hadoop.conf task=dump model_in=final.model fmap=../data/featmap.txt name_dump=dump.nice.txt
../../dmlc-core/yarn/run_hdfs_prog.py ../../xgboost mushroom.hadoop.conf task=dump model_in=final.model fmap=../data/featmap.txt name_dump=dump.nice.txt
cat dump.nice.txt


参考资料

    https://github.com/dmlc/xgboost
    https://github.com/dmlc/xgboost/issues/854
    https://github.com/dmlc/xgboost/issues/856
    https://github.com/dmlc/xgboost/issues/861
    https://github.com/dmlc/xgboost/issues/866
    https://github.com/dmlc/xgboost/issues/869
    https://github.com/dmlc/xgboost/issues/1150
    http://arxiv.org/pdf/1603.02754v1.pdf

 

=========================================================================================

xgboost原理分析以及实践_xgboost

作者:用户 来源:互联网 时间:2018-08-21 15:51:34

机器学习XGBoostxgboost原理分xgboost实践

xgboost原理分析以及实践_xgboost -摘要: 本文讲的是xgboost原理分析以及实践_xgboost, 摘要 本文在写完GBDT的三篇文章后本来就想写的,但一直没有时间,终于刚好碰上需要,有空来写这篇关于xgboost原理以及一些实践的东西(这里实践不是指给出代码然后跑结果,而是我们来手动算一算整个xgboost流程)

教程 云栖大会 Mysql 备案 文档 域名 whois查询 PHP教程 备份 互联网大学 云教程

摘要

本文在写完GBDT的三篇文章后本来就想写的,但一直没有时间,终于刚好碰上需要,有空来写这篇关于xgboost原理以及一些实践的东西(这里实践不是指给出代码然后跑结果,而是我们来手动算一算整个xgboost流程)

由于网上已经许多优秀的文章对xgboost原理进行了详细的介绍,特别是xgboost作者陈天奇的论文以及slide已经非常完整阐述了整个xgboost的来龙去脉,现有的文章基本也是参考了这两个资料。
但是却少涉及把原理对应到实际实现过程的文章,许多人看完原理之后可能对整个过程还是抱有好奇心,所以本文从另一个角度,原理到实际运行的角度来分析xgboost,相当于结合原理,仔细看看xgboost每一步到底计算了什么。 原理

当然,我们还是需要简要的回顾一下xgboost的整个推导过程,以及做一些铺垫,方便后面叙述。

我们知道,任何机器学习的问题都可以从目标函数(objective function)出发,目标函数的主要由两部分组成 损失函数+正则项。

Obj(Θ)=L(Θ)+Ω(Θ) O b j ( Θ ) = L ( Θ ) + Ω ( Θ )
损失函数用于描述模型拟合数据的程度。
正则项用于控制模型的复杂度。

 

对于正则项,我们常用的L2正则和L1正则。
L1正则: Ω(w)=λ||w||1 Ω ( w ) = λ | | w | | 1
L2正则: Ω(w)=λ||w||2 Ω ( w ) = λ | | w | | 2

 

在这里,当我选择树模型为基学习器时,我们需要正则的对象,或者说需要控制复杂度的对象就是这 K K 颗树,通常树的参数有树的深度,叶子节点的个数,叶子节点值的取值(xgboost里称为权重weight)。

所以,我们的目标函数形式如下:
L(yi,ŷ i)+∑Kk=1Ω(fk(xi)) L ( y i , y ^ i ) + ∑ k = 1 K Ω ( f k ( x i ) )

 

对一个目标函数,我们最理想的方法就选择一个优化方法算法去一步步的迭代的学习出参数。但是这里的参数是一颗颗的树,没有办法通过这种方式来学习。

既然如此,我们可以利用Boosting的思想来解决这个问题,我们把学习的过程分解成先学第一颗树,然后基于第一棵树学习第二颗树。也就是说:
ŷ 0i=常数 y ^ i 0 = 常 数
ŷ 1i=ŷ 0i+f1(xi) y ^ i 1 = y ^ i 0 + f 1 ( x i )
ŷ 2i=ŷ 1i+f2(xi) y ^ i 2 = y ^ i 1 + f 2 ( x i )
ŷ Ki=ŷ K−1i+fK(xi)(0) (0) y ^ i K = y ^ i K − 1 + f K ( x i )

 

所以,对于第K次的目标函数为:
ObjK=∑iL(yi,ŷ Ki)+Ω(fK)+constant O b j K = ∑ i L ( y i , y ^ i K ) + Ω ( f K ) + c o n s t a n t

 

==> ObjK=∑iL(yi,ŷ K−1i+fK(xi))+Ω(fK)+constant O b j K = ∑ i L ( y i , y ^ i K − 1 + f K ( x i ) ) + Ω ( f K ) + c o n s t a n t

 

上面的式子意义很明显,只需要寻找一颗合适的树 fK f K 使得目标函数最小。然后不断的迭代K次就可以完成K个学习器的训练。

那么我们这颗树到底怎么找呢。
在GBDT里面(当然GBDT没有正则),我们的树是通过拟合上一颗树的负梯度值,建树的时候采用的启发式准则。具体。

然而,在xgboost里面,它是通过对损失函数进行泰勒展开。
(其思想主要来自于文章:Additive logistic regression a statistical view of boosting也是Friedman大牛的作品)

二阶泰勒展开:
f(x+Δx)=f(x)+f′(x)Δx+12f″(x)Δx2 f ( x + Δ x ) = f ( x ) + f ′ ( x ) Δ x + 1 2 f ″ ( x ) Δ x 2

对损失函数二阶泰勒展开:
∑iL(yi,ŷ K−1i+fK(xi))=∑i[L(yi,ŷ K−1i)+L′(yi,ŷ K−1i)fK(xi)+12L″(yi,ŷ K−1i)f2K(xi)] ∑ i L ( y i , y ^ i K − 1 + f K ( x i ) ) = ∑ i [ L ( y i , y ^ i K − 1 ) + L ′ ( y i , y ^ i K − 1 ) f K ( x i ) + 1 2 L ″ ( y i , y ^ i K − 1 ) f K 2 ( x i ) ]

注意的是,这里的 yi y i 是标签值是个常数,而 ŷ K−1i y ^ i K − 1 是前一次学习到的结果,也是个常数。所以只要把变化量 Δx Δ x 看成我们需要学习的模型 fK(x) f K ( x ) 就可以展成上面的这个样子了。

这里,我们用 gi g i 记为第i个样本损失函数的一阶导, hi h i 记为第i个样本损失函数的二阶导。
gi=L′(yi,ŷ K−1i)(1) (1) g i = L ′ ( y i , y ^ i K − 1 )
hi=L″(yi,ŷ K−1i)(2) (2) h i = L ″ ( y i , y ^ i K − 1 )

(1)式和(2)非常的重要,贯穿了整个树的构建(分裂,叶子节点值的计算)。以及(2)式是我们利用xgboost做特征选择时的其中一个评价指标。

所以我们可以得到我们进化后的目标函数:
∑i[L(yi,ŷ K−1i)+gifK(xi)+12hif2K(xi)]+Ω(fK)+constant ∑ i [ L ( y i , y ^ i K − 1 ) + g i f K ( x i ) + 1 2 h i f K 2 ( x i ) ] + Ω ( f K ) + c o n s t a n t

这里,我们先回忆一下,一颗树用数学模型来描述是什么样,很简单其实就是一个分段函数。比如有下面一颗树。
xgboost原理分析以及实践_xgboost

f(x)={0.444444−0.4x1<10x1>=10 f ( x ) = { 0.444444 x 1 < 10 − 0.4 x 1 >= 10

 

也就是说,一棵树其实可以由一片区域以及若干个叶子节点来表达。
而同时,构建一颗树也是为了找到每个节点的区域以及叶子节点的值。

也就说可以有如下映射的关系 fK(x)=wq(x) f K ( x ) = w q ( x ) 。其中 q(x) q ( x ) 叶子节点的编号(从左往右编)。 w w 是叶子节点的取值。
也就说对于任意一个样本 x x ,其最后会落在树的某个叶子节点上,其值为 wq(x) w q ( x ) 。

既然一棵树可以用叶子节点来表达,那么,我们上面的正则项就了其中一个思路。我们可以对叶子节点值进行惩罚(正则),比如取L2正则,以及我们控制一下叶子节点的个数T,那么正则项有:

Ω(fK)=12λ∑Tj||wj||2+γT Ω ( f K ) = 1 2 λ ∑ j T | | w j | | 2 + γ T

其实正则为什么可以控制模型复杂度呢。有很多角度可以看这个问题,最直观就是,我们为了使得目标函数最小,自然正则项也要小,正则项要小,叶子节点个数T要小(叶子节点个数少,树就简单)。
而为什么要对叶子节点的值进行L2正则,这个可以参考一下LR里面进行正则的原因,简单的说就是LR没有加正则,整个 w w 的参数空间是无限大的,只有加了正则之后,才会把 w w 的解规范在一个范围内。(对此困惑的话可以跑一个不带正则的LR,每次出来的权重 w w 都不一样,但是loss都是一样的,加了L2正则后,每次得到的w都是一样的)

这个时候,我们的目标函数(移除常数项后)就可以改写成这样(用叶子节点表达):
∑i[giwq(xi)+12hiw2q(xi)]+12λ∑Tj||wj||2+γT(3) (3) ∑ i [ g i w q ( x i ) + 1 2 h i w q ( x i ) 2 ] + 1 2 λ ∑ j T | | w j | | 2 + γ T

其实我们可以进一步化简,那么最后可以化简成:
∑Tj=1[(∑(i∈Ij)gi)wj+12(∑(i∈Ij)hi+λ)w2j]+γT(4) (4) ∑ j = 1 T [ ( ∑ ( i ∈ I j ) g i ) w j + 1 2 ( ∑ ( i ∈ I j ) h i + λ ) w j 2 ] + γ T

(3)式子展开之后按照叶子节点编号进行合并后可以得到(4)。可以自己举T=2的例子推导一下。

下面,我们把 ∑(i∈Ij)gi ∑ ( i ∈ I j ) g i 记为 Gj G j ,把 ∑(i∈Ij)hi ∑ ( i ∈ I j ) h i 记为 Hj H j 。
Gj=∑(i∈Ij)gi(5) (5) G j = ∑ ( i ∈ I j ) g i
Hj=∑(i∈Ij)hi(6) (6) H j = ∑ ( i ∈ I j ) h i

那么目标函数可以进一步简化为:
∑Tj=1[Gjwj+12(Hj+λ)w2j]+γT(7) (7) ∑ j = 1 T [ G j w j + 1 2 ( H j + λ ) w j 2 ] + γ T

我们做了这么多,其实一直都是在对二阶泰勒展开后的式子化简,其实刚展开的时候就已经是一个二次函数了,只不过化简到这里能够把问题看的更加清楚。所以对于这个目标函数,我们对 wj w j 求导,然后带入极值点,可以得到一个极值:

w∗=−GjHj+λ(8) (8) w ∗ = − G j H j + λ
Obj=−12∑Tj=1G2jHj+λ+γT(9) (9) O b j = − 1 2 ∑ j = 1 T G j 2 H j + λ + γ T

我们花了这么大的功夫,得到了叶子结点取值的表达式。

如果有看过我们前面GBDT文章的朋友应该没有忘记当时我们也给出了一系列的损失函数下的叶子节点的取值,在xgboost里,叶子节点取值的表达式很简洁,推导起来也比GBDT的要简便许多

到这里,我们一直都是在围绕目标函数进行分析,这个到底是为什么呢。这个主要是为了后面我们寻找 fk(x) f k ( x ) ,也就是建树的过程。

具体来说,我们回忆一下建树的时候需要做什么,建树的时候最关键的一步就是选择一个分裂的准则,也就如何评价分裂的质量。比如在前面文章GBDT的介绍里,我们可以选择MSE,MAE来评价我们的分裂的质量,但是,我们所选择的分裂准则似乎不总是和我们的损失函数有关,因为这种选择是启发式的。
比如,在分类任务里面,损失函数可以选择logloss,分裂准确选择MSE,这样看来,似乎分裂的好坏和我们的损失并没有直接挂钩。

但是,在xgboost里面,我们的分裂准则是直接与损失函数挂钩的准则,这个也是xgboost和GBDT一个很不一样的地方。

具体来说,xgboost选择这个准则,计算增益 Gain G a i n

Gain=12[G2LH2L+λ+G2RH2R+λ−(GL+GR)2(HL+HR)2+λ]−γ(10) (10) G a i n = 1 2 [ G L 2 H L 2 + λ + G R 2 H R 2 + λ − ( G L + G R ) 2 ( H L + H R ) 2 + λ ] − γ

其实选择这个作为准则的原因很简单也很直观。
我们这样考虑。由(9)式知道,对于一个结点,假设我们不分裂的话。
此时损失为: (GL+GR)2(HL+HR)2+λ ( G L + G R ) 2 ( H L + H R ) 2 + λ
假设在这个节点分裂的话,分裂之后左右叶子节点的损失分别为: G2LH2L+λ G L 2 H L 2 + λ 、 G2RH2R+λ G R 2 H R 2 + λ 。

既然要分裂的时候,我们当然是选择分裂成左右子节点后,损失减少的最多(或者看回(9)式,由于前面的负号,所以欲求(9)的最小值,其实就是求(10)的最大值)
也就是找到一种分裂有: Max([G2LH2L+λ+G2RH2R+λ−(GL+GR)2(HL+HR)2+λ]) M a x ( [ G L 2 H L 2 + λ + G R 2 H R 2 + λ − ( G L + G R ) 2 ( H L + H R ) 2 + λ ] ) 。

那么 γ γ 的作用是什么呢。利用 γ γ 可以控制树的复杂度,进一步来说,利用 γ γ 来作为阈值,只有大于 γ γ 时候才选择分裂。这个其实起到预剪枝的作用。

最后就是如何得到左右子节点的样本集合。这个和普通的树一样,都是通过遍历特征所有取值,逐个尝试。

至此,我们把xgboost的基本原理阐述了一遍。我们总结一下,其实就是做了以下几件事情。
1.在损失函数的基础上加入了正则项。
2.对目标函数进行二阶泰勒展开。
3.利用推导得到的表达式作为分裂准确,来构建每一颗树。 xgboost算法流程总结

xgboost核心部分的算法流程图如下。
xgboost原理分析以及实践_xgboost
(这里的m貌似是d) 手动计算还原xgboost的过程

上面,我只是简单的阐述整个流程,有一些细节的地方可能都说的不太清楚,我以一个简单的UCI数据集,一步一步的和大家演算整个xgboost的过程。

数据集地址

数据集的样本条数只有15条,2个特征。具体如下: ID x1 x2 y 1 1 -5 0 2 2 5 0 3 3 -2 1 4 1 2 1 5 2 0 1 6 6 -5 1 7 7 5 1 8 6 -2 0 9 7 2 0 10 6 0 1 11 8 -5 1 12 9 5 1 13 10 -2 0 14 8 2 0 15 9 0 1

这里为了简单起见,树的深度设置为3(max_depth=3),树的颗数设置为2(num_boost_round=2),学习率为0.1(eta=0.1)。
另外再设置两个正则的参数, λ=1 λ = 1 , γ=0 γ = 0 。
损失函数选择logloss。

由于后面需要用到logloss的一阶导数以及二阶导数,这里先简单推导一下。

L(yi,ŷ i)=yiln(1+e−ŷ i)+(1−yi)ln(1+eŷ i) L ( y i , y ^ i ) = y i l n ( 1 + e − y ^ i ) + ( 1 − y i ) l n ( 1 + e y ^ i )
两边对 ŷ i y ^ i 求一阶导数。
L′(yi,ŷ i)=yi−e−ŷ i1+e−ŷ i+(1−yi)eŷ i1+eŷ i L ′ ( y i , y ^ i ) = y i − e − y ^ i 1 + e − y ^ i + ( 1 − y i ) e y ^ i 1 + e y ^ i
==> L′(yi,ŷ i)=yi−11+eŷ i+(1−yi)11+e−ŷ i L ′ ( y i , y ^ i ) = y i − 1 1 + e y ^ i + ( 1 − y i ) 1 1 + e − y ^ i
==> L′(yi,ŷ i)=yi∗(yi,pred−1)+(1−yi)∗yi,pred L ′ ( y i , y ^ i ) = y i ∗ ( y i , p r e d − 1 ) + ( 1 − y i ) ∗ y i , p r e d
==> L′(yi,ŷ i)=yi,pred−yi L ′ ( y i , y ^ i ) = y i , p r e d − y i ,其中 yi,pred=11+e−ŷ i y i , p r e d = 1 1 + e − y ^ i

在一阶导的基础上再求一次有(其实就是sigmod函数求导)
L″(yi,ŷ i)=yi,pred∗(1−yi,pred) L ″ ( y i , y ^ i ) = y i , p r e d ∗ ( 1 − y i , p r e d )

所以样本的一阶导数值 gi=yi,pred−yi(11) (11) g i = y i , p r e d − y i
样本的二阶导数值 hi=yi,pred∗(1−yi,pred)(12) (12) h i = y i , p r e d ∗ ( 1 − y i , p r e d )

下面就是xgboost利用上面的参数拟合这个数据集的过程: 建立第一颗树(k=1)

建树的时候从根节点开始(Top-down greedy),在根节点上的样本有ID1~ID15。那么回顾xgboost的算法流程,我们需要在根节点处进行分裂,分裂的时候需要计算式子(10)。
Gain=12[G2LH2L+λ+G2RH2R+λ−(GL+GR)2(HL+HR)2+λ]−γ(10) (10) G a i n = 1 2 [ G L 2 H L 2 + λ + G R 2 H R 2 + λ − ( G L + G R ) 2 ( H L + H R ) 2 + λ ] − γ
那么式子(10)表达是:在结点处把样本分成左子结点和右子结点两个集合。分别求两个集合的 GL G L 、 HL H L 、 GR G R 、 HR H R ,然后计算增益 Gain G a i n 。

而这里,我其实可以先计算每个样本的一阶导数值和二阶导数值,即按照式子(11)和(12)计算,但是这里你可能碰到了一个问题,那就是第一颗树的时候每个样本的预测的概率 yi,pred y i , p r e d 是多少。这里和GBDT一样,应该说和所有的Boosting算法一样,都需要一个初始值。而在xgboost里面,对于分类任务只需要初始化为(0,1)中的任意一个数都可以。具体来说就是参数base_score。(其默认值是0.5)

(值得注意的是base_score是一个经过sigmod映射的值,可以理解为一个概率值,提这个原因是后面建第二颗树会用到,需要注意这个细节)

这里我们也设base_score=0.5。然后我们就可以计算每个样本的一阶导数值和二阶导数值了。具体如下表:
xgboost原理分析以及实践_xgboost

比如说对于ID=1样本, g1=y1,pred−y1=0.5−0=0.5 g 1 = y 1 , p r e d − y 1 = 0.5 − 0 = 0.5 。
h1=y1,pred∗(1−y1,pred)=0.5∗(1−0.5)=0.25 h 1 = y 1 , p r e d ∗ ( 1 − y 1 , p r e d ) = 0.5 ∗ ( 1 − 0.5 ) = 0.25

那么把样本如何分成两个集合呢。这里就是上面说到的选取一个最佳的特征以及分裂点使得 Gain G a i n 最大。

比如说对于特征x1,一共有[1,2,3,6,7,8,9,10]8种取值。可以得到以下这么多划分方式。

以1为划分时(x1<1):
左子节点包含的样本 IL I L =[]
右子节点包含的样本 IR I R =[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
那么左子节点为空, GL=0 G L = 0 和 HL=0 H L = 0
右子节点的一阶导数和:
GR=∑(i∈IR)gi=(0.5+0.5+....−0.5)=−1.5 G R = ∑ ( i ∈ I R ) g i = ( 0.5 + 0.5 + . . . . − 0.5 ) = − 1.5
右子节点的二阶导数和:
HR=∑(i∈IR)hi=(0.25+0.25...0.25)=3.75 H R = ∑ ( i ∈ I R ) h i = ( 0.25 + 0.25...0.25 ) = 3.75

最后我们在计算一下增益Gain,可以有得到 Gain=0 G a i n = 0 。
计算出来发现Gain=0,不用惊讶,因为左子结点为空,也就是这次分裂把全部样本都归到右子结点,这个和没分裂有啥区别。所以,分裂的时候每个结点至少有一个样本。

下面,我再计算当以2划分时的增益Gain。
以2为划分时(x1<2):
左子结点包含的样本 IL I L =[1,4]
右子节点包含的样本 IR I R =[2,3,5,6,7,8,9,10,11,12,13,14,15]
左子结点的一阶导数:
GL=∑(i∈IL)gi=(0.5−0.5)=0 G L = ∑ ( i ∈ I L ) g i = ( 0.5 − 0.5 ) = 0
右子结点的二阶导数和:
HL=∑(i∈IL)hi=(0.25+0.25)=0.5 H L = ∑ ( i ∈ I L ) h i = ( 0.25 + 0.25 ) = 0.5
右子结点的一阶导数和:
GR=∑(i∈IR)gi=−1.5 G R = ∑ ( i ∈ I R ) g i = − 1.5
右子结点的二阶导数和:
HR=∑(i∈IR)hi=3.25 H R = ∑ ( i ∈ I R ) h i = 3.25
最后计算一下增益Gain:
Gain=[G2LH2L+λ+G2LH2L+λ−(GL+GR)2(HL+HR)2+λ]=0.0557275541796 G a i n = [ G L 2 H L 2 + λ + G L 2 H L 2 + λ − ( G L + G R ) 2 ( H L + H R ) 2 + λ ] = 0.0557275541796

以3为划分时(x1<3):
左子节点包含的样本ID=[1,2,4,5]
右子节点包含的样本ID=[3,6,7,8,9,10,11,12,13,14,15]
左子结点的一阶导数:
GL=∑(i∈IL)gi=0 G L = ∑ ( i ∈ I L ) g i = 0
右子结点的二阶导数和:
HL=∑(i∈IL)hi=1 H L = ∑ ( i ∈ I L ) h i = 1
右子结点的一阶导数和:
GR=∑(i∈IR)gi=−1.5 G R = ∑ ( i ∈ I R ) g i = − 1.5
右子结点的二阶导数和:
HR=∑(i∈IR)hi=2.75 H R = ∑ ( i ∈ I R ) h i = 2.75
最后计算一下增益Gain:
Gain=[G2LH2L+λ+G2LH2L+λ−(GL+GR)2(HL+HR)2+λ]=0.126315789474 G a i n = [ G L 2 H L 2 + λ + G L 2 H L 2 + λ − ( G L + G R ) 2 ( H L + H R ) 2 + λ ] = 0.126315789474

其他的值[6,7,8,9,10]类似,计算归总到下面表,供大家参考(如果错漏请和我说一下)
xgboost原理分析以及实践_xgboost

从上表我们可以到,如果特征x1以x1<10分裂时可以得到最大的增益0.615205。

按照算法的流程,这个时候需要遍历下一个特征x2,对于特征x2也是重复上面这些步骤,可以得到类似的表如下,供大家参考。

xgboost原理分析以及实践_xgboost
可以看到,以x2特征来分裂时,最大的增益是0.2186<0.615205。所以在根节点处,我们以x1<10来进行分裂。

由于我设置的最大深度是3,此时只有1层,所以还需要继续往下分裂。
左子节点的样本集合 IL=[1,2,3,4,5,6,7,8,9,10,11,12,14,15] I L = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 14 , 15 ]
右子节点的样本集合 IR=[13] I R = [ 13 ]

右子节点此时只剩一个样本,不需要分裂了,也就是已经是叶子结点。可以计算其对应的叶子结点值了,按照公式(8):
w∗=−GjHj+λ(8) (8) w ∗ = − G j H j + λ

w1=−GRHR+λ=−g13h13+1=−0.51+0.25=−0.4 w 1 = − G R H R + λ = − g 13 h 13 + 1 = − 0.5 1 + 0.25 = − 0.4

那么下面就是对左子结点 IL I L 进行分裂。分裂的时候把此时的结点看成根节点,其实就是循环上面的过程,同样也是需要遍历所有特征(x1,x2)的所有取值作为分裂点,选取增益最大的点。

这里为了说的比较清晰,也重复一下上面的过程:
此时样本有 I=[1,2,3,4,5,6,7,8,9,10,11,12,14,15] I = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 14 , 15 ]

先考虑特征x1,此时x1的取值有[1, 2, 3, 6, 7, 8, 9]

以1为分裂Gain=0。

以2为划分时(x1<2):
左子结点包含的样本 IL I L =[1,4]
右子结点包含的样本 IR I R =[2,3,5,6,7,8,9,10,11,12,14,15]
左子结点的一阶导数:
GL=∑(i∈IL)gi=(0.5−0.5)=0 G L = ∑ ( i ∈ I L ) g i = ( 0.5 − 0.5 ) = 0
右子结点的二阶导数和:
HL=∑(i∈IL)hi=(0.25+0.25)=0.5 H L = ∑ ( i ∈ I L ) h i = ( 0.25 + 0.25 ) = 0.5
右子结点的一阶导数和:
GR=∑(i∈IR)gi=−2 G R = ∑ ( i ∈ I R ) g i = − 2
右子结点的二阶导数和:
HR=∑(i∈IR)hi=3 H R = ∑ ( i ∈ I R ) h i = 3
最后计算一下增益Gain:
Gain=[G2LH2L+λ+G2LH2L+λ−(GL+GR)2(HL+HR)2+λ]=0.111111111111 G a i n = [ G L 2 H L 2 + λ + G L 2 H L 2 + λ − ( G L + G R ) 2 ( H L + H R ) 2 + λ ] = 0.111111111111

其他的同理,最后所有值都遍历完后可以得到下表:
xgboost原理分析以及实践_xgboost

可以看到,x1选择x<3时能够获得最大的增益0.2539。

同理,我们对x2再次遍历可以得到下表:
xgboost原理分析以及实践_xgboost

可以看到x2在x2<2时分裂可以获得最大的增益0.4444。
比较知道,应该选择x2<2作为分裂点。
分裂后左右叶子结点的集合如下:
左子节点的样本集合 IL=[1,3,5,6,8,10,11,15] I L = [ 1 , 3 , 5 , 6 , 8 , 10 , 11 , 15 ]
右子节点的样本集合 IR=[2,4,7,9,12,14] I R = [ 2 , 4 , 7 , 9 , 12 , 14 ]
然后继续对 IL I L 和 IR I R 进行分裂。这里就不在啰嗦了。这里直接给出第一个树的结构。
xgboost原理分析以及实践_xgboost

注意:

这里有的人可能对叶子结点取值感到困惑。为何我算出来的是-0.4,可图上却是-0.04。
这里以最左边的一个叶子结点为例,展示一下计算的过程。
落在最左边这个叶子结点上的样本有 I=[1] I = [ 1 ]
所以由公式: w∗=−GjHj+λ(8) (8) w ∗ = − G j H j + λ

w2=−GRHR+λ=−g1h1+1=−0.51+0.25=−0.4 w 2 = − G R H R + λ = − g 1 h 1 + 1 = − 0.5 1 + 0.25 = − 0.4

落在从左边数起第二个叶子结点上的样本有 I=[3,5,6,8,10,11,15] I = [ 3 , 5 , 6 , 8 , 10 , 11 , 15 ]
w3=−GRHR+λ=−g3+g5+g6+g8+g10+g11+g15h3+h5+h6+h8+h10+h11+h15+1=−−2.51+1.75=0.909 w 3 = − G R H R + λ = − g 3 + g 5 + g 6 + g 8 + g 10 + g 11 + g 15 h 3 + h 5 + h 6 + h 8 + h 10 + h 11 + h 15 + 1 = − − 2.5 1 + 1.75 = 0.909

到这里完全没有问题,但是为什么和图上的不一样呢。这里其实和我们在GBDT中的处理一样,我们会以一个学习率来乘这个值,当完全取-0.4时说明学习率取1,这个时候很容易过拟合。所以每次得到叶子结点的值后需要乘上学习率eta,在前面我们已经设置了学习率是0.1。这里也是GBDT和xgboost一个共同点,大家都是通过学习率来进行Shrinkage,以减少过拟合的风险。

至此,我们学习完了第一颗树。 建立第2颗树(k=2)

这里,我们开始拟合我们第二颗树。其实过程和第一颗树完全一样。只不过对于 yi,pred y i , p r e d 需要进行更新,也就是拟合第二颗树是在第一颗树预测的结果基础上。这和GBDT一样,因为大家都是Boosting思想的算法。

在第一颗树里面由于前面没有树,所以初始 yi,pred=0.5 y i , p r e d = 0.5 (玩家自己设置的)

假设此时,模型只有这一颗树(K=1),那么模型对样例 xi x i 进行预测时,预测的结果表达是什么呢。
由加法模型
yKi=∑Kk=0fk(xi)(13) (13) y i K = ∑ k = 0 K f k ( x i )
y1i=f0(xi)+f1(xi)(14) (14) y i 1 = f 0 ( x i ) + f 1 ( x i )

f1(xi) f 1 ( x i ) 的值是样例 xi x i 落在第一棵树上的叶子结点值。那 f0(xi) f 0 ( x i ) 是什么呢。这里就涉及前面提到一个问题base_score是一个经过sigmod映射后的值(因为选择使用Logloss做损失函数,概率 p=11+e−x p = 1 1 + e − x )

所以需要将0.5逆运算 x=lny1−y x = l n y 1 − y 后可以得到 f0(xi)=0 f 0 ( x i ) = 0 。

所以第一颗树预测的结果就是 y1i=f0(xi)+f1(xi)=0+wq(xi) y i 1 = f 0 ( x i ) + f 1 ( x i ) = 0 + w q ( x i )
(其实当训练次数K足够多的时候,初始化这个值几乎不起作用的,这个在官网文档上有说明)

所以,我们可以得到第一棵树预测的结果为下表(预测后将其映射成概率) ID yi,pred y i , p r e d 1 0.490001 2 0.494445 3 0.522712 4 0.494445 5 0.522712 6 0.522712 7 0.494445 8 0.522712 9 0.494445 10 0.522712 11 0.522712 12 0.509999 13 0.490001 14 0.494445 15 0.522712

比如对于ID=1的样本,其落在-0.04这个节点。那么经过sigmod映射后的值为
p1,pred=11+e(0+0.04)=0.490001 p 1 , p r e d = 1 1 + e ( 0 + 0.04 ) = 0.490001

有了这个之后,我们就可以计算所有样本新的一阶导数和二阶导数的值了。具体如下表: ID gi g i hi h i 1 0.490001320839 0.249900026415 2 0.494444668293 0.24996913829 3 -0.477288365364 0.249484181652 4 -0.505555331707 0.24996913829 5 -0.477288365364 0.249484181652 6 -0.477288365364 0.249484181652 7 -0.505555331707 0.24996913829 8 0.522711634636 0.249484181652 9 0.494444668293 0.24996913829 10 -0.477288365364 0.249484181652 11 -0.477288365364 0.249484181652 12 -0.490001320839 0.249900026415 13 0.490001320839 0.249900026415 14 0.494444668293 0.24996913829 15 -0.477288365364 0.249484181652

之后,我们和第一颗树建立的时候一样以公式(10)去建树。
拟合完后第二颗树如下图:
xgboost原理分析以及实践_xgboost

后面的所有过程都是重复这个过程,这里就不再啰嗦了。

至此,整个xgboost的训练过程已经完了,但是其实里面还有一些细节的东西,下面已单独一个部分来说明这个部分。 训练过程的细节-参数min_child_weight

在选择分裂的时候,我们是选取分裂点为一个最大的增益Gain。但是其实在xgboost里面有一个参数叫min_child_weight。

我先来看看官网对这个参数的解释:
xgboost原理分析以及实践_xgboost

可能看完大概知道这个权重指的应该是二阶导数,但是具体是怎么一回事呢。
其实是这样的:
在前面建立第一颗的根结点的时候,我们得到特征x1每个分裂点的这个表:
xgboost原理分析以及实践_xgboost

我们当时选取了x1<10作为分裂特征,但是。这个是有一个前提的,那就是参数min_child_weight <min(HL,HR) < m i n ( H L , H R )
如果我们设置min_child_weight=0.26的时候,分裂点就不是选择10,而是放弃这个最大增益,考虑次最大增益。如果次最大增益也不满足min_child_weight <min(HL,HR) < m i n ( H L , H R ) ,则继续往下找,如果没有找到一个满足的,则不进行分裂。(在上面中min_child_weight取的0,所以只要是最大的增益就选为分裂点) 训练过程细节-参数 γ γ

在前面例子里,我们把 γ γ 设成了0,如果我们设置成其他值比如1的话,在考虑最大增益的同时,也要考虑这个最大的增益是否比 γ γ 大,如果小于 γ γ 则不进行分裂(预剪枝)。 训练过程细节-缺失值的处理

我前面放了许多树的结构图,上面有一个方向是missing,其实这个是xgboost自动学习的一个对缺失值处理的方向。可以看到,所有的missing方向都是指向右子结点,这是因为我们数据集中没有缺失值。

xgboost对缺失值的处理思想很简单,具体看下面的算法流程:
xgboost原理分析以及实践_xgboost

首先需要注意到两个集合一个是 I I ,其包含所有的样本(包括含空缺值的样本)。
Ik I k 是不包含空缺值样本的集合。
在计算总的 G和H G 和 H 时用的是 I I 。。也就说空缺值的一阶导数和二阶导数已经包含进去了。

可以看到内层循环里面有两个for,第一个for是从把特征取值从小到大排序,然后从小到大进行扫描,这个时候在计算 GR的时候是用总的G减去GL G R 的 时 候 是 用 总 的 G 减 去 G L , HR H R 也是同样用总的H减去 HL H L ,这意味着把空缺样本归到了右子结点。

第二个for相反过来,把空缺样本归到了左子结点。
只要比较这两次最大增益出现在第一个for中还是第二个for中就可以知道对于空缺值的分裂方向,这就是xgboost如何学习空缺值的思想。 训练过程细节-近似贪婪算法

在前面,我们可以看到选取特征的时候是遍历了每个特征所有可能的取值,但是实际上,我们可以使用近似的方法来进行特征分裂的选择,具体算法流程:
xgboost原理分析以及实践_xgboost xgboost如何用于特征选择。

相信很多做过数据挖掘比赛的人都利用xgboost来做特征选择。
一般我们调用xgb库的get_fscore()。但其实xgboost里面有三个指标用于对特征进行评价,而get_fscore()只是其中一个指标weight。

这个指标大部分玩家都很熟悉,其代表着某个特征被选作分裂的次数。
比如在前面举的例子里,我们得到这两颗树:
xgboost原理分析以及实践_xgboost
xgboost原理分析以及实践_xgboost

可以看到特征x1被选作分裂点的次数为6,x2被选做分裂点的次数为2。
get_fscore()就是返回这个指标。

 

 

而xgboost还提供了另外两个指标,一个叫gain,一个叫cover。可以利用get_score()来选择。

那么gain是指什么呢。其代表着某个特征的平均增益。
比如,特征x1被选了6次作为分裂的特征,每次的增益假如为Gain1,Gain2,…Gain6,那么其平均增益为 (Gain1+Gain2+...Gain3)/6 ( G a i n 1 + G a i n 2 + . . . G a i n 3 ) / 6

 

 

最后一个cover指的是什么呢。其代表着每个特征在分裂时结点处的平均二阶导数。
这个为了加深理解,我举个例子,这个例子用的还是UCI数据集,不过此时只有max_depth=2,num_boost_round=1(不想再画表了。。太累了。。)

 

 

 

xgboost原理分析以及实践_xgboost
建树完之后,其结构如上。
在第一个结点分裂时,x1的特征增益表如下:
xgboost原理分析以及实践_xgboost

第二个结点分裂时,x2的特征增益表如下:
xgboost原理分析以及实践_xgboost

那么特征x1的cover是如下计算的:
在x1在第一个结点处被选择分裂特征,此时结点处的总二阶导数 G=3.75 G = 3.75
故x1的cover值为3.75。
这里x1只是被分裂了一次,如果后续还有就是求平均。

同样地,x2的cover值为3.5。
举这个例子就是先给大家说一下何为平均二阶导数。

其实为什么需要选择二阶导数。这个二阶导数背后有什么意义吗。我们先看看官网对cover的定义:
‘cover’ - the average coverage of the feature when it is used in trees。大概的意义就是特征覆盖了多少个样本。
这里,我们不妨假设损失函数是mse,也就是 0.5∗(yi−ypred)2 0.5 ∗ ( y i − y p r e d ) 2 ,我们求其二阶导数,很容易得到为常数1。也就是每个样本对应的二阶导数都是1,那么这个cover指标不就是意味着,在某个特征在某个结点进行分裂时所覆盖的样本个数吗。 xgboost与传统GBDT的区别与联系。

至此,我们来简单的总结一下xgboost和GBDT的区别以及联系。

区别:
1.xgboost和GBDT的一个区别在于目标函数上。
在xgboost中,损失函数+正则项。
GBDT中,只有损失函数。
2.xgboost中利用二阶导数的信息,而GBDT只利用了一阶导数。
3.xgboost在建树的时候利用的准则来源于目标函数推导,而GBDT建树利用的是启发式准则。(这一点,我个人认为是xgboost牛B的所在,也是为啥要费劲二阶泰勒展开)
4.xgboost中可以自动处理空缺值,自动学习空缺值的分裂方向,GBDT(sklearn版本)不允许包含空缺值。
5.其他若干工程实现上的不同(这个由于本文没有涉及就不说了)

联系:
1.xgboost和GBDT的学习过程都是一样的,都是基于Boosting的思想,先学习前n-1个学习器,然后基于前n-1个学习器学习第n个学习器。(Boosting)
2.建树过程都利用了损失函数的导数信息(Gradient),只是大家利用的方式不一样而已。
3.都使用了学习率来进行Shrinkage,从前面我们能看到不管是GBDT还是xgboost,我们都会利用学习率对拟合结果做缩减以减少过拟合的风险。
4.其他本文没涉及到的共同点。。

至此,关于xgboost的一些简单知识就算说完了,希望对大家的学习有帮助。

以上是xgboost原理分析以及实践_xgboost的全部内容,在云栖社区的博客、问答、云栖号、人物、课程等栏目也有xgboost原理分析以及实践_xgboost的相关内容,欢迎继续使用右上角搜索按钮进行搜索机器学习 , XGBoost , xgboost原理分 xgboost实践 ,以便于您获取更多的相关知识。

============================================================================================================

xgboost 中的gain freq, cover

2018年04月11日 18:03:44 moledyzhang 阅读数:864

assuming that you're using xgboost to fit boosted treesfor binary classification. The importance matrix is actually a data.tableobject with the first column listing the names of all the features actuallyused in the boosted trees.

The meaning of the importance data table is as follows:

  1.   The Gain      implies the relative contribution of the corresponding feature to the model calculated by taking each feature's contribution for each tree in the model. A higher value of this metric when compared to another feature implies it is more important for generating a prediction.

  2. The Cover metric means the relative number of observations related to this feature. For example, if you have 100 observations, 4 features and 3 trees, and suppose feature1 is used to decide the leaf node for 10, 5, and 2 observations in tree1, tree2 and tree3 respectively; then the metric will count cover for this feature as 10+5+2 = 17 observations. This will be calculated for all the 4 features and the cover will be 17 expressed as a percentage for all features' cover metrics.
  3. The Frequence (frequency) is the percentage representing the relative number of times a particular feature occurs in the trees of the model. In the above example, if feature1 occurred in 2 splits, 1 split and 3 splits in each of tree1, tree2 and tree3; then the weightage for feature1 will be 2+1+3 = 6. The frequency for feature1 is calculated as its percentage weight over weights of all features.

The Gain is the most relevant attribute to interpret therelative importance of each feature.

 

Gain is the improvement in accuracy brought by a feature to the branchesit is on. The idea is that before adding a new split on a feature X to thebranch there was some wrongly classified elements, after adding the split onthis feature, there are two new branches, and each of these branch is moreaccurate (one branch saying if your observation is on this branch then itshould be classified as 1, and the other branch saying the exact opposite).

Cover measures the relative quantity of observations concerned by afeature.

Frequency is a simpler way to measure the Gain. It just counts the number of times a feature isused in all generated trees. You should not use it (unless you know why youwant to use it).

 

假设您正在使用xgboost来适应用于二进制分类的增强型树。重要性矩阵实际上是一个data.table对象,第一列列出了增强树中实际使用的所有功能的名称。

重要性数据表的含义如下:

1.增益意味着相应的特征对通过对模型中的每个树采取每个特征的贡献而计算出的模型的相对贡献。与其他特征相比,此度量值的较高值意味着它对于生成预测更为重要。

2.覆盖度量指的是与此功能相关的观测的相对数量。例如,如果您有100个观察值,4个特征和3棵树,并且假设特征1分别用于决定树1,树2和树3中10个,5个和2个观察值的叶节点;那么该度量将计算此功能的覆盖范围为10 + 5 + 2 = 17个观测值。这将针对所有4项功能进行计算,并将以17个百分比表示所有功能的覆盖指标。

3.频率(频率)是表示特定特征在模型树中发生的相对次数的百分比。在上面的例子中,如果feature1发生在2个分裂中,1个分裂和3个分裂在每个树1,树2和树3中;那么特征1的权重将是2 + 1 + 3 = 6。特征1的频率被计算为其在所有特征的权重上的百分比权重。

增益是解释每个特征的相对重要性的最相关属性。

 

增益是功能对分支所带来的准确度的提高。这个想法是,在向特征X添加新的分支之前,在分支上存在一些错误分类的元素,在对该特征添加分割之后,有两个新分支,并且这些分支中的每一个更精确(一个分支说if你的观察是在这个分支上,那么它应该被归类为1,而另一个分支则说相反)。

封面测量一个要素所涉及的观测的相对数量。

频率是测量增益的一种更简单的方法。它只计算在所有生成的树中使用要素的次数。你不应该使用它(除非你知道你为什么要使用它)。

=========================================================================================================================================================================================================================================================================

Relative variable importance for Boosting

Ask Question

28

I'm looking for an explanation of how relative variable importance is computed in Gradient Boosted Trees that is not overly general/simplistic(过于笼统) like:

The measures(weights and measures度量;尺寸) are based on the number of times a variable is selected for splitting, weighted by the squared improvement to the model as a result of each split, and averaged (平均的)over all trees. [Elith et al. 2008, A working guide to boosted regression trees]

And that is less abstract than:

I2j^(T)=∑t=1J−1i2t^1(vt=j)

 

Where the summation is over the nonterminal nodes(非终端节点) t

of the J-terminal node tree T, vt is the splitting variable associated with node t, and i2t^ is the corresponding empirical improvement in squared error as a result of the split, defined as i2(Rl,Rr)=wlwrwl+wr(yl¯−yr¯)2, where yl¯,yr¯ are the left and right daughter response means respectively, and wl,wr

are the corresponding sums of the weights. [Friedman 2001, Greedy function approximation: a gradient boosting machine]

Finally, I did not find the Elements of Statistical Learning (Hastie et al. 2008) to be a very helpful read here, as the relevant(有关的) section (10.13.1 page 367) tastes very similar to the second reference above (which might be explained by the fact that Friedman is a co-author of the book).

PS: I know relative variable importance measures are given by the summary.gbm in the gbm R package. I tried to explore the source code, but I can't seem to find where the actual computation takes place.

Brownie points: I'm wondering how to get these plots in R.

machine-learning data-mining predictive-models cart boosting

shareciteimprove this question

edited Mar 20 '18 at 11:50

asked Jul 19 '15 at 13:29

Antoine

3,30742042

  •  

add a comment

1 Answer

active oldest votes

44

+50

I'll use the sklearn code, as it is generally much cleaner than the R code.

Here's the implementation of the feature_importances property of the GradientBoostingClassifier (I removed some lines of code that get in the way of the conceptual stuff)

def feature_importances_(self):
    total_sum = np.zeros((self.n_features, ), dtype=np.float64)
    for stage in self.estimators_:
        stage_sum = sum(tree.feature_importances_
                        for tree in stage) / len(stage)
        total_sum += stage_sum

    importances = total_sum / len(self.estimators_)
    return importances

This is pretty easy to understand. self.estimators_ is an array containing the individual trees in the booster, so the for loop is iterating over the individual trees. There's one hickup with the

stage_sum = sum(tree.feature_importances_
                for tree in stage) / len(stage)

this is taking care of the non-binary response case. Here we fit multiple trees in each stage in a one-vs-all way. Its simplest conceptually to focus on the binary case, where the sum has one summand, and this is just tree.feature_importances_. So in the binary case, we can rewrite this all as

def feature_importances_(self):
    total_sum = np.zeros((self.n_features, ), dtype=np.float64)
    for tree in self.estimators_:
        total_sum += tree.feature_importances_ 
    importances = total_sum / len(self.estimators_)
    return importances

So, in words, sum up the feature importances of the individual trees, then divide by the total number of trees. It remains to see how to calculate the feature importances for a single tree.

The importance calculation of a tree is implemented at the cython level, but it's still followable. Here's a cleaned up version of the code

cpdef compute_feature_importances(self, normalize=True):
    """Computes the importance of each feature (aka variable)."""

    while node != end_node:
        if node.left_child != _TREE_LEAF:
            # ... and node.right_child != _TREE_LEAF:
            left = &nodes[node.left_child]
            right = &nodes[node.right_child]

            importance_data[node.feature] += (
                node.weighted_n_node_samples * node.impurity -
                left.weighted_n_node_samples * left.impurity -
                right.weighted_n_node_samples * right.impurity)
        node += 1

    importances /= nodes[0].weighted_n_node_samples

    return importances

This is pretty simple. Iterate through the nodes of the tree. As long as you are not at a leaf node, calculate the weighted reduction in node purity from the split at this node, and attribute it to the feature that was split on

importance_data[node.feature] += (
    node.weighted_n_node_samples * node.impurity -
    left.weighted_n_node_samples * left.impurity -
    right.weighted_n_node_samples * right.impurity)

Then, when done, divide it all by the total weight of the data (in most cases, the number of observations)

importances /= nodes[0].weighted_n_node_samples

It's worth recalling that the impurity is a common name for the metric to use when determining what split to make when growing a tree. In that light, we are simply summing up how much splitting on each feature allowed us to reduce the impurity across all the splits in the tree.

In the context of gradient boosting, these trees are always regression trees (minimize squared error greedily) fit to the gradient of the loss function.

shareciteimprove this answer

edited Nov 30 '17 at 22:36

answered Jul 29 '15 at 3:40

Matthew Drury

25.3k25897

  • Thanks a lot for this very detailed answer. Let me some time to carefully go through it before I accept it. – Antoine Jul 30 '15 at 10:38

  • 4

    While it seems that various impurity(污点) criteria (标准)can be used, the Gini index was not the criterion used by Friedman. As mentioned in my question and line 878 of your third link, Friedman used the mean squared error impurity criterion with improvement score. If you could update this section of your answer, that would be great. And yes, your're right, it seems that the weights are indeed the number of observations. – Antoine Jul 31 '15 at 6:40

  •  
  • 3

    or maybe it would make your answer even better to keep both the parts about the Gini index and Friedman's original criterion, stressing that the first is used for classification and the second for regression? – Antoine Jul 31 '15 at 6:53

  • Antoine, thanks for this update. It is really helpful to know that the mean squared error is the improvement criteria used for the regression trees. It was not obvious how that would be used for classification. However, even in gradient(梯度) boosting for classification, I think that regression trees are still being used, as opposed to classification trees. At least in python, the regression analysis is being done on the current error at each boosting stage. – Fairly Nerdy Dec 3 '16 at 19:49

  •  
  • You guys are correct about regression trees. – Matthew Drury Nov 30 '17 at 19:28

show 4 more comments

===========================================================================================================================================================================================================================================================================

【原创】xgboost 特征评分的计算原理

xgboost是基于GBDT原理进行改进的算法,效率高,并且可以进行并行化运算;

而且可以在训练的过程中给出各个特征的评分,从而表明每个特征对模型训练的重要性,

调用的源码就不准备详述,本文主要侧重的是计算的原理,函数get_fscore源码如下,

源码来自安装包:xgboost/python-package/xgboost/core.py

通过下面的源码可以看出,特征评分可以看成是被用来分离决策树的次数,而这个与

《统计学习基础-数据挖掘、推理与推测》中10.13.1 计算公式有写差异,此处需要注意。

注:考虑的角度不同,计算方法略有差异。

 

def get_fscore(self, fmap=''):

       """Get feature importance of each feature.

 

       Parameters

       ----------

       fmap: str (optional)

          The name of feature map file

       """

 

       return self.get_score(fmap, importance_type='weight')

 

   def get_score(self, fmap='', importance_type='weight'):

       """Get feature importance of each feature.

       Importance type can be defined as:

           'weight' - the number of times a feature is used to split the data across all trees.

           'gain' - the average gain of the feature when it is used in trees

           'cover' - the average coverage of the feature when it is used in trees

 

       Parameters

       ----------

       fmap: str (optional)

          The name of feature map file

       """

 

       if importance_type not in ['weight', 'gain', 'cover']:

           msg = "importance_type mismatch, got '{}', expected 'weight', 'gain', or 'cover'"

           raise ValueError(msg.format(importance_type))

 

(使用treedump事件查看索引结构)

 

       # if it's weight, then omap stores the number of missing values

       if importance_type == 'weight':

           # do a simpler tree dump to save time

           trees = self.get_dump(fmap, with_stats=False)

 

           fmap = {}

           for tree in trees:

               for line in tree.split('\n'):

                   # look for the opening square bracket

                   arr = line.split('[')

                   # if no opening bracket (leaf node), ignore this line

                   if len(arr) == 1:

                       continue

 

                   # extract feature name from string between []

                   fid = arr[1].split(']')[0].split('<')[0]

 

                   if fid not in fmap:

                       # if the feature hasn't been seen yet

                       fmap[fid] = 1

                   else:

                       fmap[fid] += 1

 

           return fmap

 

       else:

           trees = self.get_dump(fmap, with_stats=True)

 

           importance_type += '='

           fmap = {}

           gmap = {}

           for tree in trees:

               for line in tree.split('\n'):

                   # look for the opening square bracket

                   arr = line.split('[')

                   # if no opening bracket (leaf node), ignore this line

                   if len(arr) == 1:

                       continue

 

                   # look for the closing bracket, extract only info within that bracket

                   fid = arr[1].split(']')

 

                   # extract gain or cover from string after closing bracket

                   g = float(fid[1].split(importance_type)[1].split(',')[0])

 

                   # extract feature name from string before closing bracket

                   fid = fid[0].split('<')[0]

 

                   if fid not in fmap:

                       # if the feature hasn't been seen yet

                       fmap[fid] = 1

                       gmap[fid] = g

                   else:

                       fmap[fid] += 1

                       gmap[fid] += g

 

           # calculate average value (gain/cover) for each feature

           for fid in gmap:

               gmap[fid] = gmap[fid] / fmap[fid]

 

           return gmap

 GBDT特征评分的计算说明原理:

链接:1、http://machinelearningmastery.com/feature-importance-and-feature-selection-with-xgboost-in-python/

详细的代码说明过程:可以从上面的链接进入下面的链接:

http://stats.stackexchange.com/questions/162162/relative-variable-importance-for-boosting

==================================================================================================================================================================================

fmap 
- feature map, used for dump model (模型转储)

==================================================================================================================================================================================

 

深度学习中 Embedding层两大作用的个人理解

2018年10月10日 17:45:33 罗大黑 阅读数:726

前一个月接触到一个概念,Embedding层。今天跟大家分享一下个人心得。

 

首先,我们有一个one-hot编码的概念。

 

假设,我们中文,一共只有10个字。。。只是假设啊,那么我们用0-9就可以表示完

比如,这十个字就是“我从哪里来,要到何处去”

其分别对应“0-9”,如下:

我  从  哪  里  来  要  到  何  处  去

0    1    2    3   4    5   6    7    8   9

那么,其实我们只用一个列表就能表示所有的对话

如:我  从  哪  里  来  要  到  何  处  去  ——>>>[0 1 2 3 4 5 6 7 8 9]

或:我  从  何  处  来  要  到  哪  里  去  ——>>>[0 1 7 8 4 5 6 2 3 9]

 

但是,我们看看one-hot编码方式(详见:https://blog.csdn.net/tengyuan93/article/details/78930285

他把上面的编码方式弄成这样

 
  1. # 我从哪里来,要到何处去

  2. [

  3. [1 0 0 0 0 0 0 0 0 0]

  4. [0 1 0 0 0 0 0 0 0 0]

  5. [0 0 1 0 0 0 0 0 0 0]

  6. [0 0 0 1 0 0 0 0 0 0]

  7. [0 0 0 0 1 0 0 0 0 0]

  8. [0 0 0 0 0 1 0 0 0 0]

  9. [0 0 0 0 0 0 1 0 0 0]

  10. [0 0 0 0 0 0 0 1 0 0]

  11. [0 0 0 0 0 0 0 0 1 0]

  12. [0 0 0 0 0 0 0 0 0 1]

  13. ]

  14.  
  15. # 我从何处来,要到哪里去

  16. [

  17. [1 0 0 0 0 0 0 0 0 0]

  18. [0 1 0 0 0 0 0 0 0 0]

  19. [0 0 0 0 0 0 0 1 0 0]

  20. [0 0 0 0 0 0 0 0 1 0]

  21. [0 0 0 0 1 0 0 0 0 0]

  22. [0 0 0 0 0 1 0 0 0 0]

  23. [0 0 0 0 0 0 1 0 0 0]

  24. [0 0 1 0 0 0 0 0 0 0]

  25. [0 0 0 1 0 0 0 0 0 0]

  26. [0 0 0 0 0 0 0 0 0 1]

  27. ]

即:把每一个字都对应成一个十个(样本总数/字总数)元素的数组/列表,其中每一个字都用唯一对应的数组/列表对应,数组/列表的唯一性用1表示。如上,“我”表示成[1。。。。],“去”表示成[。。。。1],这样就把每一系列的文本整合成一个稀疏矩阵。

 

那问题来了,稀疏矩阵(二维)和列表(一维)相比,有什么优势。

很明显,计算简单嘛,稀疏矩阵做矩阵计算的时候,只需要把1对应位置的数相乘求和就行,也许你心算都能算出来;而一维列表,你能很快算出来?何况这个列表还是一行,如果是100行、1000行和或1000列呢?

所以,one-hot编码的优势就体现出来了,计算方便快捷、表达能力强。

然而,缺点也随着来了。

比如:中文大大小小简体繁体常用不常用有十几万,然后一篇文章100W字,你要表示成100W X 10W的矩阵???

这是它最明显的缺点。过于稀疏时,过度占用资源。

比如:其实我们这篇文章,虽然100W字,但是其实我们整合起来,有99W字是重复的,只有1W字是完全不重复的。那我们用100W X 10W的岂不是白白浪费了99W X 10W的矩阵存储空间。

那怎么办???

这时,Embedding层横空出世。

插张图片休息下。

OK, keep going!

接下来给大家看一张图

 

链接:https://spaces.ac.cn/archives/4122

假设:我们有一个2 x 6的矩阵,然后乘上一个6 x 3的矩阵后,变成了一个2 x 3的矩阵。

先不管它什么意思,这个过程,我们把一个12个元素的矩阵变成6个元素的矩阵,直观上,大小是不是缩小了一半?

 

也许你已经想到了!!!对!!!不管你想的对不对,但是embedding层,在某种程度上,就是用来降维的,降维的原理就是矩阵乘法。在卷积网络中,可以理解为特殊全连接层操作,跟1x1卷积核异曲同工!!!484很神奇!!!

 

 

 

复习一下,矩阵乘法需要满足一个条件。

A X B时,B的行数必须等于A的列数

得出的结果为A的行数 X B的列数的一个矩阵

也就是说,假如我们有一个100W X10W的矩阵,用它乘上一个10W X 20的矩阵,我们可以把它降到100W X 20,瞬间量级降了。。。10W/20=5000倍!!!

这就是嵌入层的一个作用——降维。

然后中间那个10W X 20的矩阵,可以理解为查询表,也可以理解为映射表,也可以理解为过度表,whatever。

--------

--------

--------

接着,既然可以降维,当然也可以升维。为什么要升维?
这也是很神奇的。咱们再举一个例子:

这张图,我要你在10米开外找出五处不同!。。。What?烦请出题者走近两步,我先把我的刀拿出来,您再说一遍题目我没听清。

当然,目测这是不可能完成的。但是我让你在一米外,也许你一瞬间就发现衣服上有个心是不同的,然后再走近半米,你又发现左上角和右上角也是不同的。再走近20厘米,又发现耳朵也不同,最后,在距离屏幕10厘米的地方,终于发现第五个不同的地方在耳朵下面一点的云。

但是,其实无限靠近并不代表认知度就高了,比如,你只能距离屏幕1厘米远的地方找,找出五处不同。。。出题人你是不是脑袋被门挤了。。。

由此可见,距离的远近会影响我们的观察效果。同理也是一样的,低维的数据可能包含的特征是非常笼统的,我们需要不停地拉近拉远来改变我们的感受野,让我们对这幅图有不同的观察点,找出我们要的茬。

embedding的又一个作用体现了。对低维的数据进行升维时,可能把一些其他特征给放大了,或者把笼统的特征给分开了。同时,这个embedding是一直在学习在优化的,就使得整个拉近拉远的过程慢慢形成一个良好的观察点。比如:我来回靠近和远离屏幕,发现45厘米是最佳观测点,这个距离能10秒就把5个不同点找出来了。

 

回想一下为什么CNN层数越深准确率越高,卷积层卷了又卷,池化层池了又升,升了又降,全连接层连了又连。因为我们也不知道它什么时候突然就学到了某个有用特征。但是不管怎样,学习都是好事,所以让机器多卷一卷,多连一连,反正错了多少我会用交叉熵告诉你,怎么做才是对的我会用梯度下降算法告诉你,只要给你时间,你迟早会学懂。因此,理论上,只要层数深,只要参数足够,NN能拟合任何特征。

 

总之,这个东西也许一言难尽。但是目前各位只需要知道它有这些功能的就行了。

想具体理解其作用,建议大家去探究探究卷积神经网络的各种中间过程,以及反向传播理论。到时候大家再来深入理解嵌入层时,那就一通百通了。

==================================================================================================================================================================================

Linux:请允许我静静地后台运行

枕边书 民工哥技术之路 今天

 前言

常在 linux 下玩耍的开发者肯定会经常遇到需要对进程调度的情况,在 windows 中点击 最小化 去干别的就 OK 了,那么在 linux 下怎么办呢。

可能有的小伙伴会说,再开一个终端窗口不就好了么。可是开很多窗口管理会很不方便,还有万一手贱点了x,或者长时间不操作,远程终端断开了连接,进程停止了,再次打开,又是一番折腾。

今天来介绍几个命令,帮大家系统地梳理一下 linux 的进程调度,并附上一些自己的使用心得和踩过的坑。

名词

在此之前,我们必须(当然也不是必须,但了解原理有利于理解和解决错误)先弄懂几个名词。

进程组

进程组是一个或多个进程的集合,进程组方便了对多个进程的控制,在进程数较多的情况下,向进程组发送信号就行了。

它的 ID 由它的组长进程的进程 ID 决定。组长进程创建了进程组,但它并不能决定进程组的存活时间,只要进程组内还有一个进程存在,进程就存在,与组长进程是否已终止无关。

会话

会话(session)是一个或多个进程组的集合,它开始于用户登陆终端,结束于用户退出登陆。其义如其名,就是指用户与系统的一次对话的全程。

会话包括控制进程(与终端建立连接的领头进程),一个前台进程组和任意后台进程组。一个会话只能有一个控制终端,通常是登录到其上的终端设备或伪终端设备,产生在控制终端上的输入和信号将发送给会话的前台进程组中的所有进程。

控制终端

每当我们使用终端工具打开一个本地或远程 shell,我们便打开了一个控制终端,通过 ps 命令可以查看到 command 为 ttyn 的就是它对应的进程了,同时它对应 linux /dev/ 目录下的一个文件。

作业

作业的概念与进程组类似,同样由一个或多个进程组成,它分为前台作业和后台作业,一个会话会有一个前台作业和多个后台作业,与进程组不同的是,作业内的某个进程产生的子进程并不属于这个作业。

类比

以上几个概念可以类比为我们一次通过 QQ 聊天的全程,控制终端就是 QQ软件,关闭了此软件代表着聊天结束。聊天时发送的每一条信息都是一个进程,作业或进程组就是我们在聊的某一件事,它由很多条相互的信息构成。而会话则是我们指我们从开始聊天到结束聊天的全过程,可能会聊很多个事。

它们之间的相关图如下所示

 

 

 

后台执行

我们每次在终端窗口执行命令的时候,进程总会一直占用着终端,走到进程结束,这段时间内,我们在终端的输入是没有用的。而且,当终端窗口关闭或网络连接失败后,再次打开终端,会发现进程已经中断了。这是因为用户注销或者网络断开时,SIGHUP信号会被发送到会话所属的子进程,而此 SIGHUP 的默认处理方式是终止收到该信号的进程。所以若程序中没有捕捉该信号,当终端关闭后,会话所属进程就会退出。

我们要实现后台执行的目的,实际上是要完成如下两个目标:

  • 使进程让出前台终端,让我们可以继续通过终端与系统进行交互。

  • 使进程不再受终端关闭的影响,即系统在终端关闭后不再向进程发送 SIGHUP 信号或即使发送了信号程序也不会退出。

以下的命令就围绕着这两个目标来实现。

&

首先是我们最经常遇到的符号 &,将它附在命令后面可以使进程在后台执行,不会占用前台界面。它实际上是在会话中开启了一个后台作业,对作业的操作我们后面再说。

但我们会发现,如果此时终端被关闭后,进程还是会退出。这是因为,& 符号只有让进程让出前台终端的功能,无法让进程不受 SIGHUP 信号的影响。

nohup

nohup 应该是另外一个我们常用的命令了,它的作用如其字面意思,使进程不受 SIGHUP 信号的影响。但我们在使用 nohup php test.php 后会发现,进程还会一直占用前台终端,但即使终端被关闭或连接断开了,程序还是会执行,另外我们会发现在当前文件夹下多了个名为 nohup.out 的文件。

这是因为 nohup 的功能仅仅是让进程不受 SIGHUP 信号的影响,并不会让出前台终端,而且它还会在命令执行目录下建立 nohup.out 用以存储进程的输出。如果进程不需要输出,且不想让 nohup 创建文件,可以将标准输出和标准错误输出重定向。

我们常将 nohup 和 & 搭配到一块使用,执行命令如下 nohup command >/dev/null 2>&1 & 这样,就可以放心的等待进程运行结果了。

setsid

setsid 是另一个让进程在后台执行的命令,它的作用是让进程打开一个新的会话并运行进程,使用方式为 setsid command。

根据上面的概念我们得知终端关闭后进程退出是因为会话首进程向进程发送了 SIGHUP 信号,setsid 就厉害了,它直接打开一个新的会话来执行命令,那么原会话的终端的状态就再也不会影响到此进程了。

我们使用 pstree 来查看使用 setsid 和 nohup … & 两种命令来运行进程时的进程树状态。

  • nohup php test.php &

 

我是用 ssh 远程登陆的机器,所以 test.php 进程是挂在 sshd 进程下的。正常情况下,一旦 sshd 进程结束,则 test.php也无法幸免。

  • setsid php test.php

 

使用了 setsid 后,test.php 进程已经与 sshd 进程同级,属于 init 进程的子进程了。

但是 setsid 并没有为进程分配一个输出终端,所以进程还是会输出到当前终端上。

setsid 的 坑

另外,setsid 有个略坑的地方: 在终端中直接使用 setsid command 运行进程时,终端前台并不会被影响,command 会在后台默默运行。而在 shell 脚本中,我们会发现运行 setsid 的进程会一直阻塞住,直到 command 进程执行结束。

这是因为,setsid 在其是进程组长时会 fork() 一个进程,但它不会 wait() 它的子进程,而是立刻退出,所以在终端内直接使用 setsid 时,setsid 作为进程组长不会占用终端界面。

而在 shell 脚本内,setsid 不是进程组长,它不会 fork() 子进程,而是由 bash 来fork()一个子进程,而 bash 会 wait() 子进程,所以表现得像 setsid 在 wait() 子进程一样。

要解决这个问题,有两个办法:

  • 使用上面介绍的 &符号,使 setsid 强行到后台执行。

  • 使用 . 或 source 命令由终端执行 setsid;

其他

除了上面介绍的命令,还有 screen 和 tmux 等会话工具,他们都有自己的一套规范,也比较复杂,掌握本文的命令已经足够你驰骋 linux 进程控制了。当然有想了解新知识的可以查询学习一下,应该会比基础命令好用。

作业命令

使用上面的后台执行命令时可能还会遇到一些小状况:

  • 被我们放在后台的进程执行时间过长,而我们又忘记使用 nohup 命令,那么终端一旦断开,进程又需要被重新执行。

  • 我们直接开启了某个进程,又想在不中断进程的情况下让它让出前台终端;

这些都要牵涉到今天的第二个模块–作业;

 

我们在终端里运行的命令都可以理解为一个作业,有的占用前台终端,有的在后台默默执行,下面的命令就是为了调度这些作业。

jobs

jobs 是作业的基础命令,用它可以查看正在运行的作业的信息,其输出如下:

jobs

1- Running php test.php &

2+ Stopped php test.php

前面[ ]内的数字是作业 ID,也是后面我们要操作作业的标识,然后是作业状态和命令。

ctrl+z

ctrl+z 严格来说并是作业命令,它只是向当前进程发送一个 SIGSTOP 信号,促使进程进入暂停(stopped)状态,此状态下,进程状态会被系统保存,此进程会被放置到作业队列中去,而让出进程终端。

使用它,我们可以暂停正在占用终端的进程而不停止它,从而让我们使用终端命令来操作此进程。

bg

bg是 backgroud 的缩写,顾名思义,bg %id 把作业放到后台进程中执行。

结合 ctrl+z 和 bg 命令,我们可以解决上面提出的第一个问题,不停止地将正在占用终端的进程放到后台执行。

fg

fg 与 bg 相对,使用它可以把作业放到前台来执行。

disown

disown 用来将作业从作业列表中移除,即使它 不属于 会话,这样终端关闭后不再向此作业发送 SIGHUP 信号,以阻止终端对进程的影响。

使用 disown 我们可以解决上面提出的第二个问题,不重新执行将一个没使用 nohup 命令的进程不受终端关闭影响。

守护进程

以上介绍的都是一些临时进程的处理,后台运行的进程的最终方法是将进程变成守护进程。

守护进程

守护进程(daemon)是生存期较长的一种进程,一般在系统启动时启动,系统关闭时停止,没有控制终端,也不会输出。如我们的服务器、fpm 等进程就是以守护进程的形式存在的。

创建过程

要创建一个守护进程,步骤为:

必选项

  • fork 子进程,退出父进程,子进程作为孤儿进程被 init 进程收养;

  • 使用 setsid, 打开新会话,进程成为会话组长,正式脱离终端控制;

  • 设置信号处理(特别是子进程退出处理);可选项:

  • 使用 chdir 改变进程工作目录,一般到根目录下,防止占用可卸载文件系统;

  • 用 umask 重设文件权限掩码,不再继承父进程的文件权限设置;

  • 关闭父进程打开的文件描述符;

代码

以下是 php 创建守护进程的伪代码:

$pid = pcntl_fork(); 
if ($pid > 0) { 
    exit; // 父进程直接退出 
} elseif ($pid < 0) { 
throw_error(); // 进程创建失败 
} 
posix_setsid(); // setsid成为会话领导进程 
chdir($dir); // 切换目录 
umask(0); // 重置文件权限mask 
close_fd(); // 关闭父进程的文件描述符 
pcntl_signal($signal, $func); // 注册信号处理函数 
while (true) { 
do_job(); // 处理进程任务 
pcntl_signal_dispatch(); // 分发信号处理 
} 

总结

linux 是开发者的基础技能,而进程的调度更是我们常用的功能,希望读完本文的同学们能有所收获。

又有大半个月没发博客了,最近鼓捣着重构代码,经常会在一个点上纠结半天,不知不觉就加了个班。而且这个是个没法精确度量工作量和目标的活儿,优化没有尽头嘛。不过由于要更多地考虑一下代码的抽象、效率和扩展,对自己也是个挑战,算是乐在其中吧~

最近可能会考虑写一个守护进程和 cron 进程调度器,嗯,希望给我算到工作量里,哈哈~想写的太多了,只怨自己还不够强大。。。

 

==================================================================================================================================================================================

==================================================================================================================================================================================

文档集------->提取词条,去除停用词,统计词频-------->文档表示向量------》构造类别特征向量

A,采用向量空间模型进行文本表示

B ,特征选取(tf-idf)

===========================================================================================================================================================================================================================================================================

义原分类:

event|事件

entity|实体

attribute|属性值

quantity|数量

qValue|数量值

SecondaryFaeture|次要特征

syntax|语法

eventRole|动态角色

EventFeatures|动态属性

义原关系

上下位关系

同义关系

反义关系

对义关系

属性-宿主关系

部件-整体关系

材料-成品关系

事件-角色关系

==================================================================================================================================================================================

文本检测部分数据库的设计与实现

多粒度检测文本相似度算法部分的设计与实现

simhash算法的设计与实现

基于空间向量的余弦相似度算法的设计与实现

改进的余弦算法的设计与实现

基于语义理解的文本相似度算法的设计与实现

==================================================================================================================================================================

1,以往的情感分类使用的特征大多为词袋特征,存在纬度高,可解释差的缺点

 

将粒计算的思想用于文本数据的三层粒度结构(词----句----篇章)

==================================================================================================================================================================

数据挖掘 - 词集模型 & 词袋模型

2017年07月08日 09:27:54 KeeJee 阅读数:2848更多

所属专栏: 机器学习与数据挖掘

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ZK_J1994/article/details/74780030

词集模型:单词构成的集合,每个单词只出现一次。

词袋模型:把每一个单词都进行统计,同时计算每个单词出现的次数。

 

在train_x中,总共有6篇文档,每一行代表一个样本即一篇文档。我们的目标是将train_x转化为可训练矩阵,即生成每个样本的词向量。可以对train_x分别建立词集模型,词袋模型来解决。

train_x = [["my", "dog", "has", "flea", "problems", "help", "please"],
               ["maybe", "not", "take", "him", "to", "dog", "park", "stupid"],
               ["my", "dalmation", "is", "so", "cute", "I", "love", "him"],
               ["stop", "posting", "stupid", "worthless", "garbage"],
               ["him", "licks", "ate", "my", "steak", "how", "to", "stop", "him"],
               ["quit", "buying", "worthless", "dog", "food", "stupid"]]

 

1. 词集模型

算法步骤:

1)整合所有的单词到一个集合中,假设最终生成的集合长度为wordSetLen = 31。

2)假设文档/样本数为sampleCnt = 6,则建立一个sampleCnt * wordSetLen = 6 * 31的矩阵,这个矩阵被填入有效值之后,就是最终的可训练矩阵m。

3)遍历矩阵m,填入0,1有效值。0代表当前列的单词没有出现在当前行的样本/文档中,1代表当前列的单词出现在当前行的样本/文档中。

4)最终生成一个6 * 31的可训练矩阵。

 

 

 

2. 词袋模型

词袋模型中,训练矩阵不仅仅只出现0,1还会出现其他数字,这些数字代表的是当前样本中单词出现的次数。

 

 
  1. # -*- coding: utf-8 -*-

  2. import numpy as np

  3.  
  4. def load_data():

  5. """ 1. 导入train_x, train_y """

  6. train_x = [["my", "dog", "has", "flea", "problems", "help", "please"],

  7. ["maybe", "not", "take", "him", "to", "dog", "park", "stupid"],

  8. ["my", "dalmation", "is", "so", "cute", "I", "love", "him"],

  9. ["stop", "posting", "stupid", "worthless", "garbage"],

  10. ["him", "licks", "ate", "my", "steak", "how", "to", "stop", "him"],

  11. ["quit", "buying", "worthless", "dog", "food", "stupid"]]

  12. label = [0, 1, 0, 1, 0, 1]

  13. return train_x, label

  14.  
  15.  
  16. def setOfWord(train_x):

  17. """ 2. 所有单词不重复的汇总到一个列表

  18. train_x: 文档合集, 一个样本构成一个文档

  19. wordSet: 所有单词生成的集合的列表

  20. """

  21. wordList = []

  22.  
  23. length = len(train_x)

  24. for sample in range(length):

  25. wordList.extend(train_x[sample])

  26. wordSet = list(set(wordList))

  27. return wordSet

  28.  
  29.  
  30. def create_wordVec(sample, wordSet, mode="wordSet"):

  31. """ 3. 将一个样本生成一个词向量 """

  32. length = len(wordSet)

  33. wordVec = [0] * length

  34.  
  35. if mode == "wordSet":

  36. for i in range(length):

  37. if wordSet[i] in sample:

  38. wordVec[i] = 1

  39. elif mode == "wordBag":

  40. for i in range(length):

  41. for j in range(len(sample)):

  42. if sample[j] == wordSet[i]:

  43. wordVec[i] += 1

  44. else:

  45. raise(Exception("The mode must be wordSet or wordBag."))

  46. return wordVec

  47.  
  48.  
  49. def main(mode="wordSet"):

  50. train_x, label = load_data()

  51. wordSet = setOfWord(train_x)

  52.  
  53. sampleCnt = len(train_x)

  54. train_matrix = []

  55. for i in range(sampleCnt):

  56. train_matrix.append(create_wordVec(train_x[i], wordSet, "wordBag"))

  57. return train_matrix

  58.  
  59.  
  60. if __name__ == "__main__":

  61. train_x, label = load_data()

  62. wordSet = setOfWord(train_x)

  63. train_matrix = main("wordSet")

==================================================================================================================================================================

词袋模型bow和词向量模型word2vec

在自然语言处理和文本分析的问题中,词袋(Bag of Words, BOW)和词向量(Word Embedding)是两种最常用的模型。更准确地说,词向量只能表征单个词,如果要表示文本,需要做一些额外的处理。下面就简单聊一下两种模型的应用。

所谓BOW,就是将文本/Query看作是一系列词的集合。由于词很多,所以咱们就用袋子把它们装起来,简称词袋。至于为什么用袋子而不用筐(basket)或者桶(bucket),这咱就不知道了。举个例子:

                   文本1:苏宁易购/是/国内/著名/的/B2C/电商/之一

这是一个短文本。“/”作为词与词之间的分割。从中我们可以看到这个文本包含“苏宁易购”,“B2C”,“电商”等词。换句话说,该文本的的词袋由“苏宁易购”,“电商”等词构成。就像这样:

但计算机不认识字,只认识数字,那在计算机中怎么表示词袋模型呢?其实很简单,给每个词一个位置/索引就可以了。例如,我们令“苏宁易购”的索引为0,“电商”的索引为1,其他以此类推。则该文本的词袋就变成了:

 

是的,词袋变成了一串数字的(索引)的集合。这样计算机就能读懂了。如果用程序来描述的话,就会像:Set<int>(0,1,2…)。当然,刚才的例子中像“苏宁易购”等词只出现了一次,如果出现多次,可能就需要支持重复元素的容器了,如Java/C++中的MultiSet。

可是,在实际的应用中(如:文本的相似度计算),用刚才说的容器是非常不方便的(如果要用,需要额外用Map容器来存储一本字典来表征词和索引的映射关系)。因此我们考虑用更简单的数据结构来组织词袋模型。既然刚才说词是用数字(索引)来表示的,那自然我们会想到数组。例如:

         Intwords[10000] = {1,20,500,0,……}

                                     索引:{0,1,2,3,……}

                                     词:   {苏宁易购,是,国内,B2C,……}

数组的下标表示不同的词,数组中的元素表示词的权重(如:TF,TF-IDF)。更为一般的,词的索引可以用词的HashCode来计算,即:Index(苏宁易购) = HashCode(苏宁易购)。将词散列到数组的某个位置,并且是固定的(理论上会有冲突,需要考虑冲突的问题)。因此,HashCode这个函数起到了字典的作用。转化成了数组,接下来计算余弦相似度啥的就好办多了。这就是词袋模型。

下面讲讲词向量模型。实际上,单个词的词向量不足以表示整个文本,能表示的仅仅是这个词本身。往往,这个词向量是个高维的向量(几万甚至几十万)。先不说它是如何得到的,单说它的应用应该是很广泛的。再举文本相似度的例子,既然词可以用一串数字表示,那么自然可以用余弦相似度或欧式距离计算与之相近的词。这样,词的聚类什么的都可以做了。那长文本怎么办呢?一个简单的办法是把这个文本中包含的词的词向量相加。这样长文本也就表示成了一串数字。可是这种处理方法总让我们觉得怪怪的。看到过有同学做的测试,当文本只有十几个字的时候,这种处理方法还算凑合,字多了,结果就很难看了。至于词向量是怎么获得,咱下回再说。目前word2vec有多种版本可供大家使用。至于像doc2vec,sentence2vec的效果还有待评估。

==================================================================================================================================================================

gcForest模型的Python实现

GitHub上有两个star比较多的gcForest项目,在参考文献中我们已经列出来了,下面我们就使用这两个gcForest的Python模块去尝试使用gcForest模型去解决一些问题,这里要说明的是其中参考文献3是官方提供(由gcForest的作者之一Ji Feng维护)的一个Python版本。目前gcForest并没有相对好用的R语言接口,我们在近期将会推出R语言的gcForest包,敬请期待。这里我们仅介绍参考文献4中的gcForest模块,如果感兴趣,可以自行研究官方提供的gcForest模块(官方模块,集成模型的选择除了随机森林外还可以选择其他的结构像XGBoost,ExtraTreesClassifierLogisticRegression,SGDClassifier等,因此建议使用该模块)。这里就介绍参考文献4中gcForest的模块使用。

Example1: iris数据集预测

(iris数据集的中文名是安德森鸢尾花卉数据集,英文全称是Anderson’s Iris data set。iris包含150个样本,对应数据集的每行数据。每行数据包含每个样本的四个特征和样本的类别信息,所以iris数据集是一个150行5列的二维表)

from GCForest import gcForest
from sklearn.datasets import load_iris, load_digits
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

import pandas as pd
import numpy as np

iris = load_iris()
X = iris.data
y = iris.target
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.33)
print(pd.DataFrame(X_tr).head())

# 注意里边的参数设置,详细的参数意义
# 可以参考源码中的参数解释
gcf = gcForest(shape_1X=4, window=2, tolerance=0.0)
gcf.fit(X_tr, y_tr)

# 类别预测
pred_X = gcf.predict(X_te)
print(pred_X)

pred_X_prob = gcf.predict_proba(X_te)
print(pred_X_prob)

# 模型评估
accuracy = accuracy_score(y_true=y_te, y_pred=pred_X)
print('gcForest accuracy : {}'.format(accuracy))

# 模型保存
from sklearn.externals import joblib
joblib.dump(gcf, 'my_iris_model.sav')

# 模型加载
# joblib.load('my_iris_model.sav')

Example2:Digits数据集预测

(sklearn中自带的手写数字数据集(digits),这个数据集中并没有图片,而是经过提取得到的手写数字特征和标记,就免去了我们的提取数据的麻烦)

from GCForest import gcForest
from sklearn.datasets import load_iris, load_digits
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

import pandas as pd
import numpy as np

digits = load_digits()
X = digits.data
y = digits.target
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.4)

# 注意参数设定
gcf = gcForest(shape_1X=[8,8], window=[4,6], tolerance=0.0, min_samples_mgs=10, min_samples_cascade=7)
gcf.fit(X_tr, y_tr)

# 类别预测
pred_X = gcf.predict(X_te)
print(pred_X)

# 模型评估
accuracy = accuracy_score(y_true=y_te, y_pred=pred_X)
print('gcForest accuracy : {}'.format(accuracy))

Example3:多粒度扫描和级联结构分离

 

因为多粒度扫描和级联结构是相对独立的模块,因此是可以分开的,如果代码中提供了参数y,则会自动去做训练,如果参数y是None,代码会回调最后训练的随机森林进行数据切片。

 

# 模型根据y_tr进行训练
gcf = gcForest(shape_1X=[8,8], window=5, min_samples_mgs=10, min_samples_cascade=7)
X_tr_mgs = gcf.mg_scanning(X_tr, y_tr)

# 回调最后训练的随机森林模型
X_te_mgs = gcf.mg_scanning(X_te)

# 使用多粒度扫描的输出作为级联结构的输入,这里要注意
# 级联结构不能直接返回预测,而是最后一层级联Level的结果
# 因此,需要求平均并且取做大作为预测值

gcf = gcForest(tolerance=0.0, min_samples_mgs=10, min_samples_cascade=7)
_ = gcf.cascade_forest(X_tr_mgs, y_tr)

pred_proba = gcf.cascade_forest(X_te_mgs)
tmp = np.mean(pred_proba, axis=0)
preds = np.argmax(tmp, axis=1)
accuracy_score(y_true=y_te, y_pred=preds)

Example4:去除多粒度扫描的级联结构

我们也可以使用最原始的不带多粒度扫描的级联结构进行预测

 

gcf = gcForest(tolerance=0.0, min_samples_cascade=20)
_ = gcf.cascade_forest(X_tr, y_tr)

pred_proba = gcf.cascade_forest(X_te)
tmp = np.mean(pred_proba, axis=0)
preds = np.argmax(tmp, axis=1)
accuracy_score(y_true=y_te, y_pred=preds)

感兴趣的小伙伴可以研究一下,官方提供(由gcForest的作者之一 Ji Feng维护的)的模块,其实都差不多,如果感兴趣,我们后期也会做相关的介绍。

==================================================================================================================================================================

 

推荐系统 基于用户和基于物品的协同过滤 (推荐系统实践读书笔记)

基于邻域的算法是推荐系统中最基本的算法,该算法不仅在学术界得到了深入研究,而且在 业界得到了广泛应用。基于邻域的算法分为两大类,一类是基于用户的协同过滤算法,另一类是 基于物品的协同过滤算法。 

 

基于用户的协同过滤算法:

该算法主要分为两个步骤:

(1)找到和目标用户兴趣相似的用户的集合

(2)找到集合中用户喜欢的,且目标用户没有听说过的物品推荐给用户

==================================================================================================================================================================

百万用户,八十万商品,如何计算基于物品的协同过滤

问题描述:

数据包含了一百四十万用户对80万商品的打分。要利用基于物品的协同过滤来计算。如果直接两两计算140万维的向量相似度,肯定不行啊。

问题分析:

每个物品的向量虽然是140万维的,但是其实给一个物品打分的用户其实不多,这个矩阵是非常稀疏的。而且根据长尾问题来说,大部分物品只有很少的用户有过评分。

总结来说就是每个物品评分的用户远远小于140万,每个用户评价过的商品远远小于80万。所以我们的问题就转换为如何处理这个稀疏的矩阵。

解决方法:

在计算相似度时,选用了夹角余弦(因为相比于杰卡德,夹角余弦考虑了评分的大小,实测效果更好)

第一步首先按商品ID进行group by,数据转换为,商品:评价过该商品用户。然后计算商品向量的长度,也就是用户评分的平方之和再开平方。

第二步,按用户id进行group by,将数据转换为,用户id :该用户评价过得商品。然后计算两个商品的相似度。得到<item_id,item_id>:评分

第三步,累加商品对的评分,除以两个商品的向量长度得到相似度。

==================================================================================================================================================================

 

Bagging和Boosting的区别(面试准备)

Baggging 和Boosting都是模型融合的方法,可以将弱分类器融合之后形成一个强分类器,而且融合之后的效果会比最好的弱分类器更好。

 

Bagging:

先介绍Bagging方法:

Bagging即套袋法,其算法过程如下:

  1. 从原始样本集中抽取训练集。每轮从原始样本集中使用Bootstraping的方法抽取n个训练样本(在训练集中,有些样本可能被多次抽取到,而有些样本可能一次都没有被抽中)。共进行k轮抽取,得到k个训练集。(k个训练集之间是相互独立的)

  2. 每次使用一个训练集得到一个模型,k个训练集共得到k个模型。(注:这里并没有具体的分类算法或回归方法,我们可以根据具体问题采用不同的分类或回归方法,如决策树、感知器等)

  3. 对分类问题:将上步得到的k个模型采用投票的方式得到分类结果;对回归问题,计算上述模型的均值作为最后的结果。(所有模型的重要性相同)

 

Boosting:

      AdaBoosting方式每次使用的是全部的样本,每轮训练改变样本的权重。下一轮训练的目标是找到一个函数f 来拟合上一轮的残差。当残差足够小或者达到设置的最大迭代次数则停止。Boosting会减小在上一轮训练正确的样本的权重,增大错误样本的权重。(对的残差小,错的残差大)

      梯度提升的Boosting方式是使用代价函数对上一轮训练出的模型函数f的偏导来拟合残差。

Bagging,Boosting二者之间的区别

Bagging和Boosting的区别:

1)样本选择上:

Bagging:训练集是在原始集中有放回选取的,从原始集中选出的各轮训练集之间是独立的。

Boosting:每一轮的训练集不变,只是训练集中每个样例在分类器中的权重发生变化。而权值是根据上一轮的分类结果进行调整。

2)样例权重:

Bagging:使用均匀取样,每个样例的权重相等

Boosting:根据错误率不断调整样例的权值,错误率越大则权重越大。

3)预测函数:

Bagging:所有预测函数的权重相等。

Boosting:每个弱分类器都有相应的权重,对于分类误差小的分类器会有更大的权重。

4)并行计算:

Bagging:各个预测函数可以并行生成

Boosting:各个预测函数只能顺序生成,因为后一个模型参数需要前一轮模型的结果。

5)这个很重要面试被问到了

为什么说bagging是减少variance,而boosting是减少bias?

 

Bagging对样本重采样,对每一重采样得到的子样本集训练一个模型,最后取平均。由于子样本集的相似性以及使用的是同种模型,因此各模型有近似相等的bias和variance(事实上,各模型的分布也近似相同,但不独立)。由于E[\frac{\sum X_i}{n}]=E[X_i],所以bagging后的bias和单个子模型的接近,一般来说不能显著降低bias。另一方面,若各子模型独立,则有Var(\frac{\sum X_i}{n})=\frac{Var(X_i)}{n},此时可以显著降低variance。若各子模型完全相同,则Var(\frac{\sum X_i}{n})=Var(X_i)

,此时不会降低variance。bagging方法得到的各子模型是有一定相关性的,属于上面两个极端状况的中间态,因此可以一定程度降低variance。为了进一步降低variance,Random forest通过随机选取变量子集做拟合的方式de-correlated了各子模型(树),使得variance进一步降低。

(用公式可以一目了然:设有i.d.的n个随机变量,方差记为\sigma^2,两两变量之间的相关性为\rho,则\frac{\sum X_i}{n}的方差为\rho*\sigma^2+(1-\rho)*\sigma^2/n

,bagging降低的是第二项,random forest是同时降低两项。详见ESL p588公式15.1)

boosting从优化角度来看,是用forward-stagewise这种贪心法去最小化损失函数L(y, \sum_i a_i f_i(x))。例如,常见的AdaBoost即等价于用这种方法最小化exponential loss:L(y,f(x))=exp(-yf(x))。所谓forward-stagewise,就是在迭代的第n步,求解新的子模型f(x)及步长a(或者叫组合系数),来最小化L(y,f_{n-1}(x)+af(x)),这里f_{n-1}(x)

是前n-1步得到的子模型的和。因此boosting是在sequential地最小化损失函数,其bias自然逐步下降。但由于是采取这种sequential、adaptive的策略,各子模型之间是强相关的,于是子模型之和并不能显著降低variance。所以说boosting主要还是靠降低bias来提升预测精度。

==================================================================================================================================================================

如何利用gcForest为特征打分?

2018年12月26日 16:36:41 phyllisyuell 阅读数:189更多

个人分类: 大数据,机器学习 决策树 gcForest

 

楼主前面有一篇博客提到了周志华老师又一力作:gcForest:探索深度神经网络以外的方法,不了解的小伙伴可以翻前面的博客。

这个算法的确比传统的集成树算法:RandomForest,XGBoost,lightGBM都要优秀,而且引入层的概念后很好的解决了集成树算法容易过拟合的问题。

简单讲他就是借鉴了深度学习分层训练的思路,将机器学习中常用的RandomForest,XGBoost,LogisticRegression等算法进行集成,通过模型和样本的多样性让模型更加优秀。

正是因为它这种集成思想,反而抹杀了传统集成树算法的一大优势,       

 

gcForest无法给特征打分。                            

 

 

原因很简单,它每层用的基学习器像前面提到的RandomForest,XGBoost提取特征的方式是不一样的:

首先RandomForest作为Bagging的代表,它是通过给指定特征X随机加入噪声,通过加入噪声前后袋外数据误差的差值来衡量该特征的重要程度。具体请参考:随机森林之特征选择;

 

而XGBoost作为典型的Boosting算法提取特征的方式和RandomForest有很大的不同,看了下他的打分函数有weight,gain,cover三种方式,其中默认的是weight,这种方式其实就是统计特征X在每棵决策树当中出现的次数,最后特征X出现的次数之和就作为特征X的最后的得分

 

我们可以看出这两种算法打分方式不同,得到的数值也不是一个量纲的。

 

 

同样LogisticRegression提取特征的方式也和前两者不一样。

 

 

并且gcForst还提供了用户自己添加基学习器的接口

 

添加方法请了解:gcForest官方代码详解),也就意味着gcForest还可以使用更多的基学习器,如果要封装一个提取重要特征的方法,就要考虑太多太多,每进来一个基学习器都要改变特征打分的方法。

综上所述,我觉得也是gcForest作者没有封装特征打分方法的原因。

基于我的问题,我做了一些思考。我处理的数据用RandomForest,XGBoost都能得到不错的结果,我们知道RandomForest可以很好的减少方差,XGBoost可以很好的减少偏差。为了构建一个低偏差和低方差的模型,我想将这两种算法进行集成。

 

所以在gcForest中我只用了这两个基学习器。

 

 

 

通过对RandomForest,XGBoost打分函数的学习,我和小伙伴shi.chao 对gcForest封装了一个特征打分方法,利用的还是源码里手写数字识别的数据,每层只有RandomForest,XGBoost,为了方便调试,就构建了两层。

大体思想如下:

在源码目录:lib->gcforest->cascade->cascade_classifier.py  fit_transform()方法中进行了一些操作。这个方法就是gcForest进行模型训练的函数。上面也提到了不同的算法特征打分的方法是不一样的,所以在这里需要通过变量est_configs["type"]对基学习器类型进行判断。

 

 

如果是RandomForest,就直接调用RandomForest的打分函数,得到该基学习器返回的一个map,其中包含特征名称和得分,这里用一个临时变量保存,等到下一层获取RandomFores打分函数得到的另一个map,然后将这个两个map合并,相同key的将value累加,最后得到一个final_feature_rf_importance_list,是整个gcForest所有层中RandomFores得到的特征得分。

 

 

 

XGBoost,类似操作。具体见代码注释。

感兴趣的小伙伴可以在这个基础上继续对另外的基学习器特征打分算法进行封装。最后gcForest的特征得分:可以是各个基学习器特征得分的一个融合。

比如我的模型中只用到了RandomForest和XGBoost,最后gcForest的第i个特征的得分可以这样表示:

Zi  = w1 * Xi/sum(X) + w2 * Yi/sum(Y)

其中Xi代表RandomForest中第i个特征的得分,Yi代表XGBoost中第i个特征的得分,这两个值虽然不是一个量纲,但是通过处以它们全部特征之和就可以得到该特征在它的模型中的相对特征,最后通过设置w1,w2的系数,可以调整两种模型在gcForest中的重要程度。

代码已经开源到github:https://github.com/phyllisyuell/gcForest-master-feature

欢迎各位交流学习,批评指正。

有问题请联系,一起讨论:285794144.@qq.com

==================================================================================================================================================================

随机森林之特征选择

摘要:随机森林介绍中提到了随机森林一个重要特征:能够计算单个特征变量的重要性。并且这一特征在很多方面能够得到应用,例如在银行贷款业务中能否正确的评估一个企业的信用度,关系到是否能够有效地回收贷款。但是信用评估模型的数据特征有很多,其中不乏有很多噪音,所以需要计算出每一个特征的重要性并对这些特征进行一个排序,进而可以从所有特征中选择出重要性靠前的特征。

 

一:特征重要性

在随机森林中某个特征X的重要性的计算方法如下:

1:对于随机森林中的每一颗决策树,使用相应的OOB(袋外数据)数据来计算它的袋外数据误差,记为errOOB1.

2:  随机地对袋外数据OOB所有样本的特征X加入噪声干扰(就可以随机的改变样本在特征X处的值),再次计算它的袋外数据误差,记为errOOB2.

3:假设随机森林中有Ntree棵树,那么对于特征X的重要性=∑(errOOB2-errOOB1)/Ntree,之所以可以用这个表达式来作为相应特征的重要性的度量值是因为:若给某个特征随机加入噪声之后,袋外的准确率大幅度降低,则说明这个特征对于样本的分类结果影响很大,也就是说它的重要程度比较高。

 

二:特征选择

在论文 Variable Selection using Random Forests中详细的论述了基于随机森林的特征选择方法,这里我们进行一些回顾。

首先特征选择的目标有两个:

1:找到与应变量高度相关的特征变量。

2:选择出数目较少的特征变量并且能够充分的预测应变量的结果。

其次一般特征选择的步骤为:

1:初步估计和排序

a)对随机森林中的特征变量按照VI(Variable Importance)降序排序。

b)确定删除比例,从当前的特征变量中剔除相应比例不重要的指标,从而得到一个新的特征集。

c)用新的特征集建立新的随机森林,并计算特征集中每个特征的VI,并排序。

d)重复以上步骤,直到剩下m个特征。

2:根据1中得到的每个特征集和它们建立起来的随机森林,计算对应的袋外误差率(OOB err),将袋外误差率最低的特征集作为最后选定的特征集。

 

===========================================================================================================================================================================================================================================================================

1,表征学习(representation learning)能力对于深度神经网络至关重要

2,深度神经网络中的表征学习(representation learning)主要依赖于对原始特征进行逐层处理

受此启发,gcForest 采用级联结构(cascade structure),如图1所示,其中级联中的每一级接收到由前一级处理的特征信息,并将该级的处理结果输出给下一级。

图1:级联森林结构的图示。级联的每个级别包括两个随机森林(蓝色字体标出)和两个完全随机树木森林(黑色)。

 

假设有三个类要预测; 因此,每个森林将输出三维类向量,然后将其连接以重新表示原始输入。

 

为了降低过拟合风险,每个森林产生的类向量由k折交叉验证(k-fold cross validation)产生。

 

 

参与评论 您还未登录,请先 登录 后发表或查看评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值