ID3决策树实现代码
from math import log
import operator
# 计算香农熵
def calcShannonEnt(dataSet):
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.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob * log(prob, 2)
return shannonEnt
# 划分数据集
def splitDataSet(dataSet, axis, value):
retDataSet = []
for featVec in dataSet:
if featVec[axis] == 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.0; bestFeature = -1 # 用于记录最好的划分特征
for i in range(numFeatures):
featList = [example[i] for example in dataSet] #将一个特征所有可能的值记录下来
uniqueVals = set(featList) # set() 函数创建一个无序不重复元素集
newEntropy = 0.0
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value)
prob = len(subDataSet)/float(len(dataSet)) # 计算|D^v|/|D|,子样本在全集中出现概率
newEntropy += prob * calcShannonEnt(subDataSet) # 划分后的新熵和
infoGain = baseEntropy - newEntropy # 信息增益 = 全集熵-分后熵
if(infoGain > bestInfoGain): # 记录最好的划分特征
bestInfoGain = infoGain
bestFeature = i
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.items, key = operator.itemgetter(1), reverse = Ture) # 字典拆分为列表 按字典的值排序
return sortedClassCount[0][0]
# 递归创建树 数据集 标签列表
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet] # 提取标签
# 类别完全相同则停止划分
if(classList.count(classList[0]) == len(classList)): # .count() 统计在列表中出现的次数
return classList[0]
# 遍历完所有特征时返回出现次数最多的
if(len(dataSet[0]) == 1): # 数据集列数1,只有一个特征
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet) # 找出分割特征
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{}} # 创建新子树
del(labels[bestFeat]) # 删掉labels中分割出去的特征
featValues = [example[bestFeat] for example in dataSet] # bestFeat下的数据
uniqueVals = set(featValues)
for value in uniqueVals:
subLabels = labels[:]
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
return myTree
#使用决策树的分类函数
def classify(inputTree, featLabels, testVec):
firstStr = next(iter(inputTree)) # 取第一个key
secondDict = inputTree[firstStr] # 取第一个key对应的第一个value 即下方的节点
featIndex = featLabels.index(firstStr) # 将标签字符串转换为标签数组的索引
for key in secondDict:
if testVec[featIndex] == key:
if type(secondDict[key]).__name__ == 'dict':
classLabel = classify(secondDict[key], featLabels, testVec)
else: classLabel = secondDict[key]
return classLabel
ID3决策树
决策树是一种监督学习,在已知数据集上训练。通过对各特征划分,构建树形结构。使用满足划分准则的特征不断的将数据集划分为纯度更高,不确定性更小的子集的过程。
创建ID3决策树的步骤:
(1) 计算子树下各特征的信息增益
(2)选出信息增益最大的特征
(3)创建信息增益最大特征下的新子树,递归返回第一步继续划分子树
递归终止条件:
(4)如果子树中划分类别相同,停止划分子树
(5)如果划分子树下只有一个特征则选取出现最多的类别作为分类结果,停止划分子树
信息增益
信息熵
“信息熵”是度量样本集合纯度最常用的一种指标。
信息熵公式:
p k p_ k pk是样本集合D中第k类样本所占比例
信息熵越小,则D的纯度越高
信息增益
“信息增益”是划分前样本信息熵 - 划分后子集样本信息熵
划分后子集样本纯度越高,其信息熵越小,则信息增益越大。
选取信息增益最大的特征划分子集,便可以贪心的使划分达到最大纯度。
信息增益公式:
使用a特征对样本集D进行划分,则会产生V个分支结点,其中第v个分支结点包含了D中所有在属性a上取值为 a v a^v av的样本,记为 D v D^v Dv。
|D^v|/|D| 即子样本在全集中出现概率
# pickle存储数据
def storeTree(inputTree, filename):
import pickle
fw = open(filename, 'wb')
pickle.dump(inputTree, fw)
fw.close()
def grabTree(filename):
import pickle
fr = open(filename, 'rb')
return pickle.load(fr)
决策树只需一次训练便可生成,将其保存在磁盘,每次调用节省时间。
python中使用pickle模块可实现基本的数据序列化和反序列化。
那么为什么需要序列化和反序列化这一操作呢?
1.便于存储。序列化过程将文本信息转变为二进制数据流。这样就信息就容易存储在硬盘之中,当需要读取文件的时候,从硬盘中读取数据,然后再将其反序列化便可以得到原始的数据。在Python程序运行中得到了一些字符串、列表、字典等数据,想要长久的保存下来,方便以后使用,而不是简单的放入内存中关机断电就丢失数据。python模块大全中的Pickle模块就派上用场了,它可以将对象转换为一种可以传输或存储的格式。
2.便于传输。当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把這个对象转换为字节序列,在能在网络上传输;接收方则需要把字节序列在恢复为对象。
文中使用的是基本序列化、反序列化方法,注意读写都要使用二进制方式。