机器学习方法原理及编程实现--02.决策树(实现MNIST数据分类)

文章列表
1.机器学习方法原理及编程实现–01.K近邻法(实现MNIST数据分类).
2.机器学习方法原理及编程实现–02.决策树(实现MNIST数据分类).
3.机器学习方法原理及编程实现–03.朴素贝叶斯分类器(实现MNIST数据分类) .
4.机器学习方法原理及编程实现–01. .
5.机器学习方法原理及编程实现–01..
6.机器学习方法原理及编程实现–01..

2.1 决策树的理解

决策树可用3种表征方式来理解,具体如下:

2.1.1 树形结构

分类决策树是一种描述根据实例特征对实例进行分类的树形结构,示意如下图所示,树形结构由结点及有向边构成,其中结点还可分为内部结点和叶节点,内部结点表示特征,页结点表示类,整个结构的起点称为跟结点。
这里写图片描述

2.1.2 If-then规则

由决策树的跟结点到每个叶节点的路径都可以表示成一个规则,路径上每个内部结点都对应着规则的条件,而每个叶节点对应着规则的结论。每个实例都可以找到对应且唯一的路径或规则,这是if-then规则的重要性质:互斥且完备。

2.1.3 条件概率分布

决策树所表示的条件概率分布由各个单元给定条件下类的条件概率分布组成。实际中,哪个类别有较高的条件概率,就把该单元中的实例强行划分为该类别。

2.2 决策树的学习

决策树学习的本质是从训练数据集中归纳出一组分类规则,使其与训练数据矛盾较小并且具有较好的泛化能力。决策树学习的损失函数常采用正则化的极大似然函数,学习策略为损失函数最小化,但从所有决策树模型中选择最优决策树是一个NP完全问题,常采用启发式方法近似求解这个问题,这样求解到的决策树是次最优的。
决策树学习的方法通常是一个递归地选择最优特征,并根据该特征对训练数据进行分割,从而使得对子数据集有一个最好的分类的过程。利用最优特征划分数据集的函数createBranch伪代码如下:

检查数据集dataSet中的所有标签是否属于同一分类:
    If so return 类标签;
    Else
        寻找划分数据集dataSet的最优特征
        划分数据集
        创建分支节点
            For 每个划分的子集
                调用函数createBranch并增加返回结果到分支节点中
    Return 分支节点

可以看出递归函数createBranch中的一个关键步骤是寻找最优特征,那么如何进行特征选择呢?

2.2.1 特征选择

要选取有分类能力的特征才可以提高决策效率。如果一个特征的分类效果与随机分类的效果没有太大区别,那这个特征是没有分类能力的。划分数据的大原则是将无序的数据变得有序,分类特征常用的选取准则是信息增益、信息增益比及基尼系数。

2.2.1.1 信息增益

首先引入熵和条件熵的概念,熵是对信息不确定性的度量,在分类问题中,对于数据集D,取其样本容量为|D|,并且数据集D一共有K个分类Ck,|Ck|为第k个分类的样本个数,那么数据集的经验熵H(D)为:
这里写图片描述

2.2.1.2 信息增益比

直接用信息增益作为特征选取准则会导致总是选取增益较大的特征,从而偏向于取特征取值数量较多的特征。这是因为信息熵的意义是数据集D被特征A划分后不确定性的减少量,直观上可以理解,特征取值越多,数据集被划分的块数越多,不确定性就会减少许多
可使用信息增益比解决这一问题,特征A对训练数据D的信息增益比gR(D,A)定义为其信息增益g(D,A)与训练数据集D关于特征A的熵HA(D)之比:
这里写图片描述

2.2.1.3 基尼系数

这里写图片描述

2.2.2 决策树生成

具体的决策树生成方法可分为ID3、C4.5和CART这3个方法。后面会介绍,ID3算法用信息增益作为特征选取准则,C4.5算法用信息增益比作为特征选取准则。这3种算法功能依次增大:
1. ID3算法可用于离散型数据分类
2. C4.5算法可进一步对混合型数据进行分类
3. CART算法可解决分类及回归问题。

