在概念上,顾名思义,就是用来实现决策的树,本质上是分类。
决策树简介
分类树和回归树
有些地方会吧决策树分为分类树和回归树。那么什么是分类树,什么是回归树呢?
- 分类树
分类树就是最后的结果是类别,通过对属性的判断条件进行逐层分类,到叶子节点得到一个类别 - 回归树
回归树最后的预测结果是一个值。通常是将预测结果值进行范围划分,得到若干个区域,并使用和分类树类似的方法,得到一个树。预测的时候,预测值等于落到同一个区域的所有样本的平均值。有点类似于AD转换,只是取有效值为样本平均值。
决策树难点
决策树是通过非叶子节点进行条件判断从而到达表示类别的叶子节点的。下面是某婚恋网站建议网友是否见面的一个决策树:
- 我们如何选择判断属性?如何确定他们的判断次序?
- 对于连续变量,我们如何界定最佳的分离点?
实际上,对于这两个问题的不同的解决方法,就形成了不同的决策树算法
决策树的优化
为了尽可能的划分样本,我们有可能会选着过多的判断条件,划分节点过多,以致于把样本数据的特点作为了一般的性质,从而导致过度拟合。比如总共有十个训练数据,你判断了十次,每次分出一个样本,那么肯定可以将十个样本都正确的分类,但是这样的模型在泛华的时候往往不能得到一个好的结果。
为了处理这个问题我们会对已经生成的决策树进行剪枝处理。剪枝分为预剪枝,和后剪枝
预剪枝
预剪枝就是在决策树的生成过程中,没增加一个判断条件都要对数据样本的泛化能力进行测试,如果此判断节点不能增加模型的泛华能力,则不再进行分支。这个比较好理解,就是边生成,边检验。
后剪枝
后剪枝就是在决策树完全生成之后,从底往上,将每一个判定节点,进行泛化能力测试,如果此节点的对泛化能力没有提高,则删除此节点,并且使用训练数据较多的子节点代替判断节点。
比如,对于上面已经生成的决策树。我们后剪枝,首先考虑的是公务员节点,如果样本数据中落到公务员节点中的“见”的数量比“不见”的数量多,我们就可以将公务员判断节点改为“见”,此时就是,如果收入中等,判断为“见”,将修改后的决策树与原先的决策树进行泛化能力测试,从而判断是否保留“公务员”判定节点
决策树的算法
第一招:ID3
核心思想
利用信息熵原理选择信息增益最大的属性作为分类属性,递归地拓展决策树的分枝。
这里有几个关键词:
- 信息熵
根据香农信息论,一条信息的信息量取决于信息相关的不确定度。不确定度越高,信息量越大。比如“明天会下雨”的信息量比“明天太阳从东边升起”,因为明天是否下雨是不确定的,但是太阳从东方升起是一个几乎确定的事情。
信息熵,就是表达信息的不确定度的一种衡量方式。数学表达为:
H ( X ) = − ∑ p i ⋅ log p i H(X)=- \sum p_i \cdot \log p_i H(X)=−∑pi⋅logpi
其中 p i p_i pi 表示为这条消息发生的概率。 - 条件熵
条件熵 H ( Y ∣ X ) H(Y|X) H(Y∣X)表示,已知X条件下,Y的不确定性。
Y的不确定性理解为,X条件下Y的不确定度的数学期望,也就是是对X的每一个元素 x i x_i xi条件下Y的不确定度的数学期望。进一步解释为:
H ( Y ∣ X ) = ∑ p ( y i ) ⋅ H ( Y ∣ X = x i ) H(Y|X)=\sum p(y_i) \cdot H(Y|X=x_i) H(Y∣X)=∑p(yi)⋅H(Y∣X=xi)
化简可得:
H ( Y ∣ X ) = ∑ p ( y i ) ⋅ ( − ∑ p ( y j ∣ x j ) ⋅ log p ( y i ∣ x i ) ) H(Y|X)=\sum p(y_i) \cdot (-\sum p(y_j|x_j) \cdot \log p(y_i|x_i)) H(Y∣X)=∑p(yi)⋅(−∑p(yj∣xj)⋅logp(yi∣xi))
进一步:
H ( Y ∣ X ) = ∑ ∑ p ( y , x ) log p ( y ∣ x ) ) H(Y|X)=\sum \sum p(y,x) \log p(y|x)) H(Y∣X)=∑∑p(y,x)logp(y∣x)) - 信息增益
信息增益是指针对属性A对于数据集D中的信息影响,表示为
g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D,A)=H(D)-H(D|A) g(D,A)=H(D)−H(D∣A)
H ( D ) H(D) H(D): 表示为数据集合D的不确定度
H ( D ∣ A ) H(D|A) H(D∣A): 表示一直A的条件下数据D的不确定度。
不难发现,信息增益实际上说的是属性D对数据D带来的不确定度的大小。
如果信息增益越大,表示越适合作为分类的属性,因为通过这个属性会让不确定性大幅度下降。
(我们分类就是要从不确定到确定的一个过程)
算法过程
考虑下面的西瓜书的一组数据。算法过程主要是挑选变量。
根据信息增量挑选变量
- 数据D的不确定度
H ( D ) = − 5 10 log 5 10 − 5 10 log 5 10 = 1 H(D)=-\frac{5}{10} \log\frac{5}{10} -\frac{5}{10} \log\frac{5}{10}=1 H(D)=−105log105−105log105=1 - 各个属性的条件不确定度。这里只举一个例子
- 色泽{青绿=4(2,2),乌黑=4(3,1),浅白=2(0,2)}
H ( D ∣ 色 泽 ) = 4 10 [ − 2 4 log 2 4 − 2 4 log 2 4 ] + 4 10 [ − 3 4 log 3 4 − 1 4 log 1 4 ] + 2 10 [ − 0 2 log 0 2 − 2 2 log 2 2 ] = 0.7245 H(D|色泽)=\frac{4}{10}[-\frac{2}{4} \log\frac{2}{4} -\frac{2}{4} \log\frac{2}{4}] +\frac{4}{10}[-\frac{3}{4} \log\frac{3}{4} -\frac{1}{4} \log\frac{1}{4}] \\ + \frac{2}{10}[-\frac{0}{2} \log\frac{0}{2} -\frac{2}{2} \log\frac{2}{2}] =0.7245 H(D∣色泽)=104[−42log42−42log42]+104[−43log43−41log41]+102[−20log20−22log22]=0.7245
g ( D , 色 泽 ) = H ( D ) − H ( D ∣ 色 泽 ) = 0.2755 g(D,色泽)=H(D)-H(D|色泽)=0.2755 g(D,色泽)=H(D)−H(D∣色泽)=0.2755
随手写的一个计算信息增益的的python 脚本。
import numpy as np
colors={'乌黑':0, '青绿':1, '浅白':2, 'None':-1}
roots = {'蜷缩':0, '稍蜷':1,'硬挺':2, 'None':-1}
sounds = {'浊响':0, '沉闷':1, '清脆':2, 'None':-1}
veins = {'清晰':0, '稍糊':1, '模糊':2, 'None':-1}
umbilicus = {'凹陷':0,'稍凹':1,'平坦':2, 'None':-1}
feelings = {'硬滑':0,'软粘':1, 'None':-1}
lables = {'否':0, '是':1 }
def file2matrix(name):
with open(name,encoding='utf-8') as file:
lines = file.readlines()
numberOfLines = len(lines)
DataSet = np.zeros((numberOfLines,8))
DataLabels = []
index = 0
for line in lines:
line = line.strip()
lineList= line.split('\t')
lineList[1]=colors[lineList[1]]
lineList[2]=roots[lineList[2]]
lineList[3]=sounds[lineList[3]]
lineList[4]=veins[lineList[4]]
lineList[5]=umbilicus[lineList[5]]
lineList[6]=feelings[lineList[6]]
lineList[7]=lables[lineList[7]]
DataSet[index,:] = lineList[0:8]
DataLabels.append(lineList[-1])
index +=1
return DataSet, DataLabels
def GainEntropy(datalist, labelslist):
num = len(datalist)
kinds = {}
for i in range(num):
if i==0 :
kinds.update({datalist[0]:[1,labelslist[i]]})
else:
if datalist[i] in kinds.keys():
kinds[datalist[i]][0] =kinds[datalist[i]][0] +1
kinds[datalist[i]][1] +=labelslist[i]
else:
kinds.update({datalist[i]:[1,labelslist[i]]})
valuelist = list(kinds.values())
nums_true = 0
ed = 0
for i in range(len(valuelist)):
if valuelist[i][1]==0 or valuelist[i][0] == valuelist[i][1]:
d = 0
else:
d1 = np.true_divide(valuelist[i][1],valuelist[i][0])
d0 = np.true_divide((valuelist[i][0]-valuelist[i][1]), valuelist[i][0])
d = d1*np.log2(d1)+d0*np.log2(d0)
ed1 = np.true_divide(valuelist[i][0],num)*(-d)
nums_true += valuelist[i][1]
ed += ed1
p1 = np.true_divide(nums_true,num)
p0 = np.true_divide(num-nums_true,num)
en = -(p1*np.log2(p1)+p0*np.log2(p0))
return en-ed
if __name__ =='__main__':
DataSet, DataLabels = file2matrix('2_0.txt')
print(DataSet)
print(DataLabels)
print(GainEntropy(list(DataSet[:,1]), DataLabels))
2_0.txt 复制下面的信息:
1 青绿 蜷缩 浊响 清晰 凹陷 硬滑 是
2 乌黑 蜷缩 沉闷 清晰 凹陷 None 是
3 乌黑 蜷缩 浊响 清晰 凹陷 硬滑 是
4 青绿 蜷缩 沉闷 清晰 凹陷 硬滑 是
5 浅白 蜷缩 浊响 清晰 凹陷 硬滑 是
6 青绿 稍蜷 浊响 清晰 None 软粘 是
7 乌黑 稍蜷 浊响 稍糊 稍凹 软粘 是
8 乌黑 稍蜷 浊响 None 稍凹 硬滑 是
9 乌黑 稍蜷 沉闷 稍糊 稍凹 硬滑 否
10 青绿 硬挺 清脆 None 平坦 软粘 否
11 浅白 硬挺 清脆 模糊 平坦 None 否
12 浅白 蜷缩 浊响 模糊 平坦 软粘 否
13 青绿 稍蜷 浊响 稍糊 凹陷 硬滑 否
14 浅白 稍蜷 沉闷 稍糊 凹陷 硬滑 否
15 乌黑 稍蜷 浊响 清晰 None 软粘 否
16 浅白 蜷缩 浊响 模糊 平坦 硬滑 否
17 青绿 蜷缩 沉闷 稍糊 稍凹 硬滑 否
递归挑选变量。
比如,如果选择了色泽作为第一个属性,则将数据分为了三部分,分别对这三部分数据使用上面的方法挑选变量。如此循环最终得到一颗决策树
- 色泽青绿
- 色泽乌黑
- 色泽浅白
第二招:C4.5
C4.5 的进步
ID3 可以正常的生成决策树,但是依然存在巨大的弊端。
- 缺少对连续值的处理
- 缺少对缺失值的处理
- 在原理上倾向于选择属性种类多的属性。
主要对第三点进行说明。根据信息论的基础,分布均匀的情况下,种类多的属性的熵要大。
比如有一个属性分布如下(1,1,1,0,0,0)与(1,1,0,0,2,2),种类数目是2和3
第一组的熵为:
−
3
6
log
3
6
−
3
6
log
3
6
=
1
-\frac{3}{6}\log\frac{3}{6}-\frac{3}{6}\log\frac{3}{6}=1
−63log63−63log63=1
第二组的熵为:
−
2
6
log
2
6
−
2
6
log
2
6
−
2
6
log
2
6
=
log
3
-\frac{2}{6}\log\frac{2}{6}-\frac{2}{6}\log\frac{2}{6}-\frac{2}{6}\log\frac{2}{6}=\log3
−62log62−62log62−62log62=log3
我们会更加倾向选着第二组作为实际的分类,但实际上,这种区分并不明显。
增益率
相比于ID3使用信息增益,C4.5考虑到属性种类带来的影响使用的是信息增益率。
g
a
i
n
_
r
a
t
i
o
(
D
,
A
)
=
g
a
i
n
(
D
,
A
)
E
n
t
(
A
)
gain\_ratio(D,A)=\frac{gain(D,A)}{Ent(A)}
gain_ratio(D,A)=Ent(A)gain(D,A)
其中
g
a
i
n
(
D
,
A
)
gain(D,A)
gain(D,A)表示属性A的信息增益,
E
n
t
(
A
)
Ent(A)
Ent(A)表示属性A的信息熵。
直观上理解是,增益率会在一定程度上抑制由于属性数目带来的偏见。
代码上稍修改一下就可以算得
E
n
t
(
A
)
Ent(A)
Ent(A)(代码上的-iv)
iv = 0
for i in range(len(valuelist)):
if valuelist[i][1]==0 or valuelist[i][0] == valuelist[i][1]:
d = 0
v =0
else:
...
d = d1*np.log2(d1)+d0*np.log2(d0)
v0 = np.true_divide(valuelist[i][0],num)
v = v0*np.log2(v0)
...
ed += ed1
iv +=v
...
return en-ed,-iv
连续值
C4.5算法于是将连续的属性进行离散化,离散化策略就是二分法。假设某个属性
a
0
,
a
1
,
a
2
,
.
.
.
{a_0,a_1,a_2,...}
a0,a1,a2,...
我们可能选择的边界就是
b
i
=
a
i
+
a
i
+
1
2
b_i=\frac{a_i+a_{i+1}}{2}
bi=2ai+ai+1
比如,现在有一个属性的值为{70,95,100,120,125}
那么我们的可能的边界就为{82.5,97.5,110,122.5}
分别对几个边界进行信息增益计算,最后选择一个合适的边界。
缺失值
缺失值有两个问题
- 在训练样本某些特征缺失的情况下选择划分的属性,也就是说,用哪个属性来判断类别。
- 选定了划分属性,对于在该属性上缺失特征的测试样本的处理。就是说如果树已经生成,但样本的用于判断的属性没有,怎么预测样本的类别。
如上述的例子,存在一些缺失值。
比如我们指定,上述数据集D和属性色泽a,
D
′
D'
D′表示数据集合D中属性a没有缺失的样本子集。也就是{2,3,4,6,7,8,9,10,11,12,14,15,16,17}
对于第一个问题,我们可以直接使用
D
′
D'
D′来判断属性的优劣。最后乘以一个比例因子。
g
a
i
n
(
D
,
a
)
=
ρ
⋅
g
a
i
n
(
D
′
,
a
)
gain(D,a) = \rho \cdot gain(D',a)
gain(D,a)=ρ⋅gain(D′,a)
也就是说通过计算属性完全的样本集合的属性增益。
其中
ρ
\rho
ρ表示属性a 有值占的比例,比如色泽,共17个属性,有值得为14个,占比14/17
对于第二个问题,选定一个属性后。对于缺失值的样本处理,对于编号为8和10的缺失值样本,将分别以7/15、5/15、3/15的权重划分到以上3个分支。也就是说,将缺失值样本按不同的概率划分到了所有分支中,而概率则等于无缺失值样本在每个分支中所占的比例。这个实际上是不太好理解的。
举个例子,如果我们选定纹理。我们有两个缺失属性{8,10}由于清晰占7/15,稍糊5/15,模糊3/15,所以这两个缺失的样本就按照这三个比例划分到各个属性中。认为7/15的为清晰。
C4.5 虽然处理了一些ID3的问题但是依然也还有一些问题:
- C4.5 用的是多叉树,用二叉树效率更高;
- C4.5 只能用于分类;
- C4.5 使用的熵模型拥有大量耗时的对数运算,连续值还有排序运算;
- C4.5 在构造树的过程中,对数值属性值需要按照其大小进行排序,从中选择一个分割点,所以只适合于能够驻留于内存的数据集,当训练集大得无法在内存容纳时,程序无法运行。
第三招:CART
<后续>