前言

在上篇文章中介绍了决策树的一些基本概念,在这篇文章中,将介绍决策树ID3和C4.5算法的代码实现以及一些优化。

ID3实现

ID3算法的核心是在决策树各个节点上应用信息增益准则选择特征,递归地构建决策树,算法伪代码在上篇文章中给出了。在这里将给出其代码实现。

代码

import pandas as pd
from math import log

def load(filename):
    '''
        input: 文件
        output: DataFrame数据
    '''
    df = pd.read_csv(filename)
    return df

def calEnt(df):
    '''
        input: 数据集
        output: 熵
        descripion: 计算给定数据集的熵
    '''
    # 返回数据行数
    numEntries = df.shape[0]
    # 字典,用于保存每个标签(label)出现次数
    labelCounts = {}
    cols = df.columns.tolist()
    # 获取标签
    classlabel = df[cols[-1]].tolist()
    # 对每组特征向量进行统计
    for currentlabel in classlabel:
        if currentlabel not in labelCounts.keys():
            labelCounts[currentlabel] = 1
        else:
            labelCounts[currentlabel] += 1

    Ent = 0.0
    # 计算熵
    for key in labelCounts:
        prob = labelCounts[key]/numEntries
        Ent -= prob*log(prob, 2)

    return Ent

def splitDatsSet(df, axis, value):
    '''
        input: 数据集,所占列,选择值
        output: 划分数据集
        description: 按照给定特征划分数据集;选择所占列中等于选择值的项
    '''
    cols = df.columns.tolist()
    axisFeat = df[axis].tolist()
    # L = []
    # L.append(axis)

    # retDataset = df[list(set(cols)-set(L))]
    retDataset = pd.concat([df[feaVec] for feaVec in cols if feaVec != axis], axis=1)

    i = 0
    # 需要丢弃的行
    dropIndex = []
    for feaVec in axisFeat:
        if feaVec != value:
            dropIndex.append(i)
            i += 1
        else:
            i += 1
    # 划分数据集
    newDataset = retDataset.drop(dropIndex)
    return newDataset.reset_index(drop=True)


def chooseBestFeatureToSplit(df):
    '''
        input: 数据集
        output: 最优特征和信息增益
    '''
    # 获取特征数量
    numFeatures = len(df.columns) - 1
    # 计算熵
    Ent = calEnt(df)
    # 信息增益
    bestInfoGain = 0.0
    # 最优特征索引值
    bestFeature = -1
    cols = df.columns.tolist()
    # 遍历所有特征
    for i in range(numFeatures):
        # 获取第i个特征的所有不同的取值
        equalVals = set(df[cols[i]].tolist())
        # 经验条件熵
        newEnt = 0.0
        # 计算信息增益
        for value in equalVals:
            # 获取划分后的子集
            subDataset = splitDatsSet(df, cols[i], value)
            # 计算子集概率
            prob = subDataset.shape[0] / df.shape[0]
            # 根据公式计算条件熵
            newEnt += prob * calEnt(subDataset)
        infoGain = Ent - newEnt
        print(cols[i], infoGain)
        # 更新信息增益,找到最大的信息增益
        if infoGain > bestInfoGain:
            bestInfoGain = infoGain
            bestFeature = cols[i]
    return bestFeature, bestInfoGain



def majorityCnt(classList):
    '''
        input: 类别列表
        output: 子节点的分类
        description: 数据集已经处理了所有属性,但是类标签依然不是唯一的,
          采用多数判决的方法决定该子节点的分类
    '''
    classCount = {}
    # 统计classList中每个元素出现的次数
    for clas in classList:
        if clas not in classCount.keys():
            classCount[clas] = 0
        classCount[clas] += 1
    # 根据字典值降序排列
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reversed=True)
    return sortedClassCount[0][0]

def createTree(df, dropcol):
    '''
        input: 数据集和需删除特征
        output: 决策树
        description: 递归实现决策树构建
    '''
    # cols = df.columns.tolist()[:-1]
    # 取分类标签
    classList = df[df.columns.tolist()[-1]].tolist()

    # 若数据集中所有实例属于同一类,将Ck作为该节点的类标记
    if classList.count(classList[0]) == len(classList):
        return classList[0]

    # 若特征集为空集,将数据集中实例数最大的类Ck作为该节点的类标记
    if len(df[0:1]) == 0:
        return majorityCnt(classList)

    # 获取最优特征与信息增益
    bestFeature, bestInfoGain = chooseBestFeatureToSplit(df)

    print('特征集和类别:',df.columns.tolist())
    print('bestFeature:',bestFeature)
    # 根据最优特征的标签生成树
    myTree = {bestFeature:{}}
    # 得到最优特征的属性值
    featValues = df[bestFeature]
    # 去掉重复属性值
    uniqueVals = set(featValues)
    # 遍历创建决策树
    for value in uniqueVals:
        myTree[bestFeature][value] = createTree(splitDatsSet(df, bestFeature, value), bestFeature)
    return myTree

