1.理论

决策树的结构与数据结构的树差不多,树的根部输入所有样本,树的每个结点都是数据的特征,根据特征我们将样本分成一个个子集,使这一特征具有相同表现的样本汇聚,不同的分开,再对剩下的子集样本重复这些操作,直到不能分或没特征可判断为止。这个模型很好想象,它就像带着if-else的树一样。

决策树从最初提出的ID3模型,经历了很多次升级,例如C4.5,CART等。本文先介绍ID3与C4.5。

1.1信息熵、条件熵、信息增益

1.1.1信息熵

熵这个词我们最早应该是在中学的化学课本中听到的,用来描述分子无序、混乱的程度,熵是不断增加的。熵这一概念在各大学科领域都有应用,热力学,物理学,统计学等等。香农(C.E.Shannon)提出了信息论中的熵:信息熵。这个公式与期望有关,以下的分析更为简单直接,没有对公式由来作出解释,有兴趣的可以参考:通俗理解信息熵

公式:

pi表示信息中某一符号出现的频率,比如我们有本书,每个汉字出现的频率就是pi。信息熵可以用来描述语言/信息的混乱程度,但是我们知道信息的混乱程度有什么用呢?

百度百科中说:一般而言,当一种信息出现概率更高的时候,表明它被传播得更广泛,或者说,被引用的程度更高。我们可以认为,从信息传播的角度来看,信息熵可以表示信息的价值。这样子我们就有一个衡量信息价值高低的标准

从公式的角度分析,若整个信息系统只有一个字、符号或者字母,那么p=1,信息熵为0,没有不确定性,它太确定了,无法传递什么信息,你抱着只有一个字的书能学到什么知识呢?那么我们加一个,这个信息系统里有1和0,p都是0.5,信息熵结果为1,这个信息系统可以传递信息了,就像现实中我们用1和0构建了计算机的逻辑。合理想象随着符号的增加,系统能传递的信息也在增加。另一个角度,假如1和0出现频率不同呢?若1是0.7,0是0.3,信息熵计算的结果是0.88。这说明如果符号出现的频率不相同,或者出现更大的差异,信息熵比频率相同的情况小,不确定减小,很好理解,如果一本书有99个1只有1个0那么我们也很难判断出什么信息。这就是信息熵,经过这段解释,相信对于这句话有了进一步的认识:信息熵表示信息的不确定性,进而可以用于表示信息的价值。

1.1.2条件熵

理解了信息熵,条件熵的概念便利于理解了,它表示在某条件下信息的不确定性。

它利用了条件概率来计算。(默认log以2为底)

回到我们真实的应用上,我们拥有一堆x与y,x拥有不同的特征,y拥有不同的种类,可以为这些建立一个信息系统模型。信息熵的概率当然是围绕y出现的概率来进行计算,来获得系统的不确定性,条件熵呢?则是在不同x的条件下,系统关于y信息的不确定性。

我之前看到这个公式的时候就发现为什么前面乘的概率是联合概率而不是条件概率呢?这是略微复杂的推导过程,参考自:通俗理解条件熵

1.1.3信息增益

有了信息熵,(信息)条件熵,我们可以分析一下他们的关系,信息熵是整个信息系统不确定性的度量,条件熵则是系统在一定条件下不确定性的度量,显然,给定了条件束缚后,不确定性一定是减少了的,信息熵大于条件熵。

那么将信息熵减去条件熵,我们就获得了当确定某一条件后,系统不确定性损失了多少!而这个差值,我们称为信息增益,显然信息论的学者们起的这个名字十分贴切。

1.2决策树的建立

1.2.1ID3

回到决策树上来,我们似乎可以直接建一个决策树,就是没完没了的if-else就好了,比如我们要辨别西瓜的好坏,特征有西瓜的纹理,根蒂,声响等,一个个判断呗,先拍拍听听声音,分不出来看看根蒂咋样,再根据纹理等等一层层也能够分类出来,人们买瓜不就是这样的嘛。

