决策树(Decision Tree)是在已知各种情况发生概率的基础上,通过构成决策树来求取净现值的期望值大于等于零的概率,评价项目风险,判断其可行性的决策分析方法,是直观运用概率分析的一种图解法。由于这种决策分支画成图形很像一棵树的枝干,故称决策树。
1 | 不浮出水面是否可以生存 | 是否有脚蹼 | 属于鱼类 |
---|---|---|---|
2 | 是 | 是 | 是 |
3 |
是 | 是 | 是 |
4 | 是 | 否 | 否 |
5 | 否 | 是 | 否 |
6 | 否 | 是 | 否 |
以《机器学习实战》第三章的 海洋生物识别为例,有 不浮出水面是否可以生存 和 是否有脚蹼 两个特征来判断该海洋生物是不是鱼类.而 决策树 就是像图1一样根据各节点(特征)来判断得到结果.
但创建决策树时各节点否是未知的,任何一个特征好像都可以放在每个节点上,像图一的决策树 不浮出水面是否可以生存(No Surfacing) 和 是否有脚蹼(Flippers)的两个特征节点好像替换一下也可以,但肯定有一种会使运算结果相对最好.
信息熵是由信息论之父克劳德·艾尔伍德·香农提出的一个 描述信息源不确定度 的一个概念.
信息源的不确定度也可以理解为 传入信息种类的多少,熵越大种类越多.
信息熵计算公式:
其中P(Xi)代表Xi分类的概率.
Python实现:
#设置数据集
def SetData():
data = [[1,1,'yes'],[1,1,'yes'],[1,0,'no'],[0,1,'no'],[0,1,'no']]
labels = ['no surfacing','flippers']
return data,labels
计算熵
def GetEntroy(data):
l1 = len(data)
l2 = {}
for i in range(l1):
if data[i][-1] not in l2:
l2[data[i][-1]] = 0
l2[data[i][-1]] += 1
entroy = 0.0
for key in l2:
p = float(l2[key])/l1
entroy -= (p*log(p,2))
return entroy
熵的计算结果:
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
0.970950594455
而当我们在数据集里再添加一个结果,比如'or':
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no'], [0, 1, 'or']]
1.45914791703
计算出的熵较之之前的就增大不少.
通过对每个特征都计算一遍就可以获得当前最好的数据集划分方式了.
#按指定特征划分数据集
def SplitData(data,inx,rev):
splitdata = []
for i in data:
if i[inx] == rev:
x = i[:inx]
x.extend(i[inx+1:])
splitdata.append(x)
return splitdata
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0])-1
baseEntropy = GetEntroy(dataSet)
bestInfoGain =0.0
bestFeature = -1
for i in range(numFeatures):
featList = [sample[i] for sample in dataSet]
uniqueVals = set(featList)
newEntropy = 0.0
for value in uniqueVals:
subDataSet = SplitData(dataSet,i,value) #获得按指定特征i划分的各结果数据集
prob = len(subDataSet)/float(len(dataSet)) #计算按i划分后的各结果所占比例
newEntropy += prob * GetEntroy(subDataSet) #计算按i划分后各结果熵的和
infoGain = baseEntropy - newEntropy
if(infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
对例子的计算结果是:
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
0
通过对 海洋生物数据表 的观察也可以看出,当以 不浮出水面是否可以生存 为划分依据时不浮出水面可以生存的有2个属于 鱼类.1个不属于, 不浮出水面不可以生存 的都不属于鱼类,仅一个划分错误;而当以 是否有脚蹼 为划分依据时 有脚蹼的 有2个属于 鱼类.2个不属于, 没有脚蹼 的都不属于鱼类,有2个划分错误.
通过计算出决策树各层的当前最小熵就可以生成决策树了
#该函数使用分类名称的列表,然后创建键值为classList中唯一值的数据字典,字典对象存储了classList中每个类标签出现的频率,最后利用operator操作键值排序字典,并返回出现次数最多的分类名称。
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):
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:{}}
subLabels=labels[:]
del(subLabels[bestFeat])
featValues=[example[bestFeat] for example in dataSet]
uniqueVals=set(featValues)
for value in uniqueVals:
myTree[bestFeatLabel][value]=createTree(SplitData\
(dataSet,bestFeat,value),subLabels)
return myTree
运行可以得到
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
接下来就可以用分类函数来测试了
def classify(inputTree,featLabels,testVec):
firstStr=list(inputTree.keys())[0]
secondDict=inputTree[firstStr]
featIndex=featLabels.index(firstStr)
for key in secondDict.keys():
if testVec[featIndex]==key:
if type(secondDict[key]).__name__=='dict':
classLabel=classify(secondDict[key],featLabels,testVec)
else: classLabel=secondDict[key]
return classLabel
测试:
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
('Test:[0,0]', 'no')
可以通过Python的pickle序列化对象后保存
import pickle
#存决策树
def storeTree(inputTree,filename):
#这里二进制写入
fw=open(filename,'wb')
#dump函数将决策树写入文件中
pickle.dump(inputTree,fw)
#写完成后关闭文件
fw.close()
#取决策树
def grabTree(filename):
import pickle
#采用二进制读取
fr=open(filename,'rb')
return pickle.load(fr)