机器学习—决策树
本文作为周志华《机器学习》的阅读笔记。(1)基本流程一般的,一棵决策树包含一个根节点、若干个内部节点和若干个叶节点。叶节点对应于决策结果,其他每个节点则对应于一个属性测试;每个节点包含的样本集合根据属性测试的结果被划分到子节点中;根节点包含样本全集。从根节点到每个叶节点的路径对应一个判定测试序列。决策树的学习目的是得到一棵泛化能力强的决策树。决策树是递归过程,三种情形会导致递归返回:1、当前节点包
作者:WenWu_Both
出处:http://blog.csdn.net/wenwu_both/article/
版权:本文版权归作者和CSDN博客共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文链接;否则必究法律责任
本文作为周志华《机器学习》的阅读笔记。
(1)基本流程
一般的,一棵决策树包含一个根节点、若干个内部节点和若干个叶节点。叶节点对应于决策结果,其他每个节点则对应于一个属性测试;每个节点包含的样本集合根据属性测试的结果被划分到子节点中;根节点包含样本全集。从根节点到每个叶节点的路径对应一个判定测试序列。决策树的学习目的是得到一棵泛化能力强的决策树。
决策树是递归过程,三种情形会导致递归返回:
1、当前节点包含的样本全属于同一类别,无需划分
2、当前属性集为空,或是所有样本在所有属性上取值相同,无法划分
3、当前节点包含的样本集为空,不能划分
(2)划分选择
期望的目标:决策树的分支节点所包含的样本尽可能属于同一类别,即节点的“纯度”越来越高
1、信息增益
假定当前样本集合D中的第k类样本所占的比例为pk,则D的信息熵定义为:
Ent(D)的值越高,则D的纯度越高
假定离散属性a有V个可能的取值,若使用a来对样本集D进行划分,则会产生V个分支节点,其中第v个分支节点包含了D中所有在属性a上取值为的样本,记为,可计算出用属性a对样本集D进行划分所获得的“信息增益”(information gain)
一般而言,信息增益越大,则意味着使用属性a来进行划分所获得的“纯度提升”越大。因此,我们可用信息增益来进行决策树的划分属性选择。
著名的ID3决策树算法就是以信息增益为准则来选择划分属性。
2、增益率
实际上,信息增益准则对可取值数目较多的属性有所偏好,为减少这种偏好可能带来的不利影响,著名的C4.5决策树算法不直接使用信息增益,而是使用“增益率”来选择最优划分模型。增益率定义为:
3、基尼指数(Gini index)
CART决策树使用“基尼指数”来选择划分属性,数据集的纯度可用基尼值来度量:
选择那个使得划分后基尼指数最小的属性作为最优划分属性。
(3)剪枝处理
剪枝是决策树学习算法对付过拟合的主要手段。
预剪枝:在决策树生成过程中,对每个节点在划分前进行估计,若当前节点的划分不能带来决策树泛化能力的提高,则将当前节点定为叶节点。
后剪枝:完整决策树生成后,由底向上对非叶节点进行考察,若将该非叶结点替换为叶节点能带来决策树泛化能力提高,则完成替换。
下面基于python3.5进行基于信息增益的决策树(ID3)编程实现,数据集采用西瓜数据集2.0,具体如下:
编号,色泽,根蒂,敲声,纹理,脐部,触感,好瓜
1,青绿,蜷缩,浊响,清晰,凹陷,硬滑,是
2,乌黑,蜷缩,沉闷,清晰,凹陷,硬滑,是
3,乌黑,蜷缩,浊响,清晰,凹陷,硬滑,是
4,青绿,蜷缩,沉闷,清晰,凹陷,硬滑,是
5,浅白,蜷缩,浊响,清晰,凹陷,硬滑,是
6,青绿,稍蜷,浊响,清晰,稍凹,软粘,是
7,乌黑,稍蜷,浊响,稍糊,稍凹,软粘,是
8,乌黑,稍蜷,浊响,清晰,稍凹,硬滑,是
9,乌黑,稍蜷,沉闷,稍糊,稍凹,硬滑,否
10,青绿,硬挺,清脆,清晰,平坦,软粘,否
11,浅白,硬挺,清脆,模糊,平坦,硬滑,否
12,浅白,蜷缩,浊响,模糊,平坦,软粘,否
13,青绿,稍蜷,浊响,稍糊,凹陷,硬滑,否
14,浅白,稍蜷,沉闷,稍糊,凹陷,硬滑,否
15,乌黑,稍蜷,浊响,清晰,稍凹,软粘,否
16,浅白,蜷缩,浊响,模糊,平坦,硬滑,否
17,青绿,蜷缩,沉闷,稍糊,稍凹,硬滑,否
代码参考博客:http://whatbeg.com/2016/04/23/decisiontree.html
# Python 3.5
from math import log
from operator import itemgetter
def filetoDataSet(filename): # 构建训练集(从txt文件读入)
fr = open(filename,'r')
all_lines = fr.readlines()
featname = all_lines[0].strip().split(',')[1:-1]
print(featname)
dataSet = []
for line in all_lines[1:]:
line = line.strip()
lis = line.split(',')[1:]
dataSet.append(lis)
fr.close()
return dataSet,featname
def calcEnt(dataSet): #计算信息熵
numEntries = len(dataSet) # 样本数
labelCounts = {}
for featVec in dataSet:
label = featVec[-1] # 结果标签
if label not in labelCounts.keys(): # 计算不同结果标签的样本个数
labelCounts[label] = 0
labelCounts[label] += 1
Ent = 0.0
for key in labelCounts.keys():
p_i = float(labelCounts[key]/numEntries)
Ent -= p_i * log(p_i,2) # 累减
return Ent
def splitDataSet(dataSet, axis, value): #划分数据集,找出第axis个属性为value的数据
returnSet = []
for featVec in dataSet:
if featVec[axis] == value:
retVec = featVec[:axis]
retVec.extend(featVec[axis+1:])
returnSet.append(retVec)
return returnSet
def chooseBestFeat(dataSet):
numFeat = len(dataSet[0])-1 # 属性数量
Entropy = calcEnt(dataSet) # 样本集的信息熵
DataSetlen = float(len(dataSet)) # 样本数量
bestGain = 0.0
bestFeat = -1 # 初始化
for i in range(numFeat):
allvalue = [featVec[i] for featVec in dataSet] # 每一列
specvalue = set(allvalue) # 去除重复值
nowEntropy = 0.0
for v in specvalue:
Dv = splitDataSet(dataSet,i,v)
p = len(Dv)/DataSetlen
nowEntropy += p * calcEnt(Dv)
if Entropy - nowEntropy > bestGain:
bestGain = Entropy - nowEntropy
bestFeat = i
return bestFeat
def Vote(classList): # 如果属性用完,类别仍不一致,投票决定
classdic = {}
for vote in classList:
if vote not in classdic.keys():
classdic[vote] = 0
classdic[vote] += 1
sortedclassDic = sorted(classdic.items(),key=itemgetter(1),reverse=True)
return sortedclassDic[0][0]
def createDecisionTree(dataSet,featnames):
featname = featnames[:] ################
classlist = [featvec[-1] for featvec in dataSet] #此节点的分类情况
if classlist.count(classlist[0]) == len(classlist): #全部属于一类
return classlist[0]
if len(dataSet[0]) == 1: #分完了,没有属性了
return Vote(classlist) #少数服从多数
# 选择一个最优特征进行划分
bestFeat = chooseBestFeat(dataSet)
bestFeatname = featname[bestFeat]
del(featname[bestFeat]) #防止下标不准
DecisionTree = {bestFeatname:{}}
# 创建分支,先找出所有属性值,即分支数
allvalue = [vec[bestFeat] for vec in dataSet]
specvalue = sorted(list(set(allvalue))) #使有一定顺序
for v in specvalue:
copyfeatname = featname[:]
DecisionTree[bestFeatname][v] = createDecisionTree(splitDataSet(dataSet,bestFeat,v),copyfeatname) # 递归
return DecisionTree
if __name__ == '__main__':
filename = "C:\\Users\\JiaoTong\\Desktop\\CSDN\\tree_data.txt"
DataSet,featname = filetoDataSet(filename)
Tree = createDecisionTree(DataSet,featname)
print(Tree)
顺带基于matplotlib进行决策树可视化:
treePlot.py
import matplotlib.pyplot as plt
from matplotlib.pylab import *
import treesID3 as decTree
mpl.rcParams['font.sans-serif'] = ['SimHei']
decNode = dict(boxstyle="sawtooth",fc='0.8')
leafNode = dict(boxstyle="round4",fc='0.8')
arrow_args = dict(arrowstyle="<-")
def plotNode(nodeTxt, centerPt, fatherPt, nodeType):
createPlot.ax1.annotate(nodeTxt, xy=fatherPt, xycoords='axes fraction', xytext=centerPt, textcoords = 'axes fraction', va='center', ha='center', bbox=nodeType, arrowprops=arrow_args)
def getNumLeafs(Tree):
num = 0
root = list(Tree.keys())[0]
firstGen = Tree[root]
for key in firstGen.keys():
if type(firstGen[key]) == type({}):
num += getNumLeafs(firstGen[key])
else:
num += 1
return num
def DepthofTree(Tree):
maxdepth = 0
root = list(Tree.keys())[0]
firstGen = Tree[root]
for key in firstGen.keys():
if type(firstGen[key]) == type({}):
depth = 1 + DepthofTree(firstGen[key])
else:
depth = 1
if depth > maxdepth:
maxdepth = depth
return maxdepth
def plotMidText(nowPt, fatherPt, txt):
xMid = (fatherPt[0]-nowPt[0]) / 2.0 + nowPt[0]
yMid = (fatherPt[1]-nowPt[1]) / 2.0 + nowPt[1]
createPlot.ax1.text(xMid,yMid,txt)
def plotTree(Tree, fatherPt, nodeTxt):
numLeafs = getNumLeafs(Tree)
depth = DepthofTree(Tree)
root = list(Tree.keys())[0]
nowPt = (plotTree.xoff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yoff)
plotMidText(nowPt,fatherPt,nodeTxt)
plotNode(root, nowPt, fatherPt, decNode)
firstGen = Tree[root]
plotTree.yoff = plotTree.yoff - 1.0/plotTree.totalD
for key in firstGen.keys():
if type(firstGen[key]) == type({}):
plotTree(firstGen[key], nowPt, str(key))
else:
plotTree.xoff = plotTree.xoff + 1.0/plotTree.totalW
plotNode(firstGen[key], (plotTree.xoff, plotTree.yoff), nowPt, leafNode)
plotMidText((plotTree.xoff, plotTree.yoff), nowPt, str(key))
plotTree.yoff = plotTree.yoff + 1.0/plotTree.totalD
def createPlot(Tree):
fig = plt.figure(1, facecolor='white')
fig.clf()
axprops = dict(xticks=[],yticks=[])
createPlot.ax1 = plt.subplot(111,frameon=False,**axprops)
plotTree.totalW = float(getNumLeafs(Tree))
plotTree.totalD = float(DepthofTree(Tree))
plotTree.xoff = -1.0 / 2.0 / plotTree.totalW # 1分成2倍叶子数那么多份
plotTree.yoff = 1.0
plotTree(Tree, (0.5,1.0), '')
plt.show()
if __name__ == '__main__':
filename = "C:\\Users\\JiaoTong\\Desktop\\CSDN\\tree_data.txt"
DataSet,featname = decTree.filetoDataSet(filename)
Tree = decTree.createDecisionTree(DataSet,featname)
print(Tree)
createPlot(Tree)
执行程序,生成结果如下:
如果想将上面的程序改成基于增益率的决策树算法(C4.5),只需要修改chooseBestFeat函数即可,其修改如下:
def chooseBestFeat(dataSet): # 该步骤直接决定了不同的决策树算法
# 基于增益率
numFeat = len(dataSet[0])-1 # 属性数量
Entropy = calcEnt(dataSet) # 样本集的信息熵
DataSetlen = float(len(dataSet)) # 样本数量
bestGain_ratio = 0.0
bestFeat = -1 # 初始化
for i in range(numFeat):
allvalue = [featVec[i] for featVec in dataSet] # 每一列
specvalue = set(allvalue) # 去除重复值
nowEntropy = 0.0
IV = 0.0
for v in specvalue:
Dv = splitDataSet(dataSet,i,v)
p = len(Dv)/DataSetlen
nowEntropy += p * calcEnt(Dv)
IV -= p * log(p,2) # 此处计算的是IV(a)
if (Entropy - nowEntropy)/IV > bestGain_ratio:
bestGain_ratio = (Entropy - nowEntropy)/IV
bestFeat = i
return bestFeat
同样,我们可以继续改写,得到基于基尼系数的决策树算法(CART),只需要修改calcEnt函数及chooseBestFeat函数,修改如下:
a. calcEnt函数换为calcGini函数
def calcGini(dataSet): #计算基尼系数
numEntries = len(dataSet) # 样本数
labelCounts = {}
for featVec in dataSet:
label = featVec[-1] # 结果标签
if label not in labelCounts.keys(): # 计算不同结果标签的样本个数
labelCounts[label] = 0
labelCounts[label] += 1
Gini = 0.0
for key in labelCounts.keys():
p_i = float(labelCounts[key]/numEntries)
Gini -= p_i * p_i # 累减
Gini += 1
return Gini
b. 改写chooseBestFeat函数
def chooseBestFeat(dataSet): # 该步骤直接决定了不同的决策树算法
# 基于基尼系数
numFeat = len(dataSet[0])-1 # 属性数量
DataSetlen = float(len(dataSet)) # 样本数量
bestGini_index = 1.0
bestFeat = -1 # 初始化
for i in range(numFeat):
allvalue = [featVec[i] for featVec in dataSet] # 每一列
specvalue = set(allvalue) # 去除重复值
Gini_index = 0.0
for v in specvalue:
Dv = splitDataSet(dataSet,i,v)
p = len(Dv)/DataSetlen
Gini_index += p * calcGini(Dv)
if Gini_index < bestGini_index:
bestGini_index = Gini_index
bestFeat = i
return bestFeat
可以看到,在西瓜集上,基于信息增益及基尼系数得到的结果是一样的,而基于增益率生成的树与上面两种略有差别。
(4)连续与缺失值
1、连续值处理
由于连续属性的可取值数目不再有限,因此,不能直接根据连续属性的可取值对节点进行划分,此时,连续属性离散化技术派上用场。最简单的策略是采用二分法(bi-partition)对连续属性进行处理,这也是C4.5决策树采用的机制。
其基本过程如下:
a. 将样本集D(样本数为n)的连续属性a的值从小到大排序。
b. 相邻两个值求平均数,则得到n-1个值(t1,t2,…ti,…tn-1)。
c. 计算划分点为ti时的信息增益,谁的信息增益最大,则选择谁作为划分点。
2、缺失值处理
主要面临两个问题:
a. 如何在属性值缺失的情况下进行划分属性选择?
b. 给定划分属性,若样本在该属性上的值缺失,如何对样本进行划分?
下面给出C4.5的解决策略:
实际上是基于权重法,为每一个属性添加一个权重。
(5)多变量决策树
这一部分以后专门用一篇博文来讲。
PS: 记录书中的一道习题,感觉非常有意思。
Q: 试将决策树生成的深度优先搜索过程改为广度优先搜索,以参数MaxNode控制树的最大节点数,并与基于队列的决策树算法进行比较,分析哪种方式更易于控制决策树所需存储不超过内存。
更多推荐
所有评论(0)