西瓜书 课后习题4.4 基尼指数 未剪枝 预剪枝 后剪枝

import operator
import csv
import numpy as np


def readDataset(filename):
    '''
    读取数据
    :param filename: 数据文件名,CSV格式
    :return:  以列表形式返回数据列表和特征列表
    '''
    with open(filename) as f:
        reader = csv.reader(f)
        header_row = next(reader)
        labels = header_row[1:7]
        dataset = []
        for line in reader:
            tempVect = line[1:]
            dataset.append(tempVect)

    trainIndex = [1, 2, 3, 6, 7, 10, 14, 15, 16, 17]
    trainDataset = []
    testDataset = []
    for i in range(1, 18):
        if (i in trainIndex):
            trainDataset.append(dataset[i - 1])
        else:
            testDataset.append(dataset[i - 1])
    trainDataset.append(dataset[3])  # 为保持和书中结果相同,训练集中增加第四条数据
    return dataset, labels, trainDataset, testDataset


def Gini(dataset):
    '''
    计算gini基尼值
    :param dataset:  输入数据集
    :return:  返回基尼值gini
    '''
    numdata = len(dataset)
    labels = {}
    for featVec in dataset:
        label = featVec[-1]
        if label not in labels.keys():
            labels[label] = 0
        labels[label] += 1
    gini = 1
    for lab in labels.keys():
        prop = float(labels[lab]) / numdata
        gini -= prop ** 2
    return gini


def splitDataset(dataset, axis, value):
    '''
    对某个特征进行划分后的数据集
    :param dataset: 数据集
    :param axis: 划分属性的下标
    :param value: 划分属性值
    :return: 返回剩余数据集
    '''
    restDataset = []
    for featVec in dataset:
        if featVec[axis] == value:
            restFeatVec = featVec[:axis]
            restFeatVec.extend(featVec[axis + 1:])
            restDataset.append(restFeatVec)
    return restDataset


def bestFeatureSplit(dataset):
    '''
    最优属性划分
    :param dataset: 输入需要划分的数据集
    :return:  返回最优划分属性的下标
    '''
    numFeature = len(dataset[0]) - 1
    bestGiniIndex = 10000
    bestFeature = -1
    for i in range(numFeature):
        featList = [example[i] for example in dataset]
        uniqueValue = set(featList)
        giniIndex = 0
        for value in uniqueValue:
            subDataset = splitDataset(dataset, i, value)
            prop = len(subDataset) / float(len(dataset))
            giniIndex += prop * Gini(subDataset)
        if (giniIndex < bestGiniIndex):
            bestGiniIndex = giniIndex
            bestFeature = i
    return bestFeature


def majorClass(classList):
    '''
    对叶节点的分类结果进行划分,投票原则
    :param classList:  叶节点上的样本数量
    :return: 返回叶节点划分结果
    '''
    classCount = {}
    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)  # 返回数组
    return sortedClassCount[0][0]


def decideTreePredict(decideTree, testData, labelsFull):
    '''
    决策树对测试数据进行结果预测
    :param decideTree: 决策树模型
    :param testData: 测试数据
    :param labelsFull: 特征列表
    :return: 返回预测结果
    '''
    firstFeat = list(decideTree.keys())[0]
    secDict = decideTree[firstFeat]
    featIndex = labelsFull.index(firstFeat)
    classLabel = None
    for value in secDict.keys():
        if testData[featIndex] == value:
            if type(secDict[value]).__name__ == 'dict':
                classLabel = decideTreePredict(secDict[value], testData, labelsFull)
            else:
                classLabel = secDict[value]
    return classLabel


def prevReduceBranch(bestFeatLabel, trainDataset, testDataset, labelsFull):
    classList = [example[-1] for example in trainDataset]
    bestFeatIndex = labelsFull.index(bestFeatLabel)
    trainDataValues = [example[bestFeatIndex] for example in trainDataset]
    uniqueValues = set(trainDataValues)
    error = 0
    for value in uniqueValues:
        partClassList = [classList[i] for i in range(len(classList)) if trainDataValues[i] == value]
        major = majorClass(partClassList)
        for data in testDataset:
            if data[bestFeatIndex] == value and data[-1] != major:
                error += 1
    # print('预剪枝继续展开错误数:' + str(error))
    return error


def majorTest(major, testData):
    error = 0
    for i in range(len(testData)):
        if major != testData[i][-1]:
            error += 1
    # print('当前节点为结节点错误数: ' + str(error))
    return error


