在介绍决策树模型(Decision Tree Model)之前,先讲一个易于理解决策树的例子。女孩的母亲见女儿一直单着,就给女儿介绍了一个男朋友,于是有了下面的这段对话:
女儿:多大年纪了?
母亲:28岁
女儿:长的帅不帅?
母亲:长相挺一般的
女儿:收入怎么样?
母亲:也就中等情况
女儿:是公务员吗?
母亲:是,在民政局上班
女儿:那好,我去见见
对于母亲介绍的男朋友,女孩先打听对方的每一项情况,然后逐一判断是否符合自己的标准,如果各方面条件都满足,就会见对方一面,如果有一项不符合要求,那么这个男孩就没有任何机会了。下图是基于女孩相亲标准创建的树模型,通过判定多个条件决定是否与相亲对象见面。
决策树模型是一个模拟人类决策过程思想的模型,一颗决策树包含多个内部节点(internal node)和若干个叶节点(leaf node)。叶结点对应于结果,比如上例中的决策结果只有两个:见与不见;内部结点则对应于属性或特征,比如年龄、长相等。决策树遵循简单且直观的“分而治之”(divide-and-conquer)策略,首先从根结点开始,对实例的某一特征进行测试,基于测试结果分配到其子结点;然后递归的进行测试和分配,直至到达叶节点。决策树生成的伪代码如下:
输入: 训练集
D
=
{
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
,
.
.
.
,
(
x
n
,
y
n
)
}
D = \{(x_1,y_1),(x_2,y_2),...,(x_n,y_n)\}
D={(x1,y1),(x2,y2),...,(xn,yn)}
属性集
A
=
{
a
1
,
a
,
.
.
.
,
a
d
}
A= \{a_1,a_,...,a_d \}
A={a1,a,...,ad}
过程: 函数TreeGenerate(D,A)
- 生成节点node
- if D中样本全属于同一类 C k C_k Ck then
- 将node标记为 C k C_k Ck类叶结点 return
- end if
- if A = ∅ A=\emptyset A=∅ OR D中样本在A上取值相同 then
- 将node标记为叶结点,其类别标记为D中样本数最多的类 return
- end if
- 从A中选择最优划分属性 a ∗ a_* a∗
- for a ∗ a_* a∗的每一个值 a ∗ v a_*^v a∗v do
- 为node生成一个分支;令 D v D_v Dv表示 D D D中在 a ∗ a_* a∗上取值为 a ∗ v a_*^v a∗v的样本子集
- if D v D_v Dv为空 then
- 将分支结点标记为叶结点,其类别标记为D中样本最多的类 return
- else
- 以TreeGenerate( D v D_v Dv,A\ { a ∗ a_* a∗})为分支结点
- end if
- end for
输出: 以node为根结点的一颗决策树
决策树学习本质上是从训练数据集中归纳出一组分类规则,常用的算法有ID3和C4.5。两种算法的区别在于选择特征的准则不同,ID3算法是基于信息增益选择特征的,而C4.5基于信息增益比选择特征。在介绍生成树算法之前,先介绍几个基本概念:熵、信息增益、信息增益比。
熵(entropy)是度量样本集合纯度最常用的一种指标。假定当前样本集合D中第k类样本所占的比例为
p
k
(
k
=
1
,
2
,
.
.
.
,
K
)
p_k(k=1,2,..., K)
pk(k=1,2,...,K),则D的熵定义为:
H
(
D
)
=
−
∑
k
=
1
K
p
k
l
o
g
2
p
k
H(D)=-\sum_{k=1}^{K} p_k {log}_2 p_k
H(D)=−k=1∑Kpklog2pk
H
(
D
)
H(D)
H(D)的值越小,则D的纯度越高。
假定离散属性
a
a
a有
V
V
V个可能的取值{
a
1
,
a
2
,
.
.
.
,
a
V
a^1,a^2,...,a^V
a1,a2,...,aV},若使用a来对样本集
D
D
D进行划分,则会产生
V
V
V个分支结点,其中第
v
v
v个分支结结点包含了
D
D
D中所有在属性
a
a
a上的取值为
a
v
a^v
av的样本,记为
D
v
D^v
Dv。考虑到不同的分支结点包含的样本数不同,给分支结点赋予权重
∣
D
v
∣
/
∣
D
∣
|D^v|/|D|
∣Dv∣/∣D∣,即样本数越多的分支结点的影响越大。用属性
a
a
a对样本集
D
D
D进行划分所获得的信息增益(information gain)为:
G
a
i
n
(
D
,
a
)
=
H
(
D
)
−
∑
v
=
1
V
∣
D
v
∣
∣
D
∣
H
(
D
v
)
Gain(D,a)=H(D)-\sum_{v=1}^V\frac{|D^v|}{|D|}H(D^v)
Gain(D,a)=H(D)−v=1∑V∣D∣∣Dv∣H(Dv)
一般而言,信息增益越大,则意味着使用属性a来进行划分所获得的“纯度提升”越大。
信息增益准则对可取值数目较多的属性有所偏好,为减少这种偏好可能带来的不利影响,C4.5决策树算法不直接使用信息增益,而是使用信息增益率(information gain ratio)来选择最优划分属性。
G
a
i
n
_
r
a
t
i
o
(
D
,
a
)
=
G
a
i
n
(
D
,
a
)
I
V
(
a
)
Gain\_ratio(D,a)=\frac{Gain(D,a)}{IV(a)}
Gain_ratio(D,a)=IV(a)Gain(D,a)
其中
I
V
(
a
)
=
−
∑
v
=
1
V
∣
D
v
∣
∣
D
∣
l
o
g
2
∣
D
v
∣
∣
D
∣
IV(a)=-\sum_{v=1}^{V}\frac{|D^v|}{|D|}log_2\frac{|D^v|}{|D|}
IV(a)=−v=1∑V∣D∣∣Dv∣log2∣D∣∣Dv∣称为属性
a
a
a的“固有值”(intrinsic value)。属性
a
a
a的可能取值数目越多(即
V
V
V越大),则
I
V
(
a
)
IV(a)
IV(a)的值通常会越大。
信息增益率准则对可取值数目较少的属性有所偏好,因此,C4.5算法并不是直接选择增益率最大的候选划分属性,而是使用了一个启发式:先从候选划分属性中找出信息增益高于平均水平的属性,再从中选择增益率最高的。
如果我们采集到一些相亲数据,如何生成一颗相亲见面与否的决策树呢?下面是基于ID3算法实现的决策树,基于信息增益的准则选择最优的特征,训练集只包含了10个样本,让我们一起看下训练的效果如何。
import numpy as np
import operator
#创建包含10个样本的数据集
def createDataSet():
#特征:年龄、长相、收入、是否公务员,类标记:是否见面
dataSet = \
[\
['leq30','handsome','high','no','see'],\
['leq30','handsome','high','yes','see'],\
['leq30','handsome','middle','yes','see'],\
['g30','handsome','high','no','no see'],\
['leq30','ugly','high','no','no see'],\
['leq30','handsome','middle','no','no see'],\
['leq30','handsome','low','yes','no see'],\
['g30','handsome','low','no','no see'],\
['leq30','ugly','middle','yes','no see'],\
['g30','ugly','high','yes','no see']\
]
labels = ['age','looks','income','civil-service']
return dataSet, labels
#计算数据集的熵
def calcShannonEnt(dataSet):
numEntries = len(dataSet) #实例总数
labelCounts = {}
for featVec in dataSet:
currentLabel = featVec[-1] #类标签
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1 #类数+1
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries #类占比
shannonEnt -= prob * np.log2(prob)
return shannonEnt
#基于特征分配得到子数据集
def splitDataSet(dataSet,axis,value):
subDataSet = [] #创建新list对象
for featVec in dataSet:
if featVec[axis] == value: #抽取符合特征的非该特征列
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
subDataSet.append(reducedFeatVec)
return subDataSet
#基于信息增益准则选择最优的特征
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0])-1 #除最后一列为类标签外,其他列为特征列
baseEntropy = calcShannonEnt(dataSet) #数据集的原始熵
bestInfoGain = 0.0; bestFeature = -1
for i in range(numFeatures): #遍历特征
featList = [example[i] for example in dataSet]
uniqueVals = set(featList) #特征下唯一属性值
newEntropy = 0.0
for value in uniqueVals:
subDataSet = splitDataSet(dataSet,i,value) #按特征划分数据集
prob = len(subDataSet)/float(len(dataSet)) #计算类别占比
newEntropy += prob * calcShannonEnt(subDataSet) #基于划分后的新熵(条件熵)
infoGain = baseEntropy - newEntropy #信息增益
if(infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
#投票选择出现最多的类
def majorityCnt(classList):
classCount={}
for vote in classList:
if vote not in classCount.keys():classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) #反序排序,返回出现次数最多的类
return sortedClassCount[0][0]
#生成决策树
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet] #所有类标签
if classList.count(classList[0]) == len(classList): #如果类完全相同则停止划分
return classList[0]
if len(dataSet[0]) == 1: #遍历完所有特征时返回出现次数最多的类
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat] #最优的特征标签
myTree = {bestFeatLabel:{}}
del(labels[bestFeat])
featValues = [example[bestFeat] for example in dataSet] #得到列表包含的所有属性值
uniqueVals = set(featValues) #去重
for value in uniqueVals:
subLabels = labels[:]
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value),subLabels) #递归生成决策树
return myTree
#创建数据集和特征标签
myDat,labels = createDataSet()
#基于信息增益创建决策树
myTree = createTree(myDat,labels)
最终训练得到的决策树如下图所示,决策树以JSON的形式表示,key是特征标签或特征值,value是see或no see。可以看出,训练得到的决策树就是上例中女孩相亲的决策树。
决策树是基于特征对实例进行分类的树状结构,从根结点开始,递归的选择特征并分配产生新的子结点,直至叶结点。特征选择的目的在于选择对训练数据能够分类的特征,常用的准则包括信息增益和信息增益比。一般来讲,机器学习项目并不直接使用决策树分类,而是基于决策树等弱学习器,通过集成学习方法提升为强学习器,比如Boosting、Bagging和Random Forest。