本博客记录自己在学习《机器学习实战》决策树的内容,采用python3和spyder:
# -*- coding: utf-8 -*-
"""
Created on Tue Aug 15 19:54:38 2017
@author: rocky
"""
#机器学习实战第三章 决策树程序
#首先计算整个数据集的香农熵,主要是意义是划分数据集 将无序的数据变得更加有序
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
#currentLabel作为键,labelCounts[currentLabel]表示键对应的值,对于下面的简单数据集,labelcounts最终的结果是{'yes':2,'no':3}
#适用所有类标签的发生频率计算类别出现的概率,利用这个概率计算香农熵
shannonEnt=0.0
for key in labelCounts:
prob=float(labelCounts[key])/numEntries
shannonEnt-=prob*log(prob,2)
return shannonEnt
上面是求解香农熵的程序,我们可以自己加一个数据集来检测程序的生成,有下列代码:
#加入一个简单数据集
def creatDataSet():
dataSet=[[1,1,'yes'],
[1,1,'yes'],
[1,0,'no'],
[0,1,'no'],
[0,1,'no']]
labels=['no surfacing','flippers']
return dataSet, labels
对上述数据集而言对上述数据集而言,我们在以第一列或最后一列作为键值时,因为两种分布是一致的,即都是3+2的分布,所以最终的熵都是一样的(0.9709505944546686),但是在以第二列作为键值时,分布是4+1,熵就变不同(0.7219280948873623),以下就可以看出。
在命令窗口输入以下命令测试(以最后一列为键值):
dataSet,labels=creatDataSet()
dataSet
Out[3]: [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
calcShannonEnt(dataSet)
Out[4]: (0.9709505944546686, {'no': 3, 'yes': 2})
(以第一列作为键值)在命令行输入以下命令测试
dataSet,labels=creatDataSet()
dataSet
Out[7]: [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
calcShannonEnt(dataSet)
Out[8]: (0.9709505944546686, {0: 2, 1: 3})
(以第二列作为键值)在命令行输入以下命令测试
dataSet,labels=creatDataSet()
dataSet
Out[11]: [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
calcShannonEnt(dataSet)
Out[12]: (0.7219280948873623, {0: 1, 1: 4})
以上是如何度量数据集的无序程度,下面是划分数据集,我们将对每个特征划分数据集的结果计算一次信息熵,然后判断按照哪个特征划分数数据集是最好的划分方式。例如一堆散点图,我们需要划一条线将它们分成两部分,以下就是相关的代码。
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
对上述程序的解释:dataset是数据集,axis是划分数据集的特征(即第几列代表的那个特征),value表示返回值,即代表特征所赋的值。上述程序的意思是将数据集中第axis+1列中满足特征值+value的那一行数据找出来保存。例如,splitDataSet(dataSet,0,1)就是找出dataSet中第1(0+1)列都为1的那些数据,结果是[[1, ‘yes’], [1, ‘yes’], [0, ‘no’]],即前三行数据。这样就把数据分成了两部分。
循环计算信息熵和splitdataset函数,找到最好的特征划分方式
def chooseBestFeatureToSplit(dataSet):
numFeatures=len(dataSet[0])-1
#计算原始数据集信息熵,以便与划分后的数据集进行比较
baseEntropy=calcShannonEnt(dataSet)
#假设信息熵差值初值为0,最好的划分特征为最后一列
bestInfoGain=0.0;bestFeature=-1
for i in range(numFeatures):#循环特征
featList=[example[i] for example in dataSet]
#set()用于删除重复元素
uniqueVals=set(featList)
#uniqueVals得到了每一种(i)特征中的取值如(0,1)或者(yes,no)
newEntropy=0.0
#新的分类方式的信息熵
for value in uniqueVals:#遍历第i个特征的取值
#按照一种特征进行数据集划分,并计算当下的信息熵
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 classList:
if vote not in classCount.keys():classCount[vote]=0
classCount[vote]+=1
sortedClassCount=sorted(classCount.iteritems(),key=operator.itemgetter(1),reverse=True)
return sortedClassCount[0][0]
#返回出现次数最多的分类名称
#创建树程序
def createTree(dataSet,labels):
#取出dataset中的所有类标签,对于原数据集就是['yes', 'yes', 'no', 'no', 'no'],实际上是对于每个分支而言。
classList=[example[-1] for example in dataSet]
#如果该分支下全是相同的类别,即都是yes或者都是no,就返回这种分类(类别)
if classList.count(classList[0])==len(classList):
return classList[0]
if len(dataSet[0])==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中
subLabels=labels[:]
#递归调用
myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet,bestFeat,value),subLabels)
return myTree
#构建决策树程序