前言
分类决策树模型是表示基于特征对实例进行分类的树形结构。之前的博客也对决策树的定义,构造过程以及相关算法进行了详细介绍。
决策树学习算法包括三个部分:特征选择、树的生成和树的剪枝。
特征选择的目的在于选取对训练数据能够分类的特征,ID3使用信息增益,C4.5使用信息增益比,CART使用基尼指数。
决策树的优点:
- 计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以不处理不相关特征数据。
缺点:
- 可能会产生过度匹配问题
适用数据类型:数值型和标称型
决策树的一般流程
- 收集数据:可以使用任何方法
- 准备数据:树构造算法只适用于标称型数据,因此数值型数据必须离散化
- 分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期
- 训练算法:构造树的数据结构
- 测试算法:使用经验树计算错误率
- 使用算法:此步骤可以适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义
代码实现
计算给定数据的香农熵
from math import log
# 计算给定数据集的香农熵
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 # 否则,当前类别标签出现的次数+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
myDate, labels = createDataSet()
print("原始数据集:", myDate)
shannonEnt = calcShannonEnt(myDate)
print("香农熵:", shannonEnt)
熵越高,则混合的数据越多,这里可以做个测试,在数据集中添加更多的分类,观察熵的变化。
得到熵之后,我们可以按照获取最大信息增益的方法划分数据集。
测试结果:
原始数据集: [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
香农熵: 0.9709505944546686
划分数据集
# 划分数据集
def splitDataSet(dataSet, axis, value): # 待划分的数据集、划分数据集特征、需要返回的特征的值
retDataSet = [] # 创建新的list对象
for featVec in dataSet: # 抽取
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis + 1:])
retDataSet.append(reducedFeatVec)
return retDataSet
# 测试extend 和 append
a = [1, 2, 3]
b = [4, 5, 6]
a.append(b)
print(a)
a = [1, 2, 3]
a.extend(b)
print(a)
# 测试划分数据集
myDat, labels = createDataSet()
print(myDat)
print(splitDataSet(myDat, 0, 1))
print(splitDataSet(myDat, 0, 0))
测试结果:
append: [1, 2, 3, [4, 5, 6]]
extend: [1, 2, 3, 4, 5, 6]
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
[[1, 'yes'], [1, 'yes'], [0, 'no']]
[[1, 'no'], [1, 'no']]
选择最好的数据集划分方式
# 选择最好的数据集划分方式——实现选取特征,划分数据集,计算得出最好的划分数据集的特征
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 # 计算特征属性的个数
baseEntropy = calcShannonEnt(dataSet) # 调用前面写好的calcShannonEnt函数,计算整个数据集的原始香农熵
bestInfoGain = 0.0
bestFeature = -1
for i in range(numFeatures):
featList = [example[i] for example in dataSet] # 将所有可能的特征值写入新的list中
uniqueVals = set(featList) # 使用Python语言原生的集合set数据类型,从类表中得到类表中唯一元素值
newEntropy = 0.0
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value) # 调用前面写好的splitDataSet函数,对每个属性值划分一次数据集
prob = len(subDataSet) / float(len(dataSet))
newEntropy += prob * calcShannonEnt(subDataSet) # 计算数据集的新熵,并求和
infoGain = baseEntropy - newEntropy # 求信息增益
if infoGain > bestInfoGain: # 比较所有特征的信息增益,
bestInfoGain = infoGain
bestFeature = i
return bestFeature # 返回最好特征划分的索引值
# 测试
myDat, labels = createDataSet()
print(chooseBestFeatureToSplit(myDat))
测试结果
第0个特征是最好的划分数据集的特征
递归构造决策树
目前我们已经实现了从数据集构造决策树算法所需的子功能模块,其工作原理如下:
得到原始数据集,然后基于最好的属性值划分数据集,由于特征值可能多于两个,因此可能存在大于两个分支的数据集划分。第一次划分后,数据将被向下传递到树分支的下一个节点,在这个节点上,我们可以再次划分数据,因此采用递归的原则处理数据集。
需要在代码顶部加上: import operator
# 多数表决方法
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) # 利用operator操作键值排序字典,并返回出现次数最多的分类名称
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) # 使用set集合去除重复的值,得到列表包含的所有属性值
for value in uniqueVals: # 遍历所有最佳划分的分类标签
subLabels = labels[:]
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels) # 递归调用
return myTree
测试
myDat, labels = createDataSet()
myTree = createTree(myDat, labels)
print("myTree:",myTree)
测试结果:
myTree: {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
至此我们已经知道了如何正确地构造树。如何通过Python中绘制图形,可参考图灵书籍《机器学习实战》