一、决策树之ID3算法简述

  1976年-1986年,J.R.Quinlan给出ID3算法原型并进行了总结,确定了决策树学习的理论。这可以看做是决策树算法的起点。1993,Quinlan将ID3算法改进成C4.5算法,称为机器学习的十大算法之一。ID3算法的另一个分支是CART(Classification adn Regression Tree, 分类回归决策树),用于预测。这样,决策树理论完全覆盖了机器学习中的分类和回归两个领域。

  本文只做了ID3算法的回顾,所选数据的字段全部是有序多分类的分类变量。C4.5和CART有时间另花篇幅进行学习总结。本文需要有一定的pandas基础、了解递归函数。

  1、ID3算法研究的核心思想是if-then,本质上是对数据进行分组操作。

  下表是包含用户信息和购买决策的表。这张表已经对1024个样本进行了分组统计。依此为例解释if-then(决策)和数据分组。

  对于第0条和第7条数据,唯一的区别是income不同,于是可以认为,此时income不具有参考价值,而应考察student值或reputation的信息。于是:if-then定义了一套规则,用于确定各个分类字段包含的信息计算方法,以及确定优先按照哪个字段进行分类决策。

  假如根据if-then,确定优先按照age对数据集进行拆分,那么可以确定三个水平(青年、中年、老年)对应的子数据集。然后,继续对着三个子数据集分别再按照剩余的字段进行拆分。如此循环直到除了购买决策之外的所有字段都被遍历。你会发现,对于每个拆分的子数据集,根本不需要关注里面的值是汉字、字符串或数字,只需要关注有几个类别即可。

  根据if-then的分类结果,对于一个样本,可以根据其各个字段的值和if-then规则来确定它最终属于下表哪个组。

  决策树强调分组字段具有顺序性,认为字段层级关系是层层递进的;而我们直接看这张表时,所有字段都是并排展开的,存在于同一层级。这或许是最大的区别。

  当然,你也可以拿一个样本,对照此表,找到它属于的那个组,以及对应的purchase。如果purchase有不同值,根据count计算其概率即可。

  count age income student reputation purchase
0 64 青年 不买
1 64 青年 不买
2 128 中年
3 60 老年
4 64 老年
5 64 老年 不买
6 64 中年
7 128 青年 不买
8 64 青年
9 132 老年
10 64 青年
11 32 中年
12 32 中年
13 64 老年 不买

  2、确定决策规则的两个核心公式:经验熵、条件熵和信息增益

  1948年,美国信息学家香农(Shannon)定义了信息熵:

    $$I(U) = log(\frac{1}{p}) = -log(p)$$

  I被称为不确定性函数,代表事件的信息量。log表示取对数。假定对于一个信源,其发生各种事件是相互独立的,并且其值具有可加性。因此使用log函数。可见,发生的概率越大,其不确定性越低。

  考虑到信源的所有可能发生的事件,假设其概率为$p_1, p_2,..., p_i$,则可以计算其平均值(数学期望),该值被称为信息熵或者经验熵。涵义即为:一个信源的平均不确定性,或者一个信源的不确定性期望。用公式表示为:

  $$H(D) = E[-log p_i] = -\sum_{i=1}^{n}p_{i}* $$

  举个例子。计算purchase的信息熵(经验熵):

  1. init_dic = {
  2. "count": [64,64,128,60,64,64,64,128,64,132,64,32,32,64],
  3. "age": ["青年","青年","中年","老年","老年","老年","中年","青年","青年","老年","青年","中年","中年","老年"],
  4. "income": ["高","高","高","中","低","低","低","中","低","中","中","中","高","中"],
  5. "student": ["否","否","否","否","是","是","是","否","是","是","是","否","是","否"],
  6. "reputation": ["良","优","良","良","良","优","优","良","良","良","优","优","良","优"],
  7. "purchase": ["不买","不买","买","买","买","不买","买","不买","买","买","买","买","买","不买"]
  8. }
  9. data = pd.DataFrame(init_dic, columns=["count", "age", "income", "student", "reputation", "purchase"])
  10.  
  11. # 计算买和不买的样本数据
  12. purchase_yes _count= data[data["purchase"] == "买"]["count"].sum()
  13. purchase_no_count = data[data["purchase"] == "不买"]["count"].sum()
  14. # 计算各自的概率
  15. purchase_yes_p = purchase_yes_count / (purchase_yes_count + purchase_no_count)
  16. purchase_no_p = 1 - purchase_yes_p
  17. print(purchase_yes_p, purchase_no_p)
  18. # 计算此时的信息熵
  19. I_purchase = -purchase_yes_p*np.log2(purchase_yes_p) -purchase_no_p*np.log2(purchase_no_p)
  20. print(I_purchase)
  21.  
  22. # 0.625 0.375
  23. # 0.954434002924965

  在X发生的情况下,Y的熵称为条件熵H(Y|X)。显然地,有公式:

  $$H(Y|X) = H(X,Y) - H(X) = \sum_{i} p(i) H(Y|X = x)$$

  上述公式表示:(X,Y)发生所包含的熵(它是个并集),减去X的熵,即为Y发生“新”增的熵。条件熵的公式推导略。

  信息增益:表示得知特征A的信息而使得D集合的信息不确定性减少的程度。它为集合D的经验熵减去特征A的条件熵。公式表示为:

  $$g(D, A) = H(D) - H(D|A)$$

  联合上面这两个式子:

  $$g(D, A) = H(D) - H(D|A) = H(D) - (H(D,A) - H(A)) = H(D) + H(A) - H(D,A)$$

  它显示是典型的计算两个集合的交集公式,这可以表示D和A之间的互信息。这行公式的理解至关重要。

  决策树优先从信息增益大的特征列开始划分数据集。这样要更“靠谱”,因为信息增益(互信息)最大,对集合D(实际上也就是决策标签)影响力更大。

  计算age字段的(经验)条件熵以及它的信息增益。

  1. def shannon(data, column="age"):
  2. # 找到这个字段的唯一值
  3. levels = data[column].drop_duplicates().tolist() # ['青年', '中年', '老年']
  4. # 计算该字段的所有数据集,显然是整个数据集
  5. samples = data["count"].sum()
  6. # 依次计算信息熵
  7. entropy = 0
  8. for level in levels:
  9. # 获取该水平的子数据集,计算买与不买的信息熵
  10. subdata = data[data[column] == level]
  11. purchase_yes = subdata[subdata["purchase"] == "买"]["count"].sum()
  12. purchase_no = subdata[subdata["purchase"] == "不买"]["count"].sum()
  13. purchase_yes_p = purchase_yes / (purchase_yes + purchase_no)
  14. purchase_no_p = 1 - purchase_yes_p
  15. # 计算该水平上的信息熵
  16. if purchase_yes == 0 or purchase_no == 0: # 这里要处理子数据集为空的情况;这里暂未处理
  17. pass
  18. I_purchase = -purchase_yes_p*np.log2(purchase_yes_p) -purchase_no_p*np.log2(purchase_no_p)
  19. # 计算该水平上的概率值
  20. level_p = subdata["count"].sum() / samples
  21. # 计算信息增益
  22. if I_purchase > 0:
  23. entropy += level_p * I_purchase
  24. # print(level, level_p, I_purchase, purchase_yes, purchase_no, entropy)
  25. return entropy
  26.  
  27. entropy_age = shannon(data, "age")
  28. gain_age = I_purchase - entropy_age # 计算这个字段的信息增益
  29. print(gain_age)
    # 0.2657121273840979
    # 有报错0除,没做处理。本例只演示如何计算叶节点信息熵。

  3、决策树流程

  决策树的流程为:

  (1)输入需要分类的数据集和类别标签和靶标签。

  (2)检验数据集是否只有一列,或者是否最后一列(靶标签数据默认放到最后一列)只有一个水平(唯一值)。

    是:返回唯一值水平或者占比最大的那个水平

  (3)调用信息增益公式,计算所有节点的信息增益,得到最大信息增益所对应的类别标签。

  (4)建立决策树字典用以保存当次叶节点数据信息。

  (5)进入循环:

    按照该类别标签的不同水平,依次计算子数据集;

    对子数据集重复(1),(2),(3),(4),(5), (6)步。

  (6)返回决策树字典。

  决策树实际上是一个大的递归函数,其结果是一个多层次的字典。

