决策树之ID3
一、决策树
1、优点:计算复杂度不高,输出结构易于理解,对中间的缺失不敏感,可以处理不 相关特征数据。
2、缺点:可能产生过度的匹配问题。
3、使用数据类型:数值型和标称型。
二、创建决策树分支
1、创建分支的createBranch()伪代码如下所示:
检测数据集中的每个子项是否属于同一类:
If so return 类标签;
Else
寻找划分数据集的最好特征
划分数据集
创建分支节点
for 每个划分的子集
递归调用函数createBranch并增加返回到分支节点中
return 分支节点
2、那么问题来了。createBranch()函数中的划分数据集的标准是什么呢?即,最好的特征是如何定义的呢?
我们使用的信息增益来确定最优的特征。而最优的特征又跟香农熵的定义有关。有关这些定义属于理论部分,所以这里就不做过多的介绍了。直接给出ShannonEntropy的公式:
H = -Σ p(xi)log2[p(xi)] , i = 1,2,...n
p(xi) = [标签为同一类型] / [n]
3、代码实现:
from math import log
def calcShannonEnt(dataSet): #dataSet:输入的数据集
numEntries = len(dataSet) #数据的个数(n)
labelCounts = {} #创建一个标签字典
for featVec in dataSet:
currentLabel = featVec[-1] #返回当前的标签
#if currentLabel not in labelCounts.keys():
# labelCounts[currentLabel] = 0
#labelCounts[currentLabel] += 1
#设置初始化为0,并且对当前出现的标签不断累加
labelCounts[currentLabel] = labelCounts.get(currentLabel,0) + 1
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key]) / numEntries #p(xi) = [标签为同一类型] / [n]
shannonEnt -= porb * log(porb, 2) #H = -Σ p(xi)log2[p(xi)] , i = 1,2,...n
return shannonEnt
4、划分数据集
(1)、将通过最大增益(ShannonEtropy)计算得到的最优特征提取出来。
(2)、其中extend和append地主要区别是前者是添加元素值到列表中,后者是添加对象到列表中。举个例子:
a = [1,2,3]
b = [4,5,6]
a.extend(b) ---->a = [1,2,3,4,5,6]
a.append(b) ----->a = [1,2,3,[4,5,6]]
#提取分离最优特征
#@param dataSet: 待划分的数据集
#@param axis: 最优特征的列数
#@param value: 最优特征的值
#@return : 划分成功的List
def splitDataSet(dataSet, axis, value):
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
#@1、@2是将除了axis列外的所有数据添加到reducedFeatVec中
reducedFeatVec = featVec[: axis] #@1
reducedFeatVec.extend(featVec[axis+1:]) #@2
retDataSet.append(reducedFeatVec)
return retDataSet
5、选择最好的数据划分方式
我们根据最大增益原则,来判断什么样的划分才是最优的划分。具体细节看下面代码。
在函数中调用的数据要满足一定的要求:第一个要求是,数据必须是一种由列表元素组成的列表,而且所有的列表元素都是具有相同的数据长度;第二个要求是,数据的最后一列或者每个实例的最后一个元素是当前实例的类别标签。
#函数作用:根据最大增益选取最优特征值
#@param dataSet: 输入的数据集
#g(D,H) = H(D) - H(D|A)
#H(Y|X) = Σp(xi)H(Y|X=xi), i=1,2,...,n
#H(Y|X): 互信息(mutual information)
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 #返回当前数据集的特征属性(feature)个数,-1 为减去label
baseEntropy = calcShannonEnt(dataSet) #计算H(D)
bestInfoGain = 0.0 #g(D,H)
bestFeature = -1 #最优特征
for i in range(numFeatures):
featList = [example[i] for example in dataSet] #找出所有第i个特征
uniqueVals = set(featList) #找到第i个feature的所有可能取值
newEntropy = 0.0 #H(D|A)
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value) #分离提取出第i个feature值为values的子集
pro = len(subDataSet) / float(len(dataSet)) #p(xi)
newEntropy += pro * calcShannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy #g(D|A)
if(infoGain > bestFeature): #选取最大增益
bestInfoGain = infoGain
bestFeature = i
return bestFeature
三、创建决策树
1、多数表决算法
def majorityCnt(classList):
classCount = {} #分类统计
for vote in clasList:
#if vote not in classCount.keys():
# classCount[vote] = 0
#classCount[vote] += 1
classCount[vote] = classCount.get(vote, 0) + 1
#@iteritems: 对classCount(dict类型)的values进行排序
#@key = operator.itemgetter(1): key比lambda快;itemgetter(1)为指定对第二个值排序,即字典的键值
#@reverse: default=false递增;true递减
sortedClassCount = sorted(classCount.iteritems(), key = operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0] #返回出现次数最多的分类名称
2、创建树的函数代码
代码使用两个输入参数:数据集和标签列表。标签列表包含了数据集中所有特征的标签,算法本身并不需要这个变量,但是为了给出数据明确的含义,我们将它作为一个输入参数提供。
递归函数停止条件是所有的类标签完全相同,则直接返回该类标签。递归函数的第二个停止的条件是使用完所有的特征,仍然不能将数据集划分成仅包含唯一类别的分组。由于第二个条件无法简单地返回唯一的类标签,这里使用1中的多数表决算法。
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet]
if classList.count(clasList[0]) == len(classList): #类别完全相同则停止继续划分
return clasList[0]
if len(dataSet[0]) == 1: #遍历完所有特征时返回出现次数最多的
return majorityCnt(clasList)
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