总结一下前一阵对于决策树的学习。为了从算法的层次理解并熟练运用机器学习的多种算法,我首先从用Python编写算法入手:
决策树属于机器学习中的监督学习,也就是说,其数据集需要人工对feature与label标定,比如表1,天气的各种属性与天气好坏的关系表:
项目 | Outlook(Feature1) | Temperature(Feature2) | Humidity(Feature3) | Windy(Feature4) | Good_State?(Label) |
---|---|---|---|---|---|
1 | sunny | hot | high | FALSE | no |
2 | sunny | hot | high | TRUE | no |
3 | sunny | hot | high | TRUE | no |
4 | overcast | hot | high | FALSE | yes |
5 | rainy | mild | high | FALSE | yes |
6 | rainy | cool | normal | FALSE | yes |
7 | rainy | cool | normal | TRUE | no |
8 | overcast | cool | normal | TRUE | yes |
9 | sunny | mild | high | FALSE | no |
10 | sunny | cool | normal | FALSE | yes |
11 | rainy | mild | normal | FALSE | yes |
12 | sunny | mild | normal | TRUE | yes |
13 | overcast | mild | high | TRUE | yes |
14 | overcast | hot | normal | FALSE | yes |
15 | rainy | mild | high | TRUE | no |
我们需要决策树做的就是找到一种分类器根据天气的四个feature判断出其所属State的标签。
下面介绍决策树:它从一组无次序、无规则的实例中推理出以决策树表示的分类规则[1]。采用自顶向下的递归方式,在决策树的内部节点(feature)进行属性的比较(比如根据feature2比较天气的温度高低),并根据不同属性值判断从该节点向下的分支,在决策树的叶节点得到结论。
如下图1,我们可以根据此决策树来表示决策逻辑。
(1)出发点:哪一个属性将在树的根节点被测试?--> 分类能力最好的属性
(2)对于这个属性的每个可能值产生一个分支,然后将训练样例分配到样例属性值所对应的分支之下
(3)对每个节点不断测试对其而言分类能力最好的属性
(4)重复4过程直到所有属性被测试
高中物理我们学过热力学第二定律:在孤立系统中,体系与环境没有能量交换,体系总是自发地像混乱度增大的方向变化,总使整个系统的熵值增大。熵就是表示分子状态混乱程度的物理量。
在信息论中,香农用熵的概念来描述信源的不确定度。我们可以用熵来表示数据集的纯度,与热熵类似,信息熵越小,数据集越纯净,即越多的数据具有相同的类别。
其中p(i)是数据集D中标签label取值为i的数据所占比例(概率)。在这里定义0log0=0,这里的log以2为底。
至于为什么信息熵要定义为如上形式,可以参考《Pattern Recognition and Machine Learning》[2]
熵:表示随机变量的不确定性。
条件熵:在一个条件下,随机变量的不确定性。(类似条件概率的理解)
信息增益即为 **熵 - 条件熵** ,即在一个条件下,信息不确定性减少的程度。
详细的解释可以参考这篇博客[3]。
其中,v属于某个Feature(属性)A的所有可能值的集合,D(v)是数据集D中FeatureA值为v的子集。Entropy(D)是D未用FeatureA分割之前的熵,Entropy(D(v))是D用FeatureA值为v分割之后的熵。
FeatureA的每一个可能取值都有一个熵(Entropy(D(v))),该熵的权重是取该FeatureA的可能值所占数据在所有数据集D中的比例(|D(v)|/|D|)。
选择信息增益最大的属性A作为当前节点的决策Feature。
原因:熵刻画了数据集的纯度,数据集越混乱,熵就越大。熵越小,数据集越纯净,越多的数据具有相同的类别。当熵为0时,数据集中的数据都相等。
FeatureA的信息增益就是按A来划分数据集时,数据集能比原来纯净多少。通过不断地划分,得到尽可能纯的节点,相当于降低数据集的熵。
当决策树划分到叶子节点时,其熵大多为0(除了部分叶子节点中标签还未一致,但数据集中所有Feature均已划分完毕)。
如果上述讲解仍觉得不甚理解,可以去找一道关于决策树的例题,手工计算信息熵及信息增益便会有更深的理解。
至于伪代码及具体的从算法层面的编程流程,推荐《人工智能导论》鲍军鹏、张选平编著。
# -*- coding: utf-8 -*-
"""
Created on Sun Nov 19 19:58:10 2017
@author: Lesley
"""
from math import log
#------------------------数据集-------------------------------
#四个因变量(属性) -> feature : "Outlook","Temperature","Humidity","Windy"
#标签 -> 是否出门 -> 'no','yes'
#-------------------------------------------------------------
def createDataSet():
dataSet=[["sunny","hot","high",'FALSE','no'],
["sunny","hot","high",'TRUE','no'],
["overcast","hot","high",'FALSE','yes'],
["rainy","mild","high",'FALSE','yes'],
["rainy","cool","normal",'FALSE','yes'],
["rainy","cool","normal",'TRUE','no'],
["overcast","cool","normal",'TRUE','yes'],
["sunny","mild","high",'FALSE','no'],
["sunny","cool","normal",'FALSE','yes'],
["rainy","mild","normal",'FALSE','yes'],
["sunny","mild","normal",'TRUE','yes'],
["overcast","mild","high",'TRUE','yes'],
["overcast","hot","normal",'FALSE','yes'],
["rainy","mild","high",'TRUE','no']
]
feature = ["Outlook","Temperature","Humidity","Windy"]
return dataSet, feature
#-------------计算数据集整体及各个feature的不同值的熵------------
def Entropy(dataSet):
sample_num = len(dataSet)
label_category = {}
for sample in dataSet:
if sample[-1] not in label_category:
label_category[sample[-1]] = 1
else:
label_category[sample[-1]] += 1 #各类别的数量
#print (label_category)
entropy = 0
for i in label_category:
temp = label_category[i]/sample_num
entropy -= temp * log(temp,2)
return entropy
#-------------根据不同feature的不同值筛选对应的数据集------------
def Extractdata(dataSet, axis, value): #筛选出的是第axis个feature中值为value的sample集合 且此集合中不含第axis个featur对应的数据
extDataSet = []
for sample in dataSet:
if sample[axis] == value:
extSample = sample[:axis] #复制第axis个featur之前的数据
extSample.extend(sample[axis+1:])#复制第axis个featur之后的数据
extDataSet.append(extSample) #删除第axis个featur对应的数据
return extDataSet
#--------对于每一节点得到最大的信息增益及对应的feature-----------
def BestFeature(dataSet):
feature_num = len(dataSet[0]) - 1 #数据集的最后一项是标签
entropy = Entropy(dataSet)
bestEntropy = 0
bestInfoGain = 0
bestFeature = -1
for i in range(feature_num):
featuresample = [example[i] for example in dataSet]
uniqueVals = set(featuresample) #去除list中的重复元素
newEntropy = 0
for value in uniqueVals:
extDataSet = Extractdata(dataSet, i, value)
prob = len(extDataSet) / float(len(dataSet)) #当前feature中各个值所占比重
newEntropy += prob * Entropy(extDataSet) #Entropy(extDataSet) 得到的是第i个feature中的第j个值的熵
gain = entropy - newEntropy #信息增益越大越好 -> 熵越小,数据越纯净
if gain > bestInfoGain:
bestInfoGain = gain
bestFeature = i
bestEntropy = entropy - gain
return bestInfoGain, bestFeature, bestEntropy
#--------------------------多数表决---------------------------
def majorityCnt(classList):
classCount = {}
for vote in classList:
if vote not in classCount.keys():
classCount[vote] = 0
classCount[vote] += 1
return max(classCount) #classlist中出现最多的元素
#----------------------------建树-----------------------------
def CreateTree(dataSet,feature):
classList = [example[-1] for example in dataSet] #标签项
best_infogain, best_feature, best_entropy= BestFeature(dataSet) #最大信息增益增益值与其所对应的feature的顺序
#--------------------停止划分的条件------------------------
#数据集中的最后一项:标签为同一个值(类别相同) best_entropy-> 最大熵为0
if classList.count(classList[0]) ==len(classList):
#print(best_entropy)
return classList[0]
#最大信息熵小于等于0
if best_infogain < 0 or best_infogain == 0:
return majorityCnt(classList)
#所有特征已经用完 -> 剩余标签
if len(dataSet[0]) == 1:
return majorityCnt(classList) #剩余的list中所有feature已被用完,但标签中仍没有一致,这是采用多数表决
#---------------------------------------------------------
best_feature_name = feature[best_feature] #最大信息增益对应的feature的名称
myTree = {best_feature_name:{}} #根节点 -> 某一个feature
del(feature[best_feature]) #删去已经处理过的feature属性
featuresample = [example[best_feature] for example in dataSet]
uniqueVals = set(featuresample) #对于每一个feature的各个值
for value in uniqueVals:
subfeature = feature[:]#复制,使进一步的调用函数不会对原feature产生影响。 python中函数传入的list参数若修改会影响其原始值
myTree[best_feature_name][value] = CreateTree( Extractdata(dataSet, best_feature, value),subfeature)
return myTree
def main():
data, feature = createDataSet()
myTree = CreateTree(data,feature)
print (myTree)
if __name__=='__main__':
main()
{'Outlook': {'rainy': {'Windy': {'FALSE': 'yes', 'TRUE': 'no'}}, 'overcast': 'yes', 'sunny': {'Humidity': {'high': 'no', 'normal': 'yes'}}}}
即生成了图1中所得到的决策树。
不知道大家有没有发现上述算法中存在的问题。当随着决策树的生长,其深度在不断增加,这时就会出现过拟合问题。过拟合会导致数据的预测效果不好。而如何防止过拟合及欠拟合的问题呢?我们下次再讲。