机器学习(四):ID3决策树(基础篇)

在这里插入图片描述

相关的决策树文章:

  1. 数据是怎么分裂的

  2. 如何选择分类的属性

  3. 什么时候停止分裂

    ok,在了解了决策树的思想,首先当然要掌握一下决策树应该学习的基础知识了。

决策树伪代码


   输入:训练集 D=${(x_1,y_1),(x_2,y_2),...,(x_m,y_m)};$  
         属性集 A=${a_1,a_2,...,a_d}
   过程:函数TreeGenerate(D,A)
   生成结点 node;
   if D 中样本全属于同一个类别C  then
      将node 标记为C类的叶节点;return
   end if
   if A = null  or D中样本在A的取值相同 then
      将node 标记为叶结点,其类别标记为D中样本最多的类;return
   end if
   从A中选择最优的划分属性a*;
   for a*的每一个值a*v   do
      为node生成一个分支,令Dv表示D在a*上取值为a*v的样本子集;
      if Dv为空 then
         将分支点标记为叶节点,其类别标记为D中样本最多的类;return;
      else
         以 TreeGenerate(Dv, A、{a*}}为分支节点
      end if
   end for
 输出:以node为节点的决策树

问题一:什么时候停止分裂?

  • 当前节点包含的样本全属于同一个类别,无需划分
  • 当前属性集为空,或是所有样本在所有属性上取值相同,无法划分
  • 当前节点包含的样本集合为空,不能划分

问题二:如何划分?

这个是决策树的重点,也是最难点。一般来说,随着划分过程不断进行,我们希望决策树的分支节点所包含的样本尽可能属于同一个类别。即纯度越来越高。这时候就要引出信息熵这个概念了。

预备知识

1.信息熵

(1)信息熵:用来度量一个属性的信息量
假定S为训练集,s的目标属性c具有m个可能的类标号值,c={ c 1 c_1 c1, c 2 c_2 c2, c 3 c_3 c3,…, c m c_m cm},假定训练集s中, c i c_i ci在所有的样本中出现的频率为(i=1,2,3,…,m),则该训练集s所包含的信息熵定义为:

E n t r o y ( s ) = E n t r o y ( p 1 , p 2 , . . . , p m ) = − ∑ i = 1 m p i l o g 2 p i Entroy(s) = Entroy(p_1,p_2,...,p_m)=-\displaystyle\sum_{i=1}^{m}p_ilog_2p_i Entroy(s)=Entroy(p1,p2,...,pm)=i=1mpilog2pi

熵越小表示样本对目标属性的分布越纯,反之熵越大表示样本对目标属性分布越混乱。

2.信息增益

(2)信息增益
信息增益是划分前样本数据集的不纯程度(熵)和划分后样本数据集的不纯程度(熵)的差值

  • 假设划分前样本数据集为S,并用属性A来划分样本集S,则按属性A划分S的信息增益Gain(A)为样本集s的熵减去按属性A划分s后的样本子集的熵

G a i n ( S , A ) = E n t r o p y ( S ) − E n t r o p y A ( S ) Gain(S,A) = Entropy(S) - Entropy_A(S) Gain(S,A)=Entropy(S)EntropyA(S)

按属性A划分S后的样本子集的熵定义如下:假定属性A有k个不同的取值,从而将s划分为k个样本子集{ s 1 , s 2 , . . . , s k s_1,s_2,...,s_k s1,s2,...,sk},则按属性A划分s后的样本子集信息熵为:

E n t r o y A ( s ) = E n t r o y ( p 1 , p 2 , . . . , p m ) = − ∑ i = 1 k ∣ S i ∣ ∣ S ∣ E n t r o p y ( S i ) Entroy_A(s) = Entroy(p_1,p_2,...,p_m)=-\displaystyle\sum_{i=1}^{k}\frac{|S_i|}{|S|}Entropy(S_i) EntroyA(s)=Entroy(p1,p2,...,pm)=i=1kSSiEntropy(Si)

其中| S i S_i Si|(i=1,2,…,k)为样本子集 S i S_i Si中包含的样本数,|S|为样本集S中包含的样本数。 ∣ S i ∣ ∣ S ∣ \frac{|S_i|}{|S|} SSi赋予了权重。信息熵越大,说明使用属性A划分后的样本子集越纯,越有利于分类。

所谓的ID3决策树算法的核心,对各个节点上的信息增益进行计算,选择信息增益最大的特征作为节点。具体做法为:

  • 从根节点开始,对节点计算所有可能特征的信息增益,选择信息增益最大的特征作为节点
  • 对子节点递归地调用以上方法,构建决策树
  • 直到所有特征地信息增益均很小或者没有特征可以选择。

ok,关于ID3决策的原理就是这些了。我们来想一下,信息增益越大,是不是代表着决策后的结果越纯,是不是我们更应该选择信息增益大的。那么如何划分和如何选择属性的问题解决了。接下来就是根据思路进行编写代码

代码

# -*- coding: utf-8 -*-
"""
决策树ID3的实现
"""

from math import log
import operator
import numpy as np
import plotTrees


# 输入:训练集
# D =${(x_1, y_1), (x_2, y_2), ..., (x_m, y_m)};$
# 属性集
# A =${a_1, a_2, ..., a_d}
# 过程:函数TreeGenerate(DA)
# 生成结点
# node;
# if D 中样本全属于同一个类别C  then
# 将node
# 标记为C类的叶节点;return
# end if
# if A = null or D中样本在A的取值相同 then
# 将node
# 标记为叶结点,其类别标记为D中样本最多的类;return
# end if
# 从A中选择最优的划分属性a *;
# for a * 的每一个值a * v   do
#     为node生成一个分支,令Dv表示D在a * 上取值为a * v的样本子集;
#     if Dv为空 then
#     将分支点标记为叶节点,其类别标记为D中样本最多的类;return;
#     else
#     以
#     TreeGenerate(Dv, A{a *}}为分支节点
#     end if
#     end
#     for
#     输出:以node为节点的决策树

def majorityCnt(classList):
    """
    找到最大频繁向量(多数表决器)
    :param classList:训练集
    :return:最大值
    """
    classCounts = {}
    for value in classList:   #遍历训练集中的每一个变量
        if (value not in classCounts.keys()):  #如果变量不在列表中
            classCounts[value] = 0    #新建一个字典的键
        classCounts[value] += 1    #数量加一
    sortedClassCount = sorted(classCounts.items(), key=operator.itemgetter(1), reverse=True)  #排序
    return sortedClassCount[0][0]   #输出第一个,即最大值

def splitDataSet(dataSet, axis, value):
    """
    以靠指定列的指定值来划分数据集,比如划分西瓜瓜皮形状为椭圆的数据集
    :param axis: 索引列,即形状
    :param value: 索引列的特定值,即椭圆
    :return:
    """
    retDataSet = []
    for festdataVal in dataSet:
        if festdataVal[axis] == value:
            reducedFeatVal = festdataVal[:axis]    #这两行去掉索引列
            reducedFeatVal.extend(festdataVal[axis+1:])
            retDataSet.append(reducedFeatVal)
    return retDataSet

def calcShannonEnt(dataSet):
    """
    计算香农熵
    :param dataSet:
    :return:
    """
    numEntries = len(dataSet)   #获得数据集的长度
    labelCounts = {}     #计算标签的字典
    for featDataVal in dataSet:
        currentLabels = featDataVal[-1]   #取最后一个标签值
        if currentLabels not in labelCounts.keys():  #判断有没有在标签里面
            labelCounts[currentLabels] = 0
        labelCounts[currentLabels] += 1
    shannonEnt = 0.0
    for key in labelCounts.keys():   #key有几个遍历几次
        prob = labelCounts[key]/float(numEntries)  #计算频率
        shannonEnt -= prob*log(prob, 2)
    return shannonEnt

def chooseBestFeatureToSplit(dataSet):
    """
    选择信息增益最大的特征值
    :param dataSet:数据集
    :return:
    """
    numFeatures = len(dataSet[0]) - 1 #看数据集而定,数据中如果最后一行为标签,则删去
    baseEntropy = callable(dataSet)   #计算所有数据集的香农熵
    bestFeaturesindex = 0    #最佳特征的索引
    bestEntropy = 0.0     #最佳信息熵

    for i in range(numFeatures):   #有几个特征值循环几次
        featList = []    #特征值列表
        for example in dataSet:   #获得这个列的值
            featList.append(example[i])
        uniqueVals = set(featList)  #相同的数据并没有意义,去重
        newEntropy = 0.0   #新信息熵
        for value in uniqueVals:   #得到该列的特征值
            subDataSet = splitDataSet(dataSet, i, value)  #划分数据集
            prob = len(subDataSet) / float(len(dataSet))  #权重或者条件概率
            newEntropy += prob * calcShannonEnt(subDataSet)    #计算信息增益后面的条件经验熵
        infoGain = baseEntropy - newEntropy     #计算信息增益
        if infoGain > bestEntropy:   #更改最大经验熵
            baseEntropy = infoGain
            bestFeaturesindex = i
    return bestFeaturesindex    #输出最大经验熵的索引

def createTree(dataSet, label):
    """
    创建树
    :param dataSet:
    :param label:
    :return:
    """
    classList = [] #获得每一个标签
    for classVal in dataSet:
        classList.append(classVal[-1])
    if classList.count(classList[0]) == len(classList): #如果全部标签都相同
        return classList[0]  #返回该标签
    if len(dataSet[0]) == 1:  #如果一列只有一个特征
        return majorityCnt(classList)
    #获取最优的索引值
    bestFeatureIndex = chooseBestFeatureToSplit(dataSet)
    #获取最优索引值的名称
    bestFeatureLabel = label[bestFeatureIndex]
    mytree = {bestFeatureLabel:{}}  #创建根节点
    del(label[bestFeatureIndex])   #删去用过的最优节点
    bestFeature = []   #最优的特征
    for example in dataSet:
        bestFeature.append(example[bestFeatureIndex])
    uniquesVal = set(bestFeature)  #最优特征的种类
    for val in uniquesVal:
        subLabel = label[:]  #创建个子标签
        mytree[bestFeatureLabel][val] = createTree(splitDataSet(dataSet, bestFeatureIndex, val), subLabel)  #递归
    return mytree

def classify(inputTree, featLable, testVec):
    """
    获取分类的结果(算法的使用器)
    :param inputTree:决策树字典
    :param featLable: 标签列表
    :param testVec: 测试向量
    :return:
    """
    #获取根节点的名称,并且把根节点变成列表
    firstSide = list(inputTree.keys())
    #根节点名称string类型
    firstStr = firstSide[0]
    #获得根节点对应得子节点
    secondDict = inputTree[firstStr]
    #获得子节点在标签列表得索引
    featIndex = featLable.index(firstStr)
    #获得测试向量的值
    key = testVec[featIndex]
    #获取树干向量后的变量
    valueOfFeat = secondDict[key]
    #判断是子结点还是叶子节点:子结点就回调分类函数,叶子结点就是分类结果
    if isinstance(valueOfFeat, dict):
        classLabel = classify(valueOfFeat, featLable, testVec)
    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)

import pandas as pd
def loadDataSet(filename):
    """
    函数说明:从文件中下载数据,并将分离除连续型变量和标签变量
    :parameter:
            data - Iris数据集
            attributes - 鸢尾花的属性
            type - 鸢尾花的类别
            sl-花萼长度 , sw-花萼宽度, pl-花瓣长度, pw-花瓣宽度
    :return:
    """
    iris_data = pd.read_csv(filename)   #打开文件
    iris_data = pd.DataFrame(data=np.array(iris_data), columns=['sl', 'sw', 'pl', 'pw', 'type'], index=range(149))   #给数据集添加列名,方便后面的操作
    dataSet = iris_data.values.tolist()
    return dataSet

if __name__ == "__main__":
    # 因为数据集寻找比较复杂,所以采用自己随机建立数组
    dataSet = [[1, 1, 3, 4, 'yes'],
               [0, 1, 3, 3, 'yes'],
               [1, 0, 4, 3, 'no'],
               [0, 1, 2, 4, 'no'],
               [0, 0, 3, 3, 'no']]
    labels = ['no surfacing', 'flippers', 'table', 'type']
    #复制
    label1 = labels.copy()
    # #创建树
    # dataSet = loadDataSet('iris.data')
    # labels = ['sl', 'sw', 'pl', 'pw']
    # label1 = labels.copy()
    mytree = createTree(dataSet, label1)
    print(mytree)
    #使用树的操作
    # a = creatTree.classify(mytree, labels, [0,1, 3, 4])
    # print(a)
    plotTrees.createPlot(mytree)



决策树的源代码大多就这些,接下来我们看看代码运行的结果:
在这里插入图片描述
我们来就看一看树的结构:一开始的节点是no surfacing 如果有为1,则跳到了flippers。依次类推,决策树是由多重字典组成。相信在这边比较难看出来,在之后我会推出决策树的绘图方式。
再来看看结果:当我们数据为【0, 1, 3, 4】的时候,输出了yes,验证一下,并没有错误。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值