=====================================================================
github 源码同步:https://github.com/Thinkgamer/Machine-Learning-With-Python
算法实现均采用python 如需转载请注明出处,谢谢
决策树简介
如果不考虑效率等,那么样本所有特征的判断级联起来终会将某一个样本分到一个类终止块上。实际上,样本所有特征中有一些特征在分类时起到决定性作用,决策树的构造过程就是找到这些具有决定性作用的特征,根据其决定性程度来构造一个倒立的树–决定性作用最大的那个特征作为根节点,然后递归找到各分支下子数据集中次大的决定性特征,直至子数据集中所有数据都属于同一类。所以,构造决策树的过程本质上就是根据数据特征将数据集分类的递归过程,我们需要解决的第一个问题就是,当前数据集上哪个特征在划分数据分类时起决定性作用。
为了找到决定性的特征、划分出最好的结果,我们必须评估数据集中蕴含的每个特征,寻找分类数据集的最好特征。完成评估之后,原始数据集就被划分为几个数据子集。这些数据子集会分布在第一个决策点的所有分支上。如果某个分支下的数据属于同一类型,则则该分支处理完成,称为一个叶子节点,即确定了分类。如果数据子集内的数据不属于同一类型,则需要重复划分数据子集的过程。如何划分数据子集的算法和划分原始数据集的方法相同,直到所有具有相同类型的数据均在一个数据子集内(叶子节点)。
2:决策树的构造过程
一般包含三个部分1、特征选择:特征选择是指从训练数据中众多的特征中选择一个特征作为当前节点的分裂标准,如何选择特征有着很多不同量化评估标准标准,从而衍生出不同的决策树算法。
2、决策树生成: 根据选择的特征评估标准,从上至下递归地生成子节点,直到数据集不可分则停止决策树停止生长。 树结构来说,递归结构是最容易理解的方式。
3、剪枝:决策树容易过拟合,一般来需要剪枝,缩小树结构规模、缓解过拟合。剪枝技术有预剪枝和后剪枝两种。
核心伪代码如下:
检测数据集中的每个子项是否属于同一类:
If so return 类标签
else
寻找划分数据集的最好特征
划分数据集
创建分支节点
for 每个划分的子集
调用createBranch函数并增加返回结果到分支节点中
return 分支节点
3:决策树的优缺点
决策树适用于数值型和标称型(离散型数据,变量的结果只在有限目标集中取值),能够读取数据集合,提取一些列数据中蕴含的规则。在分类问题中使用决策树模型有很多的优点,决策树计算复杂度不高、便于使用、而且高效,决策树可处理具有不相关特征的数据、可很容易地构造出易于理解的规则,而规则通常易于解释和理解。决策树模型也有一些缺点,比如处理缺失数据时的困难、过度拟合以及忽略数据集中属性之间的相关性等。4:基于信息论的三种决策树算法简介
划分数据集的最大原则是:使无序的数据变的有序。如果一个训练数据中有20个特征,那么选取哪个做划分依据?这就必须采用量化的方法来判断,量化划分方法有多重,其中一项就是“信息论度量信息分类”。基于信息论的决策树算法有ID3、CART和C4.5等算法,其中C4.5和CART两种算法从ID3算法中衍生而来。
CART和C4.5支持数据特征为连续分布时的处理,主要通过使用二元切分来处理连续型变量,即求一个特定的值-分裂值:特征值大于分裂值就走左子树,或者就走右子树。这个分裂值的选取的原则是使得划分后的子树中的“混乱程度”降低,具体到C4.5和CART算法则有不同的定义方式。
ID3算法由Ross Quinlan发明,建立在“奥卡姆剃刀”的基础上:越是小型的决策树越优于大的决策树(be simple简单理论)。ID3算法中根据信息论的信息增益评估和选择特征,每次选择信息增益最大的特征做判断模块。ID3算法可用于划分标称型数据集,没有剪枝的过程,为了去除过度数据匹配的问题,可通过裁剪合并相邻的无法产生大量信息增益的叶子节点(例如设置信息增益阀值)。使用信息增益的话其实是有一个缺点,那就是它偏向于具有大量值的属性–就是说在训练集中,某个属性所取的不同值的个数越多,那么越有可能拿它来作为分裂属性,而这样做有时候是没有意义的,另外ID3不能处理连续分布的数据特征,于是就有了C4.5算法。CART算法也支持连续分布的数据特征。
C4.5是ID3的一个改进算法,继承了ID3算法的优点。C4.5算法用信息增益率来选择属性,克服了用信息增益选择属性时偏向选择取值多的属性的不足在树构造过程中进行剪枝;能够完成对连续属性的离散化处理;能够对不完整数据进行处理。C4.5算法产生的分类规则易于理解、准确率较高;但效率低,因树构造过程中,需要对数据集进行多次的顺序扫描和排序。也是因为必须多次数据集扫描,C4.5只适合于能够驻留于内存的数据集。
CART算法的全称是Classification And Regression Tree,采用的是Gini指数(选Gini指数最小的特征s)作为分裂标准,同时它也是包含后剪枝操作。ID3算法和C4.5算法虽然在对训练样本集的学习中可以尽可能多地挖掘信息,但其生成的决策树分支较大,规模较大。为了简化决策树的规模,提高生成决策树的效率,就出现了根据GINI系数来选择测试属性的决策树算法CART。
基于信息论的三种决策树算法
1、ID3算法
(1)信息熵在概率论中,信息熵给了我们一种度量不确定性的方式,是用来衡量随机变量不确定性的,熵就是信息的期望值。若待分类的事物可能划分在N类中,分别是x1,x2,……,xn,每一种取到的概率分别是P1,P2,……,Pn,那么X的熵就定义为:
从定义中可知:0≤H(X)≤log(n)
熵值越高,则数据混合的种类越高,其蕴含的含义是一个变量可能的变化越多(反而跟变量具体的取值没有任何关系,只和值的种类多少以及发生概率有关),它携带的信息量就越大。熵在信息论中是一个非常重要的概念,很多机器学习的算法都会利用到这个概念。
(2)条件熵,假设有随机变量(X,Y),其联合概率分布为:P(X=xi,Y=yi)=pij,i=1,2,⋯,n;j=1,2,⋯,m则条件熵(H(Y∣X))表示在已知随机变量X的条件下随机变量Y的不确定性,其定义为X在给定条件下Y的条件概率分布的熵对X的数学期望:
(3)信息增益,信息增益(information
gain)表示得知特征X的信息后,而使得Y的不确定性减少的程度。定义为:
2、ID3算法推导
(1)分类系统信息熵
假设一个分类系统的样本空间(D,Y),D表示样本(有m个特征),Y表示n个类别,可能的取值是C1,C2,...,Cn。每一个类别出现的概率是P(C1),P(C2),...,P(Cn)。该分类系统的熵为:
离散分布中,类别Ci出现的概率P(Ci),通过该类别出现的次数除去样本总数即可得到。对于连续分布,常需要分块做离散化处理获得。
(2)条件熵
根据条件熵的定义,分类系统中的条件熵指的是当样本的某一特征X固定时的信息熵。由于该特征X可能的取值会有(x1,x2,……,xn),当计算条件熵而需要把它固定的时候,每一种可能都要固定一下,然后求统计期望。
因此样本特征X取值为xi的概率是Pi,该特征被固定为值xi时的条件信息熵就是H(C|X=xi),那么
H(C|X)就是分类系统中特征X被固定时的条件熵(X=(x1,x2,……,xn)):
若是样本的该特征只有两个值(x1 = 0,x2=1)对应(出现,不出现),如文本分类中某一个单词的出现与否。那么对于特征二值的情况,我们用T代表特征,用t代表T出现,表示该特征出现。那么:
与前面条件熵的公式对比一下,P(t)就是T出现的概率,就是T不出现的概率。结合信息熵的计算公式,可得:
特征T出现的概率P(t),只要用出现过T的样本数除以总样本数就可以了;P(Ci|t)表示出现T的时候,类别Ci出现的概率,只要用出现了T并且属于类别Ci的样本数除以出现了T的样本数就得到了。
(3)信息增益
根据信息增益的公式,分类系统中特征X的信息增益就是:Gain(D, X) = H(C)-H(C|X)
信息增益是针对一个一个的特征而言的,就是看一个特征X,系统有它和没它的时候信息量各是多少,两者的差值就是这个特征给系统带来的信息增益。每次选取特征的过程都是通过计算每个特征值划分数据集后的信息增益,然后选取信息增益最高的特征。
对于特征取值为二值的情况,特征T给系统带来的信息增益就可以写成系统原本的熵与固定特征T后的条件熵之差:
(4)经过上述一轮信息增益计算后会得到一个特征作为决策树的根节点,该特征有几个取值,根节点就会有几个分支,每一个分支都会产生一个新的数据子集Dk,余下的递归过程就是对每个Dk再重复上述过程,直至子数据集都属于同一类。
在决策树构造过程中可能会出现这种情况:所有特征都作为分裂特征用光了,但子集还不是纯净集(集合内的元素不属于同一类别)。在这种情况下,由于没有更多信息可以使用了,一般对这些子集进行“多数表决”,即使用此子集中出现次数最多的类别作为此节点类别,然后将此节点作为叶子节点。
(5)结合实例进行说明:
上面的训练集有4个属性,即属性集合A={OUTLOOK, TEMPERATURE, HUMIDITY, WINDY};而类标签有2个,即类标签集合C={Yes, No},分别表示适合户外运动和不适合户外运动,其实是一个二分类问题。
我们已经计算过信息增益,这里直接列出来,如下所示:
数据集D包含14个训练样本,其中属于类别“Yes”的有9个,属于类别“No”的有5个,则计算其信息熵:
1 | Info(D) = -9/14 * log2(9/14) - 5/14 * log2(5/14) = 0.940 |
下面对属性集中每个属性分别计算信息熵,如下所示:
1 | Info(OUTLOOK) = 5/14 * [- 2/5 * log2(2/5) – 3/5 * log2(3/5)] + 4/14 * [ - 4/4 * log2(4/4) - 0/4 * log2(0/4)] + 5/14 * [ - 3/5 * log2(3/5) – 2/5 * log2(2/5)] = 0.694 |
2 | Info(TEMPERATURE) = 4/14 * [- 2/4 * log2(2/4) – 2/4 * log2(2/4)] + 6/14 * [ - 4/6 * log2(4/6) - 2/6 * log2(2/6)] + 4/14 * [ - 3/4 * log2(3/4) – 1/4 * log2(1/4)] = 0.911 |
3 | Info(HUMIDITY) = 7/14 * [- 3/7 * log2(3/7) – 4/7 * log2(4/7)] + 7/14 * [ - 6/7 * log2(6/7) - 1/7 * log2(1/7)] = 0.789 |
4 | Info(WINDY) = 6/14 * [- 3/6 * log2(3/6) – 3/6 * log2(3/6)] + 8/14 * [ - 6/8 * log2(6/8) - 2/8 * log2(2/8)] = 0.892 |
根据上面的数据,我们可以计算选择第一个根结点所依赖的信息增益值,计算如下所示:
1 | Gain(OUTLOOK) = Info(D) - Info(OUTLOOK) = 0.940 - 0.694 = 0.246 |
2 | Gain(TEMPERATURE) = Info(D) - Info(TEMPERATURE) = 0.940 - 0.911 = 0.029 |
3 | Gain(HUMIDITY) = Info(D) - Info(HUMIDITY) = 0.940 - 0.789 = 0.151 |
4 | Gain(WINDY) = Info(D) - Info(WINDY) = 0.940 - 0.892 = 0.048 |
3、C4.5算法
以信息增益进行分类决策时,存在偏向于取值较多的特征的问题。于是为了解决这个问题人们有开发了基于信息增益比的分类决策方法,也就是C4.5。C4.5与ID3都是利用贪心算法进行求解,不同的是分类决策的依据不同。
因此,C4.5算法在结构与递归上与ID3完全相同,区别就在于选取决断特征时选择信息增益比最大的。
信息增益比率度量是用ID3算法中的的增益度量Gain(D,X)和分裂信息度量SplitInformation(D,X)来共同定义的。分裂信息度量SplitInformation(D,X)就相当于特征X(取值为x1,x2,……,xn,各自的概率为P1,P2,...,Pn,Pk就是样本空间中特征X取值为xk的数量除上该样本空间总数)的熵。
SplitInformation(D,X) = -P1 log2(P1)-P2 log2(P)-,...,-Pn log2(Pn)
GainRatio(D,X) = Gain(D,X)/SplitInformation(D,X)
在ID3中用信息增益选择属性时偏向于选择分枝比较多的属性值,即取值多的属性,在C4.5中由于除以SplitInformation(D,X)=H(X),可以削弱这种作用。
C4.5既可以处理离散型属性,也可以处理连续性属性。在选择某节点上的分枝属性时,对于离散型描述属性,C4.5的处理方法与ID3相同。对于连续分布的特征,其处理方法是:
先把连续属性转换为离散属性再进行处理。虽然本质上属性的取值是连续的,但对于有限的采样数据它是离散的,如果有N条样本,那么我们有N-1种离散化的方法:<=vj的分到左子树,>vj的分到右子树。计算这N-1种情况下最大的信息增益率。另外,对于连续属性先进行排序(升序),只有在决策属性(即分类发生了变化)发生改变的地方才需要切开,这可以显著减少运算量。经证明,在决定连续特征的分界点时采用增益这个指标(因为若采用增益率,splittedinfo影响分裂点信息度量准确性,若某分界点恰好将连续特征分成数目相等的两部分时其抑制作用最大),而选择属性的时候才使用增益率这个指标能选择出最佳分类特征。
在C4.5中,对连续属性的处理如下:
1. 对特征的取值进行升序排序
2. 两个特征取值之间的中点作为可能的分裂点,将数据集分成两部分,计算每个可能的分裂点的信息增益(InforGain)。优化算法就是只计算分类属性发生改变的那些特征取值。
3. 选择修正后信息增益(InforGain)最大的分裂点作为该特征的最佳分裂点
4. 计算最佳分裂点的信息增益率(Gain Ratio)作为特征的Gain Ratio。注意,此处需对最佳分裂点的信息增益进行修正:减去log2(N-1)/|D|(N是连续特征的取值个数,D是训练数据数目,此修正的原因在于:当离散属性和连续属性并存时,C4.5算法倾向于选择连续特征做最佳树分裂点)
分析分类回归树的递归建树过程,不难发现它实质上存在着一个数据过度拟合问题。在决策树构造时,由于训练数据中的噪音或孤立点,许多分枝反映的是训练数据中的异常,使用这样的判定树对类别未知的数据进行分类,分类的准确性不高。因此试图检测和减去这样的分支,检测和减去这些分支的过程被称为树剪枝。树剪枝方法用于处理过分适应数据问题。通常,这种方法使用统计度量,减去最不可靠的分支,这将导致较快的分类,提高树独立于训练数据正确分类的能力。
决策树常用的剪枝常用的简直方法有两种:预剪枝(Pre-Pruning)和后剪枝(Post-Pruning)。预剪枝是根据一些原则及早的停止树增长,如树的深度达到用户所要的深度、节点中样本个数少于用户指定个数、不纯度指标下降的最大幅度小于用户指定的幅度等。预剪枝的核心问题是如何事先指定树的最大深度,如果设置的最大深度不恰当,那么将会导致过于限制树的生长,使决策树的表达式规则趋于一般,不能更好地对新数据集进行分类和预测。除了事先限定决策树的最大深度之外,还有另外一个方法来实现预剪枝操作,那就是采用检验技术对当前结点对应的样本集合进行检验,如果该样本集合的样本数量已小于事先指定的最小允许值,那么停止该结点的继续生长,并将该结点变为叶子结点,否则可以继续扩展该结点。
后剪枝则是通过在完全生长的树上剪去分枝实现的,通过删除节点的分支来剪去树节点,可以使用的后剪枝方法有多种,比如:代价复杂性剪枝、最小误差剪枝、悲观误差剪枝等等。后剪枝操作是一个边修剪边检验的过程,一般规则标准是:在决策树的不断剪枝操作过程中,将原样本集合或新数据集合作为测试数据,检验决策树对测试数据的预测精度,并计算出相应的错误率,如果剪掉某个子树后的决策树对测试数据的预测精度或其他测度不降低,那么剪掉该子树。
关于后剪枝的具体理论可以参考“数据挖掘十大经典算法--CART: 分类与回归树”剪枝部分。
(4)依旧是结合上边的图形进行解释说明
上面的训练集有4个属性,即属性集合A={OUTLOOK, TEMPERATURE, HUMIDITY, WINDY};而类标签有2个,即类标签集合C={Yes, No},分别表示适合户外运动和不适合户外运动,其实是一个二分类问题。
我们已经计算过信息增益,这里直接列出来,如下所示:
数据集D包含14个训练样本,其中属于类别“Yes”的有9个,属于类别“No”的有5个,则计算其信息熵:
1 | Info(D) = -9/14 * log2(9/14) - 5/14 * log2(5/14) = 0.940 |
下面对属性集中每个属性分别计算信息熵,如下所示:
1 | Info(OUTLOOK) = 5/14 * [- 2/5 * log2(2/5) – 3/5 * log2(3/5)] + 4/14 * [ - 4/4 * log2(4/4) - 0/4 * log2(0/4)] + 5/14 * [ - 3/5 * log2(3/5) – 2/5 * log2(2/5)] = 0.694 |
2 | Info(TEMPERATURE) = 4/14 * [- 2/4 * log2(2/4) – 2/4 * log2(2/4)] + 6/14 * [ - 4/6 * log2(4/6) - 2/6 * log2(2/6)] + 4/14 * [ - 3/4 * log2(3/4) – 1/4 * log2(1/4)] = 0.911 |
3 | Info(HUMIDITY) = 7/14 * [- 3/7 * log2(3/7) – 4/7 * log2(4/7)] + 7/14 * [ - 6/7 * log2(6/7) - 1/7 * log2(1/7)] = 0.789 |
4 | Info(WINDY) = 6/14 * [- 3/6 * log2(3/6) – 3/6 * log2(3/6)] + 8/14 * [ - 6/8 * log2(6/8) - 2/8 * log2(2/8)] = 0.892 |
根据上面的数据,我们可以计算选择第一个根结点所依赖的信息增益值,计算如下所示:
1 | Gain(OUTLOOK) = Info(D) - Info(OUTLOOK) = 0.940 - 0.694 = 0.246 |
2 | Gain(TEMPERATURE) = Info(D) - Info(TEMPERATURE) = 0.940 - 0.911 = 0.029 |
3 | Gain(HUMIDITY) = Info(D) - Info(HUMIDITY) = 0.940 - 0.789 = 0.151 |
4 | Gain(WINDY) = Info(D) - Info(WINDY) = 0.940 - 0.892 = 0.048 |
接下来,我们计算分裂信息度量H(V):
属性OUTLOOK有3个取值,其中Sunny有5个样本、Rainy有5个样本、Overcast有4个样本,则
1 | H(OUTLOOK) = - 5/14 * log2(5/14) - 5/14 * log2(5/14) - 4/14 * log2(4/14) = 1.577406282852345 |
属性TEMPERATURE有3个取值,其中Hot有4个样本、Mild有6个样本、Cool有4个样本,则
1 | H(TEMPERATURE) = - 4/14 * log2(4/14) - 6/14 * log2(6/14) - 4/14 * log2(4/14) = 1.5566567074628228 |
属性HUMIDITY有2个取值,其中Normal有7个样本、High有7个样本,则
1 | H(HUMIDITY) = - 7/14 * log2(7/14) - 7/14 * log2(7/14) = 1.0 |
属性WINDY有2个取值,其中True有6个样本、False有8个样本,则
1 | H(WINDY) = - 6/14 * log2(6/14) - 8/14 * log2(8/14) = 0.9852281360342516 |
根据上面计算结果,我们可以计算信息增益率,如下所示:
1 | IGR(OUTLOOK) = Info(OUTLOOK) / H(OUTLOOK) = 0.246/1.577406282852345 = 0.15595221261270145 |
2 | IGR(TEMPERATURE) = Info(TEMPERATURE) / H(TEMPERATURE) = 0.029 / 1.5566567074628228 = 0.018629669509642094 |
3 | IGR(HUMIDITY) = Info(HUMIDITY) / H(HUMIDITY) = 0.151/1.0 = 0.151 |
4 | IGR(WINDY) = Info(WINDY) / H(WINDY) = 0.048/0.9852281360342516 = 0.048719680492692784 |
根据计算得到的信息增益率进行选择属性集中的属性作为决策树结点,对该结点进行分裂。
4:CART算法
(1)简述
创建分类树递归过程中,CART每次都选择当前数据集中具有最小Gini信息增益的特征作为结点划分决策树。ID3算法和C4.5算法虽然在对训练样本集的学习中可以尽可能多地挖掘信息,但其生成的决策树分支、规模较大,CART算法的二分法可以简化决策树的规模,提高生成决策树的效率。下面是CART算法与C4.5相比的主要差别
1:CART只能为二叉树,而C4.5可以为多叉树
2:CART中的输入变量和输出变量可以是分类型也可以是数值型,而C4.5的输出变量只能是分类型
3:CART 使用GINI系数作为变量的不纯度量,而C4.5采用信息增益率(ID3使用信息增益作为属性选择标准)
4:如果目标变量是标称的,并且具有两个以上的类别,则CART可能考虑将目标类别合并成两个超类别
5:如果目标变量是连续的,则CART算法找出一组基于树的回归方法来预测目标变量
6:对于缺失值得处理方法不同,CART采用代理测试来估计测试的输出值,而C4.5直接将其分配到该分支中概率最大的分类
7:对决策树的剪枝方法不同,CART采用代价复杂度模型,通过交叉验证来估计对预测样本集的误分类损失,产生最小交叉验证误分类估计树。而C4.5启发式的调整在训练集样本上估计出的误差率,使用调整的误差率,以找出评分函数最大化的树
CART与C4.5的不同之处是节点分裂建立在GINI指数这个概念上,GINI指数主要是度量数据划分或训练数据集D的不纯度为主。GINI值越小,表明样本的纯净度越高(即该样本只属于同一类的概率越高)。衡量出数据集某个特征所有取值的Gini指数后,就可以得到该特征的Gini Split info,也就是GiniGain。不考虑剪枝情况下,分类决策树递归创建过程中就是每次选择GiniGain最小的节点做分叉点,直至子数据集都属于同一类或者所有特征用光了。
因为CART二分的特性,当训练数据具有两个以上的类别,CART需考虑将目标类别合并成两个超类别,这个过程称为双化。
1、Gini指数的概念:
GINI指数是一种不等性度量,通常用来度量收入不平衡,可以用来度量任何不均匀分布,是介于0~1之间的数,0-完全相等,1-完全不相等。分类度量时,总体内包含的类别越杂乱,GINI指数就越大(跟熵的概念很相似)。
对于一个数据集T,其Gini计算方式为:
(n表示类别数,pj表数据集样本不同类别的概率)
2、GiniGain
衡量出某个特征所有取值的Gini指数就可以得到Gini Split Info:
i表示特征的第i个取值
ID3算法中的信息增益相似,这个可以称为是Gini信息增益--Gini Gain。对于CART,i=(1,2),得到在Binary Split情况下的Gini信息增益:
3、属性选择
(1)分类树的属性选择
对于CART分类树的属性选择,针对属性类型分为分类型和数值型,方法有所不同
a、对于分类型属性,由于CART只能建立二叉树,对于取多个值的属性变量,需要将多类别合并成两个类别,形成“超类”,然后计算两“超类”下样本测试输出取值的差异性
b、对于数值型属性,处理方法如下
(1) 对特征的取值进行升序排序
(2) 两个特征取值之间的中点作为可能的分裂点,将数据集分成两部分,计算每个可能的分裂点的GiniGain。优化算法就是只计算分类属性发生改变的那些特征取值
(3)选择GiniGain最小的分裂点作为该特征的最佳分裂点(注意,若修正则此处需对最佳分裂点的Gini Gain减去log2(N-1)/|D|(N是连续特征的取值个数,D是训练数据数目)
实现连续特征数据集划分的Python程序为(采用Numpy matrix,连续特征取值就可以省略排序这一步了):
<span style="font-size:18px;">def binSplitDataSet(dataSet, feature, value): mat0 = dataSet[nonzero(dataSet[:,feature] > value)[0],:][0] mat1 = dataSet[nonzero(dataSet[:,feature] <= value)[0],:][0] return mat0,mat1 </span>
理想的分组是将两组样本测试输出的取值的差异性总和达到最小,即纯度最大,也就是使两组输出变量取值的差异性下降最快“纯度”增加最快
(2)回归树的属性选择
回归树确定当前最佳分组变量的策略与分类树相同,主要不同在于度量节点测试输出值差异性的指标有所不同,由于回归树的测试输出为数值型,因此方差是最理想的指标其定义为:
正是因为CART树是二叉树,所以对于样本的有N>=3个取值的离散特征的处理时也只能有两个分支,这就要通过组合人为的创建二取值序列并取GiniGain最小者作为树分叉决策点。如某特征值具有['young','middle','old']三个取值,那么二分序列会有如下3种可能性(空集和满集在CART分类中没有意义):
[(('young',), ('middle', 'old')), (('middle',), ('young', 'old')), (('old',), ('young', 'middle'))]
采用CART算法,就需要分别计算按照上述List中的二分序列做分叉时的Gini指数,然后选取产生最小的GINIGain的二分序列做该特征的分叉二值序列参与树构建的递归。如果某特征取值有4个,那么二分序列组合就有7种,5个取值就有15种组合,创建多值离散特征二分序列组合可采用Python的itertools包,程序如下:
<span style="font-size:18px;">from itertools import * import pdb def featuresplit(features): count = len(features) featureind = range(count) featureind.pop(0) #get value 1~(count-1) combiList = [] for i in featureind: com = list(combinations(features, len(features[0:i]))) combiList.extend(com) combiLen = len(combiList) featuresplitGroup = zip(combiList[0:combiLen/2], combiList[combiLen-1:combiLen/2-1:-1]) return featuresplitGroup if __name__ == '__main__': test= range(3) splitGroup = featuresplit(test) print 'splitGroup', len(splitGroup), splitGroup test= range(4) splitGroup = featuresplit(test) print 'splitGroup', len(splitGroup),splitGroup test= range(5) splitGroup = featuresplit(test) print 'splitGroup', len(splitGroup),splitGroup test= ['young','middle','old'] splitGroup = featuresplit(test) print 'splitGroup', len(splitGroup),splitGroup</span>
因此CART不适用于离散特征有多个取值可能的场景。此时,若定要使用CART,则最好预先人为的将离散特征的取值缩减。
那么对于二分后的左右分支,如果特征取值tuple中元素多于2个,该特征是否还要继续参与当前子数据集的二分呢?
我认为需要,因此该特征继续参与分类决策树递归,直至左右分支上该特征的取值都是唯一的(即不再包含该特征)。那么离散特征的datasplit函数就应该:如果按照当前分支特征分叉后,分支上特征取值tuple>=2,则分支子数据集保留该特征,该tuple继续参与上的树构建的递归;否则分支子数据集删除该特征。
def splitDataSet(dataSet, axis, valueTuple): '''return dataset satisfy condition dataSet[i][axis] == valueTuple, and remove dataSet[i][axis] if len(valueTuple)==1''' retDataSet = [] length = len(valueTuple) if length ==1: for featVec in dataSet: if featVec[axis] == valueTuple[0]: reducedFeatVec = featVec[:axis] #chop out axis used for splitting reducedFeatVec.extend(featVec[axis+1:]) retDataSet.append(reducedFeatVec) else: for featVec in dataSet: if featVec[axis] in valueTuple: retDataSet.append(featVec) return retDataSet
分析分类回归树的递归建树过程,不难发现它实质上存在着一个数据过度拟合问题。在决策树构造时,由于训练数据中的噪音或孤立点,许多分枝反映的是训练数据中的异常,使用这样的判定树对类别未知的数据进行分类,分类的准确性不高。因此试图检测和减去这样的分支,检测和减去这些分支的过程被称为树剪枝。树剪枝方法用于处理过分适应数据问题。通常,这种方法使用统计度量,减去最不可靠的分支,这将导致较快的分类,提高树独立于训练数据正确分类的能力。决策树常用的剪枝常用的简直方法有两种:预剪枝(Pre-Pruning)和后剪枝(Post-Pruning)。预剪枝是根据一些原则及早的停止树增长,如树的深度达到用户所要的深度、节点中样本个数少于用户指定个数、不纯度指标下降的最大幅度小于用户指定的幅度等;后剪枝则是通过在完全生长的树上剪去分枝实现的,通过删除节点的分支来剪去树节点,可以使用的后剪枝方法有多种,比如:代价复杂性剪枝、最小误差剪枝、悲观误差剪枝等等。
CART常采用事后剪枝方法,构建决策树过程中的第二个关键就是用独立的验证数据集对训练集生长的树进行剪枝。
关于后剪枝的具体理论可以参考“数据挖掘十大经典算法--CART: 分类与回归树”剪枝部分。
三种决策树算法的Python实现
1:ID3算法
#coding=utf-8 ''' ''' from math import log import operator def createDataSet(): dataSet =[[1,1,'yes'], [1,1,'yes'], [1,0,'no'], [0,1,'no'], [0,1,'no']] labels = ['no surfacing','flippers'] #分类的属性 return dataSet,labels #计算给定数据的香农熵 def calcShannonEnt(dataSet): numEntries = len(dataSet) labelCounts = {} for featVec in dataSet: currentLabel = featVec[-1] #获得标签 #构造存放标签的字典 if currentLabel not in labelCounts.keys(): labelCounts[currentLabel]=0 labelCounts[currentLabel]+=1 #对应的标签数目+1 #计算香农熵 shannonEnt = 0.0 for key in labelCounts: prob = float(labelCounts[key])/numEntries shannonEnt -=prob*log(prob,2) return shannonEnt #划分数据集,三个参数为带划分的数据集,划分数据集的特征,特征的返回值 def splitDataSet(dataSet,axis,value): retDataSet = [] for featVec in dataSet: if featVec[axis] ==value: #将相同数据集特征的抽取出来 reducedFeatVec = featVec[:axis] reducedFeatVec.extend(featVec[axis+1:]) retDataSet.append(reducedFeatVec) return retDataSet #返回一个列表 #选择最好的数据集划分方式 def chooseBestFeatureToSplit(dataSet): numFeature = len(dataSet[0])-1 baseEntropy = calcShannonEnt(dataSet) bestInfoGain = 0.0 beatFeature = -1 for i in range(numFeature): featureList = [example[i] for example in dataSet] #获取第i个特征所有的可能取值 uniqueVals = set(featureList) #从列表中创建集合,得到不重复的所有可能取值ֵ newEntropy = 0.0 for value in uniqueVals: subDataSet = splitDataSet(dataSet,i,value) #以i为数据集特征,value为返回值,划分数据集 prob = len(subDataSet)/float(len(dataSet)) #数据集特征为i的所占的比例 newEntropy +=prob * calcShannonEnt(subDataSet) #计算每种数据集的信息熵 infoGain = baseEntropy- newEntropy #计算最好的信息增益,增益越大说明所占决策权越大 if (infoGain > bestInfoGain): bestInfoGain = infoGain bestFeature = i return bestFeature #递归构建决策树 def majorityCnt(classList): classCount = {} for vote in classList: if vote not in classCount.keys(): classCount[vote]=0 classCount[vote]+=1 sortedClassCount = sorted(classCount.iteritems(),key =operator.itemgetter(1),reverse=True)#排序,True升序 return sortedClassCount[0][0] #返回出现次数最多的 #创建树的函数代码 def createTree(dataSet,labels): classList = [example[-1] for example in dataSet] if classList.count(classList[0])==len(classList):#类别完全相同则停止划分 return classList[0] if len(dataSet[0]) ==1: #遍历完所有特征值时返回出现次数最多的 return majorityCnt(classList) bestFeat = chooseBestFeatureToSplit(dataSet) #选择最好的数据集划分方式 bestFeatLabel = labels[bestFeat] #得到对应的标签值 myTree = {bestFeatLabel:{}} del(labels[bestFeat]) #清空labels[bestFeat],在下一次使用时清零 featValues = [example[bestFeat] for example in dataSet] uniqueVals = set(featValues) for value in uniqueVals: subLabels =labels[:] #递归调用创建决策树函数 myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet,bestFeat,value),subLabels) return myTree if __name__=="__main__": dataSet,labels = createDataSet() print createTree(dataSet,labels)
2:C4.5算法
待续.................. 3:CART算法
待续...............
吐槽一下:博主为了整理这篇博客也是整了好几天,有总结不到位或者错的地方请大家指正,还有C4.5和CART算法的Python代码会在后续更新,哪位网友如果有现成的代码的话也可以留言让博主参考一下,谢谢
=====================================================================
github 源码同步:https://github.com/Thinkgamer/Machine-Learning-With-Python
=====================================================================