用ID3算法构造决策树
/简单例子.png)
(1)计算信息熵
ID3算法用信息增益来寻找最优划分特征:计算每个特征划分数据集的信息增益,选择信息增益最大的特征
什么是信息增益? 划分数据集后,增加了多少信息?不确定性降低了多少?有序性增加了多少?这些都是信息增益。
如何计算信息增益? 用划分前后信息的增量计算,样本x的信息=$-\log_2p(x_i)$,i是样本属于的类别。一个数据集的信息可以用熵来表示,熵是信息的期望,公式为,一共有k个类别。
信息增益 = ① - ②
①划分前,计算数据集的信息熵
②划分后,计算出每个子集的信息熵,再求它们的加权和(权重是子集数/总数)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15#熵
import numpy as np
from math import log
def entropyFunc(data):
m = len(data)
labelCount = {} #统计每一类别出现的次数
for feature_vec in data: #遍历样本
labelvalue = feature_vec[-1] #该样本的类别
labelCount[labelvalue] = labelCount.get(labelvalue,0)+1
shannonEnt = 0.0
for k in labelCount:
prob = labelCount[k] / m
shannonEnt -= prob * log(prob,2)
return shannonEnt
1 | #测试 |
(2)按不同的特征值,划分出相应的子集
1 | #划分特征是'A',用该特征划分能得到2个子树 |
1 | >> splitData(a,0,1) |
(3)找到划分数据集的最优特征
1 | #选择最优划分特征 |
1 | >> chooseBestFeature(a) |
(4)构造树
当一个节点遇到以下情况时不分裂:
①数据集中的数据属于同一类别
②遍历完所有的特征(注意C4.5和CART每次划分数据时,不会减少特征),采用多数表决的方法决定该数据集的类别
其它情况时递归调用自己,继续按照相同的方式分裂1
2
3
4
5
6
7#投票表决器
def majorityClass(classlist):
classCount = {}
for vote in classlist:
classCount[vote] = classCount.get(vote, 0) + 1
sortedCount = sorted(classCount.items(), key = lambda x:x[1], reverse = True)
return sortedCount[0][0]
1 | #构造树 |
1 | >> labels = ['A','B'] |
(5)绘制树
点击查看代码:treePlotter.py1
2import treePlotter
treePlotter.createPlot(myTree)
/例子图.png)
(6)用决策树分类
在存储带有特征的数据会面临一个问题:程序无法确定特征在数据集中的位置,需要传入一个标签列表(位置与数据集的特征位置一致)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#myTree = {'A': {0: 'yes', 1: {'B': {0: 'yes', 1: 'no'}}}}
#labels = ['A','B']
#x_test = [1,1]
#输出的类别应该是'no'
def classify(myTree, labels, x_test):
first_str = list(myTree.keys())[0] # 'A'
feature_index = labels.index(first_str) # 特征A在标签的哪个位置
second_dict = myTree[first_str] # {0: 'yes', 1: {'B': {0: 'yes', 1: 'no'}}}
for key in second_dict.keys(): #遍历0、1(是特征A的值)
if x_test[feature_index] == key: #如果测试向量的A特征值是key
if type(second_dict[key]).__name__ == 'dict': #如果该特征值对应的节点不是叶子
classlabel = classify(second_dict[key], labels, x_test)
else:
classlabel = second_dict[key]
return classlabel
1 | >> myTree = {'A': {0: 'yes', 1: {'B': {0: 'yes', 1: 'no'}}}} |
(7)保存决策树
构造决策树非常耗时,而用创建好的决策树解决问题会很快。用pickle序列化对象,每次执行分类时就调用已构造好的决策树即可(k近邻算法就无法持久化分类器)1
2
3
4
5
6
7
8
9
10def storeTree(myTree,filename): #保存
import pickle
fw = open(filename, 'wb') #以二进制读写方式打开文件
pickle.dump(myTree, fw) #序列化对象
fw.close()
def grabTree(filename): #读取文件,反序列化
import pickle
fr = open(filename, 'rb')
return pickle.load(fr)
1 | >> storeTree(myTree,'classifierStorage.txt') |
预测隐形眼镜的类型
隐形眼镜数据集来源于UCI数据库,包含很多患者眼部状况的观察条件,以及医生推荐的隐形眼镜类型(分为硬材质、软材质、不适合佩戴隐形眼镜)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16>> fr = open('lenses.txt')
>> lenses = [inst.strip().split('\t') for inst in fr.readlines()] #用\t分割数据行
>> lensesLabels = ['age','prescript','astigmatic','tearRate']
>> lensesTree = createTree(lenses, lensesLabels) #构造树
>> lensesTree
{'tearRate': {'normal': {'astigmatic': {'no': {'age': {'pre': 'soft',
'presbyopic': {'prescript': {'hyper': 'soft', 'myope': 'no lenses'}},
'young': 'soft'}},
'yes': {'prescript': {'hyper': {'age': {'pre': 'no lenses',
'presbyopic': 'no lenses',
'young': 'hard'}},
'myope': 'hard'}}}},
'reduced': 'no lenses'}}
>> import treePlotter
>> treePlotter.createPlot(lensesTree)
/决策树.png)
ID3算法的优点:计算复杂度不高,输出的结果易于理解,对缺失值不敏感,可以处理不相关特征数据。
ID3算法的缺点: ①上图所示的决策树非常好地匹配了实验数据,但是而这些匹配选项可能太多了(即可能产生过拟合问题),为了降低过拟合问题,我们可以裁剪决策树,去掉一些不必要的叶子节点(如果叶子节点只能增加少许信息,则可以删除该节点,将它并人到其他叶子节点中);②ID3算法只能处理分类型或离散数值型数据,处理连续型数据可以用CART回归树算法。