学习算法的最好方式,莫过于亲手敲,程序跑一遍。也许代码不精炼,也许比较费时。但学习这事情,如果怕麻烦还怎么学的好。
本次手敲的代码是按照信息增益的大小,来决定决策树的分支。这里先说几个关键的点:
1.决策树的每个分支都要用到上一步的计算结果,故采用递归的循环方式。
2.要注意递归的终止条件。一是分支的数据里面就只有一种分类,比如二分类的话,返回的结果要么全为是,要么全为否,就需要终止了。二是所有的特征都跑完了,最后根据特征的具体取值,观察对应二分类的每个类别的个数,哪个类别的个数多,则特征具体取值对应的结果就是这个类别(见下图,仅为举例说明)。
3.决策树中的很多步骤都需要计算频率,频率的计算方法比较多,本次这里面主要用的是pandas的聚合函数,pd.crosstab(),pd.groupby(),等等。在根据特征的每个取值拆分数据集的时候,用到了groupby自动拆分的方法。(参见《利用python进行数据分析》P267页。)
4.本次使用的数据,是周志华《机器学习》中的内容。数据来源于https://blog.csdn.net/icefire_tyh/article/details/54575527
遍历本人还不是太熟,部分参考了https://blog.csdn.net/herosofearth/article/details/52347820#comments。
上代码:
#初始熵的计算
def initi_entro(data):
pk=data.iloc[:,-1].value_counts()/data.iloc[:,-1].count()
entro2=-(pk[0]*math.log(pk[0],2)+pk[1]*math.log(pk[1],2))
return entro2
#熵的计算公式
def entro_compute(data):
m=-(data*math.log(data,2))
return m
#用于计算并返回数据中信息增益最大的变量.(变量等价于特征)
def entro_compute2(data):
l=len(data.keys())-1
global gg#用于存放变量名和该变量名称对应的熵值
gg={}
for i in range(l):#循环用于计算每个变量的熵
print('选取变量:',data.keys()[i])
G=0#用于存放熵值
#建立每个变量与预测变量的交叉表,即形成频数表。
column1=pd.crosstab(data.iloc[:,i],data.iloc[:,-1],margins=True)
data1=column1.iloc[:,0]/column1.iloc[:,-1]
data2=column1.iloc[:,1]/column1.iloc[:,-1]
m=len(data.iloc[:,i].unique())
for j in range(m):
#如果某一取值对应的某一结果分类的频数为0,则该取值对应的结果分类的熵值为0.
if data1[j]==0:#x 表示变量的某一取值对应的所有结果的熵。
x=0+entro_compute(data2[j])
elif data2[j]==0:
x=0+entro_compute(data1[j])
else:
#计算每个类别下,取各个值对应的熵
x=entro_compute(data1[j])+entro_compute(data2[j])
#每个类别的权重
y=column1.iloc[j,-1]/column1.iloc[-1,-1]
G+=x*y
ll=initi_entro(data)-G#变量的信息增益
gg[data.keys()[i]]=ll#存储到gg中
print(data.keys()[i],'分类情况下的熵为','%.3f'%ll)
#找出信息增益最大的分类变量的取值
s=sorted(gg.items(),key=lambda x:x[1],reverse=True)
return s[0][0]
def splitdata(data,features):
subdata={}
for x,y in data.groupby(features):
dataa=(x,y)#生成拆分的数据
subdata[dataa[0]]=dataa[1]
return subdata
def mostfrequency(data):
frequentvar=data.iloc[:,-1].value_counts(ascending=False).index[0]
return frequentvar
def createTree(data,labels):
'''
创建决策树
:data:数据集
:labels:所有变量/特征标签
'''
if len(data.iloc[:,-1].unique())==1:
return data.iloc[0,-1] #第一个递归条件结束,值相同,返回最后一列的第一个值。
if len(labels) == 1:
return mostfrequency(data) # 第二个递归结束条件:用完了所有特征,返回频率最高的值
bestFeat=entro_compute2(data) # 最优划分特征,返回的是最有的特征
myTree = {bestFeat:{}} # 使用字典类型储存树的信息
labels.remove(bestFeat) # 删除最优的特征
uniqueVals=data.loc[:,bestFeat].unique()
subdata=splitdata(data,bestFeat)
for value in uniqueVals:
subLabels = labels[:] # 复制所有类标签,保证每次递归调用时不改变原始列表的内容
myTree[bestFeat][value] = createTree(subdata[value],subLabels)
#myTree[bestFeat][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
运行并读取数据(由于使用的原始数据集最后一列名称有个空格,故修改了变量名。你也可以直接strip 去掉空格)
data=pd.read_csv('D:\\Users\\Administrator\\Documents\\decisiontreedata.csv',encoding='GB2312',index_col=0)
data.rename(columns={'好瓜 ':'好瓜'},inplace=True)
#整理成最后一列是分类变量的形式
data=data[['色泽', '根蒂', '敲声', '纹理', '脐部', '触感', '好瓜']]
#计算初始的信息熵
labels=list(data.keys())
createTree(data,labels)
运行结果:
{'纹理': {'模糊': '否 ',
'清晰': {'根蒂': {'硬挺': '否 ',
'稍蜷': {'色泽': {'乌黑': {'触感': {'硬滑': '是 ', '软粘': '否 '}}, '青绿': '是 '}},
'蜷缩': '是 '}},
'稍糊': {'触感': {'硬滑': '否 ', '软粘': '是 '}}}}
与《机器学习》P78页图4.4对照,完全一致。附:原始数据集
色泽 根蒂 敲声 纹理 脐部 触感 好瓜
编号
1 青绿 蜷缩 浊响 清晰 凹陷 硬滑 是
2 乌黑 蜷缩 沉闷 清晰 凹陷 硬滑 是
3 乌黑 蜷缩 浊响 清晰 凹陷 硬滑 是
4 青绿 蜷缩 沉闷 清晰 凹陷 硬滑 是
5 浅白 蜷缩 浊响 清晰 凹陷 硬滑 是
6 青绿 稍蜷 浊响 清晰 稍凹 软粘 是
7 乌黑 稍蜷 浊响 稍糊 稍凹 软粘 是
8 乌黑 稍蜷 浊响 清晰 稍凹 硬滑 是
9 乌黑 稍蜷 沉闷 稍糊 稍凹 硬滑 否
10 青绿 硬挺 清脆 清晰 平坦 软粘 否
11 浅白 硬挺 清脆 模糊 平坦 硬滑 否
12 浅白 蜷缩 浊响 模糊 平坦 软粘 否
13 青绿 稍蜷 浊响 稍糊 凹陷 硬滑 否
14 浅白 稍蜷 沉闷 稍糊 凹陷 硬滑 否
15 乌黑 稍蜷 浊响 清晰 稍凹 软粘 否
16 浅白 蜷缩 浊响 模糊 平坦 硬滑 否
17 青绿 蜷缩 沉闷 稍糊 稍凹 硬滑 否