花了一天多时间,终于弄明白了决策树的完整代码,整个构树过程明白了。感觉蛮开心,爽歪歪。
下面写一下我的学习步骤,我们可以一起来~~
我尽量写明白每一步我的学习路径,用了哪些资料以及配合弄懂代码的方法。请多提提意见,谢谢各位,也欢迎粉我。
1. 首先我们用函数自己创建一个数据集
#创建数据集
def creatDataSet():
dataSet = [[1,1,'yes'],
[1,1,'yes'],
[1,0,'no'],
[0,1,'no'],
[0,1,'no']]
labels = ['no surfacing', 'flippers']
return dataSet,labels
dataSet,labels=creatDataSet()
labels
['no surfacing', 'flippers']
2.计算给定数据集的香农熵
这部分是在用代码计算香农熵公式,即用代码写公式并计算结果
def calcShannonEnt(dataSet):
#数据集行数
numEntires = len(dataSet)
#创建空字典记录yes or no的次数
labelCounts = {}
#开始用循环语句统计,该思路和本书KNN算法是一样的
for featVec in dataSet:
currentLabel = featVec[-1]
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1
#初始值是0,然后根据公式,用循环语句开始计算
shannonEnt = 0.0
for key in labelCounts:
#公式计算部分
prob = float(labelCounts[key]) / numEntires
from math import log
shannonEnt -= prob * log(prob, 2)
return shannonEnt
print(calcShannonEnt(dataSet))
0.9709505944546686
3.计算信息增益(信息增益的公式可以手抄一遍,再看代码。便于理解)
这段代码其实挺难理解的,第一个函数我是通过把数据集手写到草稿纸上,逐行代码去理解的。就知道这个函数到底是在干什么了,然后数据集最终变成啥样。知道在干嘛后,很有可能你还不明白为什么要有这步操作?那么就要看第二个函数了,看完就明白了。
两个函数合在一起,是在计算信息增益。同样,数据集手写在草稿纸上,以及公式(一定得写公式,不然可能代码看着看着就糊涂了),一步一步根据代码计算。
#这个函数后面用的上,先看懂代码。后一个函数会让你看懂它的真正作用。
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
#print(splitDataSet(dataSet,1,0))
#该函数选取最优的特征,并返回最优特征的索引值
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 #特征数量
baseEntropy = calcShannonEnt(dataSet) #计算数据集的香农熵
bestInfoGain = 0.0 #信息增益
bestFeature = -1 #最优特征的索引值,-1是随便设置的值,便于后续更新值
for i in range(numFeatures): #遍历所有特征
featList = [example[i] for example in dataSet] #获取dataSet的第i个所有特征
uniqueVals = set(featList) #创建set集合{},元素不可重复
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 #信息增益
#print("第%d个特征的增益为%.3f" % (i, infoGain)) #打印每个特征的信息增益
if (infoGain > bestInfoGain): #计算信息增益
bestInfoGain = infoGain #更新信息增益,找到最大的信息增益
bestFeature = i #记录信息增益最大的特征的索引值
return bestFeature
print("最优特征索引值:" + str(chooseBestFeatureToSplit(dataSet)))
最优特征索引值:0
4.递归构建决策树,用了递归函数
递归函数这一块,其实我理解了很久,想来想去,最后终于明白了。可能我是菜鸡一枚~~。可以辅助西瓜书的决策树那部分例子,然后去看着树。边看树边计算公式边理解代码(三合一,不然很难理解),你会明白到底如何一步一步建造出整个树的。这部分代码是重中之重,因为整个决策树的建树过程就在此,你所看到的决策树,真正的形成过程全部展现无疑。决策树完全无黑盒子过程,它是一个白盒。所以咱们一定要真的弄明白,不然可惜了这个算法!!
真的,想出代码的本书作者实在是太牛掰了!
#这个函数是在下一个函数里面第二个停止条件时候,用上的
def majorityCnt(classList):
classCount={}
for vote in classList: #统计classList中每个元素出现的次数
if vote not in classCount.keys():
classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0] #返回classList中出现次数最多的元素
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet] #取出所有分类标签,yes or no
if classList.count(classList[0]) == len(classList):
return classList[0] #如果类别完全相同,则停止继续划分。这是第一个停止条件
if len(dataSet[0]) == 1: #如果特征都遍历完了,还是没有划分的很纯净,那么就取数量多一点的那个类别。这是第二个停止条件
return majorityCnt(classList) #stop splitting when there are no more features in dataSe
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[:] #copy all of labels, so trees don't mess up existing labels
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
return myTree
myTree=createTree(dataSet,labels)
myTree
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
5.使用决策树分类,依旧用了一个递归函数
def classify(inputTree, featLabels, testVec):
firstStr = next(iter(inputTree)) #获取决策树结点
secondDict = inputTree[firstStr] #下一个字典
featIndex = featLabels.index(firstStr)
for key in secondDict.keys():
if testVec[featIndex] == key:
if type(secondDict[key]).__name__ == 'dict':
classLabel = classify(secondDict[key], featLabels, testVec)
else:
classLabel = secondDict[key]
return classLabel
labels=['no surfacing', 'flippers']
print(classify(myTree, labels, [0,1]))
print(classify(myTree, labels, [0,0]))
print(classify(myTree, labels, [1,1]))
print(classify(myTree, labels, [1,0]))
no
no
yes
no
参考:
1.《西瓜书》决策树部分,公式,数据,树图等等。建议这部分都看吧
2. B站视频,【小甲鱼】零基础入门学习Python。我主要参考了23-25节视频,讲的递归函数。