决策树学习笔记

来源:互联网 发布:单例模式 java代码 编辑:程序博客网 时间:2024/06/03 17:24
决策树是什么?
决策树是一种基本的分类与回归方法。

用决策树进行分类的概念:
从根节点开始,对实例的某一特征进行测试,根据测试结果将实例分配到其子节点;这是,每一个子节点对应着该特征的一个取值。如此递归地对实例进行测试并分配,直至达到叶节点。最后将实例分配到叶节点的类中。

决策树由节点和有向边组成,上一级节点通过一条有向边指向下一级节点,上一级节点表示实例的一个特征,有向边表示对应于该特征的取值。决策树中的节点可以分为两种类型:内部节点与叶节点。内部节点对应于实例的一个特征,叶节点对应于实例的类。

决策树学习
决策树学习的目标是根据给定的训练数据集构建一个决策树模型,使它能够对实例进行正确的分类。

决策树学习本质上是从训练数据集中归纳出一组分类规则。我们需要训练出一个与训练矛盾数据较小的决策树。决策树学习的策略是以损失函数为目标函数的最小化问题。

决策树学习的算法通常是递归的选择最优特征,对根据该特征对训练数据进行分割。这一过程对应着特征空间的划分以及决策树的生成。(过拟合)生成的决策树一般对训练数据有很好的分类能力,但对未知的测试数据未必有很好的分类能力。因此需要对生成的决策树自下而上进行剪枝,使其具有更好的泛化能力。


决策树的生成:
选择一个最优特征,按照这一特征将训练数据集分割成子集,使得子集有一个在当前条件下最好的分类。如果这些子集已经能够被基本正确分类,那么构建叶节点,并将这些子集分到所对应的叶节点中去;如果还有子集不能被正确分类,那么就对这些子集选择新的最优特征,继续对其进行分割,构建相应的节点。如此递归地进行下去,直至所有训练数据子集被基本正确分类,或者没有合适的特征为止。最后么讴歌子集都被分到叶节点上,即都有了明确的类。这就生成了一颗决策树。

决策树生成流程图:

Machine Learning in Action 中的生成决策树python实现代码分析:
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:    #若数据集特征空间当中不包含任何特征(特征空间长度为1)        return majorityCnt(classList)  #停止划分,返回数据集中出现次数最多的类作为当前节点的类标记    # choose the best feature to split first    bestFeat = chooseBestFeatureToSplit(dataSet)  #选择信息增益最大的特征    bestFeatLabel = labels[bestFeat]     #获取特征标签    myTree = {bestFeatLabel:{}}          ###用该特征标记当前节点并且产生分支,#对决策树的不断完善    del(labels[bestFeat])                #在特征标签中删去所选取的特征    featValues = [example[bestFeat] for example in dataSet]   #获取所选特征所有可能的特征值    uniqueValues = set(featValues)              #对特征值列表取集合    for value in uniqueValues:        subLabels = labels[:]    #复制当前的所有特征标签        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)#递归调用生成树的函数,在当前节点处返回子树(以划分后的数据集以及reduce后的标签集作为输入)    return myTree


这段代码使用字典的嵌套来模拟决策树。内部节点以字典中的键作为有向边指向该键所对应的值(下一级节点作为根节点的子树);若还未通过划分确定训练样本的类型则说明下一级节点还不是叶节点,则将该值继续定义为字典:该字典的键表示有向边,值为子树的根结点(或叶节点)。
字典与决策树有如下对应关系:
字典  ----  决策树 
键(奇数级)   ----  内部节点   (第一级的键代表根节点,为特征类别;第一级只有一个键值对)
键(偶数级)   ----  有向边       (第二级的键代表有向边,为特征值)
值(字典)      ----  子树 
值(字符串)  ----   叶节点    
 
递归思想的体现:这段代码return一颗生成完整的决策树。完整的决策树是在不断的递归调用函数的过程中通过
myTree = {bestFeatLabel:{}}这一操作不断对决策树进行完善,在递归调用中通过myTree[bestFeatLabel][value]
定位子树的位置。
每次迭代所进行的操作是对当前的数据集进行判断,然后给出类标记或者选择最优的特征进行对数据集进行划分。

生成决策树过程中所调用的相关函数
1)创建数据集: dataSet记录所有训练样本的特征向量最后一维表示类别标签;labels为特征标记。

def createDataSet():    dataSet = [[1, 1, 'yes'],               [1, 1, 'yes'],               [1, 0, 'no'],               [0, 1, 'no'],               [0, 1, 'no']]    labels = ['no surfacing','flippers']    #change to discrete values    return dataSet, labels

