决策树
一.问题概述
决策树(decision tree)希望从给定的数据集学得一个模型用以对新示例来进行分类,把这个样本分类的任务看作对“当前样本属于正类吗?”这个问题的“决策”或者“判定”的过程。决策树是基于树的结构进行决策的,如下图:
二.决策树学习的基本算法
三.实现算法
决策树最核心的问题就是如何选择出最优的划分属性,即上述算法中的第8行,一般而言,我们希望决策树的分支节点所包含的样本尽可能的属于同一个类别,即样本的“纯度”越来越高。
- ID3决策树算法
1.1.以信息增益为准则来选择划分属性
1.2 基本定义
信息熵(information entropy),是度量样本集合纯度最常用的一种指标,假定样本集合D中第k类样本所占的比例为Pk(k=1,2,…,|y|),则D的信息熵定义为:
假定离散属性a有V个可能的取值{a1 , a2 , … , aV},若使用a来对样本进行划分,则会产生V个节点,其中第v个分支节点包含了D中所有在属性a上取值为aV的样本,记为Dv。于是可以计算用属性a划分的“信息增益(information gain)”为:
一般而言,信息增益越大,则意味着用属性a进行划分所得的”纯度提升“越大,因此我们可以使用信息增益为准则来划分属性,即选择属性
1.3 缺点:信息增益为准则对可取值数目较多的属性有所偏好 - C4.5决策树算法
2.1增益率(gain ratio)的定义:
其中IV(a)称为属性a的固有值,属性a的可能取值的数目越多,则IV(a)的值通常会越大。
2.2 由于增益率对可取值数目较多的属性有所偏好,C4.5采用启发式方法:先从候选的划分属性中找出信息增益高于平均水平的属性,再从中选择增益率最高的。 - CART决策树算法
3.1使用“基尼指数(Gini index)”来选择划分属性。一个数据集的纯度可以使用基尼值来度量:
基尼指数Gini(D)反应了从数据集中随机抽取两个样本不一致的概率。因此,Gini(D)越小,则数据集的纯度越高。
相应的,属性a的基尼指数定义为:
4.实现的python代码如下,我这里实现了上述三个算法,根据方法chooseBestFeatureToSplit(dataSet, modelType =’ID3’)的modelType参数来选择相应的算法。
# -*- coding: utf-8 -*-
"""
Decision Tree Source Code for Machine Learning
algorithm: ID3,C4.5,CART 以信息增益、增益率为准则来选择最优的划分属性
@author leyuan
"""
from math import log
import operator
import treePlotter
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):
"""
计算给定数据集的信息熵(information entropy),
:param dataSet:
:return:
"""
numEntries = len(dataSet)
labelCounts = {}
# 统计每个类别出现的次数,保存在字典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) # 取2为底的对数
return shannonEnt
def calcGini(dataSet):
"""
计算给定数据集的基尼指数
:param dataSet:
:return:
"""
numExample = len(dataSet)
lableCounts = {}
# 统计每个类别出现的次数,保存在字典lableCounts中
for featVect in dataSet:
currentLable = featVect[-1]
# 如果当前键值不存在,则扩展字典将当前键值加入到字典中
if currentLable not in lableCounts.keys():
lableCounts[currentLable] = 0
lableCounts[currentLable] += 1
gini = 1.0
for key in lableCounts:
# 使用所有类标签的频率来计算概率
prob = float(lableCounts[key])/numExample
# 计算基尼指数
gini -= prob**2
return gini
def splitDataSet(dataSet, axis, value):
"""
按照给定特征划分数据集
dataSet:待划分的数据集
axis: 划分数据集的第axis个特征
value: 特征的返回值(比较值)
"""
retDataSet = []
# 遍历数据集中的每个元素,一旦发现符合要求的值,则将其添加到新创建的列表中
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
# extend()和append()方法功能相似,但在处理列表时,处理结果完全不同
# a=[1,2,3] b=[4,5,6]
# a.append(b) = [1,2,3,[4,5,6]]
# a.extend(b) = [1,2,3,4,5,6]
return retDataSet
def chooseBestFeatureToSplit(dataSet, modelType ='ID3'):
"""
选择最好的数据集划分方式,支持ID3,C4.5,CART
:param dataSet: 数据集
:param modelType: 决定选择最优划分属性的方式
:return: 最优分类的特征的index
"""
# 计算特征数量
numFeatures = len(dataSet[0]) - 1
baseEntropy = calcShannonEnt(dataSet)
bestInfoGain = 0.0
bestFeature = -1
infoGainList = []
gain_ratioList = []
gini_index_list = []
for i in range(numFeatures):
# 创建唯一的分类标签列表
featList = [example[i] for example in dataSet]
uniqueVals = set(featList)
# 计算用某种属性划分的信息熵和信息增益
newEntropy = 0.0
instrinsicValue = 0.0
# 基尼指数
gini_index = 0.0
for value in uniqueVals:
# 计算属性的每个取值的信息熵x权重
subDataSet = splitDataSet(dataSet, i, value)
prob = len(subDataSet)/float(len(dataSet))
newEntropy += prob * calcShannonEnt(subDataSet)
# 计算固有值(instrinsic value)
instrinsicValue -= prob * log(prob, 2)
# 计算基尼指数
gini_index += prob * calcGini(subDataSet)
# 计算信息增益
infoGain = baseEntropy - newEntropy
infoGainList.append(infoGain)
# 计算增益率
if instrinsicValue == 0:
gain_ratio = 0
else:
gain_ratio = infoGain/instrinsicValue