《机器学习实战》系列博客主要是实现并理解书中的代码,相当于读书笔记了。毕竟实战不能光看书。动手就能遇到许多奇奇怪怪的问题。博文比较粗糙,需结合书本。博主边查边学,水平有限,有问题的地方评论区请多指教。书中的代码和数据,网上有很多请自行下载。
决策树:分类决策树模型是一种描述对实例进行分类的树形结构。
决策树由 结点+有向边 构成
结点有两种类型: 内部结点,叶子结点
内部结点:表示一个特征或属性
叶子结点:表示一个类
决策树学习算法包含特征选择(3.1.1和3.1.2),决策树的生成(3.1.3),决策树的剪枝(暂时跳过)
3.1.1信息增益
特征选择在于选取对训练数据具有分类能力的特征。如果利用一个特征进行分类的结果与随机分类的结果没有很大差别则称这个特征是没有分类能力的。
通常的特征选择准则是信息增益(ID3算法)或信息增益比(C4.5算法)
下面仅介绍信息增益
信息论中,熵是表示随机变量不确定性的度量。
pi 是离散随机变量 X <script type="math/tex" id="MathJax-Element-3">X</script>的分布。
熵的计算程序
# -*- coding:utf-8 -*-
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
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob * log(prob,2)
return shannonEnt
def createDataSet():
dataSet = [[1,1,'yes'],
[1,1,'yes'],
[1,0,'no'],
[0,1,'no'],
[0,1,'no']]
labels = ['no surfacing','flippers']
return dataSet,labels
命令行输入
>>> reload(trees)
<module 'trees' from 'trees.pyc'>
>>> myDat,labels = trees.createDataSet()
>>> myDat
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
>>> labels
['no surfacing', 'flippers']
>>> trees.calcShannonEnt(myDat)
0.9709505944546686
>>> myDat[0][-1] = 'maybe'
>>> myDat
[[1, 1, 'maybe'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
>>> trees.calcShannonEnt(myDat)
1.3709505944546687
3.1.2划分数据集
上节介绍了用熵来度量一个数据集的无序程度,接下来需要遍历所有特征找到最好的那个来划分数据集。
按照给定数据集划分数据集程序
def splitDataSet (dataSet,axis,value):
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]#左闭右开把在axis左边的元素放在列表里
reducedFeatVec.extend(featVec[axis+1:])#把在axis右边的元素放在列表里
retDataSet.append(reducedFeatVec)
return retDataSet
DataSet 是列表,列表里的每一个元素也是列表。
axis是要选择的特征,Value是这个特征里的某一个值
命令行测试
>>> reload(trees)
<module 'trees' from 'trees.py'>
>>> myDat
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
>>> trees.splitDataSet(myDat,0,1)#0是第一列,1是第一列值为1,把第一列值为1的划分出来
[[1, 'yes'], [1, 'yes'], [0, 'no']]
相关函数学习
- 列表分片操作(访问一定范围内的元素)
>>> a = [0,1,2,3,4] #索引从0开始
>>> a[:3] #左闭右开
[0, 1, 2]
>>> a[4:]
[4]
>>> a[1:3]
[1, 2]
- extend append 函数
>>> a = [1,2,3]
>>> b = [4,5,6]
>>> a.extend(b)
>>> a
[1, 2, 3, 4, 5, 6]
>>> a.append(b)
>>> a
[1, 2, 3, 4, 5, 6, [4, 5, 6]]
选择最好的数据集划分方式程序
遍历数据集中的所有的特征属性(即每一列除了最后一列)然后按照这些特征划分数据集,计算熵,信息增益最大的那个就是最好的特征。
对输入数据的要求:数据由列表元素组成的列表(就像矩阵一样),最后一列是当前实例的类标签。
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0])-1 #最后一列是类别标签
baseEntropy = calcShannonEnt(dataSet)
bestInfoGain = 0.0 ; bestFeature = -1
for i in range (numFeatures):
featList = [example[i] for example in dataSet]#第i列为特征的所有值
print featList
uniqueVals = set(featList)
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 #信息增益
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
运行结果
>>> reload(trees)
<module 'trees' from 'trees.py'>
>>> trees.chooseBestFeatureToSplit(myDat)
0
>>> myDat
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
3.1.3递归构造决策树
给一个数据集,要如何的来构造决策树?
数据集的每一列是一种特征,每一行表示一个实例,最后一列是类标签。
算法思想:从数据集中选择一个最好的特征,(最好的特征是所有特征中能使信息增益最大(数据集变得越来越有序)的那一个)。我们把这个特征作为树的一个结点。按照这个特征划分数据集就能有若干块数据集也就是分支。
1.如果分支里所有的实例都有相同的类标签,那就不需要再对这个数据集再划分下去。即我们得到了一个叶子节点。
2.如果分支里的实例有不同的标签,算法就回到了开始的部分:从数据集中选择一个最好的特征。。。也就是递归。
最后:还有一个要考虑我们把所有的特征都选择完了,但是类标签还是不唯一。处理方式:少数服从多数,来确定这个叶子节点的分类。
多数表决方法程序
def majorityCnt(classList):
classCount = {}
for vote in classList:
if vote not in classList.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:{}}
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
运行结果
>>> import trees
>>> myDat,labels = trees.createDataSet()
>>> myTree = trees.createTree(myDat,labels)
>>> myTree
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}