机器学习--决策树理解

来源:互联网 发布:php ajax提交form表单 编辑:程序博客网 时间:2024/05/21 17:41

怕什么真理无穷,进一寸有一寸的欢喜。

工具:

python3.x
anaconda
代码均在Spyder
测试均在IPython

简单思路:

第一步:做出选择,第二步:生成结果,第三步:实例化

栗子

圣诞节快到了,你想要约心仪对象看电影,待挑选的影片种类有:喜剧、恐怖、文艺、科幻,你想挑选个她喜欢的电影类型,那么该如何挑选呢?

根据如下数据集
你可以根据她的爱好,性格,喜欢电影的国家,和当天的天气,投其所好!

这里写图片描述

由决策树算法,根据得到的结果(如下),可以合理的挑选影片

这里写图片描述

开始有趣的决策树:

具体思路:

一、如何决策?

1.1 如何决策?

通过划分数据集,实现决策的目的

也就是说,对例子中数据集进行分析,最终达到可以根据女孩儿的爱好,性格,喜欢电影的国家,和圣诞节当天的天气,知道女孩儿喜欢的电影类型

1.2 如何划分数据集?

通过给定的特征来划分数据集

例如本例子中,给定的特征即:爱好,性格,喜欢电影的国家,和天气

1.3 哪些特征才是最好的选择?

获得信息增益最高的特征就是最好的选择

最好的选择即树的根节点

1.4 信息增益怎么理解,如何计算?

信息增益是 划分数据集前后 信息发生的变化

信息增益越大说明该选择越好,也就是说最具有决策性

2 代码来了

2.1 计算信息熵

#计算香农熵def calcShannonEnt(dataSet):#dataset:数据集    numEntries = len(dataSet)#返回数据集行数    labelCounts = {}#类型:字典;功能:返回每个标签label出现的次数    for featVec in dataSet:         currentLabel = featVec[-1]#提取最后一列,即标签label        if currentLabel not in labelCounts.keys():  #如果标签(Label)没有放入统计次数的字典,添加进去             labelCounts[currentLabel] = 0#初始化        labelCounts[currentLabel] += 1#对标签label计数    shannonEnt = 0.0#香农熵,初始化    #计算香农熵    for key in labelCounts:        prob = float(labelCounts[key])/numEntries#选择该标签的概率        shannonEnt -= prob * log(prob,2) #根据公式计算    return shannonEnt#返回香农熵

香浓熵公式:

这里写图片描述

对字典键值的理解:

对字典键值keys的理解

#导入数据fr =open('test.txt')dataset=[inst.strip().split('\t') for inst in fr.readlines()]#转化为列表#性格 爱好  地区  天气  类别labels=['性格','爱好','地区','天气']#标签

测试:

import trees#导入源文件cal=trees.calcShannonEnt(dataset)#调用函数cal#输出香农熵结果Out[8]: 1.7822287189138017

2.2 根据特征划分数据集

#根据特征划分数据集#dataset:数据集,axis:划分数据集的特征(也就是列),value: 需要返回的特征的值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

测试:

import treesdatasetOut[11]: [['内向', '看书', '中国', '晴', '文艺'], ['外向', '跑步', '美国', '阴', '科幻'], ['内向', '看书', '日本', '阴', '文艺'], ['内向', '看书', '日本', '阴', '文艺'], ['内向', '跑步', '中国', '晴', '恐怖'], ['外向', '跑步', '中国', '阴', '喜剧'], ['外向', '看书', '中国', '晴', '文艺'], ['内向', '看书', '日本', '晴', '文艺'], ['外向', '看书', '中国', '阴', '喜剧'], ['外向', '跑步', '美国', '阴', '科幻'], ['外向', '看书', '中国', '晴', '文艺'], ['外向', '跑步', '美国', '晴', '喜剧'], ['外向', '看书', '中国', '晴', '文艺'], ['外向', '跑步', '中国', '阴', '喜剧'], ['外向', '看书', '中国', '晴', '文艺'], ['内向', '跑步', '美国', '阴', '喜剧'], ['外向', '看书', '中国', '晴', '文艺'], ['内向', '跑步', '美国', '阴', '喜剧'], ['内向', '跑步', '日本', '阴', '恐怖'], ['外向', '跑步', '美国', '阴', '科幻']]trees.splitDataSet(dataset,0,'内向')#选取:第0列 值为‘内向’的列表#由输出可以看出,用于选择的特征列被抽取Out[12]: [['看书', '中国', '晴', '文艺'], ['看书', '日本', '阴', '文艺'], ['看书', '日本', '阴', '文艺'], ['跑步', '中国', '晴', '恐怖'], ['看书', '日本', '晴', '文艺'], ['跑步', '美国', '阴', '喜剧'], ['跑步', '美国', '阴', '喜剧'], ['跑步', '日本', '阴', '恐怖']]trees.splitDataSet(dataset,2,'中国')Out[13]: [['内向', '看书', '晴', '文艺'], ['内向', '跑步', '晴', '恐怖'], ['外向', '跑步', '阴', '喜剧'], ['外向', '看书', '晴', '文艺'], ['外向', '看书', '阴', '喜剧'], ['外向', '看书', '晴', '文艺'], ['外向', '看书', '晴', '文艺'], ['外向', '跑步', '阴', '喜剧'], ['外向', '看书', '晴', '文艺'], ['外向', '看书', '晴', '文艺']]

2.3 找到最佳划分特征

def chooseBestFeatureToSplit(dataSet):    numFeatures = len(dataSet[0]) - 1      #特征数量,-1:减去标签label列    #print(numFeatures)  #测试:输出为4    baseEntropy = calcShannonEnt(dataSet)  #计算数据集的香农熵    bestInfoGain = 0.0    #信息增益,初始化    bestFeature = -1      #最优特征的索引值,初始化(可以不等于-1,特征列从0开始,所以等于-1)    for i in range(numFeatures):                        #遍历所有的特征        featList = [example[i] for example in dataSet]  #获取数据集dataSet的第i个所有特征        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                      #返回最佳信息增益特征的索引值

测试:
分别采用非数据集及数据集形式
非数据集:

test=[[1, 1, 0, 1, 1],[1, 1, 0, 1, 1],[1, 1, 0, 1, 1]]for i in range(2):    featList = [example[i] for example in test]featListOut[36]: [1, 1, 1]uniqueVals = set(featList)uniqueValsOut[38]: {1}

数据集:

在原函数中增加了两个输出

for i in range(numFeatures):                featList = [example[i] for example in dataSet]        print(featList)        uniqueVals = set(featList)              print(uniqueVals)        newEntropy = 0.0

查看结果

import treestrees.chooseBestFeatureToSplit(dataset)['内向', '外向', '内向', '内向', '内向', '外向', '外向', '内向', '外向', '外向', '外向', '外向', '外向', '外向', '外向', '内向', '外向', '内向', '内向', '外向']{'内向', '外向'}['看书', '跑步', '看书', '看书', '跑步', '跑步', '看书', '看书', '看书', '跑步', '看书', '跑步', '看书', '跑步', '看书', '跑步', '看书', '跑步', '跑步', '跑步']{'跑步', '看书'}['中国', '美国', '日本', '日本', '中国', '中国', '中国', '日本', '中国', '美国', '中国', '美国', '中国', '中国', '中国', '美国', '中国', '美国', '日本', '美国']{'日本', '中国', '美国'}['晴', '阴', '阴', '阴', '晴', '阴', '晴', '晴', '阴', '阴', '晴', '晴', '晴', '阴', '晴', '阴', '晴', '阴', '阴', '阴']{'晴', '阴'}Out[46]: 1
Out[46]: 1

说明:第1个特征是最好的用于划分数据集的特征,即‘爱好’列,通过最后的树结构以及可视化结果,也可以看出‘爱好’列为最佳化分列

二、怎么生成树?

2.1 怎么生成树?

通过递归构建决策树

