背景

这是我们为拥有 Python 基础的同学推出的精进技能的“机器学习实战” 刻意练习活动,这也是我们本学期推出的第三次活动了。

我们准备利用8周时间,夯实机器学习常用算法,完成以下任务:

  • 分类问题:K邻近算法
  • 分类问题:决策树
  • 分类问题:朴素贝叶斯
  • 分类问题:逻辑回归
  • 分类问题:支持向量机
  • 分类问题:AdaBoost
  • 回归问题:线性回归、岭回归、套索方法、逐步回归等
  • 回归问题:树回归
  • 聚类问题:K均值聚类
  • 相关问题:Apriori
  • 相关问题:FP-Growth
  • 简化数据:PCA主成分分析
  • 简化数据:SVD奇异值分解

本次任务的核心是熟悉决策树算法的原理,并实现《机器学习实战》这本书给出的两个案例。


算法原理

决策树模型是一种描述对实例进行分类的树形结构。其由结点(node)和有向边(directed edge)组成。结点有两种类型:内部结点(internal node)和叶结点(leaf node)。内部结点表示一个特征或属性(features),叶结点表示一个类(labels)。

用决策树对需要测试的实例进行分类:从根节点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到其子结点;这时,每一个子结点对应着该特征的一个取值。如此递归地对实例进行测试并分配,直至达到叶结点。最后将实例分配到叶结点的类中。

决策树模型可以认为是 if-then 规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。

决策树学习通常包括 3 个步骤:特征选择、决策树的生成和决策树的修剪。

信息熵

  • 熵(entropy): 熵指的是体系的混乱的程度,在不同的学科中也有引申出的更为具体的定义,是各领域十分重要的参量。
  • 信息论(information theory)中的熵(香农熵): 是一种信息的度量方式,表示信息的混乱程度,也就是说:信息越有序,信息熵越低。
  • 信息增益(information gain): 在划分数据集前后信息发生的变化称为信息增益。

划分数据集的原则是:将无序的数据变得更加有序。选择信息增益最大的特征来划分数据集。所用到的公式如下:

数据集的信息熵:

H(D)=−∑k=1K∣Ck∣∣D∣log2∣Ck∣∣D∣H(D)=-\sum^{K}_{k = 1}{\frac{|C_k|}{|D|}log_2\frac{|C_k|}{|D|}} H(D)=−k=1∑K​∣D∣∣Ck​∣​log2​∣D∣∣Ck​∣​

