对于《机器学习实战》中决策树的ID3算法详细说明

from math import log
import operator


def createDataSet():
    dataSet = [[1, 1, 'yes'],
               [1, 1, 'yes'],
               [1, 0, 'no'],
               [0, 1, 'no'],
               [0, 1, 'no']]
    labels = ['no surfacing', 'flippers']  # 特征值
    return dataSet, labels


def calShannonEnt(dataSet):  # 计算给定数据集的香农熵
    numEntries = len(dataSet)  # 计算数据集中实例的总数,为计算每个类标签的概率做准备
    labelCounts = {}  # 统计每个类标签出现的次数
    for featVec in dataSet:  # 遍历每一个样本
        currentLabel = featVec[-1]  # 获得当前样本的分类
        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)  # 熵的计算子公式,对其求和就可以得到熵
    return shannonEnt  # 返回熵


def splitDataSet(dataSet, axis, value):  # 按照给定特征划分数据集
    """
    :param dataSet: 待划分的数据集
    :param axis: 划分数据集的特征
    :param value: 指定的特征的值
    :return: 返回划分好了的数据集
    测试例子见文件夹下的jupyter notebook
    """
    retDataSet = []  # Python中函数参数传递的是列表的引用,所以需要重新声明新的列表对象,防止修改原始的数据集
    for featVec in dataSet:  # 遍历数据集中每一个元素
        if featVec[axis] == value:  # 如果该样本特征值与指定特征的值相符
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis + 1:])  # 上一行与这一行将当前样本除axis位以外的特征值抽取出来放入reducedFeatVec中
            retDataSet.append(reducedFeatVec)  # retDataSet存放这些样本列表(每个样本都是一个列表)
    return retDataSet


def chooseBestFeatureToSplit(dataSet):  # 选择最好的数据集划分方式
    """
    1、dataSet必须是一个由列表元素组成的列表 且所有的列表元素都要具有相同的数据长度
    2、dataSet的每个列表元素(样本)的最后一列必须是当前实例的类别标签
    :param dataSet: 要划分的数据集
    :return: 最佳特征的下标
    """
    numFeatures = len(dataSet[0]) - 1  # numFeatures存放了当前数据集包含了多少个特征属性,特征数量 = 样本长度 - 类标签(1)
    baseEntropy = calShannonEnt(dataSet)  # 计算整个样本集的原始香农熵
    bestInfoGain = 0.0
    bestFeature = -1
    for i in range(numFeatures):  # 遍历数据集中的所有特征
        featList = [example[i] for example in dataSet]  # 按顺序将每个样本的第i个特征值取出并生成列表
        uniqueVals = set(featList)  # 去除重复的特征值,从列表中创建集合是Python语言中得到列表中唯一元素值的最快方法
        newEntropy = 0.0  # 新的香农熵初始化
        for value in uniqueVals:  # 遍历当前特征中所有的特征值
            subDataSet = splitDataSet(dataSet, i, value)  # 按当前特征值进行样本集的划分
            prob = len(subDataSet) / float(len(dataSet))  # 求出取到当前特征值的样本占总样本的比例,也就是在总样本下取到该特征值的概率
            newEntropy += prob * calShannonEnt(subDataSet)  # 条件熵的计算子公式,对其所有特征值求和可得到给定特征A的条件下样本集的条件熵
        infoGain = baseEntropy - newEntropy  # newEntropy也就是给定特征A的条件下样本集的条件概率分布的熵对特征A的数学期望,信息增益 = 样本集的原始香农熵 - 当前特征的条件熵
        if (infoGain > bestInfoGain):  # 某特征的信息增益越大,说明该特征具有对样本集更强的分类能力
            bestInfoGain = infoGain
            bestFeature = i  # 记录最佳特征的下标
    return bestFeature


def majorityCnt(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 createTree(dataSet, labels):  # 创建树的函数代码
    """
    :param dataSet: 样本集
    :param labels: 特征列表
    :return: 创建好的决策树
    """
    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)  # 选择最好的划分特征,bestFeat为特征的下标
    bestFeatLabel = labels[bestFeat]  # 按照下标取出最好的划分特征
    myTree = {bestFeatLabel: {}}  # 使用字典类型存储树的信息
    del (labels[bestFeat])  # 将当前已经划分过的最好特征从特征列表中删除
    featValues = [example[bestFeat] for example in dataSet]  # 获取当前样本集中每个样本的最好划分特征的特征值并生成列表
    uniqueVals = set(featValues)  # 唯一化最好划分特征的特征值
    for value in uniqueVals:  # 遍历最好划分特征的特征值
        subLabels = labels[:]  # 复制已删除最好划分特征的labels列表,作为递归的参数,保证了每次调用createTree()时不改变原始列表的内容
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),
                                                  subLabels)  # 按最好划分特征划分样本集并递归构造子树
    return myTree


def classify(inputTree, featLabels, testVec):  # 使用决策树的分类函数
    firstStr = list(inputTree)[0]  # 获取第一层划分的特征
    secondDict = inputTree[firstStr]  # 获取第二层
    featIndex = featLabels.index(firstStr)  # 找寻特征列表中该特征的下标
    key = testVec[featIndex]  # 获取测试向量中该特征对应的特征值
    valueOfFeat = secondDict[key]  # 获取该特征值下的节点值
    if isinstance(valueOfFeat, dict):  # 节点值是否为字典,也就是是否为子树
        classLabel = classify(valueOfFeat, featLabels, testVec)  # 仍存在,则递归进行决策,将最终的类标签保存在classLabel中
    else:
        classLabel = valueOfFeat  # 不存在,则类标签就是当前节点的值
    return classLabel  # 返回类标签

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

def grabTree(filename):
    import pickle
    fr = open(filename, 'rb')
    return pickle.load(fr)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值