Python实现——决策树实例(离散数据/香农熵)
决策树的实现太...繁琐了。
如果只是接受他的原理的话还好说,但是要想用代码去实现比较糟心,目前运用了《机器学习实战》的代码手打了一遍,决定在这里一点点摸索一下该工程。
实例的代码在使用上运用了香农熵,并且都是来处理离散数据的,因此有一些局限性,但是对其进行深层次的解析有利于对于代码的运作,python语言的特点及书写肯定是有帮助的。
我们分别从每个函数开始:
- 计算香农熵
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
labelCounts = {}
for featVec in dataSet:
currentLabel = featVec[-1]
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
labelCounts[currentLabel] +=1
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob * log(prob,2)
return shannonEnt
该函数为当前的数据集计算香农熵。
其中,numEntries用来计数数据数目
numEntries = len(dataSet)
其后,该函数运用了一个字典来计算各个最终类(即我们所要最终分开的特点的所有类型,比如Titanic题目中就是是否生还,Coursera课程中的例子就是这个贷款是否安全)的出现数目,其中,该最终数据是处在数据集的最后一列的,因此运用
currentLabel = featVec[-1]
让currentLabel暂时记住当前数据的最终类型,倘使该类型不存在,就要用
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
将其插入字典并将它的键值初始化为0(即出现0次),最后用
labelCounts[currentLabel] +=1
计数代表当前最终类型出现数目+1
之后便是对于香农熵的计算,这里的代码上的理解并不困难
for key in labelCounts:
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob * log(prob,2)
log(prob,2)是以2为底数求对数
- 划分数据集
def splitDataSet(dataset, axis, value):
retDataSet = []
for featVec in dataset:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
该函数使用了三个参数:待划分的数据集,划分数据集的特征(即第axis个属性),需要返回的特征的值(该属性的取值value)。
首先由于需要将一类数据放于一起,但是python在函数中传递的是列表的引用,直接修改也会在全局上对列表产生变化,为了避免这种影响就需要新建一个空表存储目标数据。
retDataSet = []
再之后就是要将相应的数据放入这个列表中。
其中为了更好的进行以下的操作,因为本工程控制划分数目的方式看来是限定在一条线路中每个属性最多有一次作为划分指标,因此需要将其在加入retDataSet时去除,由下列代码实现:
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
书中提到了append()与extend()函数的区别,前者可以将列表整体作为一个元素加入新列表,后者则是将列表的元素分别加入新列表。
- 选出最好的划分方式
def chooseBestFeatureToSplit(dataset):
numFeatures = len(dataset[0]) - 1
baseEntropy = calcShannonEnt(dataset)
bestInfoGain = 0.0
bestFeature = -1
for i in range(numFeatures):
featList = [example[i] for example in dataset]
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>bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
当下我们所要做的是要遍历当前的数据集,不断计算所有属性划分对应的香农熵,由此选出最适合作为当前状况下划分标准的属性。
为了防止最终属性被选择,我们需要将其排除在外,默认最终属性一般会出现在最后一列,
numFeatures = len(dataset[0]) - 1
之后在for循环中numFeatures数目便不会再囊括最终属性了。
在这个时候我纠结了一下,万一最好的划分就是最终属性呢?不过目前我已经说服自己了,毕竟最终属性是我们的目标,不管怎样我们都要避开它并使用别的属性来进行划分。
之后初始化变量,分别来存储新的香农熵变化和当前数据。
bestInfoGain = 0.0
bestFeature = -1
现在进入这个遍历所有属性的for大循环。一开始我们先将当前属性的所有可能值传入一个空集合(即无重复元素的集合)。
featList = [example[i] for example in dataset]
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
其中用splitDataSet(),将当前处理的属性作为划分依据,用其内部所有可能的取值来分出子集,并计算对应的香农熵,然后根据比例再加权求和,得到该属性划分下的香农熵newEntropy以及相比直接作为叶子节点得到的香农熵的差infoGain。
if(infoGain>bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
最后与当前记录的最优属性的结果比对,如果更好就覆盖。最后返回最优属性。
- 选取出现最多的属性
def majorityCnt(classList):
classCount={}
for vote in classList:
if vote not in classCount.keys():classCount[vote]=0
classCount[vote] += 1
sortedClassCount = sorted(classClount.iteritems(),\
key=operator.itemgetter(1),reverse=True)
return sortedClassCount[0][0]
如果当前划分时已经没有更多的属性了,那么该节点自动变为叶子节点,其分类由其数据中最终属性出现最多的分类决定,即根据属性的分类创建一个字典classCount,并用分类作为键,出现次数为对应值。
随后用operator库中的sort()函数为其排序,选出出现最多的最终属性,以此作为该叶子节点的分类。
- 主体:创建树
def createTree(dataSet,labels):
classList = [example[-1] for example in dataSet]
if classList.count(classList[0]) == len(classList):
return classList[0]
if len(dataSet[0])==1:
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{}}
del(labels[bestFeat])
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
subLabels = labels[:]
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value),subLabels)
return myTree
这个函数运用了递归的想法,最终结果是以字典中的字典形式来表示的,虽然并不是特别直观,但至少是我能接受的工程量。
首先进行了两个判断,一是若当前数据集在最终分类上已经达成了共识,那么就不需要在进行分类了:
if classList.count(classList[0]) == len(classList):
return classList[0]
二,如果所有的属性已经被使用(排除,仅剩最终属性),也会停止递归:
if len(dataSet[0])==1:
return majorityCnt(classList)
在排除了以上情况后就会选择出当前最优的划分属性,并在其中开辟对应的子字典:
bestFeat = chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{ } }
并删除已经使用的属性:
del(labels[bestFeat])
紧接着要为上面刚选定的属性分类划分出各个子节点,并为其递归再次生成子树,方法集合了上文的许多技巧,暂时不再赘述。
以上,是我对于《机器学习实战》中的决策树实现代码的解析。
Python实现——决策树实例(离散数据/香农熵)的更多相关文章
- Python实现——决策树(部分函数/连续数据)
由于上一例的实现中只针对了离散数据,为了扩充处理范围,我实现了一下对线性数据的简单处理,在其中我选择用中位数作为指标,平均数.众数等等其他数据在我看来异曲同工,最终也都会有较相似的结构. 求连续数据的 ...
- python第六天 函数 python标准库实例大全
今天学习第一模块的最后一课课程--函数: python的第一个函数: 1 def func1(): 2 print('第一个函数') 3 return 0 4 func1() 1 同时返回多种类型时, ...
- python对离散数据进行编码
机器学习中会遇到一些离散型数据,无法带入模型进行训练,所以要对其进行编码,常用的编码方式有两种: 1.特征不具备大小意义的直接独热编码(one-hot encoding) 2.特征有大小意义的采用映射 ...
- python 类和实例
面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可 ...
- python使用xlrd读取excel数据时,整数变小数的解决办法
python使用xlrd读取excel数据时,整数变小数: 解决方法: 1.有个比较简单的就是在数字和日期的单元格内容前加上一个英文的逗号即可.如果数据比较多,也可以批量加英文逗号的前缀(网上都有方法 ...
- Python Socket请求网站获取数据
Python Socket请求网站获取数据 ---阻塞 I/O ->收快递,快递如果不到,就干不了其他的活 ---非阻塞I/0 ->收快递,不断的去问,有没有送到,有没有送到,. ...
- python操作txt文件中数据教程[4]-python去掉txt文件行尾换行
python操作txt文件中数据教程[4]-python去掉txt文件行尾换行 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考文章 python操作txt文件中数据教程[1]-使用pyt ...
- Python操作Mysql实例代码教程在线版(查询手册)_python
实例1.取得MYSQL的版本 在windows环境下安装mysql模块用于python开发 MySQL-python Windows下EXE安装文件下载 复制代码 代码如下: # -*- coding ...
- python类和实例以及__call__/__del__
面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可 ...
随机推荐
- Java面向对象-递归
Java面向对象-递归 递归,就是程序调用自身,我们讲的是方法递归调用,也就是在方法里自己调用自己: 我们给出一个案例,求阶乘 1*2*3*...*(n-1)*n 我们用非递归和递归方式分别实现下, ...
- javascript第三节
面向对象的程序设计 1.属性类型 ECMAScript中有两种属性:数据属性和访问器属性 数据属性: configurable设置为false,表示不能从对象中删除属性. 访问器属性: 支持定义多个属 ...
- Net Framework 4.0 和.Net Framework 4.0 Client Profile
Net Framework 4.0 和.Net Framework 4.0 Client Profile区别: .Net Framework 4.0毫无疑问就像是.Net Framework 2.0一 ...
- LibEvent代码阅读--多缓冲区和零拷贝技术
http://blog.chinaunix.net/uid-20937170-id-4827550.html
- Python基础学习三 文件操作(一)
文件读写 r,只读模式(默认). w,只写模式.[不可读:不存在则创建:存在则删除内容:] a,追加模式.[不可读: 不存在则创建:存在则只追加内容:] r+,[可读.可写:可追加,如果打开的文件不存 ...
- linux进行Java开发环境的部署
一.前言: 今天正式向linux开发进攻了,其中遇到一些问题简单的记录一下,为之后的再次部署提供方便. 二.linux的Java8安装的两种方法: 1.源安装很简单,一个命令搞定. sudo apt- ...
- 201671010127 2016—2017-2 java编程中遇到的问题
学习了Java的一些基本语法后,心里的激动无法按捺,总是比较Java与C语言语法的区别,一有闲时间就会用刚学的Java基本语法写一些简单的程序.这不,一不小心又陷入了困难,本人在此诚挚的请教各位园友, ...
- Game Develop Books
[Working On] [Pending] 3.<实时计算机图形学> 4.<游戏编程精粹1> 5.<游戏编程精粹2> 6.<3D游戏引擎设计:实时计算机图形 ...
- Spring总结二:IOC(控制反转)xml方式
1,简介: IoC :Inverse of control 控制反转 ,思想就是在项目中引入一个工厂容器,对项目中接口依赖对象的创建,实现项目中对于依赖对象解耦合. 将程序中对象的创建权以及对象的整个 ...
- 【原创】12. MYSQL++之Template Query
1. 什么是Template Query 在我们实际的编程过程中,我们很容易碰到printf这类需要在运行时来决定到底打印出什么的函数,例如 printf(“hello %s”, sth); 在这个例 ...