判断瓜太简单了, 我们实际需要面对的问题和数据会非常复杂,数据的分布会千变万化,你需要谨慎选择用来判断特征的顺序,你随便选的特征顺序可能会造成非常大规模的误判,如果特征数量很多,你总不能每种顺序都试一遍吧?

所以我们需要用到信息增益。它用来帮我们决定如何选用特征再合适不过,它会帮我们计算在已知某一特征的情况下,不确定性减少了多少,减少的越多,说明这个特征包含的信息就越大,我们越应该先进行判断!

运用这个思想,我们可以简单写出决策树的构建过程了:

数据:X,N个样本,每个样本包含M个特征。每个样本有一个标签,共有K类标签。

视整个样本集合为S。

  • 若根结点为空,建立根结点,根结点作为当前结点。否则建立新结点,当前结点作为新结点的父结点,新结点作为当前结点。
  • 对当前结点输入S,若S样本个数为1,返回,若当前特征为0,返回。
  • 计算整体样本标签的信息熵H(S),计算M个特征的条件熵H(S|X),计算信息增益。
  • 选取最大信息增益的特征作为当前结点决策特征,根据特征不同的值将S分为S1,S2,S3等,更新整体特征M变为M-1。
  • 对于每个新的样本集,重复以上过程

以上便是最早版本的决策树ID3,简单实用。


随着决策树的应用,其缺点渐渐暴露:

  • 只能处理离散特征,连续型数据无法应用。
  • 可能发生严重的过拟合,树会因为样本与特征的存在不断细分下去,使得模型不断拟合训练数据导致测试误差增加。
  • 没有应对缺失值的能力。(这一点很多地方都提到了,实际上有点奇怪,很多算法都没有对缺失值的处理,这本是特征工程的活儿,为什么要对决策树有处理缺失值的期望呢?可能是因为决策树的可解释性强,是个难得的算法,希望它更健壮完美吧)
  • 选用信息增益作为特征选择标准。

前三点很好理解,最后一点是因为信息增益并不完美,它倾向于选择取值多的特征,比如一个特征A有两个值1和0,概率相同各0.5;另一个特征B有0,1和2三种取值,概率相同各1/3,信息增益一定会选择B,但A与B的不确定性都很充足,这使得一些情况我们得不到正确的模型。

1.2.2C4.5

随后经过多次更新,新的决策树模型C4.5解决了ID3的缺点。此处参考:决策树算法--C4.5算法,强烈推荐。

特征选择:

使用信息增益比作为选择标准,信息增益比消除了特征取值数量的影响,其为信息增益与特征熵的比值,若样本为S,特征为A,特征熵:

缺失值:

  • 在有缺失值的特征上如何计算信息增益比?根据缺失比例,折算信息增益(无缺失值样本所占的比例乘以无缺失值样本子集的信息增益)和信息增益比
  • 选定了划分属性,若样本在该属性上的值是缺失的,那么如何对这个样本进行划分?将样本以不同概率同时划分到不同节点中,概率是根据其他非缺失属性的比例来得到的
  • 对新的样本进行分类时,如果测试样本特性有缺失值如何判断其类别?走所有分支,计算每个类别的概率,取概率最大的类别赋值给该样本

连续值:

离散化处理,若该连续特征有m个值,将m个值从小到大排序,计算每两个相邻的值的平均值,共m-1个平均值,将这m-1个平均值视为新的m-1个划分点来计算它们的信息增益比,选取最优的划分点,样本根据该划分点分为小于该点的子样本和大于该点的子样本。

过拟合:

决策树过拟合的来源主要因为树会不断产生分支,这个过程会丧失泛化能力,于是解决过拟合的手段就是剪枝。剪枝包括前剪枝和后剪枝两种。

前剪枝:边生长边剪枝。每次建立新的分支之前,检查是否有必要继续进行分支,是否当前结点中的子样本数量是否已经足够小不应该再分了?是否还有特征能够用来进行分支?分支后是不是准确率反而会下降?基于这三个问题可以控制树没完没了地分支下去。其思想借鉴于贪心算法,降低了过拟合的风险,但是增加了欠拟合风险。

后剪枝:生长结束后剪枝。C4.5使用的是悲观错误剪枝方法,具体理论公式较占篇幅,本人也是刚刚学习,所以直接附上参考:决策树C4.5。在发展中后剪枝有了更多方法,最小错误剪枝技术(MEP),代价复杂度剪枝(CCP)等等,参考自:【机器学习】 树的剪枝策略。

2.实践

2.1代码 V1

既然讲了C4.5,为了实践如何处理连续值继续选用iris数据集,不够完美的是特征全部为连续值。与之前线性模型相比,程序复杂度一下子提升了不少,对于我这种代码菜鸟来说想手动实现还真不容易,费劲写出来的第一版算法并不成功,训练准确率差不多67%,测试准确率50%,应该是模型还不够好。尝试分析了一下原因并总结缺点:

  1. C4.5对于连续值特征,不会像离散值特征那样用一遍就丢弃。计算所有中位划分点并选择完最优划分点后,其余的划分点仍可以在后续的判断中继续使用。但我的算法则是选择了一个最优的划分点就将整个特征抛弃了。我认为这应该是最主要的原因。
  2. 数据本身的分布可能不太适合使用决策树,作为全连续值数据,若连续使用特征会导致决策树需要分很多支,形成很大的过拟合问题,但是若像我一样只用一次,会导致欠拟合问题。
  3. 特征选择使用二分点所以只生成二叉树,在这个数据集上可能选用能多叉划分的决策点比较好,那就需要变换划分方法了。
  4. 没有用剪枝来正则化,不过这个功能在我这最初版本也用不上,我的决策树不深。
  5. 模型也没有对缺失值的处理。不过我觉得没必要,这是特征工程需要做的事情。
