前言

    最近打算系统学习下机器学习的基础算法,避免眼高手低,决定把常用的机器学习基础算法都实现一遍以便加深印象。本文为这系列博客的第一篇,关于决策树(Decision Tree)的算法实现,文中我将对决策树种涉及到的

  算法进行总结并附上自己相关的实现代码。所有算法代码以及用于相应模型的训练的数据都会放到GitHub上(https://github.com/PytLab/MLBox).

    本文中我将一步步通过MLiA的隐形眼镜处方数集构建决策树并使用Graphviz将决策树可视化。

  决策树学习

    决策树学习是根据数据的属性采用树状结构建立的一种决策模型,可以用此模型解决分类和回归问题。常见的算法包括 CART(Classification And Regression Tree), ID3, C4.5等。我们往往根据数据集来构建一棵决策树,

  他的一个重要任务就是为了数据中所蕴含的知识信息,并提取出一系列的规则,这些规则也就是树结构的创建过程就是机器学习的过程。

  决策树的结构

    以下面一个简单的用于是否买电脑预测的决策树为例子,树中的内部节点表示某个属性,节点引出的分支表示此属性的所有可能的值,叶子节点表示最终的判断结果也就是类型。

    

    借助可视化工具例如Graphviz,matplotlib的注解等等都可以讲我们创建的决策树模型可视化并直接被人理解,这是贝叶斯神经网络等算法没有的特性。

    决策树算法

    决策树算法主要是指决策树进行创建中进行树分裂(划分数据集)的时候选取最优特征的算法,他的主要目的就是要选取一个特征能够将分开的数据集尽量的规整,也就是尽可能的纯. 最大的原则就是: 将无序的数据变得更加有序

    这里总结下三个常用的方法:

    1、信息增益(information gain)

    2、增益比率(gain ratio)

    3、基尼不纯度(Gini impurity)

  信息增益 (Information gain)

    这里涉及到了信息论中的一些概念:某个事件的信息量,信息熵,信息增益等, 关于事件信息的通俗解释可以看知乎上的一个回答

    1、某个事件 i 的信息量: 这个事件发生的概率的负对数

    2、信息熵就是平均而言一个事件发生得到的信息量大小,也就是信息量的期望值

    任何一个序列都可以获取这个序列的信息熵,也就是将此序列分类后统计每个类型的概率,再用上述公式计算,使用Python实现如下:

def get_shanno_entropy(self, values):''' 根据给定列表中的值计算其Shanno Entropy'''uniq_vals = set(values)val_nums = {key: values.count(key) for key in uniq_vals}probs = [v/len(values) for k, v in val_nums.items()]entropy = sum([-prob*log2(prob) for prob in probs])return entropy

  信息增益

    我们将一组数据集进行划分后,数据的信息熵会发生改变,我们可以通过使用信息熵的计算公式分别计算被划分的子数据集的信息熵并计算他们的平均值(期望值)来作为分割后的数据集的信息熵。新的信息熵的相比未划

  分数据的信息熵的减小值便是信息增益了. 这里我在最初就理解错了,于是写出的代码并不能创建正确的决策树。

    假设我们将数据集D划分成kk 份D1,D2,…,Dk,则划分后的信息熵为:

    信息增益便是两个信息熵的差值

    在这里我主要使用信息增益来进行属性选择,具体的实现代码如下:

def choose_best_split_feature(self, dataset, classes):''' 根据信息增益确定最好的划分数据的特征:param dataset: 待划分的数据集:param classes: 数据集对应的类型:return: 划分数据的增益最大的属性索引'''base_entropy = self.get_shanno_entropy(classes)feat_num = len(dataset[0])entropy_gains = []for i in range(feat_num):splited_dict = self.split_dataset(dataset, classes, i)new_entropy = sum([len(sub_classes)/len(classes)*self.get_shanno_entropy(sub_classes)for _, (_, sub_classes) in splited_dict.items()])entropy_gains.append(base_entropy - new_entropy)return entropy_gains.index(max(entropy_gains))

  增益比率

    增益比率是信息增益方法的一种扩展,是为了克服信息增益带来的弱泛化的缺陷。因为按照信息增益选择,总是会倾向于选择分支多的属性,这样会是的每个子集的信息熵最小。例如给每个数据添加一个

  第一无二的id值特征,则按照这个id值进行分类是获得信息增益最大的,这样每个子集中的信息熵都为0,但是这样的分类便没有任何意义,没有任何泛化能力,类似过拟合。

    因此我们可以通过引入一个分裂信息来找到一个更合适的衡量数据划分的标准,即增益比率。

    分裂信息的公式表示为:

    

    当然SplitInfo有可能趋近于0,这个时候增益比率就会变得非常大而不可信,因此有时还需在分母上添加一个平滑函数,具体的可以参考参考部分列出的文章

  基尼不纯度(Gini impurity)

    基尼不纯度的定义:

    

    其中m 表示数据集D 中类别的个数, pi 表示某种类型出现的概率。可见当只有一种类型的时候基尼不纯度的值为0,此时不纯度最低。

    针对划分成k个子数据集的数据集的基尼不纯度可以通过如下式子计算:

    

     由此我们可以根据不纯度的变化来选取最有的树分裂属性

    

  树分裂

    有了选取最佳分裂属性的算法,下面我们就需要根据选择的属性来将树进一步的分裂。所谓树分裂只不过是根据选择的属性将数据集划分,然后在总划分出来的数据集中再次调用选取属性的

  方法选取子数据集的中属性。实现的最好方式就是递归了.

    关于用什么数据结构来表示决策树,在Python中可以使用字典很方便的表示决策树的嵌套,一个树的根节点便是属性,属性对应的值又是一个新的字典,其中key为属性的可能值,value为新的子树。

    下面是我使用Python实现的根据数据集创建决策树:

    

def create_tree(self, dataset, classes, feat_names):''' 根据当前数据集递归创建决策树:param dataset: 数据集:param feat_names: 数据集中数据相应的特征名称:param classes: 数据集中数据相应的类型:param tree: 以字典形式返回决策树'''# 如果数据集中只有一种类型停止树分裂if len(set(classes)) == 1:return classes[0]# 如果遍历完所有特征,返回比例最多的类型if len(feat_names) == 0:return get_majority(classes)# 分裂创建新的子树
tree = {}best_feat_idx = self.choose_best_split_feature(dataset, classes)feature = feat_names[best_feat_idx]tree[feature] = {}# 创建用于递归创建子树的子数据集
sub_feat_names = feat_names[:]sub_feat_names.pop(best_feat_idx)splited_dict = self.split_dataset(dataset, classes, best_feat_idx)for feat_val, (sub_dataset, sub_classes) in splited_dict.items():tree[feature][feat_val] = self.create_tree(sub_dataset,sub_classes,sub_feat_names)self.tree = treeself.feat_names = feat_namesreturn tree

    树分裂的终止条件有两个

    1、一个是遍历完所有的属性

      可以看到,在进行树分裂的时候,我们的数据集中的数据向量的长度是不断缩短的,当缩短到0时,说明数据集已经将所有的属性用尽,便也分裂不下去了, 这时我们选取最终子数据集中的众数作为最终的分类结果放到叶子节点上.

    2、另一个是新划分的数据集中只有一个类型。

      若某个节点所指向的数据集都是同一种类型,那自然没有必要在分裂下去了即使属性还没有遍历完.

  构建一棵决策树

    这我用了一下MLiA书上附带的隐形眼镜的数据来生成一棵决策树,数据中包含了患者眼部状况以及医生推荐的隐形眼镜类型.

    首先先导入数据并将数据特征同类型分开作为训练数据用于生成决策树

from trees import DecisionTreeClassifierlense_labels = ['age', 'prescript', 'astigmatic', 'tearRate']X = []Y = []with open('lenses.txt', 'r') as f:for line in f:comps = line.strip().split('\t')X.append(comps[: -1])Y.append(comps[-1])

  生成决策树:

clf = DecisionTreeClassifier()clf.create_tree(X, Y, lense_labels)

  查看生成的决策树:

In [2]: clf.tree

Out[2]:

{'tearRate': {'normal': {'astigmatic': {'no': {'age': {'pre': 'soft',

'presbyopic': {'prescript': {'hyper': 'soft', 'myope': 'no lenses'}},

'young': 'soft'}},

'yes': {'prescript': {'hyper': {'age': {'pre': 'no lenses',

'presbyopic': 'no lenses',

'young': 'hard'}},

'myope': 'hard'}}}},

'reduced': 'no lenses'}}

  可视化决策树

    直接通过嵌套字典表示决策树对人来说不好理解,我们需要借助可视化工具可视化树结构,这里我将使用Graphviz来可视化树结构。为此实现了讲字典表示的树生成Graphviz Dot文件内容的函数,

  大致思想就是递归获取整棵树的所有节点和连接节点的边然后将这些节点和边生成Dot格式的字符串写入文件中并绘图。

    递归获取树的节点和边,其中使用了uuid给每个节点添加了id属性以便将相同属性的节点区分开.

def get_nodes_edges(self, tree=None, root_node=None):''' 返回树中所有节点和边'''Node = namedtuple('Node', ['id', 'label'])Edge = namedtuple('Edge', ['start', 'end', 'label'])if tree is None:tree = self.treeif type(tree) is not dict:return [], []nodes, edges = [], []if root_node is None:label = list(tree.keys())[0]root_node = Node._make([uuid.uuid4(), label])nodes.append(root_node)for edge_label, sub_tree in tree[root_node.label].items():node_label = list(sub_tree.keys())[0] if type(sub_tree) is dict else sub_treesub_node = Node._make([uuid.uuid4(), node_label])nodes.append(sub_node)edge = Edge._make([root_node, sub_node, edge_label])edges.append(edge)sub_nodes, sub_edges = self.get_nodes_edges(sub_tree, root_node=sub_node)nodes.extend(sub_nodes)edges.extend(sub_edges)return nodes, edges

  生成dot文件内容

def dotify(self, tree=None):''' 获取树的Graphviz Dot文件的内容'''if tree is None:tree = self.treecontent = 'digraph decision_tree {\n'nodes, edges = self.get_nodes_edges(tree)for node in nodes:content += '    "{}" [label="{}"];\n'.format(node.id, node.label)for edge in edges:start, label, end = edge.start, edge.label, edge.endcontent += '    "{}" -> "{}" [label="{}"];\n'.format(start.id, end.id, label)content += '}'return content

  隐形眼镜数据生成Dot文件内容如下:

digraph decision_tree {

"959b4c0c-1821-446d-94a1-c619c2decfcd" [label="call"];

"18665160-b058-437f-9b2e-05df2eb55661" [label="to"];

"2eb9860d-d241-45ca-85e6-cbd80fe2ebf7" [label="your"];

"bcbcc17c-9e2a-4bd4-a039-6e51fde5f8fd" [label="areyouunique"];

"ca091fc7-8a4e-4970-9ec3-485a4628ad29" [label="02073162414"];

"aac20872-1aac-499d-b2b5-caf0ef56eff3" [label="ham"];

"18aa8685-a6e8-4d76-bad5-ccea922bb14d" [label="spam"];

"3f7f30b1-4dbb-4459-9f25-358ad3c6d50b" [label="spam"];

"44d1f972-cd97-4636-b6e6-a389bf560656" [label="spam"];

"7f3c8562-69b5-47a9-8ee4-898bd4b6b506" [label="i"];

"a6f22325-8841-4a81-bc04-4e7485117aa1" [label="spam"];

"c181fe42-fd3c-48db-968a-502f8dd462a4" [label="ldn"];

"51b9477a-0326-4774-8622-24d1d869a283" [label="ham"];

"16f6aecd-c675-4291-867c-6c64d27eb3fc" [label="spam"];

"adb05303-813a-4fe0-bf98-c319eb70be48" [label="spam"];

"959b4c0c-1821-446d-94a1-c619c2decfcd" -> "18665160-b058-437f-9b2e-05df2eb55661" [label="0"];

"18665160-b058-437f-9b2e-05df2eb55661" -> "2eb9860d-d241-45ca-85e6-cbd80fe2ebf7" [label="0"];

"2eb9860d-d241-45ca-85e6-cbd80fe2ebf7" -> "bcbcc17c-9e2a-4bd4-a039-6e51fde5f8fd" [label="0"];

"bcbcc17c-9e2a-4bd4-a039-6e51fde5f8fd" -> "ca091fc7-8a4e-4970-9ec3-485a4628ad29" [label="0"];

"ca091fc7-8a4e-4970-9ec3-485a4628ad29" -> "aac20872-1aac-499d-b2b5-caf0ef56eff3" [label="0"];

"ca091fc7-8a4e-4970-9ec3-485a4628ad29" -> "18aa8685-a6e8-4d76-bad5-ccea922bb14d" [label="1"];

"bcbcc17c-9e2a-4bd4-a039-6e51fde5f8fd" -> "3f7f30b1-4dbb-4459-9f25-358ad3c6d50b" [label="1"];

"2eb9860d-d241-45ca-85e6-cbd80fe2ebf7" -> "44d1f972-cd97-4636-b6e6-a389bf560656" [label="1"];

"18665160-b058-437f-9b2e-05df2eb55661" -> "7f3c8562-69b5-47a9-8ee4-898bd4b6b506" [label="1"];

"7f3c8562-69b5-47a9-8ee4-898bd4b6b506" -> "a6f22325-8841-4a81-bc04-4e7485117aa1" [label="0"];

"7f3c8562-69b5-47a9-8ee4-898bd4b6b506" -> "c181fe42-fd3c-48db-968a-502f8dd462a4" [label="1"];

"c181fe42-fd3c-48db-968a-502f8dd462a4" -> "51b9477a-0326-4774-8622-24d1d869a283" [label="0"];

"c181fe42-fd3c-48db-968a-502f8dd462a4" -> "16f6aecd-c675-4291-867c-6c64d27eb3fc" [label="1"];

"959b4c0c-1821-446d-94a1-c619c2decfcd" -> "adb05303-813a-4fe0-bf98-c319eb70be48" [label="1"];

}

  这样我们便可以使用Graphviz将决策树绘制出来

with open('lenses.dot', 'w') as f:

dot = clf.tree.dotify()

f.write(dot)

dot -Tgif lenses.dot -o lenses.gif

  效果如下:

  

    使用生成的决策树进行分类

    对未知数据进行预测,主要是根据树中的节点递归的找到叶子节点即可。z这里可以通过为递归进行优化,代码实现如下:

def classify(self, data_vect, feat_names=None, tree=None):''' 根据构建的决策树对数据进行分类'''if tree is None:tree = self.treeif feat_names is None:feat_names = self.feat_names# Recursive base case.if type(tree) is not dict:return treefeature = list(tree.keys())[0]value = data_vect[feat_names.index(feature)]sub_tree = tree[feature][value]return self.classify(feat_names, data_vect, sub_tree)

  决策树的存储

    通过字典表示决策树,这样我们可以通过内置的pickle或者json模块将其存储到硬盘上,同时也可以从硬盘中读取树结构,这样在数据集很大的时候可以节省构建决策树的时间.

def dump_tree(self, filename, tree=None):''' 存储决策树'''if tree is None:tree = self.treewith open(filename, 'w') as f:pickle.dump(tree, f)def load_tree(self, filename):''' 加载树结构'''with open(filename, 'r') as f:tree = pickle.load(f)self.tree = treereturn tree

  总结

    本文一步步实现了决策树的实现, 其中使用了ID3算法确定最佳划分属性,并通过Graphviz可视化了构建的决策树。本文相关的代码链接: https://github.com/PytLab/MLBox/tree/master/decision_tree

参考:

  • 《Machine Learning in Action》

  • 数据挖掘系列(6)决策树分类算法

机器学习算法实践:决策树 (Decision Tree)(转载)相关推荐

  1. 算法杂货铺——分类算法之决策树(Decision tree)

    算法杂货铺--分类算法之决策树(Decision tree) 2010-09-19 16:30 by T2噬菌体, 88978 阅读, 29 评论, 收藏, 编辑 3.1.摘要 在前面两篇文章中,分别 ...

  2. 机器学习----监督学习算法之决策树(Decision Tree)

    感谢Jack-Cui大佬的知识分享 机器学习专栏点击这里 目录 感谢Jack-Cui大佬的知识分享 0. 概述 1. 使用决策树做预测需要以下过程: 2. 决策树构建步骤 2.1 特征选择 2.1.1 ...

  3. 算法杂货铺——分类算法之决策树(Decision tree) - T2噬菌体

    3.1.摘要 在前面两篇文章中,分别介绍和讨论了朴素贝叶斯分类与贝叶斯网络两种分类算法.这两种算法都以贝叶斯定理为基础,可以对分类及决策问题进行概率推断.在这一篇文章中,将讨论另一种被广泛使用的分类算 ...

  4. 【机器学习实验二】决策树(Decision Tree)及其在图像识别任务上的应用

    经典有监督学习算法:决策树(Decision Tree) --本篇博客的决策树算法以及实验仅针对分类问题 文章目录 经典有监督学习算法:决策树(Decision Tree) 1.算法简介 2.算法思想 ...

  5. Machine Learning | (7) Scikit-learn的分类器算法-决策树(Decision Tree)

    Machine Learning | 机器学习简介 Machine Learning | (1) Scikit-learn与特征工程 Machine Learning | (2) sklearn数据集 ...

  6. Python数据挖掘入门与实践 第三章 用决策树预测获胜球队(一)pandas的数据预处理与决策树(Decision tree)

    作为一个NBA球迷,看到这一章还是挺激动的. 不过内容有点难,研究了半天... 要是赌球的,用这章的预测+凯利公式,是不是就能提升赢钱概率了? 数据预处理 回归书本内容,既然要分析,首先需要有数据: ...

  7. [机器学习笔记] (四)决策树 Decision Tree

    (四)决策树 Decision Tree 基本概念 决策树(Decision Tree)是在已知各种情况发生概率的基础上,通过构成决策树来求取净现值的期望值大于等于零的概率,评价项目风险,判断其可行性 ...

  8. 决策树分类python代码_分类算法-决策树 Decision Tree

    决策树(Decision Tree)是一个非参数的监督式学习方法,决策树又称为判定树,是运用于分类的一种树结构,其中的每个内部节点代表对某一属性的一次测试,每条边代表一个测试结果,叶节点代表某个类或类 ...

  9. 决策树Decision Tree+ID3+C4.5算法实战

    决策树Decision Tree 决策树的三种算法: 举个栗子: 熵entropy的概念: 信息熵越大,不确定性越大.信息熵越小,不确定性越小. 其实就是排列组合之中的概率,概率相乘得到其中一个组合, ...

最新文章

  1. 博问问题内容页面的前端优化
  2. MySQL中Order By与Limit不要一起用
  3. nginx php mysql 部署_Linux+Nginx+Mysql+Php运维部署
  4. java kafka 多线程消费
  5. Unity手游之路十自动寻路Navmesh之跳跃,攀爬,斜坡
  6. 通用mapper总结 如:MysqlDataTruncation: Data truncation: Truncated incorrect DOUBLE value
  7. java面向对象相关选择题_java面向对象练习题一
  8. Web.config中用customErrors可以自定义默认的出错页面
  9. 面向对象三,约束,加密
  10. latex 调整表格的行高_latex 表格如何控制行高,行距,行与行之间的距离
  11. vue打印指定的html,使用window.print()打印指定的一个vue 组件
  12. 2018年最好的8款杀毒软件
  13. matlab实现jpg转pgm,Matlab实现图片格式转换 pgm转jpg等
  14. 牛客IOI周赛17-普及组 数列统计(组合数)
  15. 后台java,前台JSP实现Excel导出(已经在后台查询,直接在前端实现下载,系统后台整体框架使用SSM)
  16. 网络号,IP,子网掩码之间的关系
  17. 店铺logo设计免费在线生成
  18. 分享一个餐饮点餐小程序--蜜蜂点餐
  19. cnforum使用MasterPages实现Web窗体模板
  20. 【Qt】 Fractal Designer 5.0 Bug Report

热门文章

  1. 英伟达CUDA 10终于开放下载了
  2. 零基础可上手 | 手把手教你用Cloud AutoML做毒蜘蛛分类器
  3. 百度悄然发布Deep Vioce 3
  4. 根据Dockerfile构建镜像
  5. 小程序中的setData的使用
  6. cocos2d-x中实现不规则按钮的点击效果
  7. python 基础干货 01
  8. CentOS 6.4 安装 media wiki 1.23.6(转)
  9. Android检测网络是否正常代码!
  10. Android中定时器的3种实现方法