机器学习实战二 (Decision Tree)
机器学习实战二 (decision tree)
我们经常使用决策树处理分类问题,近来也有研究表明,决策树是最经常使用的数据挖掘算法。决策树的概念十分简单,就是基于一系列的判断,有点像是编程语言中的条件判断,只不过条件的定义复杂一些。
决策树一个很重要的任务就是是提取数据中蕴含的知识信息,因此决策树可以使用不熟悉的数据集,从中提取规则,这个过程就是机器学习的过程,而且不像kNN每次都要重新计算,决策树训练的结果是可以保存的,只需训练一次即可,因此效率很高。
Decisiong Tree(决策树)
优点:计算相对容易,输入结果易于理解,对于缺失值不敏感,而且可以处理不相关特征数据
缺点:有时会产生过度匹配的问题
原理:决策树对于数据集的划分,是基于信息论
中的概念,不同的特征,对于类别的表征强度是不同的,这就需要对于数据集的每个特征进行评估,然后制定规则,递归的采用这个过程。
决策树的算法不止一种,书里采用的是ID3算法。
先来看看基础的概念,如何度量信息?
1、信息增益
划分数据集的最大原则是:将无序的数据变得更加有序。这种无序或者有序的状态,可以用信息熵来衡量。划分数据集前后的熵的变化,称为信息增益,能取得最高信息增益的特征,就是最具有代表性的特征。
信息熵被定义为信息的期望值,计算公式如下:
\(l\left( x_{i}\right) =-\log _{2}P\left( x_{i}\right)\)
其中\(x_{i}\)代表某一类别。
那么所有类别总的信息期望值为:
\(H=-\sum ^{n}_{i=1}p\left( x_{i}\right) \log _{2}p\left( x_{i}\right)\)
Python代码如下:
from math import log
import operator
import pickle
from treePlotter import *
#计算香农熵
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
labelCounts = {}
for featVec in dataSet:
currentLabel = featVec[-1]
# dict中get的始用,避免条件判断
labelCounts[currentLabel] = labelCounts.get(currentLabel, 0) + 1
shannonEnt = 0.0
for key in labelCounts:
prob = labelCounts[key] / numEntries
# 根据熵计算公式
shannonEnt -= prob * log(prob, 2)
return shannonEnt
信息越无序,则计算出来的熵就越大,因此可以用信息熵来定量化信息。
2、划分数据集
有了熵的计算,信息增益就比较好算了。先对原始数据集计算一次熵值,然后按照某个特征对数据集进行划分,然后计算划分后数据集的熵值,两者相减,即可计算出信息增益,取其中信息增益最大的特征,然后就是一个递归的。
根据某一特征划分数据集是简单的。
# 划分数据集
def splitDataSet(dataSet, axis, value):
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatureVec = featVec[:axis]
reducedFeatureVec.extend(featVec[axis + 1:])
retDataSet.append(reducedFeatureVec)
return retDataSet
那么选择最好的特征就要将划分数据集与信息增益结合。
# 选择最好的数据集划分
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]
uniqueVals = set(featList)
newEntropy = 0.0
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value)
# 计算划分后的熵值
prob = len(subDataSet) / len(dataSet)
newEntropy += prob * calcShannonEnt(subDataSet)
# 信息增益是熵的减少
infoGain = baseEntropy - newEntropy
if(infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
3、递归构建决策树
基本的模块已经写好,剩下的就是将它们组合成决策树的流程了。
决策树是一棵递归的树,这就需要声明递归终止的条件。条件有两个:
- 按照某一属性划分类别没有变化,终止
- 所有属性均已耗尽,但是类别标签依旧不唯一,这种情况通常采用多数表决的方式,以出现次数最多标签作为类别标签。
# 投票表决
def majorityCnt(classList):
classCount = {}
for vote in classList:
classCount[vote] = classCount.get(vote, 0) + 1
sortedClassCount = sorted(classCount.items(), 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:
# list复制
subLabels = labels[:]
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),
subLabels)
return myTree
然后就可以执行分类了。
# 根据决策树执行分类
def classify(inputTree, featLabels, testVec):
firstStr = list(inputTree.keys())[0]
secondDict = inputTree[firstStr]
featIndex = featLabels.index(firstStr)
for key in secondDict:
if testVec[featIndex] == key:
if type(secondDict[key]).__name__ == 'dict':
classLabel = classify(secondDict[key], featLabels, testVec)
else:
classLabel = secondDict[key]
return classLabel
分类也是一个递归的过程,不困难。
4、决策树的存储
构造决策树是个很复杂的过程,但好在我们不需要每次都训练,这样的效率无疑很低,因此我们需要的,就是每次决策树构造好之后,我们就将它存储,需要的时候再读取进来,这里采用的是pickle序列化对象
。
# 存储决策树
def storeTree(inputTree, filename):
fw = open(filename, 'wb')
pickle.dump(inputTree, fw)
fw.close()
# 读取决策树
def grapTree(filename):
fr = open(filename, 'rb')
return pickle.load(fr)
无论是存储还是读取,都相当的简单。现在,就可以用手上训练好的规则,去检验整个世界了!
5、决策树绘制
原书中有关于决策树绘制的部分,很复杂,与机器学习关系不大,代码一并放在Github中,仅供参考。
决策树存在的问题:
- 优势特征太多,会导致过度匹配的问题,这时就需要对决策树进行修剪
- 本次采用ID3算法,还有CART和C4.5算法,后面会介绍到
- ID3算法只能用于划分标称型数据集,无法处理数值型
Next:kNN和决策树是确定的分类算法,数据实例会被明确的划分到某个分类,下一章的分类算法将不能完全确定数据示例所属分类,只是给出概率,即朴素贝叶斯方法。
Reference: