笔记|统计学习方法:决策树(一)

决策树的基本概念

决策树就是一棵树

  • 叶结点对应于决策结果,其他每个结点则对应于一个属性测试;
  • 每个结点包含的样本集合根据属性测试的结果被划分到子结点中;
  • 根结点包含样本全集,从根结点到每个叶子结点的路径对应了一个判定测试序列。 示例:

决策树学习的关键在于如何选择最优的划分属性,所谓的最优划分属性,对于二元分类而言,就是尽量使划分的样本属于同一类别,即“纯度”最高的属性。那么如何来度量特征(features)的纯度,这时候就要用到“经验熵(information entropy)”。

经验熵

先来看看信息熵的定义:假如当前样本集D中第k类样本所占的比例为为类别的总数(对于二元分类来说,i=2)。则样本集的信息熵为:

的值越小,则D的纯度越高。

信息增益算法

  • 输入:训练数据集和特征
  • 输出:特征对训练数据的信息增益

信息增益表示了得知特征X的信息而使得类Y的信息的不确定性性减少的程度。

经验条件熵

计算特征对数据集的经验条件熵

  • 的意思是在某一特征下的特征样本中满足训练目标的个数
  • 指的是该特征的样本容量

信息增益为

信息增益=经验熵-经验条件熵

一般而言,信息增益越大,则表示使用特征 对数据集划分所获得的“纯度提升”越大。所以信息增益可以用于决策树划分属性的选择,其实就是选择信息增益最大的属性,ID3算法就是采用的信息增益来划分属性。

信息增益比

信息增益比为信息增益与训练数据集关于特征的值熵值之比: 其中: 是特征取值的个数。

ID3算法决策树生成

  • 输入:数据集,特征集,阈值
  • 输出:决策树
  1. 先对特征集求信息增益,选取最大的作为根结点的特征。
  2. 看特征集对训练数据集D的划分成几个子集(例如该特征集会划分 是或否具有该特征)
  3. 若某个子集只具有同一个类的的样本点,成为一个叶节点
  4. 否则,对子集从剩下的特征中选择新的特征,求得信息增益
  5. 如此递归操作,直到所有的特征分类,生成决策树

Python代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
from math import log
import operator

def calcShannonent(dataSet): #计算数据的熵(entropy)
numEntries = len(dataSet) #数据条数
labelCounts = {}
for featVec in dataSet:
currentLabel = featVec[-1] #每行数据的最后一个字(类别)
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1 #统计又多少个类以及每个类的数量
shannonEnt = 0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries #计算单个类的熵值
shannonEnt -= prob*log(prob, 2) #累加每个类的熵值
return shannonEnt

def createDataSet_temp():
#创造实例数据
labels = ['头发', '声音']

with open('TreeGrowth_ID3.txt', 'r', encoding='UTF-8') as f: #改了一个文件读写
dataSet = [[] for i in range(9)]
value = ['长', '短', '粗', '细', '男', '女']
num_line = 0
for line in f:
# re.split(r'(\s{8}\[\')|(\', \')|(\'\],)|(\'\])', line) #尝试使用正则划分,失败了
for i in line:
if (i in value):
dataSet[num_line].append(i)
num_line += 1
del(dataSet[0])
'''
dataSet = [
['长', '粗', '男'],
['短', '粗', '男'],
['短', '粗', '男'],
['长', '细', '女'],
['短', '细', '女'],
['短', '粗', '女'],
['长', '粗', '女'],
['长', '粗', '女']
]
'''
return dataSet, labels

def splitDataSet(dataSet, axis, value): #按某个特征分类后的数据
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value: #axis表示指定属性在label中的标号,value是该分类中属性的目标值
reducedFeatVec = featVec[:axis] #对于每一条记录,在分完类后,都要把之前使用过的属性删除
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet

def chooseBestFeatureToSplit(dataSet): #选择最优的分类特征
numFeatures = len(dataSet[0])-1 #特征的个数
baseEntropy = calcShannonent(dataSet) #原始熵
bestInfoGain = 0
bestFeature = -1
for i in range(numFeatures): #循环每一个特征
featList = [example[i] for example in dataSet]#读取每一条记录取出其中第i个属性的值,并新建一个列表
uniqueVals = set(featList) #set()创建一个无序不重复元素集,可进行关系测试,删除重复元素,进行交差并集运算
newEntropy = 0
for value in uniqueVals: #对于第i个属性值列表中每一个值
subDataSet = splitDataSet(dataSet, i, value) #subDataSet是去掉了第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): #多数表决排序,如:最后分类为2男1女则判断为男
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)
#将items返回的可遍历键值对数组,根据键值对的第二个域(值),进行降序排列
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) #找到最优特征的标号
if(bestFeat == -1): #自己添加的,不然报错
return classList[0]
bestFeatLabel = labels[bestFeat] #找到最优特征
myTree = {bestFeatLabel:{}} #分类结果以字典形式保存,得到树的当前层
del(labels[bestFeat]) #删除labels中当前最优特征
featValues = [example[bestFeat] for example in dataSet]#将记录集中当前最优特征的值形成列表
uniqueVals = set(featValues) #对值集去重
for value in uniqueVals: #对于每个值
subLabels = labels[:] #subLabels为去除当前最优特征的特征集
myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet,bestFeat,value),subLabels)
#使用递归,创造下一层树,~~~~~~~~~~~~~~~~~~~~~~
return myTree

if __name__=='__main__':
dataSet, labels = createDataSet_temp() #创造示例数据
print(createTree(dataSet, labels)) #输出决策树模型结果

参考博客: