背景知识:
决策树经常用于解决分类问题,是最常用的数据挖掘算法
k近邻应用场景很多,但是无法给出数据的内在含义。决策树可以根据数据集创建规则。
优点:计算复杂度不高,输出结果易于理解,对中间值缺失不敏感,可以处理不相关特征数据
缺点:可能产生过度匹配问题
决策树的构造:
用信息论划分数据集,如果不同类则找划分方法,一直递归
多个特征的情况下,如何判断选取的特征顺序呢:采用量化方法
计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择
以下代码为计算给定数据集的香农熵:
from math import log
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 createDataSet():
dataSet = [[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 1, 'no']]
labels = ['no surfacing','flippers']
#change to discrete values
return dataSet, labels
熵越高,则混合的数据越多,比如再添加一个maybe的分类,熵也变多了
划分数据集:
分类算法除了要计算信息熵,还要划分数据集并计算该集合的信息熵,然后判断位次。按照给定特征划分数据集:
def splitDataSet(dataSet,axis,value):#输入:待划分的数据集,划分数据集的特征,需要返回的特征的值
retDataSet = []#python不考虑内存问题,在函数中传递的是列表的引用,在函数内部是对列表对象的更改,将会影响该列表对象的整个生存周期。为了消除影响,新建一个列表对象。
for featVec in dataSet:
if featVec[axis] == value:#数据集中每个元素都是列表,遍历每个元素,发现符合的就添加到列表中;当按照某个特征划分数据集时,需要将所有符合的元素抽取出来。感觉运行结果是第axis个元素的值为value时,抽取这个元素。
reducedFeatVec = featVec[:axis]#当axis为0时,0:0是空;0:1是0的值
reducedFeatVec.extend(featVec[axis+1:])#extend是把两个列表合并
retDataSet.append(reducedFeatVec)#append是把后一个列表直接当作一个元素添加进前一个列表
return retDataSet
选择最好的数据划分方式:
"""选取特征值,划分数据集,计算出最好的划分数据集的特征"""
def chooseBestFeatureToSplit(dataSet):#dataSet需是一种由列表元素组成的列表,所有的列表元素都要具有相同的数据长度;数据的最后一列或每个元素的做后一列都是当前元素的标签。list中数据类型不限,不影响。
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#返回最好特征划分的索引值
递归:直到遍历完所有划分集的属性,或者每个分支下的实例都具有相同的分类,则得到一个叶子节点或者终止块。我们也可以设置算法可以划分的最大分组数目,若最后得到的数据子集中类标签依旧不唯一,则使用下面的程序,计算在这个子集中标签出现次数最多的,作为该子集的标签。
"""得到每个类标签出现的次数,返回出现次数最多的分类名称"""
def majorityCnt(classList):
classCount={}
for vote in classCount:
if vote not in classCount.keys():classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.iteritem(),key=operator.itemgetter(1),reverse = true) #对标签出现的频率按从大到小进行排序
return sortedClassCount[0][0] #返回出现频率最大的那个标签作为子集的标签。
创建树的函数代码,这应该是一个主函数,调用以上的函数,对一个原始数据集创建决策树。
#递归构建决策树,这是这个文件的主函数,对已有的数据集,知调用这一个函数就创建了决策树******************************************
def createTree(dataSet,labels):#输入:数据集和标签列表,标签列表中集中所有特征的标签,算法本身不需要,只作为输入参数提供
classList = [example[-1] for example in dataSet]#提取数据集最后一列数据
if classList.count(classList[0]) == len(classList):#当计算在最后一列数据中与第一个值相同的元素个数与最后一列数据个数相同时,直接返回第一个元素值,意思是所有类标签都相同
return classList[0]
if len(dataSet[0]) == 1:#当数据集中第一个也代表所有元素的长度为1时,仍然类标签不相同,就挑选出现次数最多的作为返回值
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet)#返回最佳特征值划分的索引
bestFeatLabel = labels[bestFeat]#得到最佳特征值索引的标签
myTree = {bestFeatLabel:{}}#使用字典类型存储树的信息
del(labels[bestFeat])#从标签列表中删除最好特征值对应的那个标签
featValues = [example[bestFeat] for example in dataSet]#得到最佳特征值对应的数据集中的那一列数据组成列表
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 = inputTree.keys()[0]
secondDict = inputTree[firstStr]
featIndex = featLabels.index(firstStr)#这里是将标签字符串转换成索引数字
for key in secondDict.keys():
if testVec[featIndex] == key:#如果key值等于给定的标签时
if type(secondDict[key]).__name__ == 'dict':
classLabel = classify(secondDict[key],featLabels,testVec)#递归调用分类
else: classLabel = secondDict[key]#此数据的分类结果
return classLabel
存储决策树:
#由于构建决策树是很耗时的,但用创建好的决策树就可以很快解决分类问题,最好每次次执行分类时调用已构造好的决策树,pickle可以存储对象,也可以读出对象,字典对象也不例外,k近邻不能持久分类,必须每次都计算
def storeTree(inputTree,filename):
import pickle
fw = open(filename,'w')
pickle.dump(inputTree,fw)
fw.close()
def grabTree(filename):
import pickle
fr = open(filename)
return pickle.load(fr)