"""
Author: Jhll97
"""
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import collections
import numpy as npiris = load_iris()
data = iris.data
target = iris.target
X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=30)# 编写决策树类
class DecisionTreeC45(object):def __init__(self, x, y):self.n = len(y)  # 样本数量self.m = x.shape[1]  # 特征维度self.features = list(range(self.m))self.labels = np.unique(y)  # 标签种类self.info_entropy = self.compute_entropy(y)  # 信息熵self.root = Nonedef compute_entropy(self, sub_label):  # 计算信息熵,特征熵,以及帮助计算条件熵# 可以直接利用dataframe的value_counts,这里手动写了mp = {i: 0 for i in np.unique(sub_label)}for i in range(len(sub_label)):mp[sub_label[i]] += 1res = 0for key in mp:prob = mp[key]prob /= len(sub_label)res += -prob * np.log2(prob)return resdef compute_gain_rate(self, sample, label, features):ratio = {}for i in features:feature = np.sort(sample[:, i])feature_et = self.compute_entropy(feature)  # 特征熵mids = []  # 储存划分点for j in range(sample.shape[0] - 1):mids.append((feature[j] + feature[j + 1]) / 2)best_rate = 0best_point = 0for j in range(len(mids)):sub_label1 = label[np.where(sample[:, i] <= mids[j])]sub_label2 = label[np.where(sample[:, i] > mids[j])]n1 = len(sub_label1)n2 = len(sub_label2)# 以下计算信息增益率con_et = n1 / len(label) * self.compute_entropy(sub_label1) + \n2 / len(label) * self.compute_entropy(sub_label2)  # con_et 为根据每个划分点计算出的条件熵gain = self.info_entropy - con_et  # gain 为根据每个划分点计算的信息增益gain_rate = gain / feature_et  # 信息增益率# 寻找最优增益率if gain_rate > best_rate:best_rate = gain_ratebest_point = mids[j]  # 记录这个划分点信息ratio[i] = (best_point, best_rate)  # 记录每个维度的最优信息增益比decision_feature, decision_point = self.get_best_ratio(ratio)return decision_feature, decision_point, len(features)def get_best_ratio(self, ratio):decision_feature = 0decision_point = 0gainrate = 0for key, value in ratio.items():point, temp = valueif temp > gainrate:gainrate = tempdecision_feature = keydecision_point = pointreturn decision_feature, decision_pointdef build_tree(self, data, label, least_sample=1, leaf=False):  # 建立决策树if len(label) <= least_sample or leaf:  # 如果样本数量达到阈值或被告知叶子结点,分类new_node = Node(label, 0, 0, True)if not self.root:self.root = new_nodereturn new_nodeelse:decision_feature, decision_point, feature_dim = self.compute_gain_rate(data, label, features=self.features)new_node = Node(label, decision_feature, decision_point, False)if not self.root:self.root = new_node# 分到划分点左侧的样本sub_sample1 = data[np.where(data[:, decision_feature] <= decision_point)]sub_label1 = label[np.where(data[:, decision_feature] <= decision_point)]# 分到划分点右侧的样本sub_sample2 = data[np.where(data[:, decision_feature] > decision_point)]sub_label2 = label[np.where(data[:, decision_feature] > decision_point)]new_leaf = Trueif feature_dim > 1:new_leaf = Falseself.features.remove(decision_feature)new_node.left = self.build_tree(sub_sample1, sub_label1, leaf=new_leaf)new_node.right = self.build_tree(sub_sample2, sub_label2, leaf=new_leaf)return new_nodedef test(self, test_data):result = []for i in range(len(test_data)):cur_node = self.rootx = test_data[i]while not cur_node.is_leaf:     # 在决策树中向下遍历到叶子结点if x[cur_node.decision_feature] <= cur_node.decision_point:cur_node = cur_node.leftelse:cur_node = cur_node.rightresult.append(cur_node.label)return result# 编写结点类
class Node(object):def __init__(self, y, decision_feature=0, decision_point=0, leaf=False):self.is_leaf = leaf  # 默认中间结点self.n = len(y)  # 结点样本数量self.label = 0  # 叶子结点的分类结果self.left = Noneself.right = Noneself.decision_feature = decision_featureself.decision_point = decision_pointif self.is_leaf:  # 叶子结点,分类self.label = self.set_label(y)def set_label(self, y):# 叶子结点最终分类采取众数规则,哪个种类最多选哪个mp = collections.defaultdict(int)for i in range(self.n):mp[y[i]] += 1count = 0res = 0for key, value in mp.items():if value > count:count = valueres = keyreturn resdt = DecisionTreeC45(X_train, y_train)
dt.build_tree(X_train, y_train)
# 测试误差
result = dt.test(X_train)
accuracy = collections.Counter(y_train == result)
print(accuracy)

希望后面能改进一些拿出来V2版本,不过嘿嘿有点累了,再说再说。


更新:V1版本发现了一个低级错误,第50,51行将np.where中判断的对象由feature <=(或大于) mids[j] 改为sample[:, i] <=(或大于) mids[j]。训练准确率平均升至73%左右,测试准确率平均升至65%。