2)计算数据集的香农熵:
熵:表示随机变量不确定性的度量。
from math import logdef calcShannonEnt(dataSet):    numEntrices = len(dataSet)    labelCounts = {}  #创建标签字典    for featVec in dataSet:        currentLabel = featVec[-1]   #获取当前类别标签,为当前特征空间的最后一列        if currentLabel not in labelCounts.keys(): #若当前类别不在字典的键当中            labelCounts[currentLabel] = 0          #将当前类别创建为新的键,其对应的值(表示类别出现次数)置零        labelCounts[currentLabel] += 1    #若当前类别不在字典的键当中,其对应的值(表示类别出现次数)加1    shannonEnt = 0.0     #初始化香浓熵,初值置为0    for key in labelCounts:        prob = float(labelCounts[key])/numEntrices        shannonEnt -= prob*log(prob,2)    return shannonEnt

3)按照给定特征划分数据集
def splitDataSet(dataSet, axis, value):  #输入分别为初始数据集,特征在向量中的位置,指定特征值    retDataSet = []   #初始化划分后的数据集    for featVec in dataSet:        if featVec[axis] == value:    #若当前样本对应特征值与指定特征值相同            reducedFeatVec = featVec[:axis]    #得到去除对应特征后的特征向量            reducedFeatVec.extend(featVec[axis+1:])               retDataSet.append(reducedFeatVec)  #将所得到的特征向量加入    return retDataSet



4)选择最好的数据集划分方式
     对训练数据集(或其子集),计算其每个特征的信息增益,并比较它们的大小,选择信息增益最大的特征。
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] #记录每一实例对应特征i的特征值在featList列表中        uniqueVals = set(featList)          #对特征值列表取集合        newEntropy = 0                      #初始化划分后的香农熵        #split dataSet by the feature        for value in uniqueVals:     #对每一特征值            subDataSet = splitDataSet(dataSet, i, uniqueVals )  #将数据集进行划分            prob = len(subDataSet)/float(len(dataSet))                     newEntropy += prob * calcShannonEnt(subDataSet)     #计算得到划分后的香农熵        infoGain = baseEntropy - newEntropy #计算对当前特征进行划分的信息增益        if (infoGain > bestInfoGain):            bestInfoGain = infoGain            bestFeature = i    return bestFeature

5)获取出现次数最多的分类
def majorityCnt(classList):    classCount={}    for vote in classList:        if vote not in classCount.keys():            classCount[vote] = 0        classCount[vote] += 1    iterList = []    for eachClass in classCount.items():        iterList.append(eachClass)    sortedClassCount = sorted(iterList, key=operator.itemgetter(1), reverse=True)    return sortedClassCount[0][0]


使用决策树进行分类:
def classify(inputTree,featLabels,testVec): #输入为决策树,特征标签集,以及测试样例的特征    firstStr = list(inputTree.keys())[0]    #当前节点,为字典的首个键    secondDict = inputTree[firstStr]        #下一级目录,当前节点在字典中的键所对应的值    featIndex = featLabels.index(firstStr)  #获得标记当前节点的特征在特征向量中的位置    for key in secondDict.keys():           #对所有下一级目录        if testVec[featIndex] == key:       #若测试样例相应特征标签与当前迭代节点的标签相同            if type(secondDict[key]).__name__ == 'dict':  #若当前的下一集目录类型为字典(未到达叶节点)                classLabel = classify(secondDict[key],featLabels,testVec)#递归调用,输入为原决策树的子树             else:                classLabel = secondDict[key]  #若已到达叶节点,则判定测试样例类别为此叶节点标记的类别    return classLabel

递归思想的体现:从跟节点开始,首先将字典第一级的键(字典第一级只存在一个键值对)记作 firstStr,将此键所对应的值记为secondDict。secondDict为字典类型。此字典的所有键(secondDict.keys())为一组对应于firstStr特征的特征值。
secondDict中的键所对应的值若为字典类型(子树)。找出与测试样例的特征值对应的键,将此键所对应的值(子树)作为新的决策树继续对测试样例进行分类。递归调用后会将此子树的键记为firstStr(字典中第三级的键)将此键所对应的值记为secondDict。
递归终止条件:当secondDict中与测试样例的特征值对应的键所指向的值不是字典类型,则分类完成,终止迭代;所获得的类别标签会逐级返回至最外层的函数。




原创粉丝点击