《机器学习实战》
第三章.决策树算法(ID3)
3.1决策树算法名词概念解释
信息:这个是熵和信息增益的基础概念,。例如:‘鸡’与‘狗’(加引号表示说明的名称)表示修饰和定义,没加引号即单纯的说存在的这个动物。’鸡’是用来修饰鸡这个动物的,’狗’也同样,这个定义也可以被修改,如果把狗这种动物命名为鸡,那么看到狗就可以称之为鸡。信息应该是对一个抽象事物的命名,无论用不用这个信息来命名这种事物,这种事物均是客观存在的。引用香农的话,信息是用来消除随机不确定性的东西
当然这句话虽然经典,但是还是很难去搞明白这种东西到底是个什么样,可能在不同的地方来说,指的东西又不一样,从数学的角度来说可能更加清楚一些,数学本来就是建造在悬崖之上的一种理论,一种抽象的理论,利用抽象来解释抽象可能更加恰当,同时也是再机器学习决策树中用的定义,如果待分类的事物集合可以划分为多个类别中则某个类(xi)的信息公式如下:
I(X)表示随机变量的信息
p(xi)指是当xi发生时的概率
随机变量:随机变量是概率论中的概念,是从样本空间到实数集的一个映射。在数学中,不用正面或者反面当作数学运算的介质,而是用数字0或者1来进行表示。
在这个例子中的随机变量则是:正面 - > 1,反面 - > 0这两个。。
样本空间:指所有随机事件发生的结果的并集,例如抛硬币时,有两个结果正面或者反面这两个结果称为样本空间。
随机事件:硬币是正面,硬币是反面。两个随机事件
熵:在信息论和概率论中熵是对随机变量不确定性的度量,与上边联系起来,熵便是信息的期望值。
公式如下:
熵只依赖X的分布,和X的取值没有关系,熵是用来度量不确定性,当熵越大,概率说X=xi的不确定性越大,反之越小,在机器学期中分类中说,熵越大即这个类别的不确定性更大,反之越小
条件熵:条件熵是用来解释信息增益而引入的概念,概率定义:随机变量X在给定条件下随机变量Y的条件熵,对定义描述为:X给定条件下Y的条件干率分布的熵对X的数学期望,在机器学习中为选定某个特征后的熵。
公式如下:
在机器学习中为选定某个特征后的熵
这个公式是对条件概率熵求期望,但是上边说是选定某个特征的熵,因为一个特征可以将待分类的事物集合分为多类,即一个特征对应着多个类别,因此在此的多个分类即为X的取值。
信息增益:信息增益在决策树算法中是用来选择特征的指标,信息增益越大,则这个特征的选择性越好,在概率中定义为:待分类的集合的熵和选定某个特征的条件熵之差(这里只的是经验熵或经验条件熵,由于真正的熵并不知道,是根据样本计算出来的)。
公式如下:
信息增益是经验熵和经验条件熵之差
注意:这里不要理解偏差,因为上边说了熵是类别的,但是在这里又说是集合的熵,没区别,因为在计算熵的时候是根据各个类别对应的值求期望来等到熵。
3.2决策树算法概述
决策树是一种简单高效并且具有强解释性的模型,广泛应用于数据分析领域。其本质是一颗由多个判断节点组成的树(可以是二叉树也可以是非二叉树)。在使用模型进行预测时,根据输入参数依次在各个判断节点进行判断游走,最后到叶子节点即为预测结果。决策树算法的核心是通过对数据的学习,选定判断节点,构造一颗合适的决策树。
其每个非叶节点表示一个特征属性上的测试,每个分支代表这个特征属性在某个值域上的输出,而每个叶节点存放一个类别。使用决策树进行决策的过程就是从根节点开始,测试待分类项中相应的特征属性,并按照其值选择输出分支,直到到达叶子节点,将叶子节点存放的类别作为决策结果。
决策树 |
优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据。 缺点:可能产生过度匹配的问题 适用数据类型:数值型和标称型 |
3.3决策树算法特点
一般的,一棵决策树包含一个根节点,若干个内部节点和若干个叶子结点。叶子节点对应与决策结果,其他每个节点则对应于一个属性测试,每个节点包含的样本集合根据属性测试的结果被划分到子节点中,根节点包含样本全集。从根节点到每个叶子节点的路径对应了一个判定测试序列。
决策树学习的目的是为了产生一棵泛化能力强,即处理未见示例能力强的决策树,其余本流程遵循简单且直观的分而治之的策略
3.4决策树的一般流程
决策树的一般流程 |
(1) 收集数据:可以使用任何方法 (2) 准备数据:树构造方法只适用于标称型数据,因此数值型数据必须离散化 (3) 分析数据:可以使用任何方法,沟枣树完成之后,我们应该检查图形是否符合预期 (4) 训练算法:构造树的数据结构 (5) 测试算法:使用经验树计算错误率 (6) 使用算法:此步骤可以适用于任何监督学习算法,而使用决策树可以更好的地理解数据的内在含义 |
伪代码:#创建分支的伪代码函数creatBranch() if so return 类标签 else 寻找划分数据集的最好特征 划分数据集 创建分支节点 for 每个划分的子集 调节函数creatBranch()并增加返回结果到分支节点中 return 分支节点
3.5决策树的实现过程
1.解决第一个问题:当前数据集上那个特征在划分数据分类时起了决定性作用
划分数据集的方法使用信息论度量信息。求信息增益
求其余特征的信息熵之前要先求得根结点的信息熵。下面代码求得即是根节点的信息熵
之后利用根节点的信息熵,求其余特征的信息熵
3.5.1计算信息增益代码:
#计算熵
def calcShannonEnt(dataSet):#参数传入数据集
numEntries = len(dataSet)#计算数据集中样例的总数
#print("numEntries:",numEntries)
'''求所有可能的分类创建字典'''
labelCounts = {}#定义一个数据字典存储类别与类别出现次数
for featVecin dataSet:
currentLabel = featVec[-1]#创建数据字典,键值 是最后一列的数值
#print("current:",currentLabel)
#将类别作为键,出现的次数作为值。统计值
if currentLabelnot in labelCounts.keys():#如果当前简直不存在则扩展字典并将当前键值加入字典,没个键值都记录了当前类别出现的次数
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1#该类标签下含有数据的个数
#print("labelcounts:",labelCounts)
shannonEnt = 0.0#定义一个float类型
#print(currentLabel)
'''以2为底求对数'''
#样例有两种分类根节点的信息熵为
#-(2/6*log2(2/6) + 4/6*log2(4/6))= 0.9709505944546686
#计算流程以输入例子为例
for key in labelCounts:
prob = float(labelCounts[key]) / numEntries
shannonEnt -= prob * log(prob,2)
return shannonEnt
#书上样例测试
def createDataSet():
dataSet = [
[1,1,'yes'],
[1,1,'yes'],
[0,1,'no'],
[0,1,'no'],
[1,0,'no']
]
labels = ['no surfacing','flippers']
return dataSet,labels
myDat,labels = createDataSet()
print(myDat)
ans = calcShannonEnt(myDat)
print(ans)
3.5.2按照给定特征划分数据集
'''
append与extend的函数说明
a = [1,2,3]
b = [4,5,6]
a.append(b)
a = [1,2,3,[4,5,6]]
a = [1,2,3]
a.extend(b)
a = [1,2,3,4,5,6]
'''
#按照给定特征划分数据集
def splitDataSet(dataSet,axis, value):#输入有三个参数,上面的数据集、划分数据集的特征、需要返回的特征的值
retDataSet = []#为了方便划分数据集并且在运算过程中不修改原始数据集合,创建一个新的列表(此列表中的元素也是列表)。
for featVecin dataSet:
if featVec[axis] == value:
#print("feat:",featVec[0])
reducedFeatVec = featVec[:axis]#对于矩阵运算中axis有三个值:-1(表示默认)0(列)1(表示行)
#reducedFeatVec = []
#print("reducedFeatVec:/",reducedFeatVec)
#print("featVec[:axis]:~~~~",featVec[:axis])
reducedFeatVec.extend(featVec[axis+1:])
#print("featVec[axis+1:----]",featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
3.5.3实现选取特征划分集
#a =splitDataSet(myDat,1,1)
#print(a)
#print("函数",splitDataSet(myDat,0, 1))
#实现选取特征划分数据集,计算得出最好的划分数据集的特征
#输入的数据必须满足是一种由列表元素组成的列表且所有的列表元素都要具有相同的数据长度
#第二个要求列表元素中的最后一个元素是当前实例的类别元素
def chooseBestFeatrueToSplit(dataSet):
numFeatures = len(dataSet[0]) -1#求出数据集的特征数量
#print("numFeatures",numFeatures)
baseEntropy = calcShannonEnt(dataSet)#计算整个数据集的原始熵
bestInfoGain = 0.0
bestFeature = -1
#循环遍历数据集中的所有特征,使用列表推导式子来创建新的列表,将所有可能存在的特征值存入列表中
for i in range(numFeatures):
featList = [example[i] for examplein dataSet]#得到每个特征的值存入列表种
#print(featList)
uniqueVals = set(featList)#将上面列表中的所有特征值,存入set容器中,只存入一次
newEntropy = 0.0
for valuein uniqueVals:#求出每个特征值
subDataSet = splitDataSet(dataSet,i ,value)#划分数据集
#print("subDataSet",subDataSet)
prob = len(subDataSet) /float(len(dataSet))#求熵
newEntropy += prob *calcShannonEnt(subDataSet)
infoGain = baseEntropy -newEntropy
if(infoGain > bestInfoGain):#求出最大的熵
bestInfoGain = infoGain
bestFeature = i
return bestFeature
myDat,labels = createDataSet()
#print(myDat)
#print(chooseBestFeatrueToSplit(myDat))
3.5.4递归构建决策树:
伪代码:
输入: 训练集D = {(x1,y1),(x2,y2),(x3,y3),...(xm,ym)};
属性集A = {a1,a2,......ad}
过程:函数TresGenerate(D,A)
1:生成节点node
2:ifD中样本全属于同一类别C then
3: 将node标记为C类叶节点;return
4:end if:
5:ifA = 空 OR D 中样本在A 上的取值相同then
6: 将node标记为叶节点,起类别标记为D中样本数最多的类;return
7:end if
8:从A中选择最有划分属性a*;
9:fora*的每个值a do
10: 为node生成一个分支;令Dv表示D中在a*上取值为a*的样本子集
11: ifDv为空 then
12: 将分支节点标记为叶节点,其类别标记为D中样本最多的类;return
13: else
14: 以TreeGenerate(Dv,A\{a*})为分支节点
15: endif
16:end for
输出:以node为根节点的一棵决策树
3.5.4构建决策树-递归终结点条件处理
返回出现次数最多的分类名称
函数说明:
如果划分了所有的类别之后,在末端仍然有数据是不相等的,但却无法根据特征继续划分时,则根据结果的次数进行排序
def majorityCnt(classList):#传入的参数是分类名称的列表返回一个次数最多的分类名称
classCount = {}
for votein classList:#vote作为循环列表的元素,进行统计
if votenot in classCount.keys():
classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.iteritems(),key= operator.itemgetter(1),reverse = True)#排序函数从大到小
return sortedClassCount[0][0]
'''
sorted 函数
sorted() 函数对所有可迭代的对象进行排序操作。
sort 与 sorted区别:
sort 是应用在list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
list 的 sort 方法返回的是对已经存在的列表进行操作,而内建函数 sorted 方法返回的是一个新的 list。
而不是在原来的基础上进行的操作。
sorted(iterable[, cmp[, key[,reverse]]])
参数说明:
iterable -- 可迭代对象。
cmp -- 比较的函数,这个具有两个参数,参数的值都是从可迭代对象中取出,此函数必须遵守的规则为,大于则返回1,小于则返回-1,等于则返回0。
key -- 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
reverse -- 排序规则,reverse = True 降序 , reverse = False 升序(默认)。
返回值是一个新的列表,而不是对原来的列表进行操作
递归生成决策树
决策树递归生成过程中递归基有三种情况
(1)当前结点包含的样本全属于同一类别,无需继续划分
(2)当前属性集为空,或是所有样本在所有属性上取值相同不需划分
(3)当前节点包含的样本集合为空,不能划分
说明:
在第(2)种情形下,我们把当前结点标记为叶子结点,并且将其类别设定为该节点所含样本最多的类别
在第(3)种情形下,同样把当前节点标记为叶子节点,但是将其类别设定为其父节点所含样本最多的类别
'''
def createTree(dataSet,labels):#参数说明:dataSet:数据集,labels表示的不是数据分类类别的标签,而是特征的标签
classList = [example[-1]for examplein dataSet]#从dataSet最后一列中获取类标签列表
print("class:",classList)
if classList.count(classList[0]) ==len(classList):#类别完全相同时,则停止继续划分递归基的第一种情况
return classList[0]
#classlist以第一项元素的标签计数,如果等于列表个数,说明列表中值都是一样的'''
if len(dataSet[0])==1:#若划分的特征已经结束,但是仍然不能将数据及划分成仅包含唯一类别的分组,则使用之前的函数将次数最多的类别返回
return majorityCnt(classList)
#遍历完所有特征时返回出现次数最多的类别
bestFeat =chooseBestFeatrueToSplit(dataSet)#针对当前数据集选择最优划分特征
bestFeatLabel = labels[bestFeat]#获得对应的维度标签
print("bestFeatLabel",bestFeatLabel)
myTree = {bestFeatLabel:{}}
del(labels[bestFeat])#将已选维度标签从标签列表中删除
featValues = [example[bestFeat] forexample in dataSet]
uniqueVals = set(featValues)
for valuein uniqueVals:
subLabels = labels[:]
myTree[bestFeatLabel][value] =createTree(splitDataSet(dataSet,bestFeat, value), subLabels)
return myTree
myDat, labels = createDataSet()
myTree = createTree(myDat,labels)
#分类器
def classify(inputTree,featLabels,testVec):
firstStr = list(inputTree.keys())
firstStr = firstStr[0]
secondDict = inputTree[firstStr]
featIndex =featLabels.index(firstStr)
for key in secondDict.keys():
if testVec[featIndex] == key:
if type(secondDict[key]).__name__== 'dict':
classLabel =classify(secondDict[key],featLabels,testVec)
else : classLabel = secondDict[key]
return classLabel
#决策树的存储
def storeTree(inputTree,filename):
import pickle
fw = open(filename,'w')
pickle.dump(inputTree,fw)
fw.close()
def grabTree(filename):
import pickle
fr = open(filename)
return pickle.load(fr)
pickle包:
持久性的基本思想很简单。假定有一个 Python 程序,它可能是一个管理日常待办事项的程序,您希望在多次执行这个程序之间可以保存应用程序对象(待办事项)。换句话说,您希望将对象存储在磁盘上,便于以后检索。这就是持久性。
现在已经导入了该模块,接下来让我们看一下 pickle 接口。 pickle 模块提供了以下函数对: dumps(object) 返回一个字符串,它包含一个 pickle 格式的对象; loads(string) 返回包含在 pickle 字符串中的对象; dump(object, file) 将对象写到文件,这个文件可以是实际的物理文件,但也可以是任何类似于文件的对象,这个对象具有 write() 方法,可以接受单个的字符串参数; load(file) 返回包含在 pickle 文件中的对象。
缺省情况下, dumps() 和dump() 使用可打印的 ASCII 表示来创建pickle。两者都有一个 final 参数(可选),如果为True ,则该参数指定用更快以及更小的二进制表示来创建 pickle。 loads() 和 load() 函数自动检测 pickle 是二进制格式还是文本格式。
参考文档:
numpy模块之axis
http://blog.csdn.net/fangjian1204/article/details/53055219
机器学习之决策树算法
http://blog.csdn.net/loveliuzz/article/details/77924817
http://blog.csdn.net/peng825223208/article/details/31415611
了解信息增益与决策树
https://www.cnblogs.com/wentingtu/archive/2012/03/24/2416235.html
机器学习—熵、信息、信息增益
https://www.cnblogs.com/fantasy01/p/4581803.html