机器学习实战-简单决策树编写

#!/user/bin/env python
# !-*-coding:utf-8 -*-
# !Time :2018/9/28 4:12 PM
# !Author : hyCong
# !@File  : .py
from math import log
import operator
import treePlotter

# 计算熵
def calcShannonEnt(dataSet):
    numEntries = len(dataSet)  # 数据集的长度
    labelCounts = {}  # 用来存放不同这一特征的不同类型的样例个数
    for featVec in dataSet:
        currentLabel = featVec[-1]  # 获取当前样例的类别
        if currentLabel not in labelCounts.keys():  # 判断当前扫到的类别在labelCounts字典中是否存在,如果存在则出现次数加以,否则初始化为1
            labelCounts[currentLabel] = 1
        else:
            labelCounts[currentLabel] += 1
    shannonEnt = 0.0  # 初始化香浓⤴️为0
    for key in labelCounts:
        prob = float(labelCounts[key]) / numEntries  # 计算当前key类别的样例个数/总个数,即Pk
        shannonEnt -= prob * log(prob, 2)  # 使用熵的计算公式计算熵
    return shannonEnt


# 判断简单鱼类的数据创建函数
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 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


# ID3(迭代二分器)中的划分属性选择方法:从候选的属性中选出Gain最大的属性进行划分
def chooseBestFeatureToSplitInID3(dataSet):
    numFeatures = len(dataSet[0]) - 1  # 待选属性的个数
    baseEntropy = calcShannonEnt(dataSet)  # Ent(D) 数据集的信息熵
    bestInfoGain = 0.0;
    bestFeatur = -1  # 初始化最大信息增益和相对应的属性下标
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]  # 获取第i个属性的对应列
        uniqueVals = set(featList)  # 获取属性值集合
        newEntropy = 0.0  # 属性信息熵
        for value in uniqueVals:  # 针对第i种属性的每一种属性值,进行循环,计算其对应的信息熵
            subDataSet = splitDataSet(dataSet, i, value)  # 抽取出i属性的value属性值得数据集
            prob = len(subDataSet) / float(len(dataSet))  # 计算Pk(v)
            newEntropy += prob * calcShannonEnt(subDataSet)  # Gain
        infoGain = baseEntropy - newEntropy  # i属性的信息增益
        if infoGain > bestInfoGain:  # 取出信息增益最大的属性
            bestInfoGain = infoGain
            bestFeatur = i
    return bestFeatur


# C4.5算法中的划分属性选择方法:使用启发式,先从候选划分属性中找出信息增益高于平均水平的那些属性,再从中选出Gain_ratio信息增益率最大的属性
def chooseBestFeatureToSplitInC45(dataSet):
    numFeatures = len(dataSet[0]) - 1  # 待选属性的个数
    baseEntropy = calcShannonEnt(dataSet)  # Ent(D) 数据集的信息熵
    bestGainRatio = 0.0  # 最高信息增益率
    gain_ratios = []  # 各属性的信息增益率
    gain_ratios_high = []  # 比平均信息增益大的属性的信息增益率
    feats_high = []  # 比平均信息增益大的属性下标
    sumEntropy = 0.0  # 总信息增益
    gainSet = []  # 各属性的信息增益
    bestFeatur = -1  # 初始化最大信息增益和相对应的属性下标
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]  # 获取第i个属性的对应列
        uniqueVals = set(featList)  # 获取属性值集合
        newEntropy = 0.0  # 属性信息熵
        IV = 0.0
        for value in uniqueVals:  # 针对第i种属性的每一种属性值,进行循环,计算其对应的信息熵
            subDataSet = splitDataSet(dataSet, i, value)  # 抽取出i属性的value属性值得数据集
            prob = len(subDataSet) / float(len(dataSet))  # 计算Pk(v)
            newEntropy += prob * calcShannonEnt(subDataSet)  # Gain
            IV -= prob * log(prob, 2)  # 属性i的固有值
        infoGain = baseEntropy - newEntropy  # i属性的信息增益
        gain_ratios.append(infoGain / IV)  # 记录下属性i的信息增益率
        sumEntropy += infoGain  # 计算总信息增益
        gainSet.append(infoGain)  # 将各属性的信息增益放入列表用于计算平均值
    avgEntropy = sumEntropy / numFeatures  # 信息增益平均值
    for i in range(len(gainSet)):  # 循环找出大于平均信息增益的属性
        if gainSet[i] > avgEntropy:
            gain_ratios_high.append(gain_ratios[i])
            feats_high.append(i)
    for i in range(len(gain_ratios_high)):  # 找出增益率最大的属性
        if gain_ratios_high[i] > bestGainRatio:
            bestGainRatio = gain_ratios_high[i]
            bestFeatur = feats_high[i]

    return bestFeatur


# CART决策树算法中划分属性的选择:选择属性的基尼系数最小的进行划分
def chooseBestFeatureToSplitInCART(dataSet):
    return 0


# 当候选属性已经划分完毕后,发现数据集仍然存在多个类别,则采用多数表决的方法进行决定分类
def majorityCnt(classList):
    classCount = {}  # 类别计数
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote] = 1
        else:
            classCount[vote] += 1
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)  # 将统计好的各类别的样本个数进行降序排序
    return sortedClassCount[0][0]  # 返回出现次数最多的类别


# 决策树创建代码
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 = chooseBestFeatureToSplitInID3(dataSet)  # 选取剩下的属性中最优的属性
    bestFeatLabel = labels[bestFeat]  # 最优属性的名称
    myTree = {bestFeatLabel: {}}  # 新建用来存放树节点的字典
    del (labels[bestFeat])  # 删除当前已选择了的属性名称
    featValues = [example[bestFeat] for example in dataSet]  # 循环将数据集中bestFeature的所有属性值取出
    uniqueVals = set(featValues)  # 将获取的属性值进行去重
    for vallue in uniqueVals:
        subLabels = labels[:]  # python在函数调用时,是引用传值,而labels是列表类型,实质上是一个指针,若直接传入,可能会导致操作错误。
        # 进入下一次迭代的入口,在进入子树进行创建时,首先需要将数据集进行划分,抽取出所有符合当前选择的属性的属性值的数据,将其传入下一次创建过程。
        myTree[bestFeatLabel][vallue] = createTree(splitDataSet(dataSet, bestFeat, vallue), subLabels)
    return myTree


classLabel = ''


# 针对测试向量,判断其类别
def classify(inputTree, featLabels, testVec):
    global classLabel
    firstStr = 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


# 使用pickle模块存储决策树
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)
``

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值