决策树的优点:
1.易于理解和解释,并且可以可视化。
2.几乎不需要数据预处理。决策树还不支持缺失值。
3.可以同时处理数值变量和分类变量。其他方法大都适用于分析一种变量的集合。
4.可以处理多值输出变量问题。
决策树的缺点:
决策树学习可能创建一个过于复杂的树,也就是过拟合(overfitting)但是我们可以通过修剪决策树,合并相邻的无法产生大量信息增益的叶节点来消除过度匹配的问题。
构建决策树通常进行三个步骤:特征的选择,生成决策树,执行决策树并修剪。
在此使用基本的ID3算法来构造决策树(此外还有C4.5和CART),划分数据集的原则就是将标签无序的数据分得更加有序。
我们将数据集在划分前后发生的变化称为信息增益,那么可以计算在每个特征下划分数据集的信息增益,信息增益最大的那个特征就是当前最佳的划分特征。从信息论中公式得知要计算信息增益首先要计算数据集的经验熵(又称香农熵):
假设我们有一个数据集,每个数据都有四个特征,每个特征都有其相应特征值的集合;最后有一个关于该数据的分类结果,通常将其称为标签。对此数据集构建决策树,实现利用该决策树对未知标签的数据进行预测。
(年龄0,1,2代表青年中年老年,信贷情况0,1,2代表一般良好优秀)
年龄 有工作 有房产 信贷情况 是否贷款
0 0 0 0 不贷
0 0 0 1 不贷
0 1 0 1 贷款
0 1 1 0 贷款
0 0 0 0 不贷
1 0 0 0 不贷
1 0 0 1 不贷
1 1 1 1 贷款
1 0 1 2 贷款
1 0 1 2 贷款
2 0 1 2 贷款
2 0 1 1 贷款
2 1 0 1 贷款
2 1 0 2 贷款
2 0 0 0 不贷
对其进行经验熵和信息增益的计算选择当前最佳的分类特征:
import numpy as np
import math
def calcuent(dataset): #计算经验熵
num=dataset.shape[0]
labelcount={} #标签字典
for line in dataset:
curlabel=line[-1] #读取标签:贷款或者不贷
if curlabel not in labelcount.keys():
labelcount[curlabel]=0
labelcount[curlabel]+=1
Ent=0.0
for key in labelcount:
p=float(labelcount[key]/num) #每个标签值的概率
Ent-=p*math.log(p,2) #经验熵公式
return Ent
def splitdata(dataset,axis,vaule): #依据特征axis的取值vaule划分数据集
redataset=[]
for curline in dataset:
if curline[axis]==vaule: #去掉该特征的取值
reduce=curline[:axis].tolist() #必须要tolist(),不然矩阵没有extend
reduce.extend(curline[axis+1:])
redataset.append(reduce)
return np.array(redataset) #获得划分出的特征axis值为vaule的数据集
def choosebest(dataset): #选择最佳特征
num=len(dataset[0])-1 #特征的数量
baseEnt=calcuent(dataset)
bestinfogain=0.0 #最大的信息增益
bestfeature=0 #最佳的特征
for i in range(num): #遍历所有的特征,比较它们的信息增益
featurevaule=[example[i] for example in dataset] #先将dataset按行存放到example,再将example[i]存放到featurevaule中
featurevaule=set(featurevaule) #转为集合,得到特征的取值集合
newEnt=0.0
for vaule in featurevaule:
subdataset=splitdata(dataset,i,vaule) #对该特征依据特征值划分数据集
p=float(len(subdataset)/len(dataset))
newEnt+=p*calcuent(subdataset)
infogain=baseEnt-newEnt #计算该特征的信息增益
#print("第%d个特征的信息增益为%.3f" % (i+1, infogain))
if(infogain>bestinfogain):
bestinfogain=infogain
bestfeature=i
#print("最佳分类特征是第%d个"% (bestfeature+1))
return bestfeature
就此可以得到当前数据集的最佳分类特征,然后依此特征对数据集进行分类,会分出多个子树,子树的数量就是该特征值集合元素个数的数量。
既然可以做到每次对当前数据集最佳特征的选择,下一步就是具体生成这棵决策树了
生成决策树需要用到递归的思想:递归的思想对于此处的理解很重要,因为以最佳特征为节点分出多个子树(然后使用过的特征在数据集中划出去),该多个子树又要选择选择当前数据集的最佳特征继续递归划分更小的子树,直到将数据标签全部区分开,最后一层称为叶子。
def major(classlist): #计算输出分类标签中个数最多的那一个
classcount={}
for vaule in classlist:
if vaule not in classcount.keys():
classcount[vaule]=0
classcount[vaule]+=1
sort=sorted(classcount.items(),key=operator.itemgetter(1),reverse=True)
return classcount[0][0]
def creatTree(dataset,feature,featlabels): #根据输入数据dataset创建决策树,feature是标签集
classlist=[example[-1] for example in dataset] #将分类标记放入classlist
if classlist.count(classlist[0])==len(classlist): #递归出口之一:当剩余标签结果全部一致时说明分类完毕
return classlist[0]
if len(dataset)==1: #递归出口之二:当数据特征全部使用完毕但仍未将标签结果完全区分,说明特征数量不足,只能暂且返回剩余未区分开结果的数量最多的那一个
return major(classlist)
bestfeature=choosebest(dataset) #计算选择当前最佳的特征并放入featlabels
bestfeaturelabel=feature[bestfeature]
featlabels.append(bestfeaturelabel)
mytree={bestfeaturelabel:{}} #创建以该特征为节点的树
del(feature[bestfeature]) #该特征已使用,删除掉
featurevaule=[example[bestfeature] for example in dataset] #把该特征的值全部取出再集合化,得到该特征值的集合
unique=set(featurevaule)
for vaule in unique: #特征的每一个值都会作为新的节点,递归继续向下创建它的子树直到递归出口
mytree[bestfeaturelabel[vaule]=creatTree(splitdata(dataset,bestfeature,vaule),feature,featlabels)
return mytree
对数据集使用该代码进行决策树的生成,输出依此模型得到的决策树:
dataset=[]
feature=['年龄', '有工作', '有自己的房子', '信贷情况']
featlabels=[]
f=open('简单决策树_信贷情况.txt') #常规操作,逐行读取文件
for line in f.readlines():
curline=line.strip().split()
if(len(curline)==0):
break
dataset.append(curline)
dataset=np.array(dataset)
mytree=creatTree(dataset,feature,featlabels)
print(mytree)
可以看出两个特征就将该数据集的标签完全分类了,看来这两个特征(有无房子、有无工作)对数据集标签结果(是否给其贷款)的 影响是比较大的
def classify(inputTree,featlabels,testdata): #分类预测函数,使用已经得到的决策树自顶向下开始决策
first=next(iter(inputTree))
nextdict=inputTree[first]
featindex=featlabels.index(first)
for key in nextdict.keys():
if testdata[featindex]==int(key): #注意键是str类型的,必须要转为int再进行比较
if type(nextdict[key]).__name__=='dict': #对于数据类型的判断:要使用type().__name__=='....',而type()只是会返回一个类型无法比较
#很明显,如果该键的值是一个字典,则还需要继续递归进入下一个子树进行决策;如果该键的值是不是字典则说明到达了最后一层的叶子,返回该值!
result=classify(nextdict[key],featlabels,testdata)
else:result=nextdict[key]
return result
test=[[0,1],[1,0],[0,0],[1,1]] #四个人的测试数据,每个人的测试数据信息排序必须按照featlabels的顺序,例如第一的人的信息就是没有房子但有工作
for i in range(len(test)):
testtmp=test[i]
classify(mytree,featlabels,testtmp)
if classify(mytree,featlabels,testtmp)=='贷款':
print("预测可以对该人放贷")
else:
print("预测不可以对该人放贷")
根据决策树获得的对四个人的预测,实现了分类标签的预测:
此外计算构建决策树是比较耗时的,因此常常将构建好的决策树储存下来以便下次预测直接使用。