以下内容摘选自http://www.cnblogs.com/leoo2sk/archive/2010/09/19/decision-tree.html
1、决策数的定义
决策树(decision tree)是一个树结构(可以是二叉树或非二叉树)。其每个非叶节点表示一个特征属性上的测试,每个分支代表这个特征属性在某个值域上的输出,而每个叶节点存放一个类别。使用决策树进行决策的过程就是从根节点开始,测试待分类项中相应的特征属性,并按照其值选择输出分支,直到到达叶子节点,将叶子节点存放的类别作为决策结果。
2、决策树的构造
决策树的构造中最关键的部分就是选择最优的特征进行划分数据集,其目的是将无序的数据变得更有序,其目标是让各个分裂子集尽可能地“纯”。尽可能“纯”就是尽量让一个分裂子集中待分类项属于同一类别(label相同)。分裂属性分为三种不同的情况:
1、属性是离散值且不要求生成二叉决策树。此时用属性的每一个划分作为一个分支。
2、属性是离散值且要求生成二叉决策树。此时使用属性划分的一个子集进行测试,按照“属于此子集”和“不属于此子集”分成两个分支。
3、属性是连续值。此时确定一个值作为分裂点split_point,按照>split_point和<=split_point生成两个分支。
构造决策树的关键性内容是进行属性选择度量,属性选择度量是一种选择分裂准则,是将给定的类标记的训练数据D“最好”地分成个体类的启发式方法,它决定了拓扑结构及分裂点split_point的选择。
属性选择度量的方法有很多中,机器学习实战上以ID3算法,此外常见的还有C4.5以及CART的方法。同时对于特征来说一般采取不放回的选择每个属性只能被选择一次,选择完成该属性后该特征在就被剔除出数据集,算是一种不回溯的贪心算法。
2.1 ID3算法
ID3算法采用香农熵作为度量信息度量方式,并且按照信息增益划分数据集,选择具有最高的信息增益进行划分。
香农熵定义为信息的期望值计算公式如下:
计算代码如下:
from math import log
def calcShnnonEnt(dataSet):
numEnties = len(dataSet)#训练数据的个数
labelCount = {}#新建字典记录每个分类下的数据个数
for featVec in dataSet:#统计每个分类的个数
currentLabel = featVec[-1]
if currentLabel not in labelCount.keys():
labelCount[currentLabel] = 0
labelCount[currentLabel] += 1
for key in labelCount:#针对分类进行计算香农熵
prob = float(labelCount[key]) / numEntris
shannonEnt -= prob * log(prob, 2)
return shannonEnt
在数据进行划分后计算划分后的香农熵的计算公式:
其中Dj为划分的数据子集;计算代码如下:
#此处代码中包含了一个划分数据的子函数
#数据划分函数
#参数说明:dataSet待划分的数据集, axis划分数据集的特征, value划分数据集特征的值
def splitData(dataSet, aixs, value):
reDateSet = []
for featVec in dataSet:
if featVec[axis] == value:
reduceFeatVec = featVec[:axis]
reduceFeatVec.extend(featVec[axis + 1 :]#注意append与extend的区别!
retDataSet.append(reduceFeatVec)
return retDataSet
#计算香农熵信息增益
#参数与上面的函数有相同的含义
def calcShannonGain(dataSet, axis):
featValueList = [example[axis] for example in dataSet]#选择该特征的所有的取值
uniqueValueList = set(featValueList)#将取值进行唯一化
shannonGain = 0.0
for value in uniqueValueList:
subDataSet = splitData(dataSet, axis, value)
prob = len(subDataSet) / float(len(dataSet))
shannonGain += prob * calcShnnonEnt(subDateSet)
return shannonGain
对于特征属性为连续值,可以如此使用ID3算法:
先将D中元素按照特征属性排序,则每两个相邻元素的中间点可以看做潜在分裂点,从第一个潜在分裂点开始,分裂D并计算两个集合的期望信息,具有最小期望信息的点称为这个属性的最佳分裂点,其信息期望作为此属性的信息期望。
2.2 C4.5 算法
ID3算法存在一个问题,就是偏向于多值属性,例如,如果存在唯一标识属性ID(每一个特征的取值都不同可以作为唯一标识使用),则ID3会选择它作为分裂属性,这样虽然使得划分充分纯净,但这种划分对分类几乎毫无用处。ID3的后继算法C4.5使用增益率(gain ratio)的信息增益扩充,试图克服这个偏倚。
C4.5算法首先定义了“分裂信息”,其定义可以表示成:
其中各符号意义与ID3算法相同,计算代码如下:
1 #计算分裂信息值 2 def split_info ( dataSet, axis) 3 featVec =[example[axis] for example in dataSet] 4 featSet = set(featVec) 5 info = 0.0 6 for value in featSet: 7 subDataSet = split(dataSet, axis, value) 8 ratio = float(len(subDateSet) )/ len(dataSet) 9 info -= ratio * log(ratio, 2) 10 return info
然后,增益率被定义为:
gain(A):分割前的信息增益, split_info(A):分裂信息值。
C4.5选择具有最大增益率的属性作为分裂属性,其具体应用与ID3类似。
3 关于决策数的补充问题
3.1 特征用完了怎么办
在决策树构造过程中可能会出现这种情况:所有属性都作为分裂属性用光了,但有的子集还不是纯净集,即集合内的元素不属于同一类别。在这种情况下,由于没有更多信息可以使用了,一般对这些子集进行“多数表决”,即使用此子集中出现次数最多的类别作为此节点类别,然后将此节点作为叶子节点。
关于多数表决,就是判断现在节点中的数据属于哪一类最多就将当前的节点设置成那一类,表决代码如下:
1 #对某个节点的归属进行多数表决 2 def majorityCnt(classList): 3 classCount = {} 4 for vote in classList: 5 if vote not in calssCount.keys(): 6 classCount[vote] = 0 7 classCount[vote] += 1 8 sortedClassCount = sort(classCount.iteritems(), key = operator.itemgetter(1), reverse = True) 9 return sortedClassCount[0][0]
3.2 剪枝
在实际构造决策树时,通常要进行剪枝,这时为了处理由于数据中的噪声和离群点导致的过分拟合问题。剪枝有两种:
先剪枝——在构造过程中,当某个节点满足剪枝条件,则直接停止此分支的构造。
后剪枝——先构造完成完整的决策树,再通过某些条件遍历树进行剪枝。
更多剪枝相关的详见维基百科中的剪枝。
4、建树操作
建树的基本流程图如下:
使用递归程序建树,数采用字典的数据结构进行存储:
1 #使用递归进行建树操作 2 3 def createTree(dataSet, labels): 4 #从数据集中提取标签 5 classList = [example[-1] for example in dataSet] 6 #如果标签中的值都相同,则表明建树到叶节点返回当前标签值 7 if len(classList) == classList.count(classList[0]): 8 return classList[0] 9 #如果特征用尽但是标签值不唯一进行多数表决 10 if len(dataSet[0]) == 1: 11 return majorityCnt(classList) 12 #选择最好的划分特征 13 bestFeat = chooseBestFeature(dataSet) 14 bestLabel = labels[bestFeat] 15 #建立一个节点 16 myTree = {bestLabel: {} } 17 #从特征名称表中删除已经使用特征 18 del(labels[bestFeat]) 19 featValues = [example[bestFeat] for example in dataSet] 20 uniqueValues = set(featValues) 21 #递归建树 22 for value in uniqueValues: 23 subLabels = labels[:] 24 myTree[bestLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels) 25 return myTree
代码中没有写选择最好的划分特征的代码,但是结合计算信息熵和信息增益的代码可以很快的改造出来一个选择最好的划分特征的程序。
5、决策树的优缺点
5.1 优点
- 决策树易于理解和实现.人们在通过解释后都有能力去理解决策树所表达的意义。
- 对于决策树,数据的准备往往是简单或者是不必要的.其他的技术往往要求先把数据一般化,比如去掉多余的或者空白的属性。
- 能够同时处理数据型和常规型属性。其他的技术往往要求数据属性的单一。
- 是一个白盒模型如果给定一个观察的模型,那么根据所产生的决策树很容易推出相应的逻辑表达式。
- 易于通过静态测试来对模型进行评测。表示有可能测量该模型的可信度。
- 在相对短的时间内能够对大型数据源做出可行且效果良好的结果。
5.2 缺点
- 对于那些各类别样本数量不一致的数据,在决策树当中信息增益的结果偏向于那些具有更多数值的特征。
6、决策树模型的评价指标
建立了决策树模型后需要给出该模型的评估值,这样才可以来判断模型的优劣。学习算法模型使用训练集 (training set) 建立模型,使用校验集 (test set) 来评估模型。本文通过评估指标和评估方法来评估决策树模型。 评估指标有分类准确度、召回率、虚警率和精确度等。而这些指标都是基于混淆矩阵 (confusion matrix) 进行计算的。