决策树是一种常见的机器学习算法,也是很容易理解的。顾名思义,它是基于树结构进行决策的。
如下图所示:
正方形表示 判断模块 , 椭圆形表示 终止模块,一棵决策树包含一个根结点、若干个内部结点和若干个叶结点,叶节点表示决策结果。
1. 决策树的构造
构造决策树,需要解决的第一个问题就是,当前数据集上 哪个特征 在划分数据分类时其决定性作用(就是选择哪个特征进行划分)
创建分支的伪代码createBranch()如下所示:
这是一个 递归 函数。
决策数算法的流程如下:
- 收集数据:可使用任何方法。
- 准备数据:树构造算法只适用标称型数据,因此数据必须 离散化。
- 分析数据:可使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。
- 训练算法:构造树的数据结构。
- 测试算法:使用经验树计算错误率。
- 使用算法:此步骤可以适用任何监督学习算法。
1.1 信息增益
为了进行数据的划分,即选择通过什么特征进行划分,采取的一个原则为:将无序的数据变得有序,即希望决策树的分支节点所包含的样本尽可能属于同一类别,即结点的 “纯度” 越来越高。
- 信息:符号 的信息定义为 ,其中 是选择该分类的概率。
- 信息熵:熵定义为信息的期望值,它是度量样本集合纯度最常用的一种指标,公式为 其中n为分类的数目。
- 信息增益:当我们选择一个特征 进行划分后,会产生 个分支节点,其中第 个分支结点包含 中所有在属性 上取值为 的样本,记为 。信息增益定义为:,信息增益是越大越好。
举例如下:
首先我们创建一个数据集:
#创建数据集
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
然后计算信息熵:
#计算信息熵
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
shannoEnt = 0.0
for key in labelCounts:
#计算每个类别所占样本集合的比例
prob = float(labelCounts[key])/numEntries
#求信息熵
shannoEnt -= prob * log(prob, 2)
return shannoEnt
最后求的结果就是
1.2 划分数据集
#按照给定特征划分数据集
#参数:待划分的数据集、划分数据集的特征、特征的返回值
def splitDataSet(dataSet, axis, value):
# 创建新的list对象
retDataSet = []
# 遍历数据集的每一行
for featVec in dataSet:
if featVec[axis] == value:
# 去掉axis特征
reducedFeatVec = featVec[:axis]
# 将符合条件的添加到返回的数据集
# extend() 函数用于在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)。
reducedFeatVec.extend(featVec[axis+1:])
# 列表中嵌套列表
retDataSet.append(reducedFeatVec)
return retDataSet
选择最好的数据集划分方法, 代码如下:
#选择最好的数据集划分方式
def chooseBestFeatureToSplit(dataSet):
#算出特征的数目
numFeatures = len(dataSet[0]) - 1
#print(numFeatures)
#求得所有样本的信息熵
baseEntropy = calcShannonEnt(dataSet)
#定义最优信息增益和最优划分属性
bestInfoGain = 0.0
bestFeature = -1
# 遍历所有特征
for i in range(numFeatures):
featList = [example[i] for example in dataSet]
uniqueVals = set(featList)
#print(uniqueVals)
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
表示第0个特征为最佳划分属性。
1.3 递归构建决策树
当我们选择好划分的属性之后,数据将被分支开来,被向下传递到树分支的下一个结点,在这个结点上,再次划分数据,这样就是 递归 的一个过程。
递归结束的条件为:
-
程序处理完所有划分数据集的属性,这样类标签却不是唯一的,那么就采用“少数服从多数”原则进行类别的确定。
-
每个分支下的所有实例都有相同的分类(这样就没必要在划分了),这样就得到了一个叶子结点,类别就是这个叶子结点上所有数据相同的类别。
“少数服从多数” 代码如下:
#统计classList中出现次数最多的元素(类标签)
def majorityCnt(classList):
classCount = {}
# 统计classList中每个元素出现的次数
for vote in classList:
if vote not in classCount.keys():
classCount[vote] = 0
classCount[vote] += 1
# 根据字典的值降序排序
# operator.itemgetter(1)获取对象的第1列的值
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
# 返回出现次数最多的元素
return sortedClassCount[0][0]
创建树的函数代码如下:
#创建树
def createTree(dataSet, labels, featLabels):
# 取分类标签
classList = [example[-1] for example in dataSet]
# 如果类别完全相同则停止继续划分
if classList.count(classList[0]) == len(classList):
#print(classList)
return classList[0]
# 遍历完所有特征时得不到唯一类别的分组,返回出现次数最多的类标签
if len(dataSet[0]) == 1:
return majorityCnt(classList)
# 选择最优特征
bestFeat = chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat]
featLabels.append(bestFeatLabel)
# 根据标签生成树
myTree = {bestFeatLabel:{}}
# 删除已经使用的特征标签
del(labels[bestFeat])
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
#递归构建决策树
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
return myTree
运行后,生成一棵如下的树:
1.4 使用决策树进行分类
#使用决策树的分类函数
def classify(inputTree, featLabels, testVec):
#获取根节点
firstStr = next(iter(inputTree))
#获取子树
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