1、信息熵和条件熵
在信息论中,熵是表示随机变量不确定性的度量,熵越大,则随机变量的不确定性越大。设X是一个离散随机变量,X的概率分布为:
P(X=xi)=pi,i=1,2,3...,n
则随机变量X的熵定义为:
H(X)=−∑i=1npilogpi
熵只依赖与X的分布,与X的取值无关,所以X的熵记做H(p),即
H(p)=−∑i=1npilogpi
设随机变量(X,Y)的联合概率分布为:
P(X=xi,Y=yi)=pij,i=1,2,3...,n
条件熵H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性,随机变量X给定的条件下随机变量Y的条件熵H(Y|X)定义为X给定条件下Y的条件概率分布的熵对X的数学期望:
H(Y|X)=∑i=1npiH(Y|X=xi)
当熵和条件熵中的概率有数据估计得到时,对应的熵与条件熵分别称为经验熵和经验条件熵。
2、信息增益
特征A对训练数据集D的信息增益g(D,A)定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即:
g(D,A)=H(D)−H(D|A)
给定训练数据集D和特征A,经验熵H(D)表示对数据集D进行分类时的不确定性,而经验条件熵H(D|A)表示在特征A给定的条件下对数据集D进行分类的不确定性,那么它们的差,即信息增益,就表示由于特征A而使得对数据集D的分类的不确定性减少的程度。决策树进行特征选择时,选择信息增益最大的特征作为划分依据。
3、信息增益比:
特征A对训练数据集D的信息增益比gR(D,A)定义为其信息增益g(D,A)与训练数据集D关于特征A的值的熵HA(D)之比,即
gR(D,A)=g(D,A)HA(D)
其中,HA(D)=−∑ni=1|Di||D|log2|Di||D|,n是特征A的取值个数。
4、决策树生成
决策树的生成有三种算法,分别是ID3、C4.5和CART算法,ID3算法使用信息增益来选择特征,而C4.5使用信息增益比来选择特征。ID3的做法是每次选取当前最佳的特征来分割数据(选择信息增益最大的特征作为划分依据),并按照该特征的所有可能取值来切分。也就是说,如果一个特征有4种取值,那么数据将被切成4份。某个特征的信息增益计算如下:
输入:训练数据D和特征A
输出:特征A对训练数据集D的信息增益g(D,A)或者信息增益比gR(D,A)
(1)计算数据集D的经验熵:
H(D)=−∑k=1k|Ck||D|log2|Ck||D|
其中训练数据集为D,
(2)计算特征A对数据集D的经验条件熵H(D|A):
H(D|A)=∑i=1n|Di||D|H(Di)=−∑i=1n|Di||D|∑k=1K|Dik||Di|log2|Dik||Di|
其中特征A有n个不同的取值{a1,a2,...,an},根据A的取值将D划分为n个子集D1,D2,...,Dn,|Di|为Di的样本个数,∑ni=1|Di|=|D|。子集Di中属于类Ck的样本集合为Dik,|Dik|为Dik的样本个数。
(3)计算信息增益或者信息增益比:
g(D,A)=H(D)−H(D|A)
gR(D,A)=g(D,A)HA(D)
5、决策树剪枝
决策树生成算法递归产生决策树,知道不能继续下去为止,这样产生的树往往对训练数据的分类很准确,但对未知的测试数据的分类却没那么准确,即出现过拟合现象。解决这个问题的办法是考虑决策树的复杂度,对已生成的决策树进行简化。将已生成的树进行简化的过程称为剪枝,剪枝从已生成的树上减掉一些子树或者叶节点,并将其根节点或者父节点作为新的叶节点。
决策树的剪枝往往通过极小化树整体的损失函数来实现。设树T的叶节点个数为
Cα(T)=∑t=1|T|NtHt(T)+α|T|
其中经验熵为:
Ht(T)=−∑kNtkNtlogNtkNt
可以把损失函数写为:
Cα(T)=C(T)+α|T|
其中C(T)表示模型对训练数据的预测误差,即模型与训练数据的拟合程度,|T|表示模型的复杂度。α控制两者间的影响,较大的α促使选择较简单的模型树,较小的α促使选择较复杂的模型树。剪枝就是α确定时,选择损失函数最小的模型。
6、代码实现ID3算法生成决策树
#coding=utf-8
import os
import sys
import numpy as np
from math import *
import matplotlib.pyplot as plt
#创建测试数据集'
def createDataSet():
dataSet = [[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 1, 'no']]
return dataSet
#'计算香农熵'
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
# 计算当前数据集的香农熵并返回
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob * log(prob,2)
return shannonEnt
#'数据集划分'
# ==============================================
# 输入:
# dataSet: 训练集文件名(含路径)
# axis: 用于划分的特征的列数,为 0 | 1
# value: 划分值,根据特征的 n个不同的取值来划分
# 输出:
# retDataSet: 划分后的子列表
# ==============================================
def splitDataSet(dataSet, axis, value):
# 划分结果
retDataSet = []
for featVec in dataSet: # 逐行遍历数据集
if featVec[axis] == value: # 如果目标特征值等于value
# 抽取掉数据集中的目标特征值列
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
# 将抽取后的数据加入到划分结果列表中
retDataSet.append(reducedFeatVec)
return retDataSet
#'选择最佳划分方案'
# ===============================================
# 输入:
# dataSet: 数据集
# 输出:
# bestFeature: 和原数据集熵差最大划分对应的特征的列号
# ===============================================
def chooseBestFeatureToSplit(dataSet):
# 特征个数,也是数据集列数-1
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] # 得到一列的数据,也就是某个特征所有的取值
# 将特征值列featList的值唯一化并保存到集合uniqueVals
uniqueVals = set(featList)
# 新划分法香农熵
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
#'采用多数表决的方式求出classList中出现次数最多的类标签'
# ===============================================
# 输入:
# classList: 类标签集
# 输出:
# sortedClassCount[0][0]: 出现次数最多的标签
# ===============================================
def majorityCnt(classList):
classCount={}
for vote in classList:
if vote not in classCount.keys(): classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
# '创建决策树'
#在Python中,可以用字典来具体实现树:字典的键存放节点信息,值存放分支及子树/叶子节点信息。
# ===============================================
# 输入:
# dataSet: 数据集
# labels: 划分标签集
# 输出:
# myTree: 生成的决策树
# ===============================================
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]
# 对特征值列表featValues唯一化,结果存于uniqueVals。
uniqueVals = set(featValues)
for value in uniqueVals: # 逐行遍历特征值集合
# 保存所有划分标签信息并将其伙同划分后的数据集传递进下一次递归
subLabels = labels[:]
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
#'使用决策树分类'
# ========================
# 输入:
# inputTree: 决策树文件名
# featLabels: 分类标签集
# testVec: 待分类对象
# 输出:
# classLabel: 分类结果
# ========================
def classify(inputTree,featLabels,testVec):
# 当前分类标签
firstStr = inputTree.keys()[0]
secondDict = inputTree[firstStr]
# 找到当前分类标签在分类标签集中的下标
featIndex = featLabels.index(firstStr)
# 获取待分类对象中当前分类的特征值
key = testVec[featIndex]
# 遍历
valueOfFeat = secondDict[key]
# 子树分支则递归
if isinstance(valueOfFeat, dict):
classLabel = classify(valueOfFeat, featLabels, testVec)
# 叶子分支则返回结果
else: classLabel = valueOfFeat
return classLabel
myDat = createDataSet()
labels = ['no surfacing', 'flippers']
myTree = createTree(myDat, labels)
print classify(myTree, labels, [1,1])