-
须知概念
-
信息熵(information entropy):是度量样本集合纯度常用的一种指标。假定当前样本集合D中第k类样本所占等等比例为Pk(k = 1,2,…,|y|),则D的信息熵定义为
Ent(D)的值越低,则D的纯度越高 -
信息增益(information gain):假定离散属性a有V个可能的取值{a1,a2,…,av},若使用a来对样本采集D来进行划分,则会产生V个分支节点,其中第v个分支节点包含了D中所有在属性a上取值为av 的样本,记为Dv .根据下面公式计算出Dv 的信息熵,由于不同分支结点包含的样本数不一样,给分支结点赋予权重|Dv|/|D|,即样本数越多的分支结点的影响越大,于是可知属性a对样本集进行划分所获得的‘信息增益’为
一般而言,信息增益越大,意为着使用属性a划分所获得的‘纯度提升越大’。 -
决策树概述
1、决策树算法是一种基本的分类与回归的方法,是最经常使用的数据挖掘算法之一。
2、决策树模型呈树形结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以认为是 if-then 规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。
3、决策树学习通常包括3个步骤:特征选择、决策树生成和决策树的修剪。
- 决策树的构造
通过递归函数createBranch()构造决策树
检测数据集中每个子项是否属于同一分类:
If so return 类标签;
Else
寻找划分数据集的最好特征
划分数据集
创建分支节点
for 每个分支节点的子集
调用函数createBranch并增加返回结果到分支节点中
return 分支节点
- 决策树的一般流程
(1)收集数据:可以使用任何方法。
(2)准备数据:树构造算法只适合用于标称型数据(目标变量的结果只在有限目标集中取值),因此数值型必须离散化。
(3)分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。
(4)训练数据:构造树的数据结构。
(5)测试算法:使用经验树计算错误率。
(6)使用算法:此步骤可以适用于任何监督学习算法,而使用决策树可以更好地理解数据 的内在含义。
- 决策树算法特点
优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据
缺点:可能会产生过度匹配问题
适用数据类型:数值型和标称型
- 决策树项目案例
判断鱼类与非鱼类
import operator
from math import log
# 利用createDataSet()创建表格中的数据
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
# 计算给定数据集的香农熵
def calcShnnonEnt(dataSet):
numEntries = len(dataSet)
labelCount = {}
'''
dict = {key, value}
'''
for featVec in dataSet:
currentLabel = featVec[-1] # 将每个元素最后一个元素选择出来
if currentLabel not in labelCount.keys():
labelCount[currentLabel] = 0
labelCount[currentLabel] += 1 # 对字典里的value加1
shnnonEnt = 0.0
for key in labelCount:
prob = float(labelCount[key]) / numEntries # 计算类别各占的概率
shnnonEnt -= prob * log(prob, 2) # 计算熵
return shnnonEnt
# 按照给定的特征划分数据集
def spiltDataSet(dataSet, axis, value):
'''
dataSet:待划分的数据集
axis:划分数据集的特征
value:特征的返回值
'''
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis] # 相当于创建一个新的列表list
reducedFeatVec.extend(featVec[axis+1:]) # 输出每个元素除第一位的元素
retDataSet.append(reducedFeatVec)
return retDataSet
# 选择最好的数据集划分方式
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0])-1 # 特征的个数,去掉最后一个标签
baseEntropy = calcShnnonEnt(dataSet) # 整个数据集原始的熵
bestInfoGain = 0.0
bestFeature = -1 # 最优特征索引
for i in range(numFeatures): # 遍历所有特征
featList = [example[i] for example in dataSet] # 获取dataSet第i个特征: 第一个特征[1,1,1,0,0]或第二个特征[1,1,0,1,1]
uniqueVals = set(featList) # 将第i个特征的属性值放在一个集合里{0, 1}
newEntropy = 0.0
for value in uniqueVals: # 遍历该特征所有的值{0,1}
subDataSet = spiltDataSet(dataSet, i, value) # i = 0/1, value = 0/1 如i=0,value=0,subDataSet= [[1,'no'], [1,'no']]没有有用的信息,熵的值为0
prob = len(subDataSet)/float(len(dataSet))
newEntropy += prob * calcShnnonEnt(subDataSet)
infoGain = baseEntropy - newEntropy # 信息增益,越大,表示按该属性划分所获得的‘纯度提升’越大
if infoGain > bestInfoGain:
bestInfoGain = infoGain
bestFeature = i
return bestFeature
# 返回出现次数最多的分类名称
def majorityCnt(classList):
classCount = {}
for vote in 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]
# 创建树的函数
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet]
if classList.count(classList[0]) == len(classList): # count()函数是返回string 中某字符的次数
return classList[0]
'''
递归第一个停止条件:所有的类标签完全相同,直接返回该类标签
'''
if len(dataSet[0]) == 1:
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet)
print(bestFeat)
bestFeatlabel = labels[bestFeat]
mytree = {bestFeatlabel : {}}
del(labels[bestFeat]) # del表示删除
# 将已选维度标签从标签列表中删除
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues) # {0,1}
for value in uniqueVals:
sublabels = labels[:]
mytree[bestFeatlabel][value] = createTree(spiltDataSet(dataSet, bestFeat, value),
sublabels) # 递归,自己调用自己createTree()
return mytree
输出结果
myDat = [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
myTree = {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
- 测试算法:使用决策树执行分类
def classify(inputTree, featLabels, testvec):
'''
inputTree: 决策树模型
featLabels: Feature标签对应的名称
testvec: 测试输入的数据
return: classLabels 分类结果
'''
firstStr = list(inputTree.keys())[0] # 获取tree根节点对应的key值
secondDict = inputTree[firstStr] # 通过key得到根节点对应的value
# 判断根节点名称获取根节点在label中的先后顺序,这样就知道输入的testVec怎么开始对照树来做分类
featIndex = featLabels.index(firstStr)
for key in secondDict.keys():
if testvec[featIndex] == key:
if type(secondDict[key]).__name__ == 'dict':
classLabels = classify(secondDict[key], featLabels, testvec)
else:
classLabels = secondDict[key]
return classLabels
在测试中,会报这样的错 ValueError: ‘no surfacing’ is not in list 是由于之前createTree()中将已选的标签从列表中删除了,所以只需重新添加 labels 就行了
- 决策树存储
# 使用pickle模块存储决策树
def storeTree(inputTree, filename):
import pickle
# fw = open(filename, 'w')
fw = open(filename, 'wb')
pickle.dump(inputTree, fw)
fw.close()
def grabTree(filename):
import pickle
# fr = open(filename)
fr = open(filename, 'rb')
return pickle.load(fr)
需要注意Python3写法不一致:
1、fw = open(filename, ‘w’) 会报错:TypeError: write() argument must be str, not bytes,所以要写成fw = open(filename, ‘wb’)
2、fr = open(filename) 也会报错:UnicodeDecodeError: ‘gbk’ codec can’t decode byte 0x80 in position 0: illegal multibyte sequence,所以因写成 fr = open(filename, ‘rb’)
- 该项目最大信息增益计算