下图展示了使用表中数据学习得到的一种决策树结构。这棵树有两种类型的节点,决策节点(Decision Nodes,也叫中间节点)和叶子节点(Leaf Nodes)。一个决策节点内包含针对数据实例某个属性的一些测试,而一个叶子节点则代表了一个类标。
在此决策树结构中,根节点首先提出问题:申请者的年龄是多少?这个问题有三个可能的答案。这三个可能的答案构成了根节点下边的三个分支。其他的内部节点以相似的方式工作。
每一个叶子节点代表一个类标(Yes或者No)。每一个叶子节点下的(x/y)代表到达此叶子节点的y个训练样例里有x个样例的类标与这个叶子节点所标识的类标一致。例如,最左边的叶子节点的类标是Yes。两个训练样例(样例3和4)到达这个节点,并且他们的实际类标都是Yes。
一颗决策树的构建过程是不断的分隔训练数据,以使得最终分隔所得到的各个子集尽可能纯。一个纯的子集(Pure Subset)是指的数据实例类标全部一致。如果把表中的训练数据加载到上述决策树中,我们会看到到达相同叶节点的样例类别是相同的。事实上,从叶节点的(x/y)的值也能看出这点。
一个有趣的问题是:对于某个数据集而言,决策树是唯一的么?答案是否定。事实上,对某个数据集中的数据进行学习,可以得到很多可能的决策树。
在实际应用中,我们希望能够得到一棵尽可能小并且准确的决策树。一棵更小的树意味着泛化能力更强也更准确。同时,它也更容易被人所理解。在许多的应用中,使得分类器能够被用户理解也非常重要。
对决策树进行测试的时候,我们从决策树的根节点自上而下的对测试样例的属性进行测试,直到测试样例最终到达一个叶子节点,那么我们认为这个测试样例的类标就是到达的这个叶子节点的类标。
例1:使用上述决策树来预测新的实例,这个新的实例代表了一起新的贷款申请。
Age:young
Has_job:false
Own_house:false
Credit_rating:good
使用决策树进行预测,可以发现这个样例最终到达了自左起的第二个叶子节点,这表示对这起贷款申请的预测结果是No。
上述决策树中到达每个叶子节点的训练样例都有相同的类标(查看每个叶子节点的(x/y)可知)。但是,在实际的数据集中,通常并不是这样的。也就是说,到达某个特定叶子节点的样例实际的类标可能会不同,也就是x≤y。实际上,x/y这个值表示的是关联规则挖掘的可信度(conf),x则反应了支持度(sup)。
一棵决策树可以被转化为一系列的关联规则(有关关联规则详见此链接),转化方法如下:每一条从根部到达叶子节点的路径代表了一条规则,路径上所有的决策节点构成了关联规则的条件,叶子节点代表了关联规则的结果。对于每条规则,都对应着一个支持度和可信度的值。值得注意的是在大多数分类系统中,这两个值是不提供的。我们在这里添加它们来看关联规则和决策树的联系。
例2:上述决策树产生的几条关联规则
Age=Young, Has_job=true→Class=Yes [sup=2/15, conf=2/2]
Age=Young, Has_job=false→Class=No [sup=3/15, conf=3/3]
…
Age=Middle, Own_house=false→Class=No [sup=2/15,conf=2/2]
…
上边的规则只是数据集中数据所蕴含的规则的一个子集。例如,上述决策树不包括下边的一条规则:
Own_house=true→Class=Yes [sup=6/15, conf=6/6]
因此,可以说,一个决策树只挖掘足够进行分类的规则,这些规则是训练数据中蕴含的规则的一个子集。而关联规则挖掘则是为了在最小的支持度和可信度条件下找到所有的规则。可见,这两个方法是有所区别的。
决策树的一个重要并且非常有趣的特性是它的路径是互斤的。这意味着,每一个数据实例只可能满足一条路径的要求。
一棵好的决策树应该是较小并且精准的,构建一棵好的决策树的问题是一个NP完全问题。目前所有的决策树构建算法都是基于启发式算法的。下面,我们将讨论其中最成功的技术之一。
正如我们在上边所提到的,一颗决策树把训练数据分隔为不相交的子集,每一个子集应该尽可能的纯。学习算法就是使用分治(divide-and-conquer)策略,递归的对训练数据进行分隔,从而构造决策树。最开始的时候,所有的数据样例都在根部,随着决策树的增长,样例被不断的递归的分隔。
在下图中,我们给出了决策树学习算法。这里,假定D中每一个属性具有离散的值。当然,在后边我们会发现这个假设并非是必需的。
D:当前数据集,A:可用属性集,T:当前节点
递归的终止条件(Stopping Criteria)在1~4行:当所有的当前节点中的数据都属于同一类,或者路径已经使用了所有的属性,送代终止。在学习算法中,每一个后续的递归都选择最佳分类属性(Best Attribute)作为分隔当前数据实例集的属性。最佳分类属性的选择是通过一个混杂度函数(Impurity Function)来实现的:这个函数反映了用该属性进行数据分隔以后的数据集的混杂度。因此,在决策树学习中,混杂度函数的选择非常重要(第7、9、11行)
该算法是一个不回溯的贪婪算法。一旦一个节点创建了,无论接下来发生什么,都不会对它进行修改。
在给出混杂度函数之前,我们先直观感受一下混杂度的含义。
例3:下图展示了贷款申请数据集中两种可能的根节点。
(a)中使用Age作为根节点,(b)中使用Own_house作为根节点。根节点可能的值构成了根节点的分支。对于每一个分支,我们列出了这个分支上每一个类(Yes和No)的训练数据的数目。
(b)显然是一个比(a)更好的选择,因为从分类预测的观点讲,(b)比(a)要犯更少的错误。在(b)中,Own_house=true的每一个样例都归类到Yes类中去。当Own_house=false的时候,如果我们把它们归类到No类中去,那么我们会有三个分类是错误的。反观(a),则情况就很糟糕,如果取每条分支上的大多数样例的类标作为这个分支的类标,那么将会有5个错误(看总的错误数)。因此,可以说(a)的混杂度比(b)的混杂度大。
为了学习一颗比较好的决策树,我们更希望使用Own_house作为根节点,而不是Age。
C4.5给出了一个更加数学化的方法,而不仅仅是计算可能的错误的个数来评价在构建决策树的过程中每个节点的选取的方法。
最流行的用于决策树学习的混杂度函数是信息增益(Information Gain)和信息增益率(Information Gain Ratio),这两个函数在C4.5中都是可选的。首先讨论信息增益,对此经过简单扩展之后就可得到信息增益率。
信息增益基于熵(Entropy),这是信息论(Information Theory)中的概念。
其中Pri(cj)是cj类在数据集D中的概率,也即D中cj类的实例数目除以D中实例的总数。
在熵的计算中,我们定义0log0=0。熵的单位是位。
例4:假设有一个数据集D,其中只有两个类,一个是正例类,另一个是负例类。我们来看一下D中正例类和负例类在三种不同的组分下的熵。
(1)D中有50%的正例(Pr(正例)=0.5)和50%的负例(Pr(负例)=0.5)。
entropy(D)= -0.5 x log0.5 - 0.5 x log0.5 = 1
(2)D中有80%的正例(Pr(正例)=0.8)和20%的负例(Pr(负例)=0.2)。
entropy(D)= -0.8 x log0.8 - 0.2 x log0.2 = 0.722
(3)D中有100%的正例(Pr(正例)=1),没有负例(Pr(负例)=0)。
entropy(D)= -1 x log1 - 0 x log0 = 0
Ps:我们可以看到一个趋势,当数据变得越来越纯净时,熵的值变得越来越小。事实上可以证明,在二元情况下(两个类时),当Pr(正例)=0.5,Pr(负例)=0.5时,熵取最大值,即1位。当D中所有数据都只属于一个类时,熵达到最小值0位。显然,熵可以作为数据混杂度或者混乱度的衡量指标。
本方法的思想如下:给定一个数据集D,
(1)用熵计算D的混杂度,表示成entropy(D)。(算法第7行impurityEval-1函数)
(2)对每个属性,计算按此属性划分后D的混杂度。(算法第8~10行)
设属性Ai,可取v个值,假设我们用Ai来划分D,则我们可以将D划分成v个不相交的子集D1, D2, …, Dv,划分后D的熵为
(impurityEval-2函数)
(3)属性Ai的信息增益:gain(D, Ai)=entropy(D)-entropyAi(D)
显然,信息增益衡量混杂度或混乱度的减少量。算法第11行选择Ag使得混杂度的减少量达到最大(其实可以直接选混杂度最小的)。如果Ag带来的信息增益太小,算法在这个分支上停止(第12行),这里需要一个阀值。如果Ag可以显著地减少混杂度,那Ag将用来划分数据,扩展决策树,如此继续(算法第15~21行)。这个过程递归地进行。
在接下来对树的扩展过程中我们不再需要Ag,因为在每个子树中,所有数据都有相同的Ag值。
例5:试用贷款申请数据集D计算三个属性Age、Own_house和Credit_Rating的信息增益,找出决策树的根节点。
首先计算D的熵,因为D有6个No类训练实例和9个Yes类训练实例,可计算得:
entropy(D)= -6/15 x log(6/15) - 9/15 x log(9/15) = 0.971
然后我们用Age属性来划分数据,可划分成3类,D1(Age=young),D2(Age=middle),D3(Age=old)。
entropyAge(D)= 5/15 x entropy(D1) + 5/15 x entropy(D2) + 5/15 x entropy(D3)
= 5/15 x 0.971 + 5/15 x 0.971 + 5/15 x 0.722 = 0.888
类似的,我们用Own_house属性将数据划分成两个子集D1(Own_house=Yes),D2(Own_house=No)。
entropyOwn_house(D)= 6/15 x entropy(D1) + 9/15 x entropy(D2) = 6/15 x 0 + 9/15 x 0.918 = 0.551
同样,我们可以得到entropyHas_job(D)=0.647,entropyCredit_rating(D)=0.608。
显然,Own_house是作为根节点的最好的属性。
信息增益趋向于优先选择有较多可能取值的属性。一个极端情况是数据集中每个数据都有一个ID属性,每个数据的ID值都不一样。如果我们用ID属性来划分数据,每个训练实例都各自成为一个子集,显然这样的每个子集都只包含一个类别的数据,则entropyID(D)=0。因此,采用这个属性的信息增益将达到最大。但一般来讲,这样的划分是
毫无用处的。
信息增益率利用数据集的相对于属性值分布的熵归一化信息增益,用以修正上述偏袒性。
其中s是属性Ai的可能取值的数目,Dj是数据集中具有Ai属性第j个值的子集。
类似于信息增益,我们选择可使gainRatio值达到最大的属性来扩展决策树。
这个方法是有意义的,因为如果Ai有太多可取值,则分母将变大。例如,在我们以上ID属性的例子中,分母将变成log2|D|。在C4.5中,这个分母称为分裂信息。需要注意的一点是,分裂信息可能很小甚至是0。一些启发式解决方案可以处理这些情况。
从上文的描述中,可以看到决策树算法似乎只能处理离散属性。但事实上,连续属性也能够很好地被处理。
为了将决策树构建算法应用于连续属性,在一个树节点中我们可以将属性A的值划分成几个区间,每个区间就可以被视为一个离散值。然后信息增益和信息增益率的计算就可以采用和离散属性时一样的方法。显然,我们可以将Ai的值划分成任意数目的区间,但实际中,两个区间就已经足够了。C4.5中就采用了这种二元分割(Binary Split)。所以,我们仅需要找出一个合适的分割國值(Threshold)。
显然,应该选择能将信息增益(或信息增益率)最大化的阀值。为此,需要尝试所有可能的阀值取值。这并不成问题,因为尽管属性Ai是连续的,理论上其取值是无限的,但实际上由于它的实例的数目是有限的,所以实际数据中Ai的取值是有限的。设数据集中属性Ai的取值是集合{v1, v2, …, vr},其中元素以升序排列。显然,处于vi和vi+1间的所有阀值在划分训练数据的效果上是一样的,都是把数据分成两个集合,一个的Ai取值为{v1, v2, …, vi},另一个的Ai取值为{vi+1, vi+2, …, vr}。因此,在属性Ai上,总共只有r-1种可能的分割,可以逐一进行评价。
阀值的选取可以取vi和vi+1间的均值,或直接取vi,生成Ai≤vi和Ai>vi两个区间。C4.5中采用了后一种方法。这种方法的优点是在决策树上出现的值都存在于数据集中。
上述算法第8~11行进行简单的修改后就可以同时支持离散属性和连续属性。此外,第20行也需要做一些改动。对于一个连续属性,我们不能删除属性Ag,因为一个值区间在以后扩展树时还可能进一步被分割。因此,同一个连续属性可能在一条树路径中出现多次,而这种情况在离散属性中就不会出现。
处理连续(数字)属性会影响到决策树算法的效率。如果只有离散属性,算法的复杂度将随着数据集的规模线性增加。然而,对连续属性的排序的时间复杂度为|D|log|D|,这步操作将在学习过程的复杂度中起主要作用。排序是很关键的,因为这将确保只需要遍历遍数据就可以完成信息增益和信息增益率的计算。
一个决策树算法递归地划分数据,直到混杂度为0或没有其他属性。这个过程可能得到深度很大的树,其中很多叶子节点只覆盖很少数目的训练实例。如果用这样的一棵树去预测训练集,其精度将非常高。但是当它用来对测试数据进行分类时,其精度可能会非常低。这样的学习结果是意义不大的,也即决策树不能很好地泛化到所有数据上。这种现象称为过度拟合(Over fitting)。具体来讲,对于一个分类器f1,如果存在另一个分类器f2,使得在训练数据上f1比f2精度高,但在测试数据上f1比f2精度低,则称f1过度拟合数据。
过度拟合常常是数据中的噪音引起的,也即错误的类别值/标签和/或错误的属性值,但也可能是由于具体应用领域的复杂性和随机性引起。这些问题导致决策树算法通过采用许多属性值将决策树扩展到非常深来精化决策树。
为了减少过度拟合问题,我们对树进行剪枝,也即删除一些子树或分支,随即用占多数的类的叶节点代替这些分支。有两个方法可以执行这步操作,一个是提早结束(Stopping Early)树构建过程(称为预剪枝,Pre-pruning),另一个是在树构建完毕之后再剪枝(称为后剪枝,Post-pruning)。一般认为后剪枝比较有效。预剪枝会有些危险,因为我们不知道在算法停止之前,当树进一步扩展后会发生什么。后剪枝更有效是因为当决策树完全扩展之后,我们就比较容易看清楚哪些分支可能没有用处(过度拟合了数据)。后剪枝通常的思想是对每个树节点进行错误估计。如果一个树节点的错误估计要比其子树的错误估计小则其子树将被剪枝。大多数现有的决策树学习算法都采用这个方法。
另外一个常用的剪枝技术是使用另外一个在训练和测试过程中都未被使用的独立数据集来进行,这个独立数据集通常被称为验证集合(Validation Set)。在生成了一个决策树后首先使用它来对验证集合上的数据进行分类。然后我们查找那些存在错误分类的树节点。这使得我们能够通过树节点上的错误来确定该如何剪枝。
验证集——训练集中的测试集。
规则剪枝(Rule Pruning):我们知道,一棵决策树可以被转换成一组规则的结合。事实上,C4.5也通过对规则进行裁剪来对其进行简化并减少过度拟合情况的发生。首先,决策树(C4.5使用未剪枝的决策树)被转换成一组规则集合。然后,删去某些条件使得规则变短和变少(在剪枝之后某些规则可能会是冗余的)(主要是变短)。在大多数情况下,剪枝可以得到一个更为精确的规则集合,因为较短的规则较少发生过度拟合训练数据的情况。由于剪枝使得规则变得更加一般(具有较少条件限制),它有时也被称为泛化(Generalization)。具有较多条件限制的规则被认为比具有较少条件限制的规则要特殊(Specific)。
一个值得注意的地方是在剪枝之后所得到的规则集合可能不再是互不相交且完全覆盖(Mutually Exclusive and Exhaustive)的。这时可能存在同时满足多个规则的条件的数据点,也可能存在不满足任何规则的条件的数据点。此时,需要定义规则间的一个次序来使得在对测试数据进行分类时仅有一个规则在分类过程被应用。在处理那些不满足任何一个规则条件的测试数据时,我们将它分类为一个默认类别(Default Class),通常是数据点最多的类。
在许多实际数据集中,某些属性值是缺失的或者由于某些原因而不可使用。这时,我们可以给这些缺失属性赋予一个特殊值“未知”,或者是离散属性中出现最为频繁的属性值。如果属性是连续值属性,则使用属性的平均值来填充缺失值。
C 4.5决策树算法采用了另一种方法。在树的节点上,它通过训练数据属性值的分布将缺失的数据按比例的分发。
在许多应用中,不同类别的数据所占的比例可能大相径庭。例如,在一个用于探测入侵攻击的数据集中,入侵攻击的
数据实例相对于正常数据实例来说往往是非常少的(<1%)。直接使用决策树算法来分类或者预测入侵攻击往往是很不准确的,得到的决策树常常是由一个表示“正常”的叶子节点组成,这对于入侵攻击的探测是毫无用处的。解决问题的一种方法是过度采样入侵的数据来增加它们所占的比例。另外一个解决办法是根据新来的数据可能是入侵的程度来对它排序,这样用户就可以着重研究那些排名靠前的情况。