1 决策树概念
决策树是通过一系列的规则进行分类的过程,决策树分为分类树和回归树,分类树是对离散变量进行决策,回归树是对连续变量进行决策。决策树的构造过程实际上是找到具有决定性作用的特征,决定性作用最大的那个作为根节点,然后递归找到次大的,以此类推。
一棵决策树的生成过程主要分为以下3个部分:
-
特征选择:特征选择是从特征集合中找到当前节点的分裂标准,如何选择特征有不同的量化评估标准。
-
决策树生成: 根据选择的特征评估标准,从上至下递归地生成子节点,直到数据集不可分则停止决策树停止生长。
-
剪枝:决策树容易过拟合,一般来需要剪枝,缩小树结构规模、缓解过拟合。剪枝技术有预剪枝和后剪枝两种。
2 决策树典型算法
我们上面说到了决策树算法首先要进行特征选择,那么如何选择最佳的特征作为划分依据?量化的方法有很多,其中有一项就是信息论度量信息分类。基于信息论的决策树算法有ID3、CART和C4.5等算法,C4.5和CART是从ID3算法中衍生而来。
2.1 ID3算法
ID3算法根据信息论的信息增益评估和选择特征,每次选择信息增益最大的特征作为判断模块。ID3算法可以用来划分数据集,没有剪枝的过程,为了去除过度数据匹配的问题,可通过裁剪合并相邻的无法产生大量信息增益的叶子节点(例如设置信息增益阀值)。但是使用信息增益来选择特征有一个缺点:如果某个属性的属性值越多,那么就越有可能拿它来作为分裂标准,但这样有时候是没有意义的,另外ID3不能处理连续分布的数据特征。
2.2 C4.5算法
为了解决上文提到的ID3算法的两个问题,C4.5算法应运而生。C4.5算法用信息增益率来选择分裂的属性,克服了偏向选择属性取值多的问题;能够对连续属性进行离散化处理,以及对不完整的数据进行处理。C4.5算法离散化连续属性是通过使用二元切分,即求一个特定的分裂值,如果特征值大于分裂值就走左子树,反之右子树。分裂值的选择标准是使得划分后的子树“混乱程度降低”。C4.5算法产生的分类规则易于理解、准确率较高。但是C4.5有个缺点:效率低,在构造的过程中需要对数据集进行多次顺序扫描和排序,故只适合能够驻留于内存的数据集。
对于连续分布的特征,C4.5处理方法是:
- 对特征的取值进行升序排序
- 两个特征取值之间的中点作为可能的分裂点,将数据集分成两部分,计算每个可能的分裂点的信息增益(InforGain)。优化算法就是只计算分类属性发生改变的那些特征取值。
- 选择修正后信息增益(InforGain)最大的分裂点作为该特征的最佳分裂点
- 计算最佳分裂点的信息增益率(Gain Ratio)作为特征的Gain Ratio。注意,此处需对最佳分裂点的信息增益进行修正:减去log2(N-1)/|D|(N是连续特征的取值个数,D是训练数据数目,此修正的原因在于:当离散属性和连续属性并存时,C4.5算法倾向于选择连续特征做最佳树分裂点)
2.3 CART算法
CART算法的全称是Classification And Regression Tree,采用的是Gini指数(选Gini指数最小的特征s)作为分裂标准,同时它也是包含后剪枝操作。ID3算法和C4.5算法虽然在对训练样本集的学习中可以尽可能多地挖掘信息,但其生成的决策树分支较大,规模较大。为了简化决策树的规模,提高生成决策树的效率,就出现了根据GINI系数来选择测试属性的决策树算法CART。
3 数学原理
假设一个分类系统的样本空间(D,C),D表示样本,样本有m个特征;C表示有n个类别,样本属于类别Ci的概率表示为P(Ci)。
该分类系统的熵为:
样本特征X取值为xi的概率为Pi,该特征被固定为xi的条件信息熵为H(C|X=xi)
信息增益Gain(D,X) = H(C ) - H(C|X)
信息增益比率
4 运行实例
#coding=utf-8
import operator
from math import log
import time
def createDataSet():
dataSet=[[1,1,'a'],
[1,1,'a'],
[1,0,'b'],
[0,1,'b'],
[0,1,'b']]
labels = ['a','b']
return dataSet, labels
#计算香农熵
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
labelCounts = {}
for feaVec in dataSet:
currentLabel = feaVec[-1]
if currentLabel not in labelCounts:
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 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):
numFeatures = len(dataSet[0]) - 1#因为数据集的最后一项是标签
baseEntropy = calcShannonEnt(dataSet)
bestInfoGain = 0.0
bestFeature = -1
for i in range(numFeatures):
featList = [example[i] for example in dataSet]
uniqueVals = set(featList)
newEntropy = 0.0
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value)
prob = len(subDataSet) / float(len(dataSet))
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
return max(classCount)
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])
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
def main():
data,label = createDataSet()
t1 = time.clock()
myTree = createTree(data,label)
t2 = time.clock()
print myTree
print 'execute for ',t2-t1
if __name__=='__main__':
main()