您的位置:首页 > 其它

【机器学习】决策树(上)

2018-01-05 16:05 204 查看
前言:决策树是一种基本的分类与回归算法。可以认为是if-then规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。

学习时,利用训练数据,根据损失函数最小化原则建立决策树模型。

学习包括3个步骤:特征选择、决策树的生成、决策树的修建

一、决策树模型 更多参照博文

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

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

二、学习过程:

决策树学习用损失函数表示这一目标

决策树学习的损失函数:通常是正则化的极大似然函数。

决策树学习的策略:是以损失函数为目标函数的最小化。

决策树学习的算法:通常是一个递归地选择最优特征,并根据这一特征对训练数据进行分割,使得对各个子数据集有一个最好的分类的过程。这一过程对应特征空间的划分,也对应着决策树的构建。

三、决策树的构建:开始,构建根结点,将所有训练数据都放在根结点。选择一个最优特征,按照这一特征将训练数据集分割成子集,使得各个子集有一个在当前条件下最好的分类。若这些子集已经能够被基本正确分类,那么构建叶结点,并将这些子集分到所对应的叶结点中去;若还有子集不能被正确分类,那么就对这些子集选择新的最优特征,继续对其进行分割,构建相应的结点。如此递归,直至所有训练数据子集被基本正确分类,或者没有合适的特征为止。最后每个子集都被分到叶结点上,即都有了明确的类。这就生成了决策树。

以上方法生成的决策树可能对训练数据由很好的分类能力,但对未知测试数据未必有很好的分类能力,即可能发生过拟合现象。就需要对已生成的树自下而上进行剪枝,将树变得简单,从而使它具有更好的泛化能力。具体是:去掉过于细分的叶结点,使其回退到父节点,甚至更高的结点,然后将父节点或更高的结点改为新的叶结点。

由于决策树表示一个条件概率分布,所以深浅不同的决策树对应着不同复杂度的概率模型。

决策树的生成对应着模型的局部选择,只考了局部最优;

决策树的剪枝对应于模型的全局选择,考了全局最优。

四、熵

N个数据样本中的任意m个数据作为可以贷款的样本的所有情况w:

w=CmN=N!m!(N−m)!

对于银行来说,任意样本既可以当作贷款样本,也可以当作不能贷款样本,由此,w表示了样本的不确定性。

设n1,n2为变量,则w(n1,n2)=N!n1!n2!是关于n1,n2的函数,由此n1,n2的变化反映了w的变化。当N=n1+n2很大时,w将变得非常大。为了方便度量和剔除N<
1fff7
/span>的影响,定义函数H(n1,n2)=1Nlnw=1NlnN!n1!n2!表示上述组合的不确定性。数学中String公式: lnN!~NlnN−N因此:H=1Nlnw=1NlnN!−1Nlnn1!−1Nlnn2!=−n1Nlnn1N−n2Nlnn2N

熵:表示随机变量不确定性的度量。

设X是一个取有限个值的离散随机变量,概率分布为:P(X=xi)=pi,i=1,2,...,n,

随机变量X的熵定义为:H(X)=−∑ni=1pilogpi

1、熵越大,随机变量的不确定性就越大。

2、对于而分类问题:当p=0或p=1时,随机变量完全没有不确定性。当p=0.5时,H(P)=1,熵取值最大,随机变量不确定性最大。

设有随机变量(X,Y),其联合概率分布为

P(X=xi,Y=yi)=pij,i=1,2,...n;j=1,2,...,m

条件熵H(Y|X):表示在已知随机变量X的条件下随机变量Y的不确定性。

定义: X在给定条件下Y的条件概率分布的熵对X的数学期望H(Y|X)=∑ni=1piH(Y|X=xi),这里,pi=P(X=xi),i=1,2,...,n。

五、信息增益:表示得知特征X的信息而使得类Y的信息的不确定性减少的程度。

定义:特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即

g(D,A)=H(D)−H(D|A).一般,也称为互信息。

决策树学习中的信息增益等价于训练数据集中类与特征的互信息。

设训练数据集为D,|D|表示其样本容量,即样本个数。设有K个类Ck, k=1,2,…,K,|Ck|为属于类Ck的样本个数,∑Kk=1|Ck|=|D|。设特征A有n个不同的取值{a1,a2,...,an},根据特征A的取值将D划分为n个子集D1,D2,...,Dn, |Di|为Di的样本个数, ∑ni=1|Di|=|D|。记子集Di中属于类Ck的样本的集合为Dik,即Dik=Di∩Ck,|Dik|为Dik的样本个数。

算法1:信息增益的算法:

输入:训练数据集D和特征A;

输出:特征A对训练数据集D的信息增益g(D,A).

(1)计算数据集D的经验熵:H(D)=−∑Kk=1|Ck||D|log2|Ck||D|

(2)计算特征A对数据集D的经验条件熵H(D|A)

H(D|A)=∑ni=1|Di||D|H(Di)=−∑ni=1|Di||D|∑Kk=1Dik|Di|log2|Dik||Di|

(3)计算信息增益g(D,A)=H(D)−H(D|A)

六、特征选取方法:对训练数据集D,计算其每个特征的信息增益,并比较它们的大小,选择信息增益最大的特征。

代码实现如下:

1、计算熵的代码实现如下:

# 计算给定数据集的熵
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
labelCounts = {}  # 健是类别,值是此类别出现的次数

for featVec in dataSet: #遍历每个实例,统计标签的频次
currentLabel = featVec[-1]  #向量最后一个元素表示类别
if currentLabel not in labelCounts.keys():  # dict.keys():返回一个字典所有的键。
labelCounts[currentLabel] = 0  # 若此类别是第一次出现,则对应的健值为0;否则,将此类别健对应的值+1。
labelCounts[currentLabel] += 1

shannonEnt = 0.0  # 初始熵
for key in labelCounts:  # 遍历字典中的健
prob = float(labelCounts[key]) / numEntries
shannonEnt -= prob * log(prob, 2)
return shannonEnt


补充:

1、首先计算数据集中实例的总数。

2、创建一个字典,健为类别,值为对应的类别出现的次数。

3、遍历每个实例,统计标签的频次:首先通过遍历依次取出每个实例对应的标签,肯定有重复;然后给标签计数,利用创建的字典,若实例对应的标签不在字典的健中,则,在字典中加入此标签的健,并将对应的健值复制为0;否则直接将此标签对应的健值+1。

4、利用健值对,计算每个标签出现的频率。

dict.keys():返回一个字典所有的键。

下面给出一个简单的示例:



利用函数createDataSet()得到上图所示的简单鱼坚定数据集:

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


在命令提示符下输入,计算熵:

>>> import trees
>>> myData, labels = trees.createDataSet()
>>> myData
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
>>> trees.calcShannonEnt(myData)
0.9709505944546686


进而,当我们在数据集中添加更多的分类时,发现熵增大了!如下例:

>>> myData[0][-1]='maybe' #将myData列表的第一个元素[1,1,'yes']的最后一个位置的值改为maybe。
>>> myData
[[1, 1, 'maybe'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
>>> trees.calcShannonEnt(myData)
1.3709505944546687


事实上,以信息增益做为划分训练数据集的特征,存在偏向于选择取值较多的特征。使用信息增益比矫正了这一问题。

2、划分数据集的代码实现:

将对每个特征划分数据集的结果计算一次信息熵,然后判断按照哪个特征划分数据集是最好的划分方式。

#按照给定特征划分数据集
def splitDataSet(dataSet, axis, value): #参数分别是待划分的数据集,划分数据集的特征, 需要返回的特征的值
retDataSet=[] #在函数内部对列表对象的修改,会影响该列表对象的整个生存周期。因为该函数代码在同一数据集上被调用多次,为了不修改原始数据集,创建一个新的列表对象。
for featVec in dataSet: #数据集这个列表中的各个元素也是列表,我们要遍历数据集中的每个元素,一旦发现符合要求的值,则将其添加到新创建的列表中。
if featVec[axis]==value: #将特征axis对应的取值为value的那些数据取出
reducedFeatVec=featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet


补充:

1、参数分别是待划分的数据集,划分数据集的特征, 需要返回的特征的值

2、加入给定特征年龄,对应的值取青年,则划分数据集返回的是符合特征的数据(数据集中特征年龄对应的取值为青年的那些数据),并且那些数据中去掉了年龄这一特征。

示例:命令提示符下输入:

>>> import trees
>>> myData, labels=trees.createDataSet()
>>> myData
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
>>> trees.splitDataSet(myData, 0, 1)#第一个特征取值为1的那些数据
[[1, 'yes'], [1, 'yes'], [0, 'no']]
>>> trees.splitDataSet(myData, 0,0)
[[1, 'no'], [1, 'no']]
>>> trees.splitDataSet(myData, 1, 1)
[[1, 'yes'], [1, 'yes'], [0, 'no'], [0, 'no']]
>>> trees.splitDataSet(myData, 1, 0)
[[1, 'no']]


接下来将遍历整个数据集,循环计算熵和splitDataSet()函数,找到最好的特征划分方式。

3、选择最好的数据集划分方式代码实现:

#选择最好的数据集划分方式
def chooseBestFeatureToSplit(dataSet):
numFeatures=len(dataSet[0])-1 #计算有多少特征属性:读取数据集中第一个元素的长度-1;
# 前提是数据是一种由列表元素组成的列表,而且所有的列表元素都要具有相同的数据长度;
#数据的最后一列或者每个实例的最后一个元素是当前实例的类别标签。
baseEntropy=calcShannonEnt(dataSet)
bestInfoGain=0.0 #初始化最好的信息增益值为0。0
bestFeature=-1 #初始化最好的特征

#2、遍历特征,得到特征i对应的所有不同的取值
for i in range(numFeatures): #遍历每个特征
featList=[example[i] for example in dataSet] ##第i个特征的所有取值
uniqueVals=set(featList) #创建唯一的分类标签列表。从列表中创建集合是python语言得到列表中唯一元素值的最快方法。

#3、计算特征i的条件熵,首先遍历特征i的所有不同取值,得到每个对应特征值的子数据集。如年龄这一特征中青年,中年,老年所分别对应的子数据集;计算三种特征值的概率,乘以对应数据集的熵;在累加。
newEntropy=0.0 #初始化熵
for value in uniqueVals: #第i个特征取值为value时对应的子数据集
subDataSet=splitDataSet(dataSet, i, value)
prob=len(subDataSet)/float(len(dataSet))
newEntropy+=prob*calcShannonEnt(subDataSet)

#4、计算信息增益
InfoGain=baseEntropy-newEntropy
if (InfoGain>bestInfoGain):
bestInfoGain=InfoGain
bestFeature=i
return bestFeature


补充:

1、numFeatures=len(dataSet[0])-1: 计算有多少特征属性:读取数据集中第一个元素的长度-1;

前提1、数据是一种由列表元素组成的列表,而且所有的列表元素都要具有相同的数据长度;

前提2、数据的最后一列或者每个实例的最后一个元素是当前实例的类别标签。

2、遍历特征,得到特征i的所有不同的取值。

featList=[example[i] for example in dataSet]: 第i个特征的所有取值

uniqueVals=set(featList): 创建唯一的取值列表。从列表中创建集合是python语言得到列表中唯一元素值的最快方法。

3、

在命令提示符下输入:

>>> import trees
>>> myData, labels=trees.createDataSet()
>>> trees.chooseBestFeatureToSplit(myData)
0
>>> myData
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]


代码告诉我们,第0个特征是最好的用于划分数据集的特征。

目前得到了从数据集构造决策树算法所需要的子功能模块,其工作原理如下:得到原始数据集,然后基于最好的属性值划分数据集,由于特征值可能多于两个,因此可能存在大于两个分支的数据集划分。第一次划分后,数据将被向下传递到树分支的下一个节点,在这个节点是那个再次划分数据。因此可以采用递归的原则处理数据集。

递归结束的条件:程序遍历完所有划分数据集的属性,或者每个分支下的所有实例都具有相同的分类。若所有实例具有相同的分类,则得到一个叶子节点或终止块。任何到达叶子节点的数据必然属于叶子节点的分类。

在运算开始运行前计算列的数目,查看算法是否使用了所有属性即可。如果数据集已经处理了所有属性,但是类标签依然不是唯一的,此时我们需要决定如何定义该叶子节点,在这种情况下,通常采用多数表决的方法决定该叶子节点的分类。

4、以多数表决的形式定义类标签并不统一的叶子节点:

majorityCnt(classList):
classCount={}
for vote in classList:
if vote not in classCount.keys():
classCount[vote]=0
classCount[vote] +=1
sortedClassCount=sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
#operator.itemgetter(1)定义了一个函数,获取对象的第1个域的值。这里第一个域是指字典中的值。
return sortedClassCount


补充:

”’与第二章classify部分的投票表决代码类似:创建健值为claasList中唯一值的数据字典,字典对象存储了classList中每个类标签出现的频率,最后用operator操作健值排序字典,并返回出现次数最多的分类名称。

reverse:默认为false(升序排列),定义为True时将按降序排列”’

5、创建树的函数代码:

#创建树的函数代码
def creatTree(dataSet, labels): #labels表示的不是数据分类类别的标签,而是特征的标签
classList=[example[-1] for example in dataSet] #包含了数据集所有的类标签
if classList.count(classList[0])==len(classList): #若列表classList中元素classList[0]的个数等于列表长度,即所有类标签完全相同
return classList[0]
if len(dataSet[0])==1:
return  majorityCnt(classList)
# 若特征为空集,即是单节点树,则返回实例中出现次数最多的类别。
# 使用完所有的特征,仍然不能将数据集划分成仅包含唯一类别的分组。此时用majorityVnt函数挑选出出现次数最多的类别作为返回值。

bestFeat=chooseBestFeatureToSplit(dataSet) #获取最好的特征
bestFeatLabel=labels[bestFeat] #获取最好特征的标签
myTree={bestFeatLabel:{}} #建立当前树结点,即最好特征的标签及空值
del(labels[bestFeat]) #从特征的标签中删除最好的特征对应的标签
featValues=[example[bestFeat] for example in dataSet] #获得数据集中最好特征的取值
uniqueVals=set(featValues) #set(): 是一个无序不重复元素集
for value in uniqueVals:
subLabels=labels[:] #复制了类标签
myTree[bestFeatLabel][value]=creatTree(splitDataSet(dataSet, bestFeat, value), subLabels)
return myTree


补充:

1、输入参数:数据集和特征的标签列表

2、创建classList列表变量,包含数据集的所有类标签

3、若数据集的所有类标签完全相同,则直接返回该标签

4、若使用完了所有的特征,仍然不能将数据集划分成仅包含唯一类别的分组,则使用majorityCnt函数挑选出次数最多的类别作为返回值

5、使用字典类型变量myTree存储树的所有信息,利用chooseBestFeatureToSplit函数对当前数据集选取最好特征bestFeat,bestFeat是最好特征的索引,利用labels[bestFeat]获得最好特征的标签

6、创建树{bestFeatLabel:{}},从特征标签中删除最好的那个特征

7、得到数据集中最好特征对应的取值,放在列表中,并利用set(): 得到一个无序不重复元素集,即得到最好特征的不同取值

8、遍历当前选择特征包含的所有属性值,在每个数据集划分上递归地调用creatTree函数,得到的返回值被插入到字典变量myTree中,因此当函数终止执行时,字典中将会嵌套很多代表叶结点信息的字典数据。

9、复制特征类别标签,存储在新变量中。因为当python语言中函数参数是列表类型时,参数是按照引用方式传递的。为了保证每次调用函数creatTree时不改变原始列表的内容,使用新变量subLabels代替原始列表。

在命令提示符下输入:

>>> import trees
>>> myData, labels = trees.createDataSet()
>>> myTree=trees.creatTree(myData, labels)
>>> myTree
{'no surfscing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}


解释: myTree包含了很多代表树结构信息的嵌套字典,左边开始,第一个关键字no surfscing是第一个划分数据集的特征名称,该关键字的值也是另一个数据字典;

第二个关键字是no surfscing特征划分的数据集,这些关键字的值是no surfscing节点的子结点,这些值可能是类标签,也可能是另一个数据字典。若是类标签,则该子节点是叶子节点;若值是另一个数据字典,则子节点是一个判断节点,这种格式结构不断重复就构成了整棵树。

6、测试算法:使用决策树的分类函数

#7、使用决策树的分类算法
def classify(inputTree, featLabels, testVec): #输入决策树、特征标签列表、测试向量
firstStr=inputTree.keys()[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


补充:

str.index(str, beg=0, end=len(string))

str – 指定检索的字符串

beg – 开始索引,默认为0。

end – 结束索引,默认为字符串的长度。

参数是:决策树、特征标签列表、测试向量

1、利用inputTree.keys()[0]获取第一个判断特征firsrStr,将其对应的值记为第二个字典;

2、在输入的标签列表中利用featLabels.index(firstStr)获取第一个判断特征的索引;

3、第二个字典的健实际上是第一个特征的取值,即遍历第一个特征的取值,找到测试向量相应特征的取值;

4、若测试向量相应特征的取值(第二个字典的健)对应的值的类型是一个字典,则递归这个分类算法;参数分别为:key对应的健值,特征标签列表,测试向量;

…对应值的类型是类标签,则测试数据的类型就是这个类标签。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息