决策树学习是一种逼近离散值目标函数的方法,简单来说它可以被表示为多个的if-then的规则表达式。在本文中先主要介绍决策树的基本概念,主要包括熵的概念以及如何选择最优的数据集划分方式
这里我们使用《机器学习》(Tom M.Mitchell著)中的例子作为分析的数据源。这颗决策树根据天气情况分类“星期六上午是否适合打网球”。
从图中可以看出决定是否打球的因素主要有:outlook(天气)、humidity(湿度)和wind(风力)。那么这三个属性哪一个属性是最优的数据集划分方式呢?即我们得到哪一个属性值可以有最大的概率判断出最终的结果(yes或no)呢?获得最优的数据划分方式有一个优势是可以将该属性放在前几个判断节点上,这样在处理大量的数据时可以减少整体数据集的平均搜索深度。这里就需要引入信息熵的概念。
熵的具体定义参考维基百科。简单的说熵是衡量一个系统的无序程度的物理单位,一个系统越混乱,该系统的熵越大。在信息领域中我们对数据进行分析得到结果的过程是一个熵减过程。
对于一个bool型的系统,给定包含关于某个目标概念的正反样例的样例集S,那么S相对这个布尔型分类的熵为:
根据上述这个公式,我们可以得到:S的所有成员属于同一类,Entropy(S)=0; S的正反样例数量相等,Entropy(S)=1;S的正反样例数量不等,熵介于0,1之间,如下图所示:
这个图形也符合熵的定义:系统越无序,熵越大。
已经有了熵作为衡量训练样例集合纯度的标准,现在可以定义属性分类训练数据的效力的度量标准。这个标准被称为“信息增益(information gain)”。简单的说,一个属性的信息增益就是由于使用这个属性分割样例而导致的期望熵降低(或者说,样本按照某属性划分时造成熵减少的期望)。更精确地讲,一个属性A相对样例集合S的信息增益Gain(S,A)被定义为:
直接看公式可能不好理解,这里举个例子:
假定S是一套有关天气的训练样例,描述它的属性包括可能是具有Weak和Strong两个值的Wind。像前面一样,假定S包含14个样例[9+,5-]。在这14个样例中,假定正例中的6个和反例中的2个有Wind =Weak,其他的有Wind=Strong。由于按照属性Wind分类14个样例得到的信息增益可以计算如下:
这里我们再继续计算一下Sweak。weak集合总共有8个,有6个属于正例2个属于反例即[6+,2-],根据公式计算得出
这里我们用python实现以上论述的各个模块功能。代码来自《机器学习实战》。为了保持一致,我们仍然用打球的例子作为数据源,表格如下:
假设各个属性的映射关系:
outlook –0 sunny;1 overcast; 2 rain
temperature –0 hot ; 1 mild; 2 cool
humidity –0 high; 1 normal;
wind –0 weak; 1 strong;
根据表格得到对应的数据列表:
[0,0,0,0,’no’],
[0,0,0,1,’no’],
[1,0,0,0,’yes’],
[2,1,0,0,’yes’],
[2,2,1,0,’yes’],
[2,2,1,1,’no’],
[1,2,1,1,’yes’],
[0,1,0,0,’no’],
[0,2,1,0,’yes’],
[2,1,1,0,’yes’],
[0,1,1,1,’yes’],
[1,1,0,1,’yes’],
[1,0,1,0,’yes’],
[2,1,0,1,’no’]
将上述表格转换为数据:
def createDataSet():
dataSet = [[0,0,0,0,'no'],
[0,0,0,1,'no'],
[1,0,0,0,'yes'],
[2,1,0,0,'yes'],
[2,2,1,0,'yes'],
[2,2,1,1,'no'],
[1,2,1,1,'yes'],
[0,1,0,0,'no'],
[0,2,1,0,'yes'],
[2,1,1,0,'yes'],
[0,1,1,1,'yes'],
[1,1,0,1,'yes'],
[1,0,1,0,'yes'],
[2,1,0,1,'no']]
return dataSet
给根据特定属性值抽取数据代码:
def splitDataSet(dataSet, axis, value):
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis] #chop out axis used for splitting
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
这个函数将选择属性dataSet[axis]==value的数据集合,需要注意的是输出的retDataSet去掉了dataSet[axis]的属性。举个例子:
def main():
dataset,label = createDataSet()
ret_dataset = splitDataSet(dataset, 0, 1)
print ret_dataset
if __name__ == '__main__':
main()
splitDataSet(dataset, 0, 1)
抽取属性0值为1的数据,得到结果:
[[1,0,0,0,’yes’],[1,2,1,1,’yes’],[1,1,0,1,’yes’],[1,0,1,0,’yes’]]
前面的1均是被函数剔除掉的,实际的结果是:
[[0,0,0,'yes'],[2,1,1,'yes'],[1,0,1,'yes'],[0,1,0,'yes']]
接下来编写计算信息熵的代码
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
labelCounts = {}
for featVec in dataSet: #the the number of unique elements and their occurance
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) #log base 2
return shannonEnt
这个函数比较简单,它计算了输入的dataSet
熵值。
接下来就要通过计算各个属性的熵,并找出其中熵减最大的属性。
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 #一共有numFeatures属性
baseEntropy = calcShannonEnt(dataSet)
bestInfoGain = 0.0; bestFeature = -1
for i in range(numFeatures): #iterate over all the features
featList = [example[i] for example in dataSet] #抽取属性i的值,形成一个列表
uniqueVals = set(featList) #get a set of unique values
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 #calculate the info gain; ie reduction in entropy
if (infoGain > bestInfoGain): #compare this to the best gain so far
bestInfoGain = infoGain #if better than current best, set to best
bestFeature = i
return bestFeature #returns an integer
通过该函数,我们计算得出最佳属性为属性0,即outlook
属性。
以下给出整体的测试代码。
from math import log
import operator
def createDataSet():
dataSet = [[0,0,0,0,'no'],
[0,0,0,1,'no'],
[1,0,0,0,'yes'],
[2,1,0,0,'yes'],
[2,2,1,0,'yes'],
[2,2,1,1,'no'],
[1,2,1,1,'yes'],
[0,1,0,0,'no'],
[0,2,1,0,'yes'],
[2,1,1,0,'yes'],
[0,1,1,1,'yes'],
[1,1,0,1,'yes'],
[1,0,1,0,'yes'],
[2,1,0,1,'no']]
return dataSet
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
labelCounts = {}
for featVec in dataSet: #the the number of unique elements and their occurance
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) #log base 2
return shannonEnt
def splitDataSet(dataSet, axis, value):
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis] #chop out axis used for splitting
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 #the last column is used for the labels
baseEntropy = calcShannonEnt(dataSet)
bestInfoGain = 0.0; bestFeature = -1
for i in range(numFeatures): #iterate over all the features
featList = [example[i] for example in dataSet]
uniqueVals = set(featList) #get a set of unique values
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 #calculate the info gain; ie reduction in entropy
if (infoGain > bestInfoGain): #compare this to the best gain so far
bestInfoGain = infoGain #if better than current best, set to best
bestFeature = i
return bestFeature #returns an integer
def main():
dataset = createDataSet()
best = chooseBestFeatureToSplit(dataset)
print best
if __name__ == '__main__':
main()
本文主要介绍了信息熵的概念,以及如何用熵减的方法选择出最佳划分属性。关于决策树的进一步使用将在下一篇博文写出。