决策树算法也是分类算法中非常常用的一个算法,首先我们先来了解一下它的基本实现原理:
在给定的一个训练样本中可能包含很多的属性,我们可以根据属性值来划分数据集,通过不断的划分数据集直到所有的叶子节点上的数据集属于同意类。就构造好了一个决策树算法。
接下来我们来仔细的探讨一下这个算法的实现过程:
首先我们来创建一个名为trees.py的文件,在进行编码工作之前我们需要了解一个信息增益的概念。在划分数据集之前和划分数据集之后信息发生的变化称为信息增益,这样我们就可以计算哪一个特征作为根节点,也就是哪一个特征作为第一个划分参考值,这个很重要,关系到数据划分的准确度。计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择。集合信息的度量方式称为香农熵或熵。这里公式就不给大家展示了,(确实不太容易打出来)稍后代码中会给出。这里大家拿来用就行不用进行修改。
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
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob * log(prob,2)
return shannonEnt
第一个函数是数据集的创建,dataSet是一个二维数组包含了训练数据,这里注意最后一列为标签,labels代表每个属性值的含义。接下来的一个函数就是香浓熵的计算,首先计算出数据集中实例的个数,然后遍历数据集并创建一个字典包含标签健以及其在数据集中出现的次数。第二个循环就是用来计算香浓熵。知道怎么计算香浓熵我们就可以用它来进行划分数据集:
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
上面代码就是实现选取特征,划分数据集,计算出最好的划分数据集的特征。在函数调用中数据需要满足一定的数据要求:第一就是数据必须是一种由列表元素组成的元素,而且所有的列表元素都要具有相同的数据长度;第二个要求就是,数据的最后一列或者每个实例的最后一个元素是当前实例的类别标签。这里的数据类型可以是数字或者是字符串。在开始划分数据集之前计算出整个数据集的原始香浓熵,代码的第3行。第一层循环遍历整个数据集创建一个唯一的分类标签。第2层循环就是重点,计算出每种划分方式的信息熵,if语句用于选择出最大的信息增益,返回最好划分的索引值。到此我们已经找出最好划分的特征值。接下来就可以进行构建决策树,下面我们就用递归的方式构建决策树:
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)
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])
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 classify(inputTree,featLabels,testVec):
firstStr = inputTree.keys()[0]
secondDict = inputTree[firstStr]
featIndex = featLabels.index(firstStr)
key = testVec[featIndex]
valueOfFeat = secondDict[key]
if isinstance(valueOfFeat, dict):
classLabel = classify(valueOfFeat, featLabels, testVec)
else: classLabel = valueOfFeat
return classLabel
上面的代码就是一个测试代码,输入参数为刚才建立好的树,属性标签,还有测试数据。这个函数不再进行详细介绍,最后返回测试结果,大家可以在IDLE输入一下代码进行测试:
>>>myDat,labels = trees.createDataSet()
>>>mytree = trees.createTree(myDat,labels)
>>>trees.classify(mytree,labels,[1,0])
'no'
由于决策树是一个计算复杂的算法,它需要消耗大量的时间,所以我们可以将生成的树模型存储起来,下次使用的时候就不用再次训练模型:
def storeTree(inputTree,filename):
import pickle
fw = open(filename,'w')
pickle.dump(inputTree,fw)
fw.close()
def grabTree(filename):
import pickle
fr = open(filename)
return pickle.load(fr)
上面两个函数第一个是将模型进行序列化然后存储起来,第二个是在使用的时候将它读出来。到此这个算法已经全部讲解完毕!