概要记录
Decision Tree基本学习,学习自《机器学习实战》P32 - P42 (基于信息增益的决策树构建)
决策树核心问题:当前数据集使用哪个属性/特征进行数据分类时起到了决定性作用,能够得到最好的划分结果。
信息增益:
划分数据集的大原则:将无序的数据变得更加有序
划分数据集前后信息发生的变化成为信息增益,计算每个特征值划分数据集得到的信息增益,获得信息增益最高的特征(属性)就是最好的特征(属性)
-
信息增益计算部分还可参见周志华《机器学习》P76,基本步骤是:
-
- 根据属性的取值划分子集;
-
- 计算每个子集的熵;
-
- 根据每个子集占总数据集的数量比与S2计算的每个子集的熵,计算
关于该属性的熵。
- 根据每个子集占总数据集的数量比与S2计算的每个子集的熵,计算
-
另外一个度量无序程度的方法是"基尼不纯度(GiNi impurity)",简单地说就是从一个数据集中司机选取子项,度量其被错误地分类到其他分组的概率
划分数据集
对每个特征划分数据集的结果计算一次熵,然后判断哪个特征是划分数据集的最好方式;
数据集划分可以这样想象:假设有2个维度的特征,则存在在二维空间的数据散点图,需要在数据之间画一条线将他们分为两部分,那么应该按照x轴还是y轴划线呢?
(按照哪个特征划线,就是在哪个维度划线,目的在于每次划线后得到一个数据较纯即更加有序的类别的数据)选择最好特征chooseBestFeatureToSplit()就是在选择最好的数据集划分方式。
信息增益是熵的减少或数据无序度的减少,信息增益越大,数目数据从无序变得有序的程度就大
注意:这里的特征其实就是属性
递归构建决策树
得到原始数据集,然后基于最好属性值划分数据集,由于特征值可能多于2个,因此可能存在大于2个分支的数据集划分。第一次划分后,数据集将被向下传递到树分支的下一个节点,在这个节点上可以再次划分数据,因此可以采用递归的原则处理数据。
递归结束的条件是:程序遍历完所有划分数据集的属性,或每个分支下的所有实例都居于相同的分类
-
书中实例说明:
以[[1,1,'yes'],[1,1,'yes'],[1,0,'no'],[0,1,'no'],[0,1,'no']]为例,首先选出了最佳属性是第0个属性(no surfacing),当值为0是都为no;当值为1时不一致[[1,yes],[1,yes],[1,no]],因此再次选择最佳属性是第1个特征(flippers),当值为1时种类都是yes,值为0时种类都是no。
最终得到划分结果: {'no surfacing':{0:'no',1:{'flippers':{0:'no',1:'yes'}}}}
代码:
# -*- coding: utf-8 -*-
"""
整理自:《机器学习实战》P32 - P42
"""
from math import log
import operator
def createDataSet():
dataSet = [[1,1,'yes'],
[1,1,'yes'],
[1,0,'no'],
[0,1,'no'],
[0,1,'no']
]
# QUESTION:labels是第i个特征的含义
laebls = ['no surfacing', 'flippers']
return dataSet,laebls
def clacShannonEnt(dataSet):
"""
功能:计算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
# 以2为底求对数
shannonEnt -= prob * log(prob,2)
return shannonEnt
def chooseBestFeatureToSplit(dataSet):
"""
功能:选取特征,划分数据集,计算得出最好的划分数据集的特征
param: dataSet: 数据集
return: i: 返回i则说明第i个特征是最好的用于划分数据集的特征
"""
numFeatures = len(dataSet[0]) - 1
baseEntropy = clacShannonEnt(dataSet)
baseInfoGain = 0.0
beatFeature = -1
for i in range(numFeatures):
featList = [example[i] for example in dataSet]
uniqueVals = set(featList)
newEntropy = 0.0
for value in uniquevVals:
subDataSet = splitDataSet(dataSet,i,value)
# 计算每种划分方式的信息熵
prob = len(subDataSet) / float(len(dataSet))
newEntropy += prob * clacShannonEnt(subDataSet)
# 信息增益是熵的减少或数据无序度的减少
infoGain = baseEntropy - newEntropy
# 信息增益
if infoGain > baseInfoGain:
baseInfoGain = infoGain
baseFeature = i
return beseFeature
def splitDataSet(dataSet, axis, value):
'''
功能:按照给定特征划分数据集 数据集第axis位上值为value的集合
注意:1.划分数据集的大原则:将无序的数据变得更加有序
2. 划分数据集前后信息发生的变化成为信息增益,获得信息增益高的特征就是最好的选择
eg. 若dataSet = [[1,1,'yes'], [1,1,'yes'], [1,0,'no'], [0,1,'no'],[0,1,'no']],splitDataSet(dataset,0,1)
结果为[[1,'yes'],[1,'yes'],[0,'no']]
注:当featVec = [1,0,'no']时,featVec[:axis] = featVec[:0] = [], featVec[axis+1:] = featVec[1:] = [0,'no']
'''
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
# 抽取
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis + 1:])
retDataSet.append(reducedFeatVec)
return retDataSet
def majorityCnt(classList):
"""
功能:用多数表决方法确定叶子节点的分类
"""
classCount = {}
for vote in classList:
if vote not in classCount.keys():
classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.items())
key = operator.itemgetter(1,reverse=True)
return sortedClassCount[0][0]
def createTree(dataSet,labels):
"""
param: dataSet: 数据集
param: labels: 标签列表(每个属性的名字)
"""
classList = [example[-1] for example in dataSet]
# 类别完全相同则停止划分
if classList.count(classList[0]) == len(classList):
return classList[0]
# 便利完所有特征时返回出现次数最多的
# QUESTION: 为什么??len(dataSet[0]) == 1
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]
# uniquevVals 是最佳属性值的所有取值
uniquevVals = set(featValues)
for value in uniquevVals:
subLabels = labels[:]
# 最佳划分属性的每个取值下都对应一颗子树,这颗子树可能一个子树,也可能是一个叶子节点
'''
例如,讨论bestFeat=0时的情况:
当value=0,时候,splitDataSet()的结果是[1,no],[1,no],因此crrateTree([[1,no],[1,no]],subLabels)的结果可以直接返回no
当bestFeat=0,value=1,时候,splitDataSet()的结果是[1,no],[1,no],因此crrateTree([[1,yes],[1,yes],[1,no]],subLabels)无法直接确定种类,因此要再次选择最佳属性
'''
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value),subLabels)
return myTree
if __name__ == '__main__':
dataSet,laebls = createDataSet()
print (createTree(dataSet,labels))
2018.05.21