解读python如何实现决策树算法
数据描述
每条数据项储存在列表中,最后一列储存结果
多条数据项形成数据集
data=[[d1,d2,d3...dn,result], [d1,d2,d3...dn,result], . . [d1,d2,d3...dn,result]]
决策树数据结构
classDecisionNode: '''决策树节点 ''' def__init__(self,col=-1,value=None,results=None,tb=None,fb=None): '''初始化决策树节点 args: col--按数据集的col列划分数据集 value--以value作为划分col列的参照 result--只有叶子节点有,代表最终划分出的子数据集结果统计信息。{‘结果':结果出现次数} rb,fb--代表左右子树 ''' self.col=col self.value=value self.results=results self.tb=tb self.fb=fb
决策树分类的最终结果是将数据项划分出了若干子集,其中每个子集的结果都一样,所以这里采用{‘结果':结果出现次数}的方式表达每个子集
defpideset(rows,column,value):
'''依据数据集rows的column列的值,判断其与参考值value的关系对数据集进行拆分
返回两个数据集
'''
split_function=None
#value是数值类型
ifisinstance(value,int)orisinstance(value,float):
#定义lambda函数当row[column]>=value时返回true
split_function=lambdarow:row[column]>=value
#value是字符类型
else:
#定义lambda函数当row[column]==value时返回true
split_function=lambdarow:row[column]==value
#将数据集拆分成两个
set1=[rowforrowinrowsifsplit_function(row)]
set2=[rowforrowinrowsifnotsplit_function(row)]
#返回两个数据集
return(set1,set2)
defuniquecounts(rows):
'''计算数据集rows中有几种最终结果,计算结果出现次数,返回一个字典
'''
results={}
forrowinrows:
r=row[len(row)-1]
ifrnotinresults:results[r]=0
results[r]+=1
returnresults
defginiimpurity(rows):
'''返回rows数据集的基尼不纯度
'''
total=len(rows)
counts=uniquecounts(rows)
imp=0
fork1incounts:
p1=float(counts[k1])/total
fork2incounts:
ifk1==k2:continue
p2=float(counts[k2])/total
imp+=p1*p2
returnimp
defentropy(rows):
'''返回rows数据集的熵
'''
frommathimportlog
log2=lambdax:log(x)/log(2)
results=uniquecounts(rows)
ent=0.0
forrinresults.keys():
p=float(results[r])/len(rows)
ent=ent-p*log2(p)
returnent
defbuild_tree(rows,scoref=entropy):
'''构造决策树
'''
iflen(rows)==0:returnDecisionNode()
current_score=scoref(rows)
#最佳信息增益
best_gain=0.0
#
best_criteria=None
#最佳划分
best_sets=None
column_count=len(rows[0])-1
#遍历数据集的列,确定分割顺序
forcolinrange(0,column_count):
column_values={}
#构造字典
forrowinrows:
column_values[row[col]]=1
forvalueincolumn_values.keys():
(set1,set2)=pideset(rows,col,value)
p=float(len(set1))/len(rows)
#计算信息增益
gain=current_score-p*scoref(set1)-(1-p)*scoref(set2)
ifgain>best_gainandlen(set1)>0andlen(set2)>0:
best_gain=gain
best_criteria=(col,value)
best_sets=(set1,set2)
#如果划分的两个数据集熵小于原数据集,进一步划分它们
ifbest_gain>0:
trueBranch=build_tree(best_sets[0])
falseBranch=build_tree(best_sets[1])
returnDecisionNode(col=best_criteria[0],value=best_criteria[1],
tb=trueBranch,fb=falseBranch)
#如果划分的两个数据集熵不小于原数据集,停止划分
else:
returnDecisionNode(results=uniquecounts(rows))
defprint_tree(tree,indent=''):
iftree.results!=None:
print(str(tree.results))
else:
print(str(tree.col)+':'+str(tree.value)+'?')
print(indent+'T->',end='')
print_tree(tree.tb,indent+'')
print(indent+'F->',end='')
print_tree(tree.fb,indent+'')
defgetwidth(tree):
iftree.tb==Noneandtree.fb==None:return1
returngetwidth(tree.tb)+getwidth(tree.fb)
defgetdepth(tree):
iftree.tb==Noneandtree.fb==None:return0
returnmax(getdepth(tree.tb),getdepth(tree.fb))+1
defdrawtree(tree,jpeg='tree.jpg'):
w=getwidth(tree)*100
h=getdepth(tree)*100+120
img=Image.new('RGB',(w,h),(255,255,255))
draw=ImageDraw.Draw(img)
drawnode(draw,tree,w/2,20)
img.save(jpeg,'JPEG')
defdrawnode(draw,tree,x,y):
iftree.results==None:
#Getthewidthofeachbranch
w1=getwidth(tree.fb)*100
w2=getwidth(tree.tb)*100
#Determinethetotalspacerequiredbythisnode
left=x-(w1+w2)/2
right=x+(w1+w2)/2
#Drawtheconditionstring
draw.text((x-20,y-10),str(tree.col)+':'+str(tree.value),(0,0,0))
#Drawlinkstothebranches
draw.line((x,y,left+w1/2,y+100),fill=(255,0,0))
draw.line((x,y,right-w2/2,y+100),fill=(255,0,0))
#Drawthebranchnodes
drawnode(draw,tree.fb,left+w1/2,y+100)
drawnode(draw,tree.tb,right-w2/2,y+100)
else:
txt='\n'.join(['%s:%d'%vforvintree.results.items()])
draw.text((x-20,y),txt,(0,0,0))
对测试数据进行分类(附带处理缺失数据)
defmdclassify(observation,tree):
'''对缺失数据进行分类
args:
observation--发生信息缺失的数据项
tree--训练完成的决策树
返回代表该分类的结果字典
'''
#判断数据是否到达叶节点
iftree.results!=None:
#已经到达叶节点,返回结果result
returntree.results
else:
#对数据项的col列进行分析
v=observation[tree.col]
#若col列数据缺失
ifv==None:
#对tree的左右子树分别使用mdclassify,tr是左子树得到的结果字典,fr是右子树得到的结果字典
tr,fr=mdclassify(observation,tree.tb),mdclassify(observation,tree.fb)
#分别以结果占总数比例计算得到左右子树的权重
tcount=sum(tr.values())
fcount=sum(fr.values())
tw=float(tcount)/(tcount+fcount)
fw=float(fcount)/(tcount+fcount)
result={}
#计算左右子树的加权平均
fork,vintr.items():
result[k]=v*tw
fork,vinfr.items():
#fr的结果k有可能并不在tr中,在result中初始化k
ifknotinresult:
result[k]=0
#fr的结果累加到result中
result[k]+=v*fw
returnresult
#col列没有缺失,继续沿决策树分类
else:
ifisinstance(v,int)orisinstance(v,float):
ifv>=tree.value:branch=tree.tb
else:branch=tree.fb
else:
ifv==tree.value:branch=tree.tb
else:branch=tree.fb
returnmdclassify(observation,branch)
tree=build_tree(my_data)
print(mdclassify(['google',None,'yes',None],tree))
print(mdclassify(['google','France',None,None],tree))
决策树剪枝
defprune(tree,mingain): '''对决策树进行剪枝 args: tree--决策树 mingain--最小信息增益 返回 ''' #修剪非叶节点 iftree.tb.results==None: prune(tree.tb,mingain) iftree.fb.results==None: prune(tree.fb,mingain) #合并两个叶子节点 iftree.tb.results!=Noneandtree.fb.results!=None: tb,fb=[],[] forv,cintree.tb.results.items(): tb+=[[v]]*c forv,cintree.fb.results.items(): fb+=[[v]]*c #计算熵减少情况 delta=entropy(tb+fb)-(entropy(tb)+entropy(fb)/2) #熵的增加量小于mingain,可以合并分支 ifdelta