文章目录
1.算法定义
- 灵魂:根据某种指标进行树的分裂达到分类/回归的目的,希望纯度越高越好,即熵为0
- 从数据集合中提取出一系列规则,可以更好的理解数据的内在含义
- 与KNN一样,是结果确定的分类算法,,数据实例会被明确分到某个类中
- 优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征的数据
缺点:可能会产生过度匹配问题 - 适用于数值型与标称型数据。树构造算法只适用于标称型数据,数值型数据必须离散化
- 类型:分类树,回归树
分类树分析是指预测结果是数据所属的类(比如某个电影去看还是不看)
回归树分析是指预测结果可以被认为是实数(例如房屋的价格,或患者在医院中的逗留时间)
分类和回归树(CART,Classification And Regression Tree)分析是用于指代上述两种树的总称
2.决策树构造
需考虑的问题:1.数据集的哪个特征在分类时起决定性作用——评估特征,直到所有具有相同类型的数据均在一个数据子集内(量化分类效果,如:信息增益ID3、信息增益率C4.5、基尼系数CART等)
创建分支的伪代码createBranch函数
检测数据集中的每一个子项是否属于同一分类:
If so return 类标签;
Else
寻找划分数据集的最好的特征
划分数据集
创建分支节点
for 每个划分的子集
调用函数createBranch并增加返回结果到分支节点中
return 分支节点
2.1信息增益
定义1:划分数据集之前之后信息发生的变化成为信息增益,获得信息增益最高的就是的特征是最好的选择。——熵的减少or无序度的减少
定义2:集合信息的度量方式称为香农熵或熵(信息的期望值)。——度量数据集的无序程度。熵值越高,则混合的数据也越多。数据集中类别数增加,则相应的熵增加。
如何计算熵?
表示所有类别所有可能值包含的信息期望值,log里表示选择某个分类的概率
若所有样本属于同一类,则H=0
所有样本正负样例相等,则H=1
正负样例数量不等,则H基于0到1之间
如何度量信息增益?
熵相减
2.2划分数据集方法
方法:——获取最大信息增益,熵减少,无序度减少(属性划分方法不一样)
1.二分法
2.ID3算法(划分标称型数据集,无法直接处理数值型数据)——信息增益(度量标准:熵)
核心思想:以信息增益度量属性选择,选择分裂后信息增益最大的属性进行分裂
3.C4.5——信息增益率
4.CART——GINI基尼指数(gini impurity 基尼不纯度)
注意:这些算法在运行时并不是在每次划分时都消耗特征,即特征数目并不是在每次划分数据集时都会减少
原则:将无序的数据变得更加有序——如何度量?——信息论量化度量信息——对每个特征划分数据集的结果计算一次信息熵
2.3递归构建决策树
递归终止条件:
1.程序遍历完所有划分数据集的属性。如果处理完所有属性,类标签依然不唯一,应用多数表决的方法决定改叶子节点的分类
2.每个分支下的实例都具有相同的分类
决策树上有两种节点:叶子节点(含有类标签)和判断节点
使用python内嵌的数据结构字典存储节点信息,并不构造新的数据结构
3.使用matplotlib注解绘制(plot)树形图
3.1 matplotlib 注解
注解工具annotations:可以在数据图形上添加文本注释
3.2 构造注解树
4.测试与存储分类器
测试
需要决策树以及用于构造树的标签向量(特征标签列表用于确定用于划分数据集的特征在哪个位置)
比较测试数据与决策树上的数值,递归执行进入叶子节点
测试数据定义为叶子节点所属的类型
存储——可持久化分类器
python模块序列化对象pickle,在磁盘上保存对象并在需要的时候读取调用
任何对象都可以执行序列化操作,包括字典对象
预先提炼并存储数据集中的知识,在对事物进行分类时再使用这些知识
5.应用实例
使用决策树预测隐形眼镜类型(根据眼部状况推荐眼镜)
存在问题
决策树能很好匹配实验数据,但是匹配选项可能过多——过度匹配
解决:裁剪决策树,去掉一些不必要的叶子结点(如果叶子结点只能增加少许信息,则可以删除该节点,将他加入到其他叶子结点中)
'''
Created on Sep 20, 2018
Decision Tree Source Code for Machine Learning in Action Ch. 3
@author: Peter Harrington
'''
from math import log
import operator
def createDataSet():
dataSet = [[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 1, 'no']]
labels = ['no surfacing','flippers'] #特征值:不浮出水面是否可以生存;是否有脚蹼;
#change to discrete values
return dataSet, labels
#计算给定数据集的香农熵
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) #log base 2
return shannonEnt
#按照给定特征划分数据集
def splitDataSet(dataSet, axis, value): #参数:带划分的数据集;划分数据的特征;需要返回的特征的值
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value: #按照某个特征划分数据集,需要将所有符合元素抽取出来
reducedFeatVec = featVec[:axis] #chop out axis used for splitting
reducedFeatVec.extend(featVec[axis+1:]) #extend将列表中的每个元素加进来; append()将整个列表当做一个元素加进来
retDataSet.append(reducedFeatVec)
return retDataSet
#遍历整个数据集,循环计算香农熵和splitDataSet()函数,找到最好的划分方式
#选择最好的数据集划分方式
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 #获取特征的个数,最后一列为标签 the last column is used for the labels
baseEntropy = calcShannonEnt(dataSet) #整个数据集原始香农熵(无序度量值)
bestInfoGain = 0.0; bestFeature = -1
for i in range(numFeatures): #循环遍历所有特征
featList = [example[i] for example in dataSet]#获取这一列特征的所有值create a list of all the examples of this feature
uniqueVals = set(featList) #获取某一列独立的特征值 get a set of unique values
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 #calculate the info gain; ie reduction in entropy
if (infoGain > bestInfoGain): #compare this to the best gain so far
bestInfoGain = infoGain #if better than current best, set to best
bestFeature = i
return bestFeature #returns an integer
#叶子节点中类标签不唯一,采取多数表决机制
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是终止条件
if classList.count(classList[0]) == len(classList): #类别完全相同则停止划分
return classList[0]#stop splitting when all of the classes are equal
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[:] #copy all of labels, so trees don't mess up existing labels
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
#使用决策树的分类函数
def classify(inputTree,featLabels,testVec): #训练好的树,特征标签列表,待测试数据
firstStr = list(inputTree.keys())[0] #获得树的第一个键,第一个划分的特征
secondDict = inputTree[firstStr] #获得内嵌的第二个字典
featLabels=['no surfacing','flippers']
featIndex = featLabels.index(firstStr) #确定划分标签的索引
key = testVec[featIndex] #获得测试集上的特征
valueOfFeat = secondDict[key] #测试集在这个划分特征上的取值
if isinstance(valueOfFeat, dict): # 判断当前特征值对应的是否是个叶节点。是叶节点则对应一个值,不是叶节点则对应一个字典
#sinstance() 函数来判断一个对象是否是一个已知的类型
classLabel = classify(valueOfFeat, featLabels, testVec)
else: classLabel = valueOfFeat #是叶节点,返回当前节点的分类标签
return classLabel
#使用pickle模块存储决策树
def storeTree(inputTree,filename):
import pickle
fw = open(filename,'wb')
pickle.dump(inputTree,fw)
fw.close()
def grabTree(filename):
import pickle
fr = open(filename,'rb')
return pickle.load(fr)
if __name__=="__main__":
dataSet, labels=createDataSet()
print("香农熵=",calcShannonEnt(dataSet)) #计算数据的香农熵
print(splitDataSet(dataSet,0,1)) #根据某个特征划分得到的数据集
print(chooseBestFeatureToSplit(dataSet)) #选择最好的特征
myTree=createTree(dataSet,labels)
print(myTree)
print(classify(myTree,labels,[1,1])) #测试决策树的分类效果
storeTree(myTree,'decisionTreeStorage.txt')
grabTree('decisionTreeStorage.txt')