def main():
    filename = "data.csv"
    dataset = load(filename)
    dropCol = []
    myTree = createTree(dataset, dropCol)
    print(myTree)

if __name__ == '__main__':
    main()

输入

输入选取统计学习方法中给出的数据集

age,work,hourse,loan,class
青年,否,否,一般,否
青年,否,否,好,否
青年,是,否,好,是
青年,是,是,一般,是
青年,否,否,一般,否
中年,否,否,一般,否
中年,否,否,好,否
中年,是,是,好,是
中年,否,是,非常好,是
中年,否,是,非常好,是
老年,否,是,非常好,是
老年,否,是,好,是
老年,是,否,好,是
老年,是,否,非常好,是
老年,否,否,一般,否

输出

age 0.08300749985576883
work 0.32365019815155627
hourse 0.4199730940219749
loan 0.36298956253708536
特征集和类别: ['age', 'work', 'hourse', 'loan', 'class']
bestFeature: hourse
age 0.2516291673878229
work 0.9182958340544896
loan 0.47385138961004514
特征集和类别: ['age', 'work', 'loan', 'class']
bestFeature: work
{'hourse': {'是': '是', '否': {'work': {'是': '是', '否': '否'}}}}

ID3算法的缺点

  1. ID3算法对于缺失值没有进行考虑
  2. 没有考虑过拟合的情况
  3. ID3没有考虑连续特征
  4. 上篇文章中有提到,ID3以信息增益作为划分训练数据集的特征,存在偏向于选择取值较多的特征的问题。
  5. ID3采用信息增益大的特征优先建立决策树的节点。很快就被人发现,在相同条件下,取值比较多的特征比取值少的特征信息增益大。

C4.5实现

C4.5就是在上面提出ID3的缺点中的第4条进行的改正,大部分代码都相同,除了在划分数据集的时候以信息增益比为划分标准,这在上篇文章中也提到过。

def chooseBestFeatureToSplit(df):
    '''
        input: 数据集
        output: 最优特征和信息增益
    '''
    # 获取特征数量
    numFeatures = len(df.columns) - 1
    # 计算熵
    Ent = calEnt(df)
    # 信息增益
    bestInfoGain = 0.0
    splitInfo = 0.0
    # 最优特征索引值
    bestFeature = -1
    cols = df.columns.tolist()
    # 遍历所有特征
    for i in range(numFeatures):
        # 获取第i个特征的所有不同的取值
        equalVals = set(df[cols[i]].tolist())
        # 经验条件熵
        newEnt = 0.0
        # 计算信息增益
        for value in equalVals:
            # 获取划分后的子集
            subDataset = splitDatsSet(df, cols[i], value)
            # 计算子集概率
            prob = subDataset.shape[0] / df.shape[0]
            # 根据公式计算条件熵
            newEnt += prob * calEnt(subDataset)
            # 计算特征熵
            splitInfo += -prob * log(prob, 2)
        infoGain = Ent - newEnt
        print(cols[i], infoGain)
        # 计算信息增益比
        infoGainRatio = infoGain / splitInfo
        # 更新信息增益比,找到最大的信息增益比
        if infoGainRatio > bestInfoGain:
            bestInfoGain = infoGainRatio
            bestFeature = cols[i]
    return bestFeature, infoGainRatio

结语

这篇文章给出了ID3与C4.5的代码实现,同时也提到了其中的一些缺点,对于缺点2中提到的过拟合现象,我们可以通过剪枝来简化模型,从而降低这一现象的产生。对于剪枝的实现及介绍,将在后面介绍CART算法时再讲述。

参考:

  1. 《统计学习方法》——李航
  2. 《机器学习实战》
  3. 鱼佬的文章 https://zhuanlan.zhihu.com/p/30352406
  4. https://blog.csdn.net/jiaoyangwm/article/details/79525237

版权声明:本文为csu-lmw原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/csu-lmw/p/10575897.html