其中`CkC_kCk​表示集合DDD中属于第kkk类样本的样本子集。

针对某个特征AAA,对于数据集DDD的条件熵H(D∣A)H(D|A)H(D∣A)为:

H(D∣A)=∑i=1n∣Di∣∣D∣H(Di)H(D|A)=\sum^{n}_{i = 1}{\frac{|D_i|}{|D|}H(D_i)} H(D∣A)=i=1∑n​∣D∣∣Di​∣​H(Di​)
其中DiD_iDi​表示DDD中特征AAA取第iii个值的样本子集。

信息增益 = 信息熵 - 条件熵:

Gain(D,A)=H(D)−H(D∣A)Gain(D,A)=H(D)-H(D|A) Gain(D,A)=H(D)−H(D∣A)

信息增益越大表示使用特征AAA来划分所获得的“纯度提升越大”。

如何构造一个决策树?

我们使用 createBranch() 方法,如下所示:

def createBranch():
'''
此处运用了迭代的思想。
'''检测数据集中的所有数据的分类标签是否相同:If so return 类标签Else:寻找划分数据集的最好特征(划分之后信息熵最小,也就是信息增益最大的特征)划分数据集创建分支节点for 每个划分的子集调用函数 createBranch (创建分支的函数)并增加返回结果到分支节点中return 分支节点

项目案例1:判定鱼类和非鱼类

项目概述

根据以下 2 个特征,将动物分成两类:鱼类和非鱼类。

  • 不浮出水面是否可以生存
  • 是否有脚蹼

开发流程

Step1:收集数据

Step2:准备数据

def createDataSet():dataSet = [[1, 1, 'yes'],[1, 1, 'yes'],[1, 0, 'no'],[0, 1, 'no'],[0, 1, 'no']]labels = ['no surfacing', 'flippers']return dataSet, labels

Step3:分析数据

可以使用任何方法,构造树完成之后,我们可以将树画出来。

计算给定数据集的香农熵的函数如下:

import mathdef calcShannonEnt(dataSet):# 求list的长度,表示计算参与训练的数据量numEntries = len(dataSet)# 计算分类标签label出现的次数labelCounts = {}# the the number of unique elements and their occurrencefor featVec in dataSet:# 将当前实例的标签存储,即每一行数据的最后一个数据代表的是标签currentLabel = featVec[-1]# 为所有可能的分类创建字典,如果当前的键值不存在,则扩展字典并将当前键值加入字典。每个键值都记录了当前类别出现的次数。if currentLabel not in labelCounts.keys():labelCounts[currentLabel] = 0labelCounts[currentLabel] += 1# 对于 label 标签的占比,求出 label 标签的香农熵shannonEnt = 0.0for key in labelCounts:# 使用所有类标签的发生频率计算类别出现的概率。prob = float(labelCounts[key]) / numEntries# 计算香农熵,以 2 为底求对数shannonEnt -= prob * math.log(prob, 2)return shannonEnt

按照给定特征划分数据集

将指定特征的特征值等于 value 的行剩下列作为子数据集。

def splitDataSet(dataSet, index, value):"""splitDataSet(通过遍历dataSet数据集,求出index对应的colnum列的值为value的行)就是依据index列进行分类,如果index列的数据等于 value的时候,就要将 index 划分到我们创建的新的数据集中Args:dataSet 数据集                 待划分的数据集index 表示每一行的index列        划分数据集的特征value 表示index列对应的value值   需要返回的特征的值。Returns:index列为value的数据集【该数据集需要排除index列】"""retDataSet = []for featVec in dataSet:# index列为value的数据集【该数据集需要排除index列】# 判断index列的值是否为valueif featVec[index] == value:# chop out index used for splitting# [:index]表示前index行,即若 index 为2,就是取 featVec 的前 index 行reducedFeatVec = featVec[:index]reducedFeatVec.extend(featVec[index + 1:])# [index+1:]表示从跳过 index 的 index+1行,取接下来的数据# 收集结果值 index列为value的行【该行需要排除index列】retDataSet.append(reducedFeatVec)return retDataSet

选择最好的数据集划分方式:

def chooseBestFeatureToSplit(dataSet):"""chooseBestFeatureToSplit(选择最好的特征)Args:dataSet 数据集Returns:bestFeature 最优的特征列"""# 求第一行有多少列的 Feature, 最后一列是label列numFeatures = len(dataSet[0]) - 1# 数据集的原始信息熵baseEntropy = calcShannonEnt(dataSet)# 最优的信息增益值, 和最优的Featurn编号bestInfoGain, bestFeature = 0.0, -1# iterate over all the featuresfor i in range(numFeatures):# 获取对应的feature下的所有数据featList = [example[i] for example in dataSet]# 获取剔重后的集合,使用set对list数据进行去重uniqueVals = set(featList)# 创建一个临时的信息熵newEntropy = 0.0# 遍历某一列的value集合,计算该列的信息熵# 遍历当前特征中的所有唯一属性值,对每个唯一属性值划分一次数据集,计算数据集的新熵值,并对所有唯一特征值得到的熵求和。for value in uniqueVals:subDataSet = splitDataSet(dataSet, i, value)# 计算概率prob = len(subDataSet) / float(len(dataSet))# 计算条件熵newEntropy += prob * calcShannonEnt(subDataSet)# gain[信息增益]: 划分数据集前后的信息变化, 获取信息熵最大的值# 信息增益是熵的减少或者是数据无序度的减少。最后,比较所有特征中的信息增益,返回最好特征划分的索引值。infoGain = baseEntropy - newEntropyprint('infoGain=', infoGain, 'bestFeature=', i, baseEntropy, newEntropy)if infoGain > bestInfoGain:bestInfoGain = infoGainbestFeature = ireturn bestFeature

Step4:训练算法

构造树的数据结构,创建树的函数代码如下:

import operatordef majorityCnt(classList):"""majorityCnt(选择出现次数最多的一个结果)Args:classList label列的集合Returns:bestFeature 最优的特征列"""classCount = {}for vote in classList:if vote not in classCount.keys():classCount[vote] = 0classCount[vote] += 1# 倒叙排列classCount得到一个字典集合,然后取出第一个就是结果(yes/no),即出现次数最多的结果sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)return sortedClassCount[0][0]def createTree(dataSet, labels):classList = [example[-1] for example in dataSet]# 如果数据集的最后一列的第一个值出现的次数=整个集合的数量,也就说只有一个类别,就只直接返回结果就行# 第一个停止条件:所有的类标签完全相同,则直接返回该类标签。# count() 函数是统计括号中的值在list中出现的次数if classList.count(classList[0]) == len(classList):return classList[0]# 如果数据集只有1列,那么最初出现label次数最多的一类,作为结果# 第二个停止条件:使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组。if len(dataSet[0]) == 1:return majorityCnt(classList)# 选择最优的列,得到最优列对应的label含义bestFeat = chooseBestFeatureToSplit(dataSet)# 获取label的名称bestFeatLabel = labels[bestFeat]# 初始化myTreemyTree = {bestFeatLabel: {}}# 注:labels列表是可变对象,在PYTHON函数中作为参数时传址引用,能够被全局修改# 所以这行代码导致函数外的同名变量被删除了元素,造成例句无法执行,提示'no surfacing' is not in listdel (labels[bestFeat])# 取出最优列,然后它的branch做分类featValues = [example[bestFeat] for example in dataSet]uniqueVals = set(featValues)for value in uniqueVals:# 求出剩余的标签labelsubLabels = labels[:]# 遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数createTree()myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)return myTree

存储决策树

def storeTree(inputTree, filename):import picklefw = open(filename, 'wb')pickle.dump(inputTree, fw)fw.close()def loadTree(filename):import picklefr = open(filename, 'rb')return pickle.load(fr)

代码测试

if __name__ == '__main__':dataSet, labels = createDataSet()tree = createTree(dataSet, labels)print(tree)# infoGain= 0.4199730940219749 bestFeature= 0 0.9709505944546686 0.5509775004326937
# infoGain= 0.17095059445466854 bestFeature= 1 0.9709505944546686 0.8
# infoGain= 0.9182958340544896 bestFeature= 0 0.9182958340544896 0.0
# {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}storeTree(tree, 'classifierStorage.pkl')tree = loadTree('classifierStorage.pkl')print(tree)# {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}

Step5:测试算法

使用决策树执行分类

def classify(inputTree, featLabels, testVec):"""classify(给输入的节点,进行分类)Args:inputTree  决策树模型featLabels Feature标签对应的名称testVec    测试输入的数据Returns:classLabel 分类的结果值,需要映射label才能知道名称"""# 获取tree的根节点对于的key值firstStr = list(inputTree.keys())[0]# 通过key得到根节点对应的valuesecondDict = inputTree[firstStr]# 判断根节点名称获取根节点在label中的先后顺序,这样就知道输入的testVec怎么开始对照树来做分类featIndex = featLabels.index(firstStr)# 测试数据,找到根节点对应的label位置,也就知道从输入的数据的第几位来开始分类key = testVec[featIndex]valueOfFeat = secondDict[key]print('+++', firstStr, 'xxx', secondDict, '---', key, '>>>', valueOfFeat)# 判断分枝是否结束: 判断valueOfFeat是否是dict类型if isinstance(valueOfFeat, dict):classLabel = classify(valueOfFeat, featLabels, testVec)else:classLabel = valueOfFeatreturn classLabel

Step6:使用算法

此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义。

if __name__ == '__main__':dataSet, labels = createDataSet()labelsCopy = labels[:]inputTree = createTree(dataSet, labels)result = classify(inputTree, labelsCopy, [1, 0])print(result)# +++ no surfacing xxx {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}} --- 1 >>> {'flippers': {0: 'no', 1: 'yes'}}
# +++ flippers xxx {0: 'no', 1: 'yes'} --- 0 >>> no
# no

项目案例2:使用决策树预测隐形眼镜类型

项目概述

隐形眼镜类型包括硬材质、软材质以及不适合佩戴隐形眼镜。我们需要使用决策树预测患者需要佩戴的隐形眼镜类型。

开发流程

Step1:收集数据

文本文件数据格式如下:

young    myope   no    reduced    no lenses
young   myope   no    normal    soft
pre    myope    yes    reduced    no lenses
pre    myope    yes    normal    hard
presbyopic    myope    yes    normal    hard
presbyopic    hyper    no    reduced    no lenses

Step2:准备数据

解析 tab 键分隔的数据行

# 加载隐形眼镜相关的 文本文件 数据
fr = open('lenses.txt')
# 解析数据,获得 features 数据
lenses = [inst.strip().split('\t') for inst in fr.readlines()]
# 得到数据的对应的 Labels
lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']

Step3:分析数据

同案例1,计算香农熵、按照给定的特征划分数据集、选择最好的数据集划分方式。

Step4:训练算法

使用 createTree() 函数

# 使用上面的创建决策树的代码,构造预测隐形眼镜的决策树
lensesTree = createTree(lenses, lensesLabels)
storeTree(lensesTree, 'classifierLenes.pkl')
print(lensesTree)# infoGain= 0.03939650364612124 bestFeature= 0 1.3260875253642983 1.286691021718177
# infoGain= 0.039510835423565815 bestFeature= 1 1.3260875253642983 1.2865766899407325
# infoGain= 0.37700523001147723 bestFeature= 2 1.3260875253642983 0.9490822953528211
# infoGain= 0.5487949406953986 bestFeature= 3 1.3260875253642983 0.7772925846688997
# infoGain= 0.22125183600446618 bestFeature= 0 1.5545851693377994 1.3333333333333333
# infoGain= 0.09543725231055489 bestFeature= 1 1.5545851693377994 1.4591479170272446
# infoGain= 0.7704260414863776 bestFeature= 2 1.5545851693377994 0.7841591278514218
# infoGain= 0.2516291673878229 bestFeature= 0 0.9182958340544896 0.6666666666666666
# infoGain= 0.4591479170272448 bestFeature= 1 0.9182958340544896 0.4591479170272448
# infoGain= 0.9182958340544896 bestFeature= 0 0.9182958340544896 0.0
# infoGain= 0.3166890883150208 bestFeature= 0 0.6500224216483541 0.3333333333333333
# infoGain= 0.19087450462110933 bestFeature= 1 0.6500224216483541 0.4591479170272448
# infoGain= 1.0 bestFeature= 0 1.0 0.0
# {'tearRate': {'normal': {'astigmatic': {'yes': {'prescript': {'hyper': {'age': {'presbyopic': 'no lenses', 'young': 'hard', 'pre': 'no lenses'}}, 'myope': 'hard'}},
# 'no': {'age': {'presbyopic': {'prescript': {'hyper': 'soft', 'myope': 'no lenses'}}, 'young': 'soft', 'pre': 'soft'}}}},
# 'reduced': 'no lenses'}}

Step5:测试算法

同案例1,编写测试函数以便验证决策树可以正确分类给定的数据实例。

Step6:使用算法

if __name__ == '__main__':lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']lensesTree = loadTree('classifierLenes.pkl')result = classify(lensesTree, lensesLabels, ['young', 'myope', 'no', 'reduced'])print(result)# +++ tearRate xxx {'reduced': 'no lenses', 'normal': {'astigmatic': {'yes': {'prescript': {'hyper': {'age': {'presbyopic': 'no lenses', 'young': 'hard', 'pre': 'no lenses'}}, 'myope': 'hard'}}, 'no': {'age': {'presbyopic': {'prescript': {'hyper': 'soft', 'myope': 'no lenses'}}, 'young': 'soft', 'pre': 'soft'}}}}} --- reduced >>> no lenses
# no lenses

总结

书中案例 2 给出的是一个过度匹配(overfitting)的例子,我们可以通过裁剪决策树,合并相邻的无法产生大量信息增益的叶子节点,消除过度匹配问题。有关于剪枝的方法将在“回归问题:树回归”部分来讨论。今天就到这里吧!See You!


参考文献

  • https://github.com/apachecn/AiLearning/tree/master/docs/ml
  • https://blog.csdn.net/c406495762/column/info/16415
  • https://www.bilibili.com/video/av36993857
  • https://space.bilibili.com/97678687/channel/detail?cid=22486
  • https://space.bilibili.com/97678687/channel/detail?cid=13045
  • https://zhuanlan.zhihu.com/p/85731206

往期活动

LSGO软件技术团队会定期开展提升编程技能的刻意练习活动,希望大家能够参与进来一起刻意练习,一起学习进步!

  • Python基础刻意练习活动即将开启,你参加吗?
  • Task01:变量、运算符与数据类型
  • Task02:条件与循环
  • Task03:列表与元组
  • Task04:字符串与序列
  • Task05:函数与Lambda表达式
  • Task06:字典与集合
  • Task07:文件与文件系统
  • Task08:异常处理
  • Task09:else 与 with 语句(1day)
  • Task10:类与对象
  • Task11:魔法方法
  • Task12:模块

刻意练习:机器学习实战 -- Task01. 决策树相关推荐

  1. 机器学习实战之决策树熵的概述

    机器学习实战之决策树熵的概述 一.决策树简介 二.决策树的一般流程 三.决策树构建的准备工作 1.特征选择 (1)香农熵 (2)编写代码计算经验熵 (3)信息增益 (4)编写代码计算信息增益 2.决策 ...

  2. 刻意练习:机器学习实战 -- Task01. K邻近算法

    背景 这是我们为拥有 Python 基础的同学推出的精进技能的"机器学习实战" 刻意练习活动,这也是我们本学期推出的第三次活动了. 我们准备利用8周时间,夯实机器学习常用算法,完成 ...

  3. 机器学习实战——绘制决策树(代码)

    最近在学习Peter Harrington的<机器学习实战>,代码与书中的略有不同,但可以顺利运行. import matplotlib.pyplot as plt# 定义文本框和箭头格式 ...

  4. 机器学习实战之决策树(一)构造决策树

    决策树(一)构造决策树 1.简介 1.1 优缺点 1.2 流程 1.3 决策树的构造 1.4 海洋生物数据 2.信息增益 2.1 几个概念 2.2 计算给定数据集的熵 3 划分数据集 3.1 按照给定 ...

  5. 《机器学习实战》—— 决策树

    目录 一.决策树的构造 1. 信息增益 2. 划分数据集 3. 递归构建决策树 二.在 Python 中使用 Matplotlib 注解绘制树形图 1. Matplotlib 2. 构造注解树 三.测 ...

  6. 《机器学习实战》——决策树

    一 决策树 决策树是什么?决策树(decision tree)是一种基本的分类与回归方法.举个通俗易懂的例子,如下图所示的流程图就是一个决策树,长方形代表判断模块(decision block),椭圆 ...

  7. 机器学习实战之决策树

    你是否玩过二十个问题的游戏,游戏的规则很简单:参与游戏的一方在脑海里想某个事物,其他参与者向他提问题,只允许提20个问题,问题的答案也只能用对或错回答.问问题的人通过 推断分解,逐步缩小待猜测事物的范 ...

  8. 《机器学习实战》决策树的应用

    课本中给出的是一个预测隐形眼镜的例子. 数据集样式如下: young    myope    no    reduced    no lenses young    myope    no    no ...

  9. 机器学习实战之决策树(四)示例:预测隐形眼镜类型(含数据集)

    决策树(四)示例:预测隐形眼镜类型 流程 代码 决策树小结 转载请注明作者和出处:https://blog.csdn.net/weixin_45814668 微信公众号:qiongjian0427 知 ...

最新文章

  1. jQueryUI Repeater 无刷新删除 新建 更新数据 - JQueryElement [7]
  2. Swift4 - 动态计算UITableView中tableHeaderView的高度 - 获取子控件高度和宽度
  3. 和ISP合作需要了解什么?—Vecloud微云
  4. How is Attachment property retrieved
  5. c语言字符比较思路,C语言讲解思路资料
  6. C++ STL 线性容器的用法
  7. Vue 实现 Open Graph 分享预览
  8. TypeScript Never 与 Unknown
  9. MySql中执行 GROUP BY 分组 遇到 1055错误
  10. 全球自动驾驶第一梯队“成团出道”,谁站 C 位?
  11. JAVA-ZIP和GZIP压缩实现
  12. 苹果手机怎么在照片上添加文字_不管用什么手机,这样操作一下,就能直接给照片添加文字...
  13. 《笨方法学Python》第一期
  14. 梦幻西游端游脚本制作教程
  15. OpenG入门之配置lib文件+include文件
  16. Android中的占位符
  17. 一文了解刀片服务器与机架服务器,原创好文!
  18. [题解][CF-1292C]Xenon‘s Attack on the Gangs
  19. JS逆向 --- 易盾有感滑块
  20. dnf剑魂buff等级上限_DNF:国服鬼剑85版本以来的变迁,剑魂最惨,阿修罗起伏不大...

热门文章

  1. 千呼万唤始出来——uFUN开发板2.0开箱评测
  2. 王牌战争文明重启服务器维护中,王牌战争文明重启常见问题解答-王牌战争文明重启常见问题一览_牛游戏网...
  3. Docker容器域名解析失败
  4. ​​​​Convolutional Neural Networks on Graphswith Fast Localized Spectral Filtering
  5. Rcurl--炼数成金课程第一周
  6. 后台管理页面通过点击左侧导航栏的菜单项实现右边内容的改变
  7. Java ThreadFactory接口用法
  8. 爱思服务器能不能更新苹果手机系统,iPhone 无法更新系统怎么办?
  9. 你不可不知的Java引用类型之——SoftReference源码详解
  10. Glide加载图片模糊问题