决策树(decision tree)是一类常见的的机器学习方法,亦称“判定树”,以二分类任务为例,希望从给定训练数据集学的一个模型用以对新示例进行分类。
大量数据—>模型
一般的,一颗决策树包含一个根结点,若干个内部节点和若干个叶结点;
中间结点是决策过程,叶结点是决策结果。根结点包含样本全集。
决策树学习基本算法(是一个递归算法)为:
14:从A中去掉a*;
关键:如何选择最优划分属性。
一般而言,随着划分过程不断进行,我们希望决策树的分支结点所包含的样本尽可能属于同一类别,即结点的"纯度" (purity)越来越高.
“信息熵” (information entropy)是度量样本集合纯度最常用的一种指标。
假设样本集合为D,则D的信息熵定义为:
计算信息熵时约定:若p=0,则plog2p=0.
Ent(D)的最小值为0,最大值为log2|m|。
其中样本集合D中第i类样本所占的比例为Pi (i = 1,2,. . . ,|m|)。
Ent(D) 的值越小,则 D 的纯度越高。
给分结点赋予权重|Dv|/|D|,样本数越多的分支结点的影响越大。
用属性a对样本集D进行划分所获得的“信息增益”公式:
一般来说,一个属性的信息增益越大,就意味着使用该属性来进行划分所得到的“纯度提升”越大。因此,我们可以使用信息增益来进行决策树的第一个结点划分属性选择。(选出决策树的第一个顶头划分的属性,再继续往下划分)
信息熵越小,数据就越纯,信息增益就越大。
计算下图西瓜数据集中各个属性的信息增益,选取值最大的一个属性作为划分属性。
信息增益率公式:
IV(a)称为属性a的“固定值”,属性a的可能取值数目越多(即V越大),则IV(a)的值通常会越大。
启发式:信息增益高——>增益率高
一、基尼指数的概念(CART决策树)
基尼指数(Gini不纯度)表示在样本集合中一个随机选中的样本被分错的概率。
注意:Gini指数越小表示集合中被选中的样本被参错的概率越小,也就是说集合的纯度越高,反之,集合越不纯。当集合中所有样本为一个类时,基尼指数为0.
二、基尼系数的计算公式
基尼指数的计算公式为:
在机器学习中,CART分类树算法使用基尼系数来代替信息增益比,基尼系数代表了模型的不纯度,基尼系数越小,不纯度越低,特征越好。这和信息增益(比)相反。
剪枝处理(pruning)是决策树学习算法中对付“过拟合”的主要手段, 在决策树学习中, 为了尽可能正确分类训练样本, 节点划分过程不断重复, 有时候会造成决策树分支过多, 以至于将训练样本集自身特点当作泛化特点, 而导致过拟合。 因此可以采用剪枝处理来去掉一些分支来降低过拟合的风险。 剪枝的基本策略有预剪枝(prepruning)和后剪枝(postprunint).
预剪枝是指在决策树生成过程中, 在每个节点划分前先估计其划分后的泛化性能, 如果不能提升, 则停止划分, 将当前节点标记为叶结点。要对划分前后的泛化性能进行评估。
后剪枝是指生成决策树以后,再自下而上对非叶结点进行考察, 若将此节点标记为叶结点可以带来泛化性能提升, 则修改之.
往标记为叶结点方向走
留出法:预留一部分数据用作“验证集”以进行性能评估。
**后剪枝决策树通常比预剪枝决策树保留了更多的分支。**一般情况,后剪枝决策树的欠拟合风险小,泛化能力往往优于预剪枝决策树。但是后剪枝决策树的训练时间更长,首先后剪枝决策树本身就是基于完全决策树上建立的,并且还需要自底向上的对树中地所有非叶子节点进行逐一考察。
代码部分(作业):
from math import log
import operator # 导入operator模块,提供一套与python的内置运算符对应的高效率函数
def calcShannonEnt(dataSet):
# 计算数据的熵(entropy)
numEntries = len(dataSet) # 数据条数 numEntries = 8
labelCounts = {
}
for featVec in dataSet:
currentLabel = featVec[-1] # 每行数据的最后一个字(类别)
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1 # 统计有多少个类以及每个类的数量
shannonEnt = 0
for key in labelCounts:
prob = float(labelCounts[key]) / numEntries # 计算单个类的熵值 prob 为每个值出现的频率
shannonEnt -= prob * log(prob, 2) # 数学公式,计算熵 累加每个类的熵值
return shannonEnt
def createDataSet1(): # 创造示例数据
dataSet = [['长', '粗', '男'],
['短', '粗', '男'],
['短', '粗', '男'],
['长', '细', '女'],
['短', '细', '女'],
['短', '粗', '女'],
['长', '粗', '女'],
['长', '粗', '女']]
labels = ['头发', '声音'] # 两个特征
return dataSet, labels
def splitDataSet(dataSet, axis, value):
# 按某个特征分类后的数据
# 查找数据集 dataSet 第 axis 列值== value 的元素,再排除第 axis 列的数据,组成一个新的数据集
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis] # chop out axis used for splitting
reducedFeatVec.extend(featVec[axis + 1:])
retDataSet.append(reducedFeatVec)
return retDataSet
# 该函数使用了三个输入参数:带划分的数据集、划分数据集的特征(数据集第几列)、需要返回的特征的值(按哪个值划分)。
# 函数先选取数据集中第axis个特征值为value的数据,从这部分数据中去除第axis个特征,并返回。
def chooseBestFeatureToSplit(dataSet): # 选择最优的分类特征
numFeatures = len(dataSet[0]) - 1 # 这里的dataSet 最后一列是分类 numFeatures=2;我们按2列数据进行划分
baseEntropy = calcShannonEnt(dataSet) # 原始的熵 ,计算出整个数据数据集的熵
bestInfoGain = 0
bestFeature = -1
for i in range(numFeatures): # 循环每一列特征
featList = [example[i] for example in dataSet] # 创建一个新的 列表,存放数据集第 i 列的数据
uniqueVales = set(featList) # 使用集合,把数据去重
newEntropy = 0 # 以下是计算每一列的熵 求某列最大的熵
for value in uniqueVales: # 循环第 i 列的特征值
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 # 返回按某列划分数据集的最大 熵
def majorityCnt(classList):
# 按分类后类别数量排序,比如:最后分类为2男1女,则判定为男;
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):
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] # bestFeatLabel = 列 描述
myTree = {
bestFeatLabel: {
}} # 分类结果以字典形式保存
del (labels[bestFeat]) # 删除已计算过的列
featValues = [example[bestFeat] for example in dataSet]
uniqueVales = set(featValues) # 获取某列 所有不重复值
for value in uniqueVales:
subLabels = labels[:]
myTree[bestFeatLabel][value] = createTree(splitDataSet \
(dataSet, bestFeat, value), subLabels) # 递归
return myTree
if __name__ == '__main__':
dataSet, labels = createDataSet1() # 创造示列数据
print(createTree(dataSet, labels)) # 输出决策树模型
结果
{
'声音': {
'细': '女', '粗': {
'头发': {
'短': '男', '长': '女'}}}}