AI实践之路:决策树相关推荐

  1. AI实践之路:朴素贝叶斯

    1.朴素贝叶斯 1.1背景 朴素贝叶斯来源于贝叶斯学派,他们的理论与传统的统计学观点相悖,其中最大的分歧点之一是关于"先验"的使用.传统统计学认为概率即频率,计算概率只应从观测到的 ...

  2. AI实践之路:线性/逻辑回归背后的广义线性模型与最大似然估计

    写上一篇文章的过程中,讲到逻辑回归是如何利用Sigmoid函数将线性回归的数值转换为概率时,才意识到自己对逻辑回归的理解十分浅显,为什么是Sigmoid函数?它一个就说是概率了?数学原理是什么?为了增 ...

  3. OceanBase在蚂蚁金服的智能运维实践之路

    OB君:蚂蚁金服资深技术专家虞舜将在本文为大家分享蚂蚁金服数据库所面对的业务挑战,解读OceanBase的自治数据库体系,解密OceanBase在天猫双11大促期间的稳定性解决方案,探索OceanBa ...

  4. 亿级用户百TB级数据的 AIOps 技术实践之路(增强版)

    作者简介 周荣,华为消费者BG云运维部 AIOps 负责人,GOPS 2018 深圳站金牌讲师,07年加入华为,先后分别负责下一代智能网.中间件平台.运维工具等产品的研发与规划,在分布式系统.大数据分 ...

  5. 讯飞智慧交通的实践之路

    来源:ITS智能交通 9月13日上午,由深圳市智能交通行业协会主办的第十七届中国(国际)城市智能交通论坛成功举行,科大讯飞股份有限公司智慧城市事业群副总裁谭昶发表题为<讯飞智慧交通的实践之路&g ...

  6. 《探寻AI创新之路——游戏科技与人工智能创新发展报告》正式发布

    近日,中国科学院虚拟经济与数据科学研究中心.中国科学院大数据挖掘与知识管理重点实验室在京联合举办<探寻AI创新之路--游戏科技与人工智能创新发展报告>发布暨研讨会. 报告下载地址:http ...

  7. 猪八戒网CI/CD最佳实践之路

    序言 本文旨在介绍ZBJ DevOps团队倾力打造的DevOps平台中关于CI/CD流水线部分的实践.历经三次大版本迭代更新的流水线,完美切合ZBJ各种业务发展需求,在满足高频率交付的同时,提高了研发 ...

  8. 京东千万并发 API 网关实践之路!

    来源:京东零售技术(id:jd-sys) 链接:京东API网关实践之路 概述 1 背景 京东作为电商平台,近几年用户.业务持续增长,访问量持续上升,随着这些业务的发展,API网关应运而生. API网关 ...

  9. 【干货】百度联合清华大学发布国内首个基于AI实践的《产业智能化白皮书》(附报告全文)...

    来源:百度AI 在4月9日举行的"百度大学 Alpha 学院首期学员毕业典礼"上,百度联合清华大学发布国内首个基于 AI 实践的行业重磅报告,<产业智能化白皮书--人工智能产 ...

最新文章

  1. PHP之factory
  2. 使用Hibernate(JPA)一键式删除
  3. 第6篇:Flowable-Modeler详述之常见问题Table act_ge_property doesn't exist
  4. python库下载哪些_Windows 下各种Python库的下载与安装
  5. 创业一定要做自己“喜欢”并且有“优势”的事情
  6. [转载] Python数据分析与可视化学习笔记(一)数据分析与可视化概述
  7. 10月19日 蒟蒻的流水账
  8. Mycat跨库join实现方式总结
  9. Ubuntu安装语言包
  10. 爬虫python下载网站所有图片_Python爬虫-搜索并下载图片
  11. OpenStack安装CentOS镜像:Device eth0 does not seem to be present, delaying initialization
  12. win10修改DNS
  13. Sql Server 2008 通用语法总结
  14. 如何使用KMS激活win10和office
  15. IOS获取UDID添加到开发者账号用来安装测试包
  16. 『实用教程』使用Visual Studio自带的Git管理回滚代码版本
  17. Java 实现分段截取视频 生成gif图 使用ffmpeg操作
  18. netty对http协议解析原理解析
  19. 【信息安全技术】实验报告:木马及远程控制技术
  20. 如何选择大数据培训机构

热门文章

  1. Aspose.Cells 隐藏行和列(隐藏单行、隐藏多行、隐藏单列、隐藏多列
  2. 全网最全解决这idea乱码问题
  3. 兄弟连官方微博上线!http://t.sina.com.cn/lampbrother
  4. QT UTC(T和Z格式)时间转换为北京时间
  5. stm32智能家居+微信小程序接收控制
  6. Windows无缝息屏锁屏
  7. PI 实时数据库的ODBC配置
  8. 阿里云移动研发平台EMAS,2月产品动态
  9. VUE路由守卫_前端实现权限验证
  10. 如何解决websocket 打开连接就立刻关闭