#以下为ID3算法个征独家解(tu)说(cao)版本。
from math import log
import operator
#1.构建样本数据集,前两个维度是特征维度(对应于labels里的特征名),最后一个维度是类别维度(鱼类、非鱼类)
def createDataSet():
dataSet = [[1, 1,'yes'],
[1, 1,'yes'],
[1, 0,'no'],
[0, 1,'no'],
[0, 1,'no']]
labels = ['nosurfacing','flippers']
return dataSet,labels
#2.计算样本数据集的熵。注意:只计算类别维度!
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
#3.划分样本数据集。注意凡需复制链表时,一定要使用[:]截取,否则后续操作可能会改变原始链表的值
#每次进行划分时,选取的特征维度(axis)会被损失
def splitDataSet(dataSet, axis, value):
retDataSet = []
for featVec indataSet:
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] #链表推导:复制数据集第i列
uniqueVals = set(featList) #set()返回链表元素的集合
newEntropy = 0.0
for value in uniqueVals: #计算划分后的熵
subDataSet= splitDataSet(dataSet, i, value)
prob =len(subDataSet)/float(len(dataSet))
newEntropy+= prob * calcShannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy #计算信息增益
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i #如果信息增益最大,这个特征就是被选中的特征
return bestFeature
#5.投票表决函数
#递归终止条件(样本集不可再分)有两种情况:
#1)样本集中所有实例类别都相同(虽然特征可能不同)
#2)
样本集中所有特征都损失掉了(虽然类别可能不同)
#1)好说,直接用那个类别作为叶子结点;2)的话,执行多数暴政法则,用出现最多的类别作为叶子结点
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)
returnsortedClassCount[0][0]
#6.递归构建决策树
def createTree(dataSet,labels):
#####################################递归终止条件######################################
classList = [example[-1]for example in dataSet] #链表推导:复制数据集最后一列(类别列)
ifclassList.count(classList[0]) == len(classList): #终止条件1)
return classList[0]
if len(dataSet[0]) == 1: #终止条件2)
return majorityCnt(classList) #叶子结点被谁承包?投票表决!
##############################终止条件完毕,以下进入正文#################################
bestFeat =chooseBestFeatureToSplit(dataSet) #选择划分特征(列索引)
bestFeatLabel =labels[bestFeat] #按照列索引找到对应特征的名字
myTree ={bestFeatLabel:{}} #第一次划分的特征作为根结点
del(labels[bestFeat]) #删除该特征(接下来在splitDataSet()中对应的列也会被删除)
featValues =[example[bestFeat] for example in dataSet]
uniqueVals =set(featValues)
for value inuniqueVals:
subLabels =labels[:] #重申:复制链表时要使用[:]截取,否则后续操作可能会改变原始链表的值
myTree[bestFeatLabel][value] =createTree(splitDataSet(dataSet, bestFeat,value),subLabels)
#递归开始,接下来就是见证奇迹的时刻(●'◡'●)ノ
returnmyTree #递归终止时,返回决策树
#7.使用决策树进行分类
def classify(inputTree,featLabels,testVec):
firstStr =inputTree.keys()[0] #当前树的根结点
secondDict =inputTree[firstStr] #根结点的子树们
featIndex =featLabels.index(firstStr) #找到根结点特征的索引
key = testVec[featIndex] #根据索引找到测试样本对应于此特征的特征值
valueOfFeat =secondDict[key] #该特征值对应的子树,准备下一次分类
ifisinstance(valueOfFeat, dict): #如果子树是字典,说明还可以再分
classLabel = classify(valueOfFeat, featLabels,testVec) #递归调用分类器
else: classLabel =valueOfFeat #如果子树不是字典,说明已到达叶子结点
return classLabel #返回叶子结点的类别名
#8.存储决策树
def storeTree(inputTree,filename):
import pickle
fw = open(filename,'w') #以写模式打开文件(文件原有内容会被覆盖)
pickle.dump(inputTree,fw) #把决策树放进去
fw.close() #把文件关上
#9.打开存储的决策树
def grabTree(filename):
import pickle
fr =open(filename)
return pickle.load(fr)