机器学习实战-决策树的构造(代码详细解释)
**决策树的思想:
1.计算原始数据的熵
2.选取数据中的特征进行分类
3.分类以后再求熵,根据对比即可知道那个特征对原始数据的影响最大,此时
就以该特征进行分类(相当于树根据这个特征分叉了)
4.使用递归循环,直到按照最后一个特征分类
5.此时,一棵树就出来了。
from math import log
import operator
# 计算给定数据集的香农熵
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
labelCounts = {}#建立一个空字典,准备装原始数据的每种分类的数目,"yes"=是,"no"=否
for featVec in dataSet:
currentLabel = featVec[-1]#取是否属于”鱼类“的值,既每组数据的最后一个值,作为标签
if currentLabel not in labelCounts.keys():#如果这个标签不在创建的字典中,则填加进去
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1#对该标签对应的值+1
shannonEnt = 0.000#创建一个变量,用于输出熵
for key in labelCounts: #根据求熵的公式进行编程
prob = float(labelCounts[key])/numEntries #利用for循环,求"是"鱼类占总数据的比例和"否"占总数据的比率
shannonEnt -= prob * log(prob,2) #求熵公式
return shannonEnt#
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 splitDataSet(dataSet,axis,value):#分别为数据集,列数,用作分类的数(在这里是0和1)
retDataSet = []
reduceFeatVec = []
for featVec in dataSet:#取出每行数
if featVec[axis] == value:#假如axis=1,则表示是否这行数的第2个和value相同
reduceFeatVec = featVec[:axis]#如果相同,则取[0:1]中的数 并赋给空列表reduceFeatVec,书中这个reduceFeatVec列表没有定义
reduceFeatVec.extend(featVec[axis+1:])#同理 既表示除了这列数的其他列都存到一个列表中 注意extend的用法
retDataSet.append(reduceFeatVec)#将每行剩下的数据当作一组,分别存入一个列表中
return retDataSet# 例如:返回第一列是0但又不包括0的数据组
def chooseBestFeatureToSplit(dataSet):#选择最好的数据集划分方式,既用那个特征分类最好呢?
numFeatures = len(dataSet[0] ) - 1#求数据列的长度-1,因为每行有三个数据,而最后一个数据是分类结果,不能当作特征
baseEntropy = calcShannonEnt(dataSet) #求的原始数据的熵值,目的是要和划分之后的熵做对比
bestInfoGain = 0;bestFeature = -1#设置两个变量,第一个用于当作中间变量,第二个用于表示那个变量最好
for i in range(numFeatures):#一共两个变量,用for循环分别求出根据用每个特征值分类以后的熵
featList = [example[i] for example in dataSet]#写出每列的数字,并存储到列表中
uniqueVals = set(featList)#创建集合为{0,1}
newEntropy = 0.0
for value in uniqueVals:#集合中一共有0,1两个数 value= 0/1
subDataSet = splitDataSet(dataSet,i,value)#例如:收到第一列中是0但又不包括0的数据组
prob = len(subDataSet)/float(len(dataSet))#例如:第一列中是0但又不包括0的数据组/总数据长度
newEntropy += prob * calcShannonEnt(subDataSet)#算出这组新数据的熵再乘以这列数据所占的比例+当value=0时算出这组新数据的熵再乘以这列数据所占的比例
infoGain = baseEntropy - newEntropy#原始数据的熵-新数据的熵
print("第%d个特征的增益为%.3f" % (i, infoGain))
if(infoGain > bestInfoGain): #得出 原始数据的熵-新数据的熵 最小时 的 i(特征值)
bestInfoGain = infoGain
bestFeature = i
return bestFeature
if __name__ == '__main__':
dataSet, features = createDataSet()
print("最优特征索引值:" + str(c**hooseBestF**eatureToSplit(dataSet)))
**
运行结果:
**
第0个特征的增益为0.420
第1个特征的增益为0.171
最优特征索引值:0
**
创建树:
**
"""如果数据集已经处理了所有属性,但是类型标签依然不是唯一的,此时我们需要如何定义该叶子节点,在这种情况下,我们
通常会采用多数表决的方法决定叶子节点的分类"""
def majorityCnt(classList):
classCount = {}
for vote in classList:
if vote not in classCount.keys:
classCount[vote] = 0
classCount[vote] += 1
"""在classCount这个字典中,operatpr.itemgetter(1)表示按照值的大小进行排序"""
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse =True)
return sortedClassCount[0][0]#取出字典中第一个键值对的键
#创建树的函数代码
def creatTree(dataSet,labels):
classList = [example[-1] for example in dataSet] #取数据集中每行最后一个数,装在一个新的列表中
if classList.count(classList[0]) == len(classList): #count(classList[0])表示classList[0]的数目,所有的标签都相等,则返回这个分类标签
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] = creatTree(splitDataSet(dataSet,bestFeat,value),subLabels)
return myTree
if __name__ == '__main__':
dataSet, features = createDataSet()
myTree = creatTree(dataSet,features)
print(myTree)
**
运行结果
**
{'no surfacing ': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}