二、python3实现ID3算法

  1、python3实现ID3决策树

  参照书上的代码,用的数据结构不是列表而是pandas的DataFrame。数据文件下载地址:https://files.cnblogs.com/files/kuaizifeng/ID3data.txt.zip。

  信息熵和信息增益其实可以提炼出来,作为单独的计算方法。方便替换其它的计算方式,如信息增益率,基尼不纯度等。

  LoadDataSet用来载入数据,TreeHandler用来持久化数据。

  ID3Tree中,

    _best_split用来遍历标签并计算最大信息增益对应的标签;

    _entropy就是计算熵;

    _split_dataSet用于切割数据集;

    _top_amount_level是递归终止条件触发时的返回值。即只有一个特征列的一个水平的子集时,如果对应的purchase还有买和不买(level),就返回最大占比的level;

    mktree主程序,递归生成决策树,并将其保存在tree字典中;

    predict主程序,用于预测分类;

    _unit_test,单元测试程序,用于测试上面一些函数。

  1. import numpy as np
  2. import pandas as pd
  3. import json
  4.  
  5. class LoadDataSet(object):
  6. def load_dataSet(self):
  7. """数据文件下载地址:https://files.cnblogs.com/files/kuaizifeng/ID3data.txt.zip"""
  8. data = pd.read_csv("ID3data.txt", sep="\t", header=None)
  9. data.rename(columns={0: "age", 1: "income", 2: "student", 3: "reputation", 4: "purchase"}, inplace=True)
  10. return data
  11.  
  12. class TreeHandler(object):
  13. def __init__(self):
  14. self.tree = None
  15. def save(self, tree):
  16. self.tree = tree
  17. with open("tree.txt", mode="w", encoding="utf-8") as f:
  18. tree = json.dumps(tree, indent=" ", ensure_ascii=False)
  19. f.write(tree)
  20. def load(self, file):
  21. with open(file, mode="r", encoding="utf-8") as f:
  22. tree = f.read()
  23. self.tree = json.loads(tree)
  24. return self.tree
  25.  
  26. class ID3Tree(LoadDataSet, TreeHandler):
  27. """主要的数据结构是pandas对象"""
  28. __count = 0
  29. def __init__(self):
  30. super().__init__()
  31. """认定最后一列是标签列"""
  32. self.dataSet = self.load_dataSet()
  33. self.gain = {}
  34.  
  35. def _entropy(self, dataSet):
  36. """计算给定数据集的熵"""
  37. labels= list(dataSet.columns)
  38. level_count = dataSet[labels[-1]].value_counts().to_dict() # 统计分类标签不同水平的值
  39. entropy = 0.0
  40. for key, value in level_count.items():
  41. prob = float(value) / dataSet.shape[0]
  42. entropy += -prob * np.log2(prob)
  43. return entropy
  44.  
  45. def _split_dataSet(self, dataSet, column, level):
  46. """根据给定的column和其level来获取子数据集"""
  47. subdata = dataSet[dataSet[column] == level]
  48. del subdata[column] # 删除这个划分字段列
  49. return subdata.reset_index(drop=True) # 重建索引
  50.  
  51. def _best_split(self, dataSet):
  52. """计算每个分类标签的信息增益"""
  53. best_info_gain = 0.0 # 求最大信息增益
  54. best_label = None # 求最大信息增益对应的标签(字段)
  55. labels = list(dataSet.columns)[: -1] # 不包括最后一个靶标签
  56. init_entropy = self._entropy(dataSet) # 先求靶标签的香农熵
  57. for _, label in enumerate(labels):
  58. # 根据该label(也即column字段)的唯一值(levels)来切割成不同子数据集,并求它们的香农熵
  59. levels = dataSet[label].unique().tolist() # 获取该分类标签的不同level
  60. label_entropy = 0.0 # 用于累加各水平的信息熵;分类标签的信息熵等于该分类标签的各水平信息熵与其概率积的和。
  61. for level in levels: # 循环计算不同水平的信息熵
  62. level_data = dataSet[dataSet[label] == level] # 获取该水平的数据集
  63. prob = level_data.shape[0] / dataSet.shape[0] # 计算该水平的数据集在总数据集的占比
  64. # 计算香农熵,并更新到label_entropy中
  65. label_entropy += prob * self._entropy(level_data) # _entropy用于计算香农熵
  66. # 计算信息增益
  67. info_gain = init_entropy - label_entropy # 代码至此,已经能够循环计算每个分类标签的信息增益
  68. # 用best_info_gain来取info_gain的最大值,并获取对应的分类标签
  69. if info_gain > best_info_gain:
  70. best_info_gain = info_gain
  71. best_label = label
  72. # 这里保存一下每一次计算的信息增益,便于查看和检查错误
  73. self.gain.setdefault(self.__count, {}) # 建立本次函数调用时的字段,设其value为字典
  74. self.gain[self.__count][label] = info_gain # 把本次函数调用时计算的各个标签数据存到字典里
  75. self.__count += 1
  76. return best_label
  77.  
  78. def _top_amount_level(self, target_list):
  79. class_count = target_list.value_counts().to_dict() # 计算靶标签的不同水平的样本量,并转化为字典
  80. # 字典的items方法可以将键值对转成[(), (), ...],可以使用列表方法
  81. sorted_class_count = sorted(class_count.items(), key=lambda x:x[1], reverse=True)
  82. return sorted_class_count[0][0]
  83.  
  84. def mktree(self, dataSet):
  85. """创建决策树"""
  86. target_list = dataSet.iloc[:, -1] # target_list 靶标签的那一列数据
  87. # 程序终止条件一: 靶标签(数据集的最后一列因变量)在该数据集上只有一个水平,返回该水平
  88. if target_list.unique().shape[0] <= 1:
  89. return target_list[0] # !!!
  90. # 程序终止条件二: 数据集只剩下把标签这一列数据;返回数量最多的水平
  91. if dataSet.shape[1] == 1:
  92. return self._top_amount_level(target_list)
  93. # 不满足终止条件时,做如下递归处理
  94. # 1.选择最佳分类标签
  95. best_label = self._best_split(dataSet)
  96. # 2.递归计算最佳分类标签的不同水平的子数据集的信息增益
  97. # 各个子数据集的最佳分类标签的不同水平...
  98. # ...
  99. # 直至递归结束
  100. best_label_levels = dataSet[best_label].unique().tolist()
  101. tree = {best_label: {}} # 生成字典,用于保存树状分类信息;这里不能用self.tree = {}存储
  102. for level in best_label_levels:
  103. level_subdata = self._split_dataSet(dataSet, best_label, level) # 获取该水平的子数据集
  104. tree[best_label][level] = self.mktree(level_subdata) # 返回结果
  105. return tree
  106.  
  107. def predict(self, tree, labels, test_sample):
  108. """
  109. 对单个样本进行分类
  110. tree: 训练的字典
  111. labels: 除去最后一列的其它字段
  112. test_sample: 需要分类的一行记录数据
  113. """
  114. firstStr = list(tree.keys())[0] # tree字典里找到第一个用于分类键值对
  115. secondDict = tree[firstStr]
  116. featIndex = labels.index(firstStr) # 找到第一个建(label)在给定label的索引
  117. for key in secondDict.keys():
  118. if test_sample[featIndex] == key: # 找到test_sample在当前label下的值
  119. if secondDict[key].__class__.__name__ == "dict":
  120. classLabel = self.predict(secondDict[key], labels, test_sample)
  121. else:
  122. classLabel = secondDict[key]
  123. return classLabel
  124.  
  125. def _unit_test(self):
  126. """用于测试_entropy函数"""
  127. data = [[1, 1, "yes"],
  128. [1, 1, "yes"],
  129. [1, 0, "no"],
  130. [0, 1, "no"],
  131. [0, 1, "no"],]
  132. data = pd.DataFrame(data=data, columns=["a", "b", "c"])
  133. # return data # 到此行,用于测试_entropy
  134. # return self._split_dataSet(data, "a", 1) # 到此行,用于测试_split_dataSet
  135. # return self._best_split(data) # 到此行,用于测试_best_split
  136. # return self.mktree(self.dataSet) # 到此行,用于测试主程序mktree
  137. self.tree = self.mktree(self.dataSet) # 到此行,用于测试主程序mktree
  138. labels = ["age", "income", "student", "reputation"]
  139. test_sample = [0, 1, 0, 0] # [0, 1, 0, 0, "no"]
  140. outcome = self.predict(self.tree, labels, test_sample)
  141. print("The truth class is %s, The ID3Tree outcome is %s." % ("no", outcome))

  测试代码如下:

  1. model = ID3Tree()
  2. model._unit_test()
  3. # print(json.dumps(model.gain, indent=" ")) # 可以查看每次递归时的信息熵
  4. # print(json.dumps(model.tree, indent=" ")) # 查看树
  5.  
  6. # The truth class is no, The ID3Tree outcome is no.

  2、sklearn实现ID3算法

  sklearn将决策时算法分为两类:DecisionTreeClassifier和DecisionTreeRegressor。在实例化对象时,可以选择设置一些参数。DecisionTreeClassifier适用于分类变量,DecisionTreeRegressor适用于连续变量。

  1. import sklearn
  2. from sklearn.datasets import load_iris
  3. from sklearn.model_selection import cross_val_score
  4. from sklearn.tree import DecisionTreeClassifier
  5. clf = DecisionTreeClassifier(random_state=0, criterion="entropy", )
  6. data = np.array(model.dataSet.iloc[:, :-1]) # model是上面代码的model
  7. target = np.array(model.dataSet.iloc[:, -1])
  8. clf.fit(data, target)
  9. clf.predict([data[0]]) # 预测第一条数据
  10.  
  11. # array(['no'], dtype=object) # target[0]也为no

  3、ID3的局限性:

    1.ID3没有考虑连续特征

    2.ID3采用信息增益大的特征优先建立决策树的节点。在相同条件下,取值比较多的特征比取值少的特征信息增益大。

    3.ID3算法对于缺失值的情况没有做考虑

    4.没有考虑过拟合的问题