2.2.2.1 ID3算法

ID3方法全称Interactive Dichotomizer-3,中文名为交互式二分法,虽然名字里有二分,但其同样适用于多了分类,它以信息增益为特征选取准则,针对离散型数据进行划分。其实现伪代码如下:

创建决策树的函数createTree(dataSet, dataSet)
    If 数据集dataSet中的所有标签是否属于同一分类 return 类标签;
    elif (特征集合Aset)==0 return 出现次数最多的类标签;
    Else
        寻找划分数据集dataSet的最优特征
            bestA = -1, maxEA = -1e5
            For 特征集合Aset中的每个特征A【比如特征A为颜色,取值有{红,黄,蓝,…}】 
                If 数据集dataSet对特征A的信息增益> maxEA
                    bestA = A, maxEA =数据集dataSet对特征A的信息增益
            从特征集合dataSet中删除特征bestA,得到newdataSet
        创建分支节点tree={bestA:{}}
        划分数据集
            For 每个划分的子集
                调用函数createBranch并增加返回结果到分支节点中
            For 特征bestA中的每个取值feature
                从数据集dataSet中划分出feature对应的数据集subDataSet
                tree[bestA][feature]= createTree(subDataSet, newdataSet)
Return 分支节点tree

2.2.2.2 C4.5算法

C4.5算法采用信息增益比作为特征选取准则,避免了ID3算法中总是倾向于选择特征取值数量较多的特征。其算法流程与ID3算法类似,只需要将ID3算法流程的第3步中的信息增益改为信息增益比即可。

2.2.2.3 CART

CART假设决策树是二叉树,内部结点特征的取值是“是”与“否”,左边取值是“是”分支,右边取值是“否”分支,决策树等价于递归地二分每个特征,对回归问题采用平方误差最小化准则,对分类问题用基尼指数最小化准则进行特征选取,从而生成二叉树。

2.3代码实现

2.3.1用ID3实现MNIST数据分类

由于计算特别慢,我用10000个训练集训练决策树,再用10000个测试数据进行测试,准确率为83.7%
这里写图片描述

import sys;
import numpy as np
from math import log
sys.path.append("./../Basic/")
#from LoadData import *
import LoadData as ld
import treePlotter as tp

def createDataSet(iTrainNum,iTestNum):
    temp = np.array(ld.getImageDataSet('./../MNISTDat/train-images-idx3-ubyte', iTrainNum, 1, 784))
    temp[temp <= 100] = 0
    temp[temp > 100] = 1
    trainDataSet   = temp.tolist()
    trainLabels    = np.array(ld.getLabelDataSet('./../MNISTDat/train-labels-idx1-ubyte', iTrainNum).tolist())
    [trainDataSet[i].extend(str(trainLabels[i])) for i in range(len(trainDataSet))]
    trainLabelSet  = [str(i) for i in range(784)]
    temp = np.array(ld.getImageDataSet('./../MNISTDat/t10k-images-idx3-ubyte', iTestNum, 1, 784))
    temp[temp <= 100] = 0
    temp[temp > 100] = 1
    testDataSet    = temp.tolist()
    testLabels     = np.array(ld.getLabelDataSet('./../MNISTDat/t10k-labels-idx1-ubyte', iTestNum))
    [testDataSet[i].extend(str(testLabels[i])) for i in range(len(testLabels))]
    return trainDataSet, trainLabelSet, testDataSet

def calcShannonEnt(dataSet, index):
    numEntries = len(dataSet)
    labelCounts = {}
    for featVec in dataSet: #the the number of unique elements and their occurance
        currentLabel = featVec[index]
        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) #log base 2
    return shannonEnt

def splitDataSet(dataSet, axis, value):
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]     #chop out axis used for splitting
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

def chooseBestFeatureToSplit(dataSet, Feature):
#    print(np.array(dataSet)[:,-1])
    featureNum = len(dataSet[0]) - 1      #the last column is used for the labels
#    print(featureNum)
    baseEntropy = calcShannonEnt(dataSet,-1)
    bestInfoGain = 0.0; bestFeature = -1
    for i in range(featureNum):        #iterate over all the features
        featList = [example[i] for example in dataSet]#create a list of all the examples of this feature
        uniqueVals = set(featList)       #get a set of unique values
        newEntropy = 0.0
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)
            prob = len(subDataSet)/float(len(dataSet))
            newEntropy += prob * calcShannonEnt(subDataSet,-1)     
        if (Feature == 'InformationGain'):
            infoGain = baseEntropy - newEntropy     #calculate the info gain; ie reduction in entropy
        if (Feature == 'InformationGainRatio'):
            temp = calcShannonEnt(dataSet,i)
            if (temp<1e-7): continue
            infoGain = (baseEntropy - newEntropy)/temp     #calculate the info gain; ie reduction in entropy
        if (infoGain > bestInfoGain):       #compare this to the best gain so far
            bestInfoGain = infoGain         #if better than current best, set to best
            bestFeature = i
    return bestFeature                      #returns an integer

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,originlabels,feature):
#    print('depth')
    labels = originlabels[:]
    classList = [example[-1] for example in dataSet]
#    if len(set(classList))==1: 
    if classList.count(classList[0]) == len(classList):  # 类别一致
        return classList[0]                              # 直接返回叶节点
    if len(dataSet[0]) == 1:                             # 已经遍历完所有特征
        return majorityCnt(classList)                    # 返回出现次数最多的特征
    bestFeat = chooseBestFeatureToSplit(dataSet,feature)
    bestFeatLabel = labels[bestFeat]
    myTree = {bestFeatLabel:{}}
    del(labels[bestFeat])                                # 删除当前选定的最好特征
#    print(len(labels))
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)                         # 最好的特征对应的特征值归一化
    for value in uniqueVals:
        subLabels = labels[:]       #copy all of labels, so trees don't mess up existing labels
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels,feature)
    return myTree                            

def classify(inputTree,featLabels,testVec):
#    firstStr = inputTree.keys()[0]
    firstStr = list(inputTree.keys())[0]
    secondDict = inputTree[firstStr]
    featIndex = featLabels.index(firstStr)
    key = testVec[featIndex]
#    print('secondDict%s' %(secondDict)) 
#    print('key%s' %(key)) 
    valueOfFeat = secondDict[key]
    if isinstance(valueOfFeat, dict): 
        classLabel = classify(valueOfFeat, featLabels, testVec)
    else: classLabel = valueOfFeat
    return classLabel

def storeTree(inputTree,filename):
    import pickle
    fw = open(filename,'w')
    pickle.dump(inputTree,fw)
    fw.close()

def grabTree(filename):
    import pickle
    fr = open(filename)
    return pickle.load(fr)

if __name__ == '__main__':
    iTrainNum      = 60000
    iTestNum       = 10000
    iTrainNum      = 20000
    iTestNum       = 10000
    trainDataSet, trainLabelSet, testDataSet = createDataSet(iTrainNum, iTestNum)
    myTree = createTree(trainDataSet, trainLabelSet, 'InformationGain')
#    myTree = createTree(trainDataSet, trainLabelSet, 'InformationGainRatio')
    print('%s' %(myTree))
    iErrNum = 0
    for ind in range(iTestNum):
#        print('decisionTreePredict:%d __ label:%d' %(int(classify(myTree, trainLabelSet,testDataSet[ind][:-1])), int(testDataSet[ind][-1])))
        if(int(classify(myTree, trainLabelSet,testDataSet[ind][:-1])) != int(testDataSet[ind][-1])):
            iErrNum += 1
    print('errorRatio:%0.2f%%' %(100*iErrNum/float(iTestNum)))    
#    tp.createPlot(myTree)

文件路径:https://pan.baidu.com/s/1OUB90duLVdRwyS_ZOF4myQ

猜你喜欢

转载自blog.csdn.net/drilistbox/article/details/79836369
今日推荐