#找出叶子节点def majorityCnt(classList):    classCount = {}    #统计classList中每个元素出现的次数    for vote in classList:            if vote not in classCount.keys():        classCount[vote] = 0           classCount[vote] += 1      sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)                     #用sorted函数对字典的值降序(reverse=True)排序    return sortedClassCount[0][0]   #返回classList中出现次数最多的元素#创建决策树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]                      #最优特征的标签    featLabels.append(bestFeatLabel)    myTree = {bestFeatLabel:{}}                           #根据最优特征的标签生成树    del(labels[bestFeat])                                 #删除已经使用特征标签    featValues = [example[bestFeat] for example in dataSet]#得到训练集中所有最优特征的属性值    uniqueVals = set(featValues)                           #属性值去重    for value in uniqueVals:                               #遍历特征,创建决策树。                               myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)    return myTree

测试:

import treestrees.createTree(dataset,labels)Out[49]: {'爱好': {'看书': {'天气': {'晴': '文艺', '阴': {'性格': {'内向': '文艺', '外向': '喜剧'}}}},  '跑步': {'地区': {'中国': {'性格': {'内向': '恐怖', '外向': '喜剧'}},    '日本': '恐怖',    '美国': {'性格': {'内向': '喜剧', '外向': {'天气': {'晴': '喜剧', '阴': '科幻'}}}}}}}}

三、实例化

import matplotlib.pyplot as pltdecisionNode = dict(boxstyle="sawtooth", fc="0.8")leafNode = dict(boxstyle="round4", fc="0.8")arrow_args = dict(arrowstyle="<-")def getNumLeafs(myTree):    numLeafs = 0    firstStr = list(myTree.keys())[0]    secondDict = myTree[firstStr]    for key in secondDict.keys():        if type(secondDict[key]).__name__=='dict':#test to see if the nodes are dictonaires, if not they are leaf nodes            numLeafs += getNumLeafs(secondDict[key])        else:   numLeafs +=1    return numLeafsdef getTreeDepth(myTree):    maxDepth = 0    firstStr = list(myTree.keys())[0]    secondDict = myTree[firstStr]    for key in secondDict.keys():        if type(secondDict[key]).__name__=='dict':#test to see if the nodes are dictonaires, if not they are leaf nodes            thisDepth = 1 + getTreeDepth(secondDict[key])        else:   thisDepth = 1        if thisDepth > maxDepth: maxDepth = thisDepth    return maxDepthdef plotNode(nodeTxt, centerPt, parentPt, nodeType):    createPlot.ax1.annotate(nodeTxt, xy=parentPt,  xycoords='axes fraction',             xytext=centerPt, textcoords='axes fraction',             va="center", ha="center", bbox=nodeType, arrowprops=arrow_args )def plotMidText(cntrPt, parentPt, txtString):    xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]    yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]    createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)def plotTree(myTree, parentPt, nodeTxt):#if the first key tells you what feat was split on    numLeafs = getNumLeafs(myTree)  #this determines the x width of this tree    depth = getTreeDepth(myTree)    firstStr = list(myTree.keys())[0]     #the text label for this node should be this    cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff)    plotMidText(cntrPt, parentPt, nodeTxt)    plotNode(firstStr, cntrPt, parentPt, decisionNode)    secondDict = myTree[firstStr]    plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD    for key in secondDict.keys():        if type(secondDict[key]).__name__=='dict':#test to see if the nodes are dictonaires, if not they are leaf nodes               plotTree(secondDict[key],cntrPt,str(key))        #recursion        else:   #it's a leaf node print the leaf node            plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW            plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)            plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))    plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD#if you do get a dictonary you know it's a tree, and the first element will be another dictdef createPlot(inTree):    fig = plt.figure(1, facecolor='white')    fig.clf()    axprops = dict(xticks=[], yticks=[])    createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)        plotTree.totalW = float(getNumLeafs(inTree))    plotTree.totalD = float(getTreeDepth(inTree))    plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0;    plotTree(inTree, (0.5,1.0), '')    plt.savefig('movie.png')    plt.show()

这里写图片描述

四、 参考

以上均参考《机器学习实战》