决策树原理及相关概念细节我们知道决策树的学习算法主要包括3个步骤特征选择、决策树生成算法、决策树剪枝我们按照这个思路来一一实现相关功能。
本文的实现目前主要涉及特征选择、ID3及C4.5算法。剪枝及CART算法暂未涉及后期补上。
1.ID3及C4.5算法基础
前面文章我们提到过ID3与C4.5的主要区别是特征选择准则的不同
ID3信息增益
C4.5信息增益比
1.1 计算香农熵
不管是这两者的哪一种都涉及到信息增益的计算而计算信息增益的基础又是计算香农熵。所以我们先来实现计算香农熵的代码。
from math import log
import operator
# 计算给定数据集的香农熵
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) # 以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
myDat,labelscreateDataSet()
myDat # [[1, 1, yes], [1, 1, yes], [1, 0, no], [0, 1, no], [0, 1, no]]
calcShannonEnt(myDat) # 0.9709505944546686
1.2 按照给定特征划分数据集
# 按照给定特征划分数据集
def splitDataSet(dataSet, axis, value):
retDataSet []
for featVec in dataSet:
if featVec[axis] value:
reducedFeatVec featVec[:axis]
reducedFeatVec.extend(featVec[axis1:])
retDataSet.append(reducedFeatVec)
return retDataSet
测试
splitDataSet(myDat,0,1) # [[1, yes], [1, yes], [0, no]]
1.3 选择最优特征
# 选择最好的数据集划分方式
# 选择最好的数据集划分方式
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 # ID3
# infoGain baseEntropy - newEntropy # C4.5
# 计算最好的信息增益
if (infoGain > bestInfoGain):
bestInfoGain infoGain
bestFeature i
return bestFeature
1.4 多数表决实现
在ID3、C4.5算法的停止条件之一是没有特征可以选择时停止算法但如果这时该结点类标签依然不是唯一的此时我们需要决定如何定义该叶子结点。在这种情况下通常采用多数表决的方法决定该叶子结点的分类。
# 多数表决实现
def majorityCnt(classList):
classCount{}
for vote in classList:
if vote not in classCount.keys():
classCount[vote] 0
classCount[vote] 1
# 对字典进行排序
sortedClassCount sorted(classCount.items(),keyoperator.itemgetter(1),reverseTrue)
# Python3中不再支持iteritems()将iteritems()改成items()
return sortedClassCount[0][0]
对于“多数表决实现”函数的注释
1.dict.items()
作用是可以将字典中的所有项以列表方式返回。因为字典是无序的所以用items方法返回字典的所有项也是没有顺序的。
2.operator.itemgetter()
operator模块提供的itemgetter函数用于获取对象的哪些维的数据参数为一些序号.
3.sorted()函数排序
list.sort()是对已经存在的列表进行操作进而可以改变进行操作的列表
sorted返回的是一个新的list而不是在原来的基础上进行的操作
2.基于ID3、C4.5生成算法创建决策树
这里主要介绍基于ID3生成算法创建决策树C4.5只需要在ID3生成决策树代码上将chooseBestFeatureToSplit(dataSet)函数中infoGain baseEntropy - newEntropy换成infoGain baseEntropy - newEntropy即可 。
# 创建树的函数代码
def creatTree(dataSet,labels):
classList [example[-1] for example in dataSet]
labels2 labels[:]
# 类别完全相同则停止继续划分
if classList.count(classList[0]) len(classList):
return classList[0]
# 遍历完所有特征时返回出现次数最多的类别
if len(dataSet[0]) 1:
return majorityCnt(classList)
bestFeat chooseBestFeatureToSplit(dataSet)
bestFeatLabel labels2[bestFeat]
myTree {bestFeatLabel:{}}
del (labels2[bestFeat])
# 得到列表包含的(选定为最佳特征的)所有属性值
featValues [example[bestFeat] for example in dataSet]
uniqueVals set(featValues)
for value in uniqueVals:
subLabels labels2[:] # 复制类标签
# 递归
myTree[bestFeatLabel][value] creatTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
对于“creatTree”函数的注释
1.list.count(obj)
统计某个元素在列表中出现的次数
2.del,list.remove(),list.pop()
del:根据索引位置来删除单个值或指定范围内的值
list.remove():删除单个元素删除首个符合条件的元素按值删除返回值为空;
list.pop():删除索引位置元素无参情况下删除最后一个元素返回删除的元素值;
测试
myTree creatTree(myDat, labels)
myTree # {no surfacing: {0: no, 1: {flippers: {0: no, 1: yes}}}}
3.使用决策树进行分类
# 使用决策树的分类函数
def classify(inputTree, featLabels, testVec):
firstStr list(inputTree.keys())[0]
secondDict inputTree[firstStr]
featIndex featLabels.index(firstStr)
for key in secondDict.keys():
if testVec[featIndex] key:
if type(secondDict[key]).__name__dict:
#if isinstance(secondDict[key], dict): 这个也可以
classLabel classify(secondDict[key],featLabels,testVec)
else:
classLabel secondDict[key]
return classLabel
对于“classify”的注释
1.type(a).name ‘dict’:
可判断a的类型是否类型为dictlist tuple 这些也适用
2.也可以用isinstance(变量名类型)判断类型
判断该变量是否是该类型或者是否是该类和该类的父类类型
小注
type(变量名)获取该变量名的类型,结合判断该变量的类型是否等于目标类型(等号右边value值)
比如a类继承b类,实例ca()
isinstance(c,a)和isinstance(c,b)都是True
type(c)的value值是aa是不等于b的所以ab为False即type(c)b为False
3.和is
变量名的value值是否相等
is变量名的id(地址)是否相等(数字类型的value值相等则id相等)
测试
classify(myTree, labels, [1,0]) # no
classify(myTree, labels, [1,1]) # yes
4.存储决策树
import pickle
# 使用pickle模块存储决策树
def storeTree(inputTree, filename):
with open(filename, wb) as f:
pickle.dump(inputTree, f)
# 加载决策树
def grabTree(filename):
with open(filename, rb) as f:
return pickle.load(f)
测试
storeTree(myTree,classifierStorage.txt)
grabTree(classifierStorage.txt) # {no surfacing: {0: no, 1: {flippers: {0: no, 1: yes}}}}
参考资料
《机器学习实战》第三章
【感谢龙石为本站提供数据底座技术支撑http://www.longshidata.com/pages/government.html】