决策树
决策树(decision tree)亦称“判定树”是一类常见的机器学习方法。以二分类任务为例,我们希望从给定训练集学得一个用以对新示例进行分类。比如根据书上的西瓜各种特征来最终得到这个瓜是不是好瓜。决策树的分类算法是基于实例的归纳学习算法。能从给定的一些完全无序的训练样本中,提炼出树形的分类模型。决策树的生成是一个递归的过程。树的非叶子节点记录了使用哪一个特征进行判断;叶子节点给出了最后的判断。决策树是一种监督学习
决策树的学习过程主要是:
- 特征选择:选择哪个特征作为当前节点的判断依据
- 决策树生成:以递归的方式生成树
- 剪枝:解决“过拟合”的主要手段,主要有“预剪枝”和“后剪枝”
常见的决策树算法分为ID3、 C4.5、CART算法,ID3算法主要是根据“信息增益“来选择特征的;C4.5算法是根据”信息增益比“来选择特征;而CART算法则是根据”基尼指数“来选择特征。
- 特征选择
- 信息增益
“信息熵”是度量样本集合纯度最常用的一种指标。假定当前样本集合D中第k类样本所占的比例为pk(k=1,2,…),信息熵的定义为:
Ent(D)的值越小,D的纯度越高。由此得到“信息增益”的公式:
信息增益越大,则意味着使用属性a来进行划分所获得的“纯度提升”越大。
ID3算法就是通过获得各个特征的“信息增益”的值,选择其中最大的作为当前非叶子节点的判断依据。
- 增益率
信息增益准则对可取值数目较多的属性有所偏好,为了减少这种偏好可能带来的不利影响。C4.5算法使用“增益率”来划分最优属性。增益率定义:
IV(a)成为属性a的“固有值”,a的可能取值数目越多,则IV(a)的值通常会越大。注意:C4.5算法并不是直接选择增益率最大的属性作为候选划分属性,而是:先从候选划分属性中找出信息增益高于平均水平的属性,再从中选择增益率最高的。
- 基尼指数
CART算法使用“基尼指数”来选择划分属性,并且选择基尼指数最小的那个属作为最优划分属性。数据集D的纯度可用基尼值来度量:
Gini(D)的意义是:从数据集D中随机抽取两个样本,其类别标记不一致的概率,因此,Gini(D)越小,数据集(D)纯度越高。
- 决策树生成(ID3)
这里使用书上的那个西瓜例子
数据集
def data_set():
dataSet = [['青绿', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '是'],
['乌黑', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', '是'],
['乌黑', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '是'],
['青绿', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', '是'],
['浅白', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '是'],
['青绿', '稍蜷', '浊响', '清晰', '稍凹', '软粘', '是'],
['乌黑', '稍蜷', '浊响', '稍糊', '稍凹', '软粘', '是'],
['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '硬滑', '是'],
['乌黑', '稍蜷', '沉闷', '稍糊', '稍凹', '硬滑', '不是'],
['青绿', '硬挺', '清脆', '清晰', '平坦', '软粘', '不是'],
['浅白', '硬挺', '清脆', '模糊', '平坦', '硬滑', '不是'],
['浅白', '蜷缩', '浊响', '模糊', '平坦', '软粘', '不是'],
['青绿', '稍蜷', '浊响', '稍糊', '凹陷', '硬滑', '不是'],
['浅白', '稍蜷', '沉闷', '稍糊', '凹陷', '硬滑', '不是'],
['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '软粘', '不是'],
['浅白', '蜷缩', '浊响', '模糊', '平坦', '硬滑', '不是'],
['青绿', '蜷缩', '沉闷', '稍糊', '稍凹', '硬滑', '不是']]
labels = ['色泽', '根蒂', '敲声', '纹理', '脐部', '触感', '是否好瓜']
return dataSet, labels
#计算信息熵
def cal_entro(dataSet):
num = len(dataSet)
labels = {}
#计算同一属性不同类别的个数
for row in dataSet:
label = row[-1]
if label not in labels.keys():
labels[label] = 0
labels[label] += 1
Ent = 0.0
#计算信息熵
for key in labels:
prob = float(labels[key]) / num
Ent -= prob * log(prob, 2)
return Ent
#返回第i个特征的值为value的全部数据集
def split_dataSet(dataSet, i, value):
ret_dataSet = []
for row in dataSet:
if row[i] == value:
re_row = row[:i]
re_row.extend(row[i+1:])
ret_dataSet.append(re_row)
return ret_dataSet
#选择最佳的特征
def choose_best_feature(dataSet):
baseEnt = cal_entro(dataSet)
bestFeature = -1
bestGain = 0.0
num = len(dataSet[0]) - 1
for i in range(num):
eveEnt = 0.0
# 按历遍历数据集,选取一个特征的全部取值
featList = [example[i] for example in dataSet]
uniqueVals = set(featList)
for value in uniqueVals:
sub_dataSet = split_dataSet(dataSet, i, value)
prob = len(sub_dataSet) / float(len(dataSet))
eveEnt += prob * cal_entro(sub_dataSet)
#信息增益
infoEnt = baseEnt - eveEnt
if infoEnt > bestGain:
bestFeature = i
bestGain = infoEnt
return bestFeature
#
def majorityCnt(classList):
count = {}
for value in classList:
if value not in count.keys():
count[value] = 0
count[value] += 1
sorted(count.items(), key=operator.itemgetter(1), reverse=True)
return count[0][0]
#建立决策树
def create_tree(dataSet, labels):
copylabels = labels[:]
classList = [example[-1] for example in dataSet]
#如果只有一种类别,则停止划分
if classList.count(classList[0]) == len(classList):
return classList[0]
#没有特征了
if dataSet[0] == 1:
return majorityCnt(classList)
bestFeature = choose_best_feature(dataSet)
bestLabel = copylabels[bestFeature] #最佳特征
tree = {bestLabel:{}}
del copylabels[bestFeature]
featList = [example[bestFeature] for example in dataSet]
uniquelVals = set(featList)
for value in uniquelVals:
copy2labels = copylabels[:]
tree[bestLabel][value] = create_tree(split_dataSet(dataSet, bestFeature, value), copy2labels)
return tree
#使用决策树来测试用例进行预测
def classify(tree, labels, testData):
key = list(tree.keys())[0]
dict = tree[key]
featIndex = labels.index(key)
for key in dict:
if key == testData[featIndex]:
if type(dict[key]).__name__ == 'dict':
classLabel = classify(dict[key], labels, testData)
else:
classLabel = dict[key]
return classLabel
if __name__ == '__main__':
dataSet, labels = data_set()
myTree = create_tree(dataSet, labels)
print(myTree)
treePlotter.createPlot(myTree)
testData = ['浅白', '蜷缩', '清脆', '清晰', '平坦', '硬滑']
result = classify(myTree, labels, testData)
print(result)
通过该算法最后生成一棵决策树,这是一棵训练好了的树,可以带入实例进行验证

不过与树上的决策树不同的是:书上色泽有一个纯白分支,但是训练集中没有满足{清晰、稍蜷、纯白、......}的例子,所以上面的代码中训练不出来有该特征的分支。
- 剪枝
剪枝(pruning)是决策树学习算法对付“过拟合”的主要手段。决策树剪枝的基本策略有“预剪枝”(prepruning)和“后剪枝”(postpruning)
预剪枝:决策树生成过程中,对每个节点在划分之前进行估计。若当前节点的划分不能带来决策树泛化性能提升,则停止划分并将当前节点作为叶节点标记。
编号 | 色泽 | 根蒂 | 敲声 | 纹理 | 脐部 | 触感 | 好瓜 |
4 | 青绿 | 蜷缩 | 沉闷 | 清晰 | 凹陷 | 硬滑 | 是 |
5 | 浅白 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | 是 |
8 | 乌黑 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 硬滑 | 是 |
9 | 乌黑 | 稍蜷 | 沉闷 | 稍糊 | 稍凹 | 硬滑 | 否 |
11 | 浅白 | 硬挺 | 清脆 | 模糊 | 平坦 | 硬滑 | 否 |
12 | 浅白 | 蜷缩 | 浊响 | 模糊 | 平坦 | 软粘 | 否 |
13 | 青绿 | 稍蜷 | 浊响 | 稍糊 | 凹陷 | 硬滑 | 否 |
以程序获得的决策树为例,假如在根节点不分的话,标记为好瓜(类别标记为训练样例最多的类别);则{4 5 8}的样例被分类正确。即accuracy=3/7=42.9%
在用属性“纹理”划分之后,纹理的三个分支分别为{16},{7 14 17},{1 2 3 6 10 15 }分别被标记为“坏瓜” “坏瓜” “好瓜”,验证{4 5 8 9 11 12 13}accuracy=100%>42.9%,所以,用“纹理”进行划分得以确定。
同理,继续比较,不过由于上一个已经是100%了,后面的一定不会比它大,所以最终结果直接得以确定,不用再分;不过这个100%是因为训练集和数据集太小,不足以说明情况
后剪枝:线直接生成一课完整的决策树,然后自底向上对非叶子节点进行考察,若将该节点对应的子树替换成叶子节点能提高决策树的泛化能力,则将该节点替换为叶子节点。
后剪枝同理,只不过后剪枝是自底向上,这里不再赘述
后剪枝通常要比预剪枝保留了更多的分支,后剪枝决策树的欠拟合风险很小,泛化能力往往优于预剪枝,但其训练时间开销比未剪枝和预剪枝决策树都要大。
当训练集中有连续值的时候,使用连续属性离散化来进行处理;首先将该属性的所有值从小到大排序得到{},通过划分点t将D分为子集
,其中一个装着所有比t小的值,另一个装所有比t大的值
将{}中任意两个连续的值的中位数即
,以课本机器学习P84中表4.3中的数据为例
对于属性“密度”,将17个值从小到大排序,然后通过计算出T={0.244,0.294,0.351,... ... ,0.708,0.746}
,然后分别以为t将D分为两个子集,分别计算信息增益
比如以0.351为例:
挨个计算之后,选择最大的值作为属性“密度”的信息增益。
当训练集中有缺失值的时候,将某一属性非缺失的那些值按照原来的方法计算,然后在计算得到的Gain(D, a)的基础上乘以ρ
,Di为非缺失的数据项的数目,D为总数目;
即为该属性的信息增益。
然后需要注意的是:在将某一个含有缺失值的属性标记为非叶子节点的时候,含有缺失值的那一项分配到每一个分支节点中,但是权重在各个节点中分别调整为(Di为该属性总的非缺失的项,Dj为该分支中含有的项).