文章结构:
一 、算法思想
二、关键代码及注解(代码源于书中)
//参考资料 《机器学习实战》 & 《统计学习方法》
一、算法思想
书中确定最优特征的方法是采用最大熵增益原则,所以书中的算法是ID3算法(信息增益比对应ID5算法)
大体决策树构建过程:
具体算法流程:
二 关键代码及注解(代码源于书中)
//(1)最主要的数据结构:字典和列表 ;字典用于生成树结构,其中字典的键就是决策树结构中的节点(叶子、内部特征节点),列表用来保存特征名称等
//(2)如何使新来的样本被正确却分类?一种想法就是使样本落在一个数据集里,这个数据集的类标签应该尽量是一致的。那么如何使具有不同类标签的数据集划分成一个个具有相同类标签的数据集呢?我们知道,熵表示数据的混乱程度,考虑以类标签来计算熵:如果子数据集的类标签越一致,则熵越小,反之越大。所以,可以考虑将数据集熵作为划分数据集的判决依据。那么,使用什么来划分数据集呢?我们知道样本往往有很多特征,而且,类标签相同的样本往往特征取值相同,所以,可以考虑使用样本具有的特征作为划分数据集的依据。
//(3)所需基本知识:香农熵、树结构、字典、迭代
from math import log import operator #---------------------香侬熵计算-------------------# def calcShannonEnt(dataSet): numEntries = len(dataSet) labelCounts = {} for featVec in dataSet: #取行 currentLabel = featVec[-1] #数据集的最后一列为标签 if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0 #如果标签字典里没有相应的标签“键”,则创建这一个键,并在程序下一步 #相应的“值”+1 labelCounts[currentLabel] += 1 #有相应标签“键”,则“统计值”+1 shannonEnt = 0.0 for key in labelCounts: #香农熵计算公式 prob = float(labelCounts[key])/numEntries shannonEnt -= prob * log(prob,2) return shannonEnt #-----------------创建数据集------------------- def creatDataSet(): dataSet = [[1,1,'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']] labels = ['no ssurfacing','flippers'] # 特征属性,单独一列 return dataSet,labels #----------------划分数据集--------------- def splitDataSet(dataSet,axis,value): # 数据集,axis意思就是划分的特征,value就是特征的取值 retDataSet = [] for featVec in dataSet: #取行 if featVec[axis] == value: reducedFeatVec = featVec[:axis] #取不到选取的axis这一列 reducedFeatVec.extend(featVec[axis+1:]) retDataSet.append(reducedFeatVec) return retDataSet #得到按照某个特征划分数据集之后的子数据集集合 #-----------------选择最佳划分数据集特征(最大熵减原则)------------ def chooseBestFeatureToSplit(dataSet): numFeatures = len(dataSet[0]) - 1 #数据集的最后一列为标签列,所以减1 baseEntropy = calcShannonEnt(dataSet) #计算原始数据集的熵 bestInfoGain = 0.0 bestFeature = -1 for i in range(numFeatures): #大循环:特征 小循环:特征取值 featList = [example[i] for example in dataSet] #“for example in dataSet”取每一行(样本),“example[i] ” #对某一行1,取第i个特征的值,得到的featList就是该特征每个样本的值 uniqueVals = set(featList) #对featList去重 newEntropy = 0.0 for value in uniqueVals: subDataSet = splitDataSet(dataSet,i,value) # 对每个特征都按照该特征的不同取值划分数据集 prob = len(subDataSet)/float(len(dataSet)) #ID3算法计算划分数据集之后的熵 newEntropy += prob * calcShannonEnt(subDataSet) infoGain = baseEntropy - newEntropy if (infoGain > bestInfoGain): #如果熵减值>0, 则选择该特征作为当前最优特征 bestInfoGain = infoGain bestFeature = i return bestFeature #返回值是最优特征对应的序号,不是特征标签 #-----------考虑特征都用完子数据集标签仍旧不统一,按标签数量值最大原则选择类标签------------ 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] #返回键--值最大对应的特征 #----------------迭代法构建决策树-------------------- 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) #特征-特征值(键-值) 如果子数据集满足上面两个if条件,则返回类别值, # 若不满足其一,则返回特征标签树结构 # classList样本类别的向量, labels是表示特征的向量 return myTree