实验2
前言
(复习)
参考: 机器学习实战教程(二):决策树基础篇之让我们从相亲说起
一、ID3算法
ID3作为一种经典的决策树算法,是基于信息熵来选择最佳的测试属性,其选择了当前样本集中具有最大信息增益值的属性作为测试属性。
样本集的划分则依据了测试属性的取值进行,测试属性有多少种取值就能划分出多少的子样本集;同时决策树上与该样本集相应的节点长出新的叶子节点。
ID3算法根据信息论理论,采用划分后样本集的不确定性作为衡量划分样本子集的好坏程度,用“信息增益值”度量不确定性——信息增益值越大,不确定性就更小,这就促使我们找到一个好的非叶子节点来进行划分。
二、ID3算法的基本原理以及实例分析
1.我们假设一个这样的数据样本集S,其中数据样本集S包含了s个数据样本,假设类别属性具有m个不同的值(判断指标):Ci(i=1,2,3,…,m)
Si是Ci中的样本数,对于一个样本集
总的信息熵为:
其中,Pi表示任意样本属于Ci的概率,也可以用si/s 进行估计。
我们假设一个属性A具有k个不同的值 {a1,a2,…,ak}, 利用属性A将数据样本S划分为k个子集{S1,S2,…, Sk},其中Sj包含了集合S中属性A取aj值的样本。若是选择了属性A为测试属性,则这些子集就是从集合S的节点生长出来的新的叶子节点。
设Sij是子集Sj中类别为Ci的样本数,则根据属性A划分样本的信息熵值为:
最后,我们利用属性A划分样本集S后得到的信息熵增益为:
2.实例分析
我们假设有这么一个案例,小学校门口总是会出现一些零食小贩,判断某一天这些零食小贩的收入是否良好可以依据很多因素,如:天气、学生是否放假、保安是否打压、是否进行活动促销等等多个属性来衡量。
对于天气属性,我们将是否下雨作为一个天气好坏的叛别,则天气具有“好”与“不好”两种属性值;
学生是否放假也有两种属性值“是”与“否”;
保安是否打压也分为“好”与“不好”;
活动促销的两种属性值为“是”与“否”
而对于小贩的收入,我们假设取全年的平均收入为平均值,高于平均值为“高”,否则为“低”。
由此,我们得到了这样的一个表格:
计算过程:
首先,我们先计算总的信息熵,其中共有10条记录,收入为“高”有5条,收入为“低”也有5条
步骤1 总信息熵:
下面计算各个属性的信息熵:
步骤2 天气属性:
天气好的情况下,收入为“高”的记录为4条,收入为“低”的记录为1条,可以表示为(4,1);天气不好的情况下,收入为“高”的记录为1条,收入为“低”的记录为4条,可以表示为(1,4).
则天气属性的信息熵的计算过程如下:
步骤3 学生放假属性:
学生放假时,收入为“高”有0条,收入为“低”有4条。记为(0,4);
学生不放假时,收入为“高”有5条,收入为“低”有1条。记为(5,1);
学生放假属性的计算过程为:
步骤4 促销属性:
当小贩进行促销时,收入为“高”有3条,收入为“低”有2条。记为(3,2);
当小贩不进行促销时,收入为“高”有2条,收入为“低”有3条。记为(2,3);
促销属性的计算过程为:
步骤5 计算信息增益值
由步骤4可以知道“学生放假”的信息增益值最大,所以以“学生放假“为节点进行划分,划分为两个分支分别为“是”、“否”(指学生是否放假),然后再循环进行步骤1 ~步骤5的过程(排除已经进行划分的节点步骤),对剩下的2个节点分支进行划分,再进行信息增益的计算。
直至无法形成新的节点,从而生成一棵“决策树”。
三、举例代码实现
编写代码计算经验熵
from math import log
"""
函数说明:创建测试数据集
Parameters:
无
Returns:
dataSet - 数据集
labels - 分类属性
Author:
Jack Cui
Modify:
2017-07-20
"""
def createDataSet():
dataSet = [[0, 0, 0, 0, 'no'], #数据集
[0, 0, 0, 1, 'no'],
[0, 1, 0, 1, 'yes'],
[0, 1, 1, 0, 'yes'],
[0, 0, 0, 0, 'no'],
[1, 0, 0, 0, 'no'],
[1, 0, 0, 1, 'no'],
[1, 1, 1, 1, 'yes'],
[1, 0, 1, 2, 'yes'],
[1, 0, 1, 2, 'yes'],
[2, 0, 1, 2, 'yes'],
[2, 0, 1, 1, 'yes'],
[2, 1, 0, 1, 'yes'],
[2, 1, 0, 2, 'yes'],
[2, 0, 0, 0, 'no']]
labels = ['不放贷', '放贷'] #分类属性
return dataSet, labels #返回数据集和分类属性
"""
函数说明:计算给定数据集的经验熵(香农熵)
Parameters:
dataSet - 数据集
Returns:
shannonEnt - 经验熵(香农熵)
Author:
Jack Cui
Modify:
2017-03-29
"""
def calcShannonEnt(dataSet):
numEntires = len(dataSet) #返回数据集的行数
labelCounts = {} #保存每个标签(Label)出现次数的字典
for featVec in dataSet: #对每组特征向量进行统计
currentLabel = featVec[-1] #提取标签(Label)信息
if currentLabel not in labelCounts.keys(): #如果标签(Label)没有放入统计次数的字典,添加进去
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1 #Label计数
shannonEnt = 0.0 #经验熵(香农熵)
for key in labelCounts: #计算香农熵
prob = float(labelCounts[key]) / numEntires #选择该标签(Label)的概率
shannonEnt -= prob * log(prob, 2) #利用公式计算
return shannonEnt #返回经验熵(香农熵)
if __name__ == '__main__':
dataSet, features = createDataSet()
print(dataSet)
print(calcShannonEnt(dataSet))
结果如下:
编写代码计算信息增益
from math import log
"""
函数说明:计算给定数据集的经验熵(香农熵)
Parameters:
dataSet - 数据集
Returns:
shannonEnt - 经验熵(香农熵)
Author:
Jack Cui
Modify:
2017-03-29
"""
def calcShannonEnt(dataSet):
numEntires = len(dataSet) #返回数据集的行数
labelCounts = {} #保存每个标签(Label)出现次数的字典
for featVec in dataSet: #对每组特征向量进行统计
currentLabel = featVec[-1] #提取标签(Label)信息
if currentLabel not in labelCounts.keys(): #如果标签(Label)没有放入统计次数的字典,添加进去
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1 #Label计数
shannonEnt = 0.0 #经验熵(香农熵)
for key in labelCounts: #计算香农熵
prob = float(labelCounts[key]) / numEntires #选择该标签(Label)的概率
shannonEnt -= prob * log(prob, 2) #利用公式计算
return shannonEnt #返回经验熵(香农熵)
"""
函数说明:创建测试数据集
Parameters:
无
Returns:
dataSet - 数据集
labels - 分类属性
Author:
Jack Cui
Modify:
2017-07-20
"""
def createDataSet():
dataSet = [[0, 0, 0, 0, 'no'], #数据集
[0, 0, 0, 1, 'no'],
[0, 1, 0, 1, 'yes'],
[0, 1, 1, 0, 'yes'],
[0, 0, 0, 0, 'no'],
[1, 0, 0, 0, 'no'],
[1, 0, 0, 1, 'no'],
[1, 1, 1, 1, 'yes'],
[1, 0, 1, 2, 'yes'],
[1, 0, 1, 2, 'yes'],
[2, 0, 1, 2, 'yes'],
[2, 0, 1, 1, 'yes'],
[2, 1, 0, 1, 'yes'],
[2, 1, 0, 2, 'yes'],
[2, 0, 0, 0, 'no']]
labels = ['不放贷', '放贷'] #分类属性
return dataSet, labels #返回数据集和分类属性
"""
函数说明:按照给定特征划分数据集
Parameters:
dataSet - 待划分的数据集
axis - 划分数据集的特征
value - 需要返回的特征的值
Returns:
无
Author:
Jack Cui
Modify:
2017-03-30
"""
def splitDataSet(dataSet, axis, value):
retDataSet = [] #创建返回的数据集列表
for featVec in dataSet: #遍历数据集
if featVec[axis] == value:
reducedFeatVec = featVec[:axis] #去掉axis特征
reducedFeatVec.extend(featVec[axis+1:]) #将符合条件的添加到返回的数据集
retDataSet.append(reducedFeatVec)
return retDataSet #返回划分后的数据集
"""
函数说明:选择最优特征
Parameters:
dataSet - 数据集
Returns:
bestFeature - 信息增益最大的(最优)特征的索引值
Author:
Jack Cui
Modify:
2017-03-30
"""
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 #特征数量
baseEntropy = calcShannonEnt(dataSet) #计算数据集的香农熵
bestInfoGain = 0.0 #信息增益
bestFeature = -1 #最优特征的索引值
for i in range(numFeatures): #遍历所有特征
#获取dataSet的第i个所有特征
featList = [example[i] for example in dataSet]
uniqueVals = set(featList) #创建set集合{},元素不可重复
newEntropy = 0.0 #经验条件熵
for value in uniqueVals: #计算信息增益
subDataSet = splitDataSet(dataSet, i, value) #subDataSet划分后的子集
prob = len(subDataSet) / float(len(dataSet)) #计算子集的概率
newEntropy += prob * calcShannonEnt(subDataSet) #根据公式计算经验条件熵
infoGain = baseEntropy - newEntropy #信息增益
print("第%d个特征的增益为%.3f" % (i, infoGain)) #打印每个特征的信息增益
if (infoGain > bestInfoGain): #计算信息增益
bestInfoGain = infoGain #更新信息增益,找到最大的信息增益
bestFeature = i #记录信息增益最大的特征的索引值
return bestFeature #返回信息增益最大的特征的索引值
if __name__ == '__main__':
dataSet, features = createDataSet()
print("最优特征索引值:" + str(chooseBestFeatureToSplit(dataSet)))
结果如下:
总结
本次实验让我了解了ID3算法的决策树,选择什么特征进行分类是最好的,以及如何计算熵和信息增益。