算法概述
决策树和K近邻一样,都是解决分类问题的算法。决策树从名字解读,目标是建立一棵树,数据看作树的根或者叶,选取数据的特征作为树的一个个分支(决策点),每个分支把数据集分为了不同的数据子集,这里体现了分类的决策,最后直到数据集无法再进行划分。决策树作为预测模型,代表了对象属性和对象标签之间的映射关系,我们可以发现数据内部蕴含的知识(k近邻无法获取数据的内在含义)
一般一棵决策树包含一个根节点、若干内部节点、若干叶节点,内部节点代表了特征或者属性,叶节点代表了一个分类(决策结果)。根节点包含样本全集,每个节点包含的样本集合根据属性测试的结果划分到子节点中,从根节点到每个叶节点的路径对应一个判定测试序列,从而建立一棵泛化能力强的决策树。
算法原理
在构建决策树时,首先需要选取某个特征将原始数据划分为n个子数据集,那么第一个问题就是确认哪个特征在划分数据集时起决定性作用,即如何选择最优划分特征,一般而言,随着划分过程的不断进行,我们希望决策树的分支节点所包含的样本尽可能属于同一类别,使节点的纯度越来越高。这里我们采用信息增益来进行划分
信息熵:用来度量样本集合纯度最常用的一种指标,假设当前样本集合D中第k类(西瓜的好坏)样本所占的比例为,则信息熵的定义为:
Ent(D)的值越小,则D的纯度越高。
信息增益:假设离散属性(特征)a有n个不同的取值{a1,a2,...an},使用属性a来对样本集进行划分,则会产生n个不同的分支节点,其中第i个分支节点包含了D中所有在属性i上取值为ai的样本,我们根据公式计算当前样本集中第i类的信息熵Di,再 考虑到不同的分支节点所包含的样本数不同,给不同的分支节点赋予权重,即样本数越多的分支节点的影响越大,最后计算属性a对样本集D进行划分所获得的信息增益
一般而言:信息增益越大,则意味着使用属性a来进行划分获得的纯度提升越大,ID3决策树学习算法就是采用信息增益为准则来选择划分属性。
ID3决策树算法思路:采用信息增益大小来判断当前节点应该用什么特征来构建决策树,并且用计算出的信息增益最大的特征来建立决策树的当前节点。
举例计算信息增益
西瓜数据集(机器学习),该数据集中包含17个训练数据,用于学习一棵预测是否好瓜的决策树。在决策树学习开始,根节点包含数据集D所有样本,这里的初始k等于2(好瓜/坏瓜),其中正例p=8/17,反例q=9/17,通过公式计算根节点的信息熵为:
然后我们需要计算出每个属性(特征)的信息增益(色泽,根蒂,敲声,纹理,脐带,触感),这里我们以色泽为例,色泽取值为{青绿,乌黑,浅白},使用该属性对D进行划分为3个子集,记为D1,D2,D3,然后分别计算各子集的信息熵,D1中有6个样例,正例p=3/6,反例q=3/6,D2中有6个样例,正例p=4/6,反例q=2/6,D3中有5个样例,正例p=1/5,反例q=4/5。
分别计算各子集的信息熵:
最后计算根据属性色泽划分样本的信息增益为:通过计算不同属性的信息增益,选择最大信息增益的属性作为划分依据。
ID3不足:1)没考虑到连续特征,比如长度、密度等;2)信息增益准则对取值数目较多的属性有所偏好;3)没考虑过拟合情况;4)没有考虑缺失值处理。
示例1:
from math import log
import operator
# 创建训练数据集
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
# 计算信息熵(Ent(D)=sum(pi*log(pi,2))
def calcShannonEnt(dataSet):
# 训练数据集长度
numEntries = len(dataSet)
# 生成训练数据集标签字典
labelCounts = {}
for featVec in dataSet:
# 获得训练数据集标签
currentLabel = featVec[-1]
# 若该标签不在标签字典中,则加一
labelCounts[currentLabel] = labelCounts.get(currentLabel, 0) + 1
# 初始化信息熵
shannonEnt = 0.0
for key in labelCounts.keys():
# 计算不同类标签概率
prob = float(labelCounts[key]) / numEntries
shannonEnt -= prob * log(prob, 2)
return shannonEnt
# 按照给定特征划分数据集
def splitDataSet(dataSet, axis, value):
"""
:param dataSet: 待划分数据集
:param axis: 划分数据集的特征
:param value: 需要返回的特征值
:return:
"""
# 为了保证不修改原始列表(列表为可变对象),创建新列表保存数据
retDataSet = []
# 删除返回的特征值保存到retDataSet列表中
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis + 1:])
retDataSet.append(reducedFeatVec)
return retDataSet
# 选择最优的分类特征
def chooseBestFeatureToSplit(dataSet):
# 获取训练数据的特征个数
numFeatures = len(dataSet[0]) - 1
# 计算信息熵初始值
baseEntropy = calcShannonEnt(dataSet)
# 设置信息增益初始值
bestInfoGain = 0
# 设置初始划分特征
bestFeature = -1
for i in range(numFeatures):
# 创建唯一的分类标签属性值
featureList = [example[i] for example in dataSet]
uniqueVals = set(featureList)
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
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:
subLabels = labels[:]
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
return myTree
if __name__ == '__main__':
dataSet, labels = createDataSet()
# print(dataSet[0], len(dataSet[0]))
# labelCounts = calcShannonEnt(dataSet)
# retDataSet = splitDataSet(dataSet, 1, 1)
# feature = chooseBestFeatureToSplit(dataSet)
myTree = createTree(dataSet, labels)
print(myTree)
C4.5决策树算法思路:采用信息增益率来选择最优划分属性。
增益率定义:
其中:
增益率准则对取值数目较少的属性有所偏好,因此,C4.5算法不是直接选择增益率最大的来作为划分属性,而是先从候选划分属性中找出信息增益高于平均水平的属性,然后选择增益率最高的作为划分属性。
示例2:使用决策树对web站点的用户在线浏览行为及最终购买行为(选择的服务类型或者用户类型)进行预测。
特征/属性:每个用户的来源网站,用户的ip位置,是否阅读FAQ,浏览网页数目
标签:游客,基本用户,高级用户
数据集如下:
data=[['slashdot','USA','yes',18,'None'],
['google','France','yes',23,'Premium'],
['digg','USA','yes',24,'Basic'],
['kiwitobes','France','yes',23,'Basic'],
['google','UK','no',21,'Premium'],
['(direct)','New Zealand','no',12,'None'],
['(direct)','UK','no',21,'Basic'],
['google','USA','no',24,'Premium'],
['slashdot','France','yes',19,'None'],
['digg','USA','no',18,'None'],
['google','UK','no',18,'None'],
['kiwitobes','UK','no',19,'None'],
['digg','New Zealand','yes',12,'Basic'],
['slashdot','UK','no',21,'None'],
['google','UK','yes',18,'Basic'],
['kiwitobes','France','yes',19,'Basic']]