先摆一个博客,介绍诸多算法的概念的。
http://www.cnblogs.com/leoo2sk/archive/2010/09/19/decision-tree.html
决策树(decision tree)是一个树结构(可以是二叉树或非二叉树)。其每个非叶节点表示一个特征属性上的测试,每个分支代表这个特征属性在某个值域上的输出,而每个叶节点存放一个类别。使用决策树进行决策的过程就是从根节点开始,测试待分类项中相应的特征属性,并按照其值选择输出分支,直到到达叶子节点,将叶子节点存放的类别作为决策结果。
构造决策树时,需要解决的第一个问题就是:当前数据集上的哪个特征在划分数据时启决定性的作用? 找出这个特征,并以此来作为划分的依据,得出的结果也就更加“纯”,所谓“纯”,是指在尽量让在该子集下的所有样本属于一个类别,也就得到了更加准确有效的结果。
创建分支的伪代码createBranch()可以这样描述:
检测数据集中的每个子项是否属于同一分类
If so return 该类标签
else
寻找划分该数据集的“最好“特征
划分数据集
创建分支节点
for 划分的子集
自调用该函数createBranch()并增加返回结果到分支节点中
return 分支节点
为了解决这个问题,提出一个基于信息论的算法,称之为ID3算法。
利用信息论量化度量信息的内容去比较划分数据集之前和之后的信息值,这就是该算法的主要思想。
信息增益:在划分数据集之前之后发生的信息量的变化
熵:信息的期望值
设D为用类别对数据集进行的划分,则D的熵(entropy)表示为:
其中 pi 表示第 i个类别在数据集中出现的概率。具体的解释可以见我前面给出的那个博客。
总之,期望信息(熵)越小,信息增益越大,从而纯度越高。 因此,我们可以计算对每个划分的信息增益,选取其最大的划分,作为当前选择的最好划分。这样就解决了在之前提出的问题。
那为了完成对数据集的划分,我们首先应该做的就是计算数据集的熵。
from math import log
def calShannonEnt(dataset):
numEntries = len(dataset)
#获取所有样本数目
labelcounts = {}
#创建标签字典
for featVec in dataset:
#遍历数据集中的每一行
currentlabel = featVec[-1]
#原数据集中每个样本的标签存储在最后一列
if currentlabel non in labelcounts.keys():
#统计各个标签在整个数据集的个数
labelcounts[currentlabel] = 0
labelcounts[currentlabel] += 1
shanonEnt = 0
for key in labelcounts:
#遍历存储着标签和其个数的字典
prob = float(labelcounts[key])/numEntries
shanonEnt -= prob*log(prob,2)
#计算香农熵
return shanonEnt
熵越高,则混合的数据也就越多。之后,我们对每个按照特征划分过后的数据集进行计算,得出其信息熵,然后根据“信息增益最大”的原则来判断以哪种特征进行划分。所以,现在需要的代码是根据给定特征划分数据集
def splitDataset(dataset,axis,value):
# axis : 待划分数据集的特征
# value : 需要返回的特征的值
retDataset = []
for featVec in dataset:
#遍历数据集中每一行
#当我们按照某种特征划分数据集时,就需要将所有符合需求的值抽取出来。
if featVec[axis] == value:
reducedFeatvec= featVec[:axis]
reducedFeatvec.extend(featVec[axis+1:])
# 将每一行axis的值(以axis的位置为划分特征)丢弃之后,把其他位置的值存入列表中
retDataset.append(reducedFeatvec)
return retDataset
上面已经完成了计算香农熵和划分数据集的操作,现在就是将最好的特征方式确定下来。代码如下:
def chooseBestFeaturetosplit(dataset):
numFeatures = len(dataset[0])-1
#减去标签列,剩下的都是特征特征列
baseEntropy =calShannonEnt(dataset)
#计算划分之前的熵值
bestInfoGain=0.0; bestFeature = -1
for i in range(numFeatures):
#对每个特征进行信息增益的计算
featlist = [example[i] for example in dataset]
#遍历特征i的所有属性,以列表形式储存在featlist中
print('in feature i : %d, value list:' %i,featlist)
uniqueVals = set(featlist)
newEntropy = 0.0
for value in uniqueVals:
subDataset = splitDataset(dataset,i,value)
prob = len(subDataset)/float(len(dataset))
newEntropy += prob*calShannonEnt(subDataset)
#以特征i进行划分之后,算出特征i的每个属性出现的概率,求出其熵
#由于需要对每个标签都进行一次计算,这样才能算出该划分之后的熵,故需要for循环。
infoGain = baseEntropy - newEntropy
print(infoGain)
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
测试代码:
我们先建立如下的一个数据集:
def createDataSet_me():
dataSet = [ ['sunny', 'busy', 'male', 'no'],
['rainy', 'not busy', 'female', 'no'],
['cloudy', 'relax', 'male', 'maybe'],
['sunny', 'relax', 'male', 'yes'],
['cloudy', 'not busy', 'male', 'maybe'] ,
['sunny','not busy', 'female', 'yes'] ]
return dataSet
利用python运行:
dataset = createDataSet_me()
bestfea=chooseBestFeaturetosplit(dataset)
print(bestfea)
可以看出选择最优特征时,choose函数是如何工作的。
现在就可以根据前面给出的伪代码来写出实现决策树所需要的创建分支函数。
注意到 伪代码中第一行 检测数据集中的每个子项是否属于同一分类 ,但还有一种情况是,哪怕数据集处理完了所有的特征,也无法使得类标签是唯一的,则在这种情况下,需要返回出现次数最多的类别。
def majorityCnt(classlist):
classcount={}
for vote in classlist:
if vote not in classcount.keys(): classcount[vote]=0
classcount[vote] += 1
sortedclasscount = sorted(classcount.iter(),\
key=operator.itemgetter(1),reverse=Ture)
(dataset,bestfeat,value这里写代码片ublabels)
return mytree
def createTree(dataset,labels):
#labels包含了数据集中的所有特征,如“是否有脚蹼”,“浮出水面能否生存”这样的特征
classlist = [example[-1] for example in dataset]
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 = labels[:]
mytree[bestfeatlabel][value]=createTree(splitDataset\
(dataset,bestfeat,value),sublabels)
return mytree