def postReduceBranch(subTree, testData, labelsFull):
    error = 0
    for i in range(len(testData)):
        if decideTreePredict(subTree, testData[i], labelsFull) != testData[i][-1]:
            error += 1
    # print('后剪枝保留子树错误数: ' + str(error))
    return error


def createTree(trainDataset, labels, datasetFull, labelsFull, testDataset):
    '''
    递归创建决策树
    :param dataset: 数据集列表
    :param labels:  标签集列表
    :param datasetFull: 数据集列表,再传一次
    :param labelsFull:  标签集列表,再传一次
    :param testData:  测试数据集列表
    :return: 返回决策树字典
    '''
    classList = [example[-1] for example in trainDataset]
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    if len(dataset[0]) == 1:
        return (majorClass(classList))
    bestFeat = bestFeatureSplit(trainDataset)
    bestFeatLabel = labels[bestFeat]

    # 预剪枝
    # if prevReduceBranch(bestFeatLabel, trainDataset, testDataset, labelsFull) < majorTest(
    #         majorClass(classList),
    #         testDataset):
    #     myTree = {bestFeatLabel: {}}
    # else:
    #     return majorClass(classList)
    myTree = {bestFeatLabel: {}}

    del (labels[bestFeat])
    featValues = [example[bestFeat] for example in trainDataset]
    uniqueVal = set(featValues)
    # 创建所有属性标签的所有值,以防漏掉某些取值,例如西瓜数据集2.0中的  色泽:浅白
    bestFeatIndex = labelsFull.index(bestFeatLabel)
    featValuesFull = [example[bestFeatIndex] for example in datasetFull]
    uniqueValFull = set(featValuesFull)
    if uniqueVal == uniqueValFull:
        for value in uniqueVal:
            subLabels = labels[:]  # 递归回退过程需要继续使用标签,所以前行过程标签副本
            myTree[bestFeatLabel][value] = createTree(splitDataset(trainDataset,             
            bestFeat, value),subLabels, datasetFull,labelsFull,splitDataset(testDataset, 
            bestFeat, value))
    else:
        for value in uniqueVal:
            subLabels = labels[:]  # 递归回退过程需要继续使用标签,所以前行过程标签副本
            myTree[bestFeatLabel][value] = createTree(splitDataset(trainDataset, 
            bestFeat, value),subLabels, datasetFull,labelsFull,splitDataset(testDataset, 
            bestFeat, value))
            uniqueValFull.remove(value)
        for value in uniqueValFull:
            myTree[bestFeatLabel][value] = majorClass(classList)

    return myTree
    # 后剪枝
    # print(myTree)
    # if postReduceBranch(myTree, testDataset, labelsFull) <= 
          majorTest(majorClass(classList), testDataset):
    #     return myTree
    # else:
    #     return majorClass(classList)


if __name__ == '__main__':
    filename = 'C:\\Users\\14399\\Desktop\\西瓜2.0.csv'
    dataset, labels, trainDataset, testDataset = readDataset(filename)
    datasetFull = trainDataset[:]
    labelsFull = labels[:]
    myTree = createTree(trainDataset, labels, datasetFull, labelsFull, testDataset)
    print(myTree)
未剪枝:{'脐部': {'凹陷': {'色泽': {'浅白': '否', '青绿': '是', '乌黑': '是'}}, '稍凹': {'根蒂': {'蜷缩': '否', '稍蜷': {'色泽': {'青绿': '是', '乌黑': {'纹理': {'稍糊': '是', '清晰': '否', '模糊': '是'}}, '浅白': '是'}}, '硬挺': '是'}}, '平坦': '否'}}

预剪枝:{'脐部': {'稍凹': '是', '平坦': '否', '凹陷': '是'}}

后剪枝: {'脐部': {'稍凹': {'根蒂': {'蜷缩': '否', '稍蜷': {'色泽': {'乌黑': '是', '青绿': '是', '浅白': '是'}}, '硬挺': '是'}}, '凹陷': '是', '平坦': '否'}}

西瓜2.0数据集:链接:https://pan.baidu.com/s/12aVngexje2RdizgOg1Fr0A   提取码:uywy 

参考:https://blog.csdn.net/sysu_cis/article/details/51874229

           https://blog.csdn.net/wzmsltw/article/details/51057311   

猜你喜欢

转载自blog.csdn.net/weixin_41056428/article/details/84073374
今日推荐