决策树之ID3算法的更多相关文章

  1. 【Machine Learning】决策树之ID3算法 (2)

    决策树之ID3算法 Content 1.ID3概念 2.信息熵 3.信息增益 Information Gain 4. ID3 bias 5. Python算法实现(待定) 一.ID3概念 ID3算法最 ...

  2. 决策树之ID3算法实现(python)

    决策树的概念其实不难理解,下面一张图是某女生相亲时用到的决策树: 基本上可以理解为:一堆数据,附带若干属性,每一条记录最后都有一个分类(见或者不见),然后根据每种属性可以进行划分(比如年龄是>3 ...

  3. 鹅厂优文 | 决策树及ID3算法学习

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~. 作者:袁明凯|腾讯IEG测试开发工程师 决策树的基础概念 决策树是一种用树形结构来辅助行为研究.决策分析以及机器学习的方式,是机器学习中的 ...

  4. 【Machine Learning·机器学习】决策树之ID3算法(Iterative Dichotomiser 3)

    目录 1.什么是决策树 2.如何构造一棵决策树? 2.1.基本方法 2.2.评价标准是什么/如何量化评价一个特征的好坏? 2.3.信息熵.信息增益的计算 2.4.决策树构建方法 3.算法总结 @ 1. ...

  5. 简单易学的机器学习算法——决策树之ID3算法

    一.决策树分类算法概述     决策树算法是从数据的属性(或者特征)出发,以属性作为基础,划分不同的类.例如对于如下数据集 (数据集) 其中,第一列和第二列为属性(特征),最后一列为类别标签,1表示是 ...

  6. 机器学习-决策树之ID3算法

    概述 决策树(Decision Tree)是一种非参数的有监督学习方法,它是一种树形结构,所以叫决策树.它能够从一系列有特征和标签的数据中总结出决策规则,并用树状图的结构来呈现这些规则,以解决分类和回 ...

  7. 机器学习之决策树(ID3 、C4.5算法)

    声明:本篇博文是学习<机器学习实战>一书的方式路程,系原创,若转载请标明来源. 1 决策树的基础概念 决策树分为分类树和回归树两种,分类树对离散变量做决策树 ,回归树对连续变量做决策树.决 ...

  8. 决策树ID3算法实现

    决策树的ID3算法基于信息增益来选择最优特征,于是自己实现了一把,直接上代码. """ CreateTime : 2019/3/3 22:19 Author : X Fi ...

  9. ID3算法(MATLAB)

    ID3算法是一种贪心算法,用来构造决策树.ID3算法起源于概念学习系统(CLS),以信息熵的下降速度为选取测试属性的标准,即在每个节点选取还尚未被用来划分的具有最高信息增益的属性作为划分标准,然后继续 ...

随机推荐

  1. “全栈2019”Java第十六章:下划线在数字中的意义

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  2. jquery源码解析:jQuery工具方法Callbacks详解

    我们首先来讲下Callbacks是如何使用的:第一个例子 function a(){} function b(){} var cb = $.Callbacks(); cb.add(a); cb.add ...

  3. [独家] Adobe Flash 直接复制元件不改变原元件

    正在考网页高级考证,然后会需要做Adobe Flash相关操作.在网上看了下,基本都是错误的.于是,自己研究解决该问题. 首先,你需要更改元件不改变原文件是不可能的. 所以: 1.你需要对原元件所在的 ...

  4. 一款不错的jQuery分页插件--pagination

    一.前言: 分页功能在项目中时常用到,一款可以快速实现分页功能的插件非常有必要,pagination--这款插件功能非常完美,几乎我所有项目中使用到分页的地方都会第一时间考虑到这个插件,但是其实有能力 ...

  5. WebStorm 2017 激活方法

    https://www.cnblogs.com/woaic/p/6024975.html 注册时,在打开的License Activation窗口中选择“License server”,在输入框输入下 ...

  6. PyQt5(1)——Qt Designer初探

    相关环境配置我们就不介绍了(网上有很多教程) Qt Designer 是一款十分强大的GUI工具,生成的文件为 .UI文件  可以通过命令转化为Py文件,简单来说我们可以通过拖拽方式生成界面,在通过简 ...

  7. Flink学习笔记:Flink API 通用基本概念

    本文为<Flink大数据项目实战>学习笔记,想通过视频系统学习Flink这个最火爆的大数据计算框架的同学,推荐学习课程: Flink大数据项目实战:http://t.cn/EJtKhaz ...

  8. WPF中的DataGridTemplateColumn实现点击列标题排序

    在DataGrid中使用模板列时,默认功能中对点击列标题是不对列值进行排序的,要排序就需要添加以下两个属性: 1.CanUserSort="True" 2.SortMemberPa ...

  9. MyEclipse 汉化后切换回英文(中英文切换)

    没事玩玩MyEclipse,按网上的办法把它汉化了!搞了些教程看,教程用的都是英文,还是把MyEclipse也切换回原来的英文得了! 方法:1.复制MyEclipse的快捷方式:2.右键快捷方式-&g ...

  10. springboot项目,执行查询方法报错

    org.hibernate.LazyInitializationException: could not initialize proxy [com.myproject.sell.dataobject ...