首先是树的概念我们都比较熟悉了,然后决策树其实就是一棵树,通过在每一个几点通过特征的不同,走向不同的子树直到走到叶子节点找到分类的标签,算是完成了分类的过程。分类的过程不难理解,主要的是数据构造过程。
首先是构造的依据是什么呢,以什么依据作为特征使用的选择条件呢。这里使用的信息增益,通过计算信息增益的方式来选择特征作为划分数据集合的依据,信息增益最高的特征就是划分数据的最佳方式。
信息增益(information gain)就是选择一个特征之后所计算的熵(entropy),与原来的熵的差值即 infoGain=newEntropy−oldEntropy ,
这里熵的计算方式是 H=−∑ni=1p(xi)log2p(xi)
计算熵的python代码如下
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)
#计算香农熵
return shannonEnt
计算熵之后通过计算每个特征的信息增益,来找到最大的信息增益,选择出最佳分类特征,划分数据集合。
划分数据集合利用该特征的不同数据分类,比如选择人的视力作为分类标准,就可以有近视和正常两类。划分数据集合的代码如下
def splitDataSet(dataSet,axis,value):
#这个函数的作用就是从dataSet的第axis维中找到值维value的行,
#然后将axis这个位置的值去掉后返回
retDataSet=[]#存储返回结果
for featVec in dataSet:
if featVec[axis]==value:#找到在axis列上值为value的行
reduceFeatVec=featVec[:axis]
# print reduceFeatVec
reduceFeatVec.extend(featVec[axis+1:])#构造去掉axis位置值的list
retDataSet.append(reduceFeatVec)#添加到结果list
return retDataSet
通过计算每一个特征作为分类依据划分数据集合后计算响应的熵,减去原来的熵计算信息增益,遍历完所有的特征后选择信息增益最大的特征作为划分数据集合的依据。
实现:
def chooseBestFeatureToSplit(dataSet):
numFeatures=len(dataSet[0])-1
#除去最后一列分类标签,得到所有特征的数量
baseEntropy=calcShannonEnt(dataSet)
#最初的香农熵
baseInfoGain=0.0#基础信息增益初始化
bestFeature=-1#选择的特征初始化
for i in range(numFeatures):
#遍历所有的特征选择计算每个特征的增益
featlist=[example[i] for example in dataSet]
#抽取出该维的所有特征
uniqueVals=set(featlist)
# print i,uniqueVals
#对特征进行驱虫,创建唯一分类标签列表
newEntropy=0.0#初始化去掉该特征之后的香农熵
for value in uniqueVals:
#计算新的香农熵
subDataSet=splitDataSet(dataSet, i, value)
#利用前边的划分函数抽取出该维特征
prob=len(subDataSet)/float(len(dataSet))
#计算value类在数据中的概率
newEntropy+=prob*calcShannonEnt(subDataSet)
#计算新的熵
infoGain=baseEntropy-newEntropy
#计算该维特征的信息增益
# print infoGain,baseEntropy,newEntropy
if infoGain>baseInfoGain:
#如果大于原来的信息增益,则进行替换,并标记增益最大的特征维
baseInfoGain=infoGain
bestFeature=i
return bestFeature
#返回增益最大的特征的列
能够选择出最佳的划分数据集合的特征之后,要做的就是构建决策树,这里使用递归的方式来构建决策树,每次选择信息增益最大的特征作为一次分类的根节点,根据特征的不同值来继续递归构建子树。
递归的结束条件有两个:
1、集合内所有的数据都是一个类别
2、用完了所有的特征依然没有能够得到分类,这个时候选择出现次数最多的类别作为返回结果。
def createTree(dataSet,labels):
classList=[example[-1] for example in dataSet]
if classList.count(classList[0])==len(classList):
#count计算classlist【0】这个标签出现的数量,
#如果和总数一致,则说明所有的都是这一个类的,可以作为终止条件
return classList[0]
if len(dataSet[0])==1:
#dataset[0]是数据集的第一行,
#如果第一行只有一列则说明已经使用完了所有的特征
#这时候室友出现次数最多的类别作为返回
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:
#对特征列的每一个类别建立子树
subLables=labels[:]
myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet, bestFeat, value), subLables)
return myTree
构建决策树后可以进行存储,减少以后的计算。
def storeTree(inputTree,filename):
import pickle
fw=open(filename,'w')
pickle.dump(inputTree, fw)
#将树序列化之后存储到文件中
fw.close()
def grabTree(filename):
import pickle
fr=open(filename,'r')
return pickle.load(fr)
#将序列化的数解析成树的形式,返回结果
利用决策树进行分类,通过已有的训练好的决策树,给未知类别但是知道不同特征数据的变量进行分类
def classify(inputTree,featLabels,testVec):
firstStr=inputTree.keys()[0]
secondDict=inputTree[firstStr]
featIndex=featLabels.index(firstStr)
#确顶当前特征在数据矩阵中的第几维
for key in secondDict.keys():
if testVec[featIndex]==key:
#testVex[featIndex]是待分类数据在该特征出的数据值,
#通过这个数据值找到子树的内容
if type(secondDict[key]).__name__=='dict':
#如果是字典则需要继续向下进行从他的子树中找到分类结果
classLabel=classify(secondDict[key], featLabels, testVec)
else:
#走到叶子节点则返回分类类型
classLabel=secondDict[key]
return classLabel
到此决策树的功能基本实现,决策树的好处在于我们能够看到其工作过程,每个特征在决策书中所在的位置也是可以看到的,所以可以用可视化的方式绘制决策树。
完整版的代码见https://code.csdn.net/snippets/2593867.git
测试生成的决策树结果
决策树的优缺点
优点:计算复杂度不高,输出结果利于理解,对中间值缺失不敏感,可以处理不相关的特征数据
缺点:可能会产生过度匹配的问题
适用数据类型:数值型和标称型