一些关于决策树自己的理解
决策树是根据信息增益自己选择规则,并且递归构建树的过程。
所谓的信息增益可以理解为:原本系统是混乱的,当选择一个属性划分,划分后两个子系统的混乱程度是否减轻了;比如;原来系统正负样本,比例为1:1 ,如果你选择一个属性进行划分后,两个子系统的正负样本比例变成了 8:2 和3:7 ,那么这次 的划分就是有意义的,而根据信息增益,更大的信息增益,表示更好的划分属性,从而大大减小系统的混乱程度也就是熵。
【注】熵本来是物理学的概念,便是一个区域内,事物的混乱程度,越混乱熵越大;也就是所,世界上所有物质都是在熵增的过程 中,比如桌子会越来越来乱,地上会越来越脏,但是也可以通过消耗能量的方式,降低一个区域的熵,比如消耗生物能收拾下桌子,通过消耗热能把碎铁融成铁块。通过选择属性降低数据集的混乱程度。
本次使用的数据集是【西瓜数据集2.0】
编号,色泽,根蒂,敲声,纹理,脐部,触感,好瓜
1,青绿,蜷缩,浊响,清晰,凹陷,硬滑,是
2,乌黑,蜷缩,沉闷,清晰,凹陷,硬滑,是
3,乌黑,蜷缩,浊响,清晰,凹陷,硬滑,是
4,青绿,蜷缩,沉闷,清晰,凹陷,硬滑,是
5,浅白,蜷缩,浊响,清晰,凹陷,硬滑,是
6,青绿,稍蜷,浊响,清晰,稍凹,软粘,是
7,乌黑,稍蜷,浊响,稍糊,稍凹,软粘,是
8,乌黑,稍蜷,浊响,清晰,稍凹,硬滑,是
9,乌黑,稍蜷,沉闷,稍糊,稍凹,硬滑,否
10,青绿,硬挺,清脆,清晰,平坦,软粘,否
11,浅白,硬挺,清脆,模糊,平坦,硬滑,否
12,浅白,蜷缩,浊响,模糊,平坦,软粘,否
13,青绿,稍蜷,浊响,稍糊,凹陷,硬滑,否
14,浅白,稍蜷,沉闷,稍糊,凹陷,硬滑,否
15,乌黑,稍蜷,浊响,清晰,稍凹,软粘,否
16,浅白,蜷缩,浊响,模糊,平坦,硬滑,否
17,青绿,蜷缩,沉闷,稍糊,稍凹,硬滑,否
源码分析下:
首先导入读取csv,并把提取列名,供以后使用:
df=pd.read_csv("./ml2.0.csv")
# 属性集合
attr=df.columns.values.tolist()[1:]
data_org=np.array(df[attr[0:]])
static_attr=df.columns.values.tolist()[1:]#这里的属性 不改变,仅仅作为索引
获得各个列名下的详细分类:
attr_dict={}#用于记录每一个属性的取值
for x in attr[:-1]:
temp=np.array(df[x])
attr_dict[x]=set(temp)
attr_dict 格式如下:
{'色泽': {'乌黑', '浅白', '青绿'},
'根蒂': {'蜷缩', '硬挺', '稍蜷'},
'敲声': {'浊响', '清脆', '沉闷'},
'纹理': {'清晰', '稍糊', '模糊'},
'脐部': {'平坦', '稍凹', '凹陷'},
'触感': {'硬滑', '软粘'}
}
判断一个数据集中,是否全部为正样本或者全部为负样本
def lable_is_same(D):
l=[D[i][-1] for i in range(len(D))]
#将数据集最后一列,也就是label 标签提取出来.
#然后使用set 去掉重复元素,
#如果只有一个样本,表明全正或者全负
return len(list(set(l)))==1
下面判断,是否数据集中属性全部都相同:
def all_attr_is_same(D,d_attr):
if D.shape[0]==0:return True #如果是空集
for x in d_attr:
index=static_attr.index(x)
for i in D[0:] :
if i[index]!=D[0][index]:
return False
return True
以上是两个结束条件的判断:
在第一种情况下;所有的值都是正或者负样本:
这时候,只要把这个分类的label值作为叶子节点,结束构造即可。
比如 在纹理分类下:纹理模糊的瓜都不是好瓜,因此,可得出结论
在第二种情况下:数据集合中所有属性都一样,一次不用再进行划分了
比如,属性集合中只有[颜色,根蒂]两个属性,但是他们都相同,无法进行划分
这时候,将只要把出现最多的label值作为叶子节点,父节点是上一个属性,这里选择是好瓜。然后结束构造即可。
下面进行选择最佳节点的操作:
首先,我们需要一个函数统计一下,一个样本集中,正样本和负样本的比例,并根据这个概率计算信息熵
def collect(D):
if D.shape[0]==0:
return 0.0000000001
count= 0
for i in D:
count=count+1 if i[-1]=='是' else count
return count /(D.shape[0])
下面进行信息熵的计算
def Ent(D):
p=collect(D)
if (fabs(1.0 - p) < 0.000001) or (fabs(0.0 - p) < 0.000001):
return 0 #表示完全分割开了
return -p*np.log2(p)-(1-p)*np.log2(1-p)
计算信息增益:信息增益是什么,原来系统是混乱的,当以一个属性区分后,混乱度减小了,那怎么量化这个减小呢? 信息增益就是就是标准。
def Gain(D,a):
G=Ent(D)
index = static_attr.index(a)
lis=attr_dict[a]
temp_dict=split_data(D,a)
sum=0
for x in lis:
d=np.array(temp_dict[x])
sum+=(d.shape[0]/D.shape[0])*Ent(d)
return G-sum
#由于合理的划分时熵减小的过程,所以G在前
信息增益的函数中,我们使用了一个划分函数 ,这个划分函数的作用时,将数据集根据属性进行划分。比如 以纹理划分:则将数据集划分为三类,一类纹理清晰,一类纹理稍糊和纹理模糊。下面是具体实现:
def split_data(D,a):
index = static_attr.index(a)
lis=attr_dict[a]
temp_dict={}
for i in lis:
temp_dict[i]=[]
for i in D:
for at in lis:
if i[index]==at:
temp_dict[at].append(i)
break;
return temp_dict
下面进行最优属性的选择:什么是最优属性,那还用说,肯定是信息增益最大的属性呗,我们只要找到信息增益最大的那个属性就可以了。
def select_opt_attr(D,d_attr):
li=[]
for x in d_attr:
li.append(Gain(D,x))
return d_attr[li.index(max(li))]
做完了所有的准备工作,下面我们开始主算法
def TreeGenerate(D,d_attr,node,father):#ID3算法
if D.shape[0]==0:
return
if label_is_same(D):
node['final']=D[-1][-1]
return
if len(d_attr)==0 or all_attr_is_same(D,d_attr):# 属性集为空 或者所有样本在说有属性上取值相同
node['final']='是' if collect(D)>0.5 else '否'
return
#选择最优的划分属性:
opt_attr=select_opt_attr(D,d_attr)
d_attr.remove(opt_attr)
#根据最优属性划分集合:
attr_dict=split_data(D,opt_attr)
node[opt_attr]={}
for i in attr_dict:
d1=np.array(attr_dict[i])
node[opt_attr][i]={}
TreeGenerate(d1,d_attr[:],node[opt_attr][i],i)# 递归调用生成函数
运行结果如下:
{'纹理': {
'清晰': {
'根蒂': {
'硬挺': {'final': '否'},
'稍蜷': {
'色泽': {
'浅白': {},
'乌黑': {
'触感': {
'软粘': {'final': '否'},
'硬滑': {'final': '是'}
}
},
'青绿': {'final': '是'}
}
},
'蜷缩': {'final': '是'}
}
},
'稍糊': {
'触感': {
'软粘': {'final': '是'},
'硬滑': {'final': '否'}
}
},
'模糊': {'final': '否'}}}
到此整个算法就实现了。这次的设计是基于 ID3 算法 依然存在很多问题,如过拟合等。