源代码在github(https://github.com/fansking/Machine/blob/master/Machine/trees.py) 里
某一事物有多种属性,但是很多属性并不决定它是否为这一事物的。比如说有的猫是有毛的,有的猫是无毛的,所有没有毛并不能决定是不是猫。那么对于如此之多的属性,我们如何从这里挑选出一个或多个属性来作为决定性因素呢。这里就要介绍信息增益和熵的概念了。
当我们给某一个事物增加一个属性从而使类别和属性保持一致,比如,给鱼增加是否可以再水里生活,大部分鱼都是是的,而不是鱼的大部分也无法在水里生活。这就将是不是鱼很好的区分开,能否在水下生活带来了很好的信息增益。那么我们如何找到这个属性呢?
那就是(所有的属性在一起的信息期望值)-(去掉某属性的信息期望值),值越大则说明这一量时对分类正确性有最大的影响。
香农熵的计算方法如下,其中p(xi)是此属性特征出现的频率。
而决策树的构建就是一级一级向下按照影响因素从大到小依次排列构建的。
下面是一个函数一个函数的说明:
def createDataSet():
dataSet = [[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 1, 'no']]
labels = ['no surfacing','flippers']
#change to discrete values
return dataSet, labels
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
labelCounts = {}
for featVec in dataSet: #the the number of unique elements and their occurance
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) #log base 2
return shannonEnt
这里第一个函数是构建数据集,我们接下来的数据都是它。data里第一列第二列对应label里的两个数据特征,最后一列是他的类别。第二个函数就是给定一个数据集来计算香农熵的值了。
def splitDataSet(dataSet, axis, value):
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis] #chop out axis used for splitting
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 #the last column is used for the labels
baseEntropy = calcShannonEnt(dataSet)
bestInfoGain = 0.0; bestFeature = -1
for i in range(numFeatures): #iterate over all the features
featList = [example[i] for example in dataSet]#create a list of all the examples of this feature
uniqueVals = set(featList) #get a set of unique values
newEntropy = 0.0
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value) #subDataSet是除去本次遍历的列的内容,我们需要得到他的长度,并根据长度求得权重系数
prob = len(subDataSet)/float(len(dataSet))
newEntropy += prob * calcShannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy #calculate the info gain; ie reduction in entropy
if (infoGain > bestInfoGain): #compare this to the best gain so far
bestInfoGain = infoGain #if better than current best, set to best
bestFeature = i
return bestFeature #returns an integer
这里第一个函数就是对一个数据集,删除指定索引列,并返回这个子数据集。
第二个函数就是一开头说的,遍历所有列,对删除此列后计算熵变化最大的就是要找的影响最大的数据特征,返回的是对应数据特征的索引。
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]#stop splitting when all of the classes are equal
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[:] #python是使用引用的方式来传递变量,所以直接用等号会导致同步变化,我们需要拷贝一份新的值给子标签
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)#递归得到下一层内容
return myTree
第一个函数就是举手表决,不多解释了
第二个函数是递归构造决策树(字典),每次加入的都是影响最高的数据特征。
得到的结果如下: