基本原理:每个数据集都有一个或者多个特征作为已知条件,现在根据这些特征分类。K-近邻算法是已知部分分类,通过比较特征距离来将新的数据划分到旧的分类,但是你不知道它是如何划分,也就不知道分类是什么含义。决策树则是将一堆全新的数据,通过一个一个问答的形式,逐步缩小范围,给出最终分类,行成一个树形图。它通过遍历比较信息熵和信息增益选出当前最适合分类的特征,从而得出一系列规则。
(参考博客园描述)因此先回忆一下信息论中有关信息量(就是“熵”)的定义。说有这么一个变量X,它可能的取值有n多种,分别是x1,x2,……,xn,每一种取到的概率分别是P1,P2,……,Pn,那么X的熵就定义为:
意思就是一个变量可能的变化越多(反而跟变量具体的取值没有任何关系,只和值的种类多少以及发生概率有关),它携带的信息量就越大。
对分类系统来说,类别C是变量,它可能的取值是C1,C2,……,Cn,而每一个类别出现的概率是P(C1),P(C2),……,P(Cn),因此n就是类别的总数。此时分类系统的熵就可以表示为:
信息增益是针对一个一个的特征而言的,就是看一个特征t,系统有它和没它的时候信息量各是多少。于是有了条件熵:(1)系统不包含特征t;(2)系统虽然包含特征t,但是t已经固定了,不能变化。
特征X被固定为值xi时的条件熵:
特征X被固定时的条件熵:
注意区别。从刚才计算均值的讨论可以看出来,第二个式子与第一个式子的关系就是:
因此固定t时系统的条件熵就有了,为了区别t出现时的符号与特征t本身的符号,我们用T代表特征,而用t代表T出现,那么:
与刚才的式子对照一下,含义很清楚对吧,P(t)就是T出现的概率,就是T不出现的概率。这个式子可以进一步展开,其中的
另一半就可以展开为:
因此特征T给系统带来的信息增益就可以写成系统原本的熵与固定特征T后的条件熵之差:
关键词:信息熵; 条件熵; 信息增益; 树形图
信息增益Gain(R)表示属性R给分类带来的信息量,我们寻找Gain最大的属性,就能使分类尽可能的纯,即最可能的把不同的类分开。不过我们发现对所以的属性Info(D)都是一样的,所以求最大的Gain可以转化为求最新的InfoR(D)。这里引入Info(D)只是为了说明背后的原理,方便理解,实现时我们不需要计算Info(D)。举一个例子,数据集D如下:
记录ID | 年龄 | 输入层次 | 学生 | 信用等级 | 是否购买电脑 |
1 | 青少年 | 高 | 否 | 一般 | 否 |
2 | 青少年 | 高 | 否 | 良好 | 否 |
3 | 中年 | 高 | 否 | 一般 | 是 |
4 | 老年 | 中 | 否 | 一般 | 是 |
5 | 老年 | 低 | 是 | 一般 | 是 |
6 | 老年 | 低 | 是 | 良好 | 否 |
7 | 中年 | 低 | 是 | 良好 | 是 |
8 | 青少年 | 中 | 否 | 一般 | 否 |
9 | 青少年 | 低 | 是 | 一般 | 是 |
10 | 老年 | 中 | 是 | 一般 | 是 |
11 | 青少年 | 中 | 是 | 良好 | 是 |
12 | 中年 | 中 | 否 | 良好 | 是 |
13 | 中年 | 高 | 是 | 一般 | 是 |
14 | 老年 | 中 | 否 | 良好 | 否 |
这个数据集是根据一个人的年龄、收入、是否学生以及信用等级来确定他是否会购买电脑,即最后一列“是否购买电脑”是类标。现在我们用信息增益选出最最佳的分类属性,计算按年龄分裂后的信息量:
整个式子由三项累加而成,第一项为青少年,14条记录中有5条为青少年,其中2(占2/5)条购买电脑,3(占3/5)条不购买电脑。第二项为中年,第三项为老年。类似的,有:
可以得出Info年龄(D)最小,即以年龄分裂后,分得的结果中类标最纯,此时已年龄作为根结点的测试属性,根据青少年、中年、老年分为三个分支:
注意,年龄这个属性用过后,之后的操作就不需要年龄了,即把年龄从attributeList中删掉。往后就按照同样的方法,构建D1,D2,D3对应的决策子树。ID3算法使用的就是基于信息增益的选择属性方法。
算法实施:
1. 创建数据集,给出特征标签——createDataSet()
2. 计算香农熵——getShannonEnt()
3. 根据特征、特征值抽出符合条件的分类结果——splitDataSet()
4. 选择最佳划分的特征:每个特征按不同的值划分类别,分别计算各个类别香农熵并相加作为该特征划分的香农熵。用初始香农熵减去求出的新熵即为信息增益。信息增益越大,说明熵越小,包含信息种类越少,该特征就越好。遍历所有特征,选出最大的信息增益,选择该增益对应的特征作为返回值.调用2,3.
——chooseBestFeatureToSplit()
5. 选择最后一个特征内部的最大类返回:作为终止条件——majorityCnt()
6. 创建树:两个终止条件——类别完全相同 or 到达最后一个特征。首先选择最佳划分特征,打印出它的label;删除该label,剩余的标签作为新的递归标签组;针对之前选好的特征,遍历其取值划分出一个类别,在此基础上建立子树。递归调用形成整棵决策树。——createTree()
# coding:utf-8
from math import log
def getDataSet():
dataSet = [[1,1,'yes'],[1,1,'yes'],[1,0,'no'],[0,1,'no'],[0,1,'no']]
labels = ['no surfacing', 'flippers']
return dataSet, labels
def getShangnonEnt(dataset):
####### 得到信息熵,首先统计实例总数 #######
N = len(dataset)
labelCounts = {}
for item in dataset:
nowLabel = item[-1]
if nowLabel not in labelCounts.keys():
labelCounts[nowLabel] = 0
labelCounts[nowLabel] += 1
# 开始求熵
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/N
shannonEnt -= prob * log(prob, 2)
return shannonEnt
def splitDataSet(dataSet, axis, value):
####### 根据特征项axis的value值划分类,并返回该分类 #######
newData = []
for item in dataSet:
if item[axis] == value:
reducePart = item[:axis]
reducePart.extend(item[axis+1:])
newData.append(reducePart)
return newData
def getBestFeatureToSplit(dataSet):
###### 计算最大的信息增益,选出最佳划分特征。信息增益:基本熵减新熵######
numFeature = len(dataSet[0])-1 # 特征总数
baseEntropy = getShangnonEnt(dataSet)
bestInfoGain = 0.0
bestFeature = -1
# 选择所有特征,逐个计算
for i in range(numFeature):
myList = [item[i] for item in dataSet]
uniqueList = set(myList)
newEntropy = 0.0
# 计算当前特征的所有可能值的信息熵
for value in uniqueList:
subDataSet = splitDataSet(dataSet, i, value)
prob = len(subDataSet)/float(len(dataSet))
newEntropy += prob * getShangnonEnt(subDataSet)
infoGain = baseEntropy - newEntropy
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
print("bestFeature",bestFeature)
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.iteritems(), \
key = operator.itemgetter(1), reverse = True)
return sortedClassCount
def createTree(dataSet, labels):
classList = [item[-1] for item in dataSet]
if classList.count(classList[0]) == len(classList):
# 类别完全相同则停止划分
return classList[0]
if len(dataSet[0]) == 1:
# 遍历玩所有特征,返回出现次数最多的
return majorityCnt(classList)
bestFeat = getBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{}}
del(labels[bestFeat])
# 得到列表包含的所有属性值
featValues = [item[bestFeat] for item in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
subLabels = labels[:]
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
return myTree
dataSet, labels = getDataSet()
print("dataSet:", dataSet, labels)
tree = createTree(dataSet, labels)
print("myTree:", tree)