机器学习实战(二)决策树
决策树是经常使用的数据挖掘算法之一,概念非常简单。
优点:计算复杂度不高,输出结果易于理解,对中间值缺失不敏感,可以处理不相关特征数据。
缺点:可能会产生过度匹配的问题
适用数据类型:数值型和标称型
3.1 决策树的构造
3.1.1 信息增益的计算
香农熵(熵):集合信息的度量方式
计算公式:
p是选择该分类的概率,n是分类的数目。
信息增益:划分数据集前后信息发生的变化 E(信息增益) = S0(初始信息熵) - S1(当前信息熵)
举个栗子:
feature1:根据不浮出水面是否可以生存这一特征分类的信息增益
feature2:根据是否有脚蹼这一特征分类的信息增益
哪一个信息增益大就先依据该特征划分
代码实现:
from math import log
def calcShannonEnt(dataSet):
# 返回数据集的行数
numEntires = len(dataSet)
# 保存每个标签(Label)出现次数的“字典”
labelCounts = {}
# 对每组特征向量进行统计
for featVec in dataSet: #为所有可能的分类创造字典
#print(featVec)
# 提取标签(Label)信息
currentLabel = featVec[-1]
#print(currentLabel)
# 如果标签(Label)没有放入统计次数的字典,添加进去
if currentLabel not in labelCounts.keys():
# 创建一个新的键值对,键为currentLabel值为0
labelCounts[currentLabel] = 0
# Label计数
labelCounts[currentLabel] += 1
#print(labelCounts)
# 经验熵(香农熵)
shannonEnt = 0.0
# 计算香农熵
for key in labelCounts:
# 选择该标签(Label)的概率
prob = float(labelCounts[key]) / numEntires
# 利用公式计算
shannonEnt -= prob*log(prob, 2)
#print(shannonEnt)
# 返回经验熵(香农熵)
return shannonEnt
创建数据集,其中前四列是特征,最后一列是标签
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
>>> myDat, labels = createDataSet()
>>> Shannon = calcShannonEnt(myDat)
>>> shannon
> 0.9709505944546686
3.1.2 划分数据集
划分数据集
对每个特征划分数据集的结果计算一次信息熵,然后判断按照哪个特征划分数据集是最佳选择。
def splitDataSet(dataSet, axis, value): #dataSet - 待划分的数据集,axis - 划分数据集的特征,values - 需要返回的特征的值,
# 创建返回的数据集列表
retDataSet = []
# 遍历数据集的每一行
for featVec in dataSet:
if featVec[axis] == value:
# 去掉axis特征
reducedFeatVec = featVec[:axis]
# 将符合条件的添加到返回的数据集
# extend() 函数用于在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)。
reducedFeatVec.extend(featVec[axis+1:])
# 列表中嵌套列表
retDataSet.append(reducedFeatVec)
# 返回划分后的数据集
return retDataSet
>>> splitDataSet(myDat,1,1)
>[[0, 0, 1, 'yes'],
[0, 1, 0, 'yes'],
[1, 1, 1, 'yes'],
[2, 0, 1, 'yes'],
[2, 0, 2, 'yes']]
Python语言在函数中传递的是列表的引用,在函数内部对列表对象的修改,将会影响该列表对象的整个生存周期。为了消除这个不良影响,我们需要在函数的开始声明一个新列表对象。因为该函数代码在同一数据集上被调用多次,为了不修改原始数据集,创建一个新的列表对象。
选择最优特征
选取特征,划分数据集,计算最佳选择
def chooseBestFeatureToSplit(dataSet):
# 特征数量
numFeatures = len(dataSet[0]) - 1
# 计算数据集的香农熵
baseEntropy = calcShannonEnt(dataSet)
# 信息增益
bestInfoGain = 0.0
# 最优特征的索引值
bestFeature = -1
# 遍历所有特征
for i in range(numFeatures):
# 获取dataSet的第i个所有特征存在featList这个列表中(列表生成式)
featList = [example[i] for example in dataSet]
# 创建set集合{},元素不可重复,重复的元素均被删掉
# 从列表中创建集合是python语言得到列表中唯一元素值得最快方法
uniqueVals = set(featList)
# 经验条件熵
newEntropy = 0.0
# 计算信息增益
for value in uniqueVals:
# subDataSet划分后的子集
subDataSet = splitDataSet(dataSet, i, value)
# 计算子集的概率
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