决策树の学习
一.概述
决策树(Decision Tree)是有监督学习中的一种算法,并且是一种基本的分类与回归的方法。也就是说,决策树有 两种:分类树和回归树。这里先学了分类树,回归树挖个坑,以后学了再更新。
什么是决策树?
我们先来看一看大佬们举的栗子!
在这个例子中我们看出算法的本质其实是在树上进行选择的一个过程。这种最基本的数据结构我们这里简单的再回顾一下哈:
我们可以把决策树看作是一个if-then规则的集合。将决策树转换成if-then规则的过程是这样的:
- 由决策树的根节点到叶节点的每一条路径构建一条规则
- 路径上中间节点的特征对应着规则的条件,也叶节点的类标签对应着规则的结论
决策树的路径或者其对应的if-then规则集合有一个重要的性质:互斥并且完备。也就是说,每一个实例都被 有且仅有一条路径或者规则所覆盖。这里的覆盖是指实例的特征与路径上的特征一致,或实例满足规则的条件。
二.决策树の准备阶段!
使用决策树做分类的每一个步骤都很重要,首先我们要收集足够多的数据,如果数据收集不到位,将会导致没有足 够的特征去构建错误率低的决策树。数据特征充足,但是不知道用哪些特征好,也会导致最终无法构建出分类效果好的决策树。从算法方面来看的话,决策树的构建就是我们的核心内容。
决策树如何构建呢?通常,这一过程可以概括为3个步骤:特征选择、决策树的生成和决策树的剪枝。关于剪枝部分,等俺学了回归树再接着说哈。
2.1特征の选择
通过懒汉养宠物的例子我们可以看到,我们每一次向下一层查询都是通过对特征的判断。因此我们选择用哪个特征来进行特征空间的划分,同时也是是对分类能力的选择。如果我们选择了一个特征,我们根据其进行分类的结果与随机分类的结果差别不大的话,那么这个特征也就相当于没有分类的能力,那么根据经验来说我们就可以去掉这样的特征。
那如何来选择最优的特征来划分呢?一般而言,随着划分过程不断进行,我们希望决策树的分支节点所包含的样本 尽可能属于同一类别,也就是节点的纯度(purity)越来越高。
下面三个图表示的是纯度越来越低的过程,最后一个表示的是纯度最低的状态。
在实际使用中,我们衡量的常常是不纯度。度量不纯度的指标有很多种,比如:熵、增益率、基尼值数。
这里我们使用的是熵,也叫作香农熵,这个名字来源于信息论之父 克劳德·香农。
2.1.1 香农熵以及其相应的python函数实现
根据小学二年级的知识(不是)可以知道:熵为信息的期望值。在信息论与概率统计中,熵是表示随机变量不确定性的度量。
假定当前样本集合D中一共有n类样本,第i类样本为xi,那么的xi信息定义为:
通过上式,我们可以得到所有类别的信息。为了计算熵,我们需要计算所有类别所有可能值包含的信息期望值(数 学期望),通过下面的公式得到:
我们根据定义以及上述饼图的例子可以知道,Ent的值越小,D的不纯度就越低,换句话也就是纯度越高。
代码如下:
def calEnt(data):
n = data.shape[0]
iterset = data.iloc[: , -1].value_counts()
p = iterset/n
ent = (-p*np.log2(p)).sum()
return ent
我们以树上的海洋生物为例子哈,其数据长这样:
我们在这里同时准备一下数据:
def load_data():
data = {'no surfacing':[1,1,1,0,0],
'flippers':[1,1,0,1,1],
'fish':['yes','yes','no','no','no']}
dataset = pd.DataFrame(data)
return dataset
计算出来的熵:0.9709505944546686
2.1.2信息增益
信息增益(Information Gain)的计算公式其实就是父节点的信息熵与其下所有子节点总信息熵之差。但这里要注 意的是,此时计算子节点的总信息熵不能简单求和,而要求在求和汇总之前进行修正。
假设离散属性a有V个可能的取值 ,若使用a对样本数据集D进行划分,则会产生V个分支节点, 其中第v个分支节点包含了D中所有在属性a上取值为 的样本,记为 .我们可根据信息熵的计算公式计算出 的 信息熵,再考虑到不同的分支节点所包含的样本数不同,给分支节点赋予权重 ,这就是所谓的的修正。
所以信息增益的计算公式为
我们先算一下第0列的信息增熵:
a=(3/5)*(-(2/3)*np.log2(2/3)-(1/3)*np.log2(1/3))
calEnt(dataSet)-a
2.2数据集最佳切分函数
划分数据集的最大准则是选择最大信息增益,也就是信息下降最快的方向。
返回值是最佳切分列的下标,之后我们用这个下标进行切分
# 1.初始化信息增熵,最佳下标以及计算树根的香农熵
# 2.循环每一列,取出每一列的所有可能取值
# 循环每一个取值并计算取值节点的香农熵
# 3.计算信息增熵 并判断是否要更新最大信息增熵
def bestsplit(data):
baseEnt = calEnt(data)
bestGain = 0 #初始化信息增益
axis = -1 #初始化列下标
for i in range(data.shape[1] -1):
levels = data.iloc[: , i].value_counts().index #该列的所有取值可能 [1,0]
ents = 0
for j in levels:
childset = data[data.iloc[: , i] == j] #分别找出该列中每一类的所有信息
ent = calEnt(childset)
ents += ent * (childset.shape[0] / data.shape[0])
infoGain = baseEnt - ents
# print('第i列的信息增熵为:' , infoGain)
if(infoGain > bestGain):
bestGain = infoGain
axis = i
return axis
2.3 切分函数
简单的按照2.2中最佳切分函数返回回来的下标进行切分就可以了,代码实现如下:
def Split(data , axis , value):
col = data.columns[axis]
dataSet = data.loc[data[col]==value,:].drop(col , axis = 1)
return dataSet
三.用递归法构建决策树
目前我们已经学习了从数据集构造决策树算法所需要的子功能模块,其工作原理如下:得到原始数据集,然后基于 最好的属性值划分数据集,由于特征值可能多于两个,因此可能存在大于两个分支的数据集划分。第一次划分之 后,数据集被向下传递到树的分支的下一个结点。在这个结点上,我们可以再次划分数据。因此我们可以采用递归 的原则处理数据集。
决策树生成算法递归地产生决策树,直到不能继续下去未为止。这样产生的树往往对训练数据的分类很准确,但对 未知的测试数据的分类却没有那么准确,即出现过拟合现象。过拟合的原因在于学习时过多地考虑如何提高对训练 数据的正确分类,从而构建出过于复杂的决策树。解决这个问题的办法是考虑决策树的复杂度,对已生成的决策树进行简化,也就是常说的剪枝处理。剪枝处理的具体学习之后再说哈。
3.1 ID3算法
构建决策树的算法有很多,比如ID3、C4.5和CART,基于《机器学习实战》这本书,我们选择ID3算法。
ID3算法的核心是在决策树各个节点上对应信息增益准则选择特征,递归地构建决策树。具体方法是:从根节点开 始,对节点计算所有可能的特征的信息增益,选择信息增益最大的特征作为节点的特征,由该特征的不同取值建立 子节点;再对子节点递归地调用以上方法,构建决策树;直到所有特征的信息增益均很小或没有特征可以选择为 止。最后得到一个决策树。
递归结束的条件是:程序遍历完所有的特征列,或者每个分支下的所有实例都具有相同的分类。如果所有实例具有 相同分类,则得到一个叶节点。任何到达叶节点的数据必然属于叶节点的分类,即叶节点里面必须是标签。
3.2 代码实现:
# 1.提取出标签行featlist
# 2.将标签列整合后提取,同时判断是否需要返回
# 返回条件:(1)标签列全相同
# (2)数据没有列了
# 返回值:当前的标签值
# 3.选出最佳的划分列的下标后,将该列标签加入Tree中并删除该标签
# 4.找出最佳划分列的列值,不重复的存储(set容器)
# 5.循环最佳划分列的列值,进行递归,递归结果存入Tree中
def creatTree(data):
featlist = list(data.columns)#标签行
classlist = data.iloc[ : , -1].value_counts()
if(data.shape[1] == 1 or classlist.shape[0] == data.shape[0]):
return classlist.index[0] #返回标签值 (no surfacing , flippers , fish)
axis = bestsplit(data)
bestfeat = featlist[axis]
ID3Tree = {bestfeat:{}}
del featlist[axis] #当前特征存到数后删除
valuelist = set(data.iloc[ : , axis])
for value in valuelist:
ID3Tree[bestfeat][value] = creatTree(Split(data,axis,value))
return ID3Tree
四.使用决策树进行分类
老样子,我们把数据集划分为训练和测试两部分,然后建树后与原本的lable进行比较并计算准确率。
代码如下:
def classify(inputTree,labels, testVec):
firstStr = next(iter(inputTree)) #获取决策树第一个节点
secondDict = inputTree[firstStr] #下一个字典
featIndex = labels.index(firstStr) #第一个节点所在列的索引
for key in secondDict.keys():
if testVec[featIndex] == key:
if type(secondDict[key]) == dict :
classLabel = classify(secondDict[key], labels, testVec)
else:
classLabel = secondDict[key]
return classLabel
def acc_classify(train,test):
inputTree = creatTree(train) #根据测试集生成一棵树
labels = list(train.columns) #数据集所有的列名称
result = []
for i in range(test.shape[0]): #对测试集中每一条数据进行循环
testVec = test.iloc[i,:-1] #测试集中的一个实例
classLabel = classify(inputTree,labels,testVec) #预测该实例的分类
result.append(classLabel) #将分类结果追加到result列表中
test['predict']=result #将预测结果追加到测试集最后一列
acc = (test.iloc[:,-1]==test.iloc[:,-2]).mean() #计算准确率
print(f'模型预测准确率为{acc}')
这里数据提前要进行处理的:
data = load_data()
train = data
test = data.iloc[:3,:]
acc_classify(train,test)
结果非常的阿妹惊啊!
准确率居然达到了100%!!!!
好像可以用SKlearn中graphviz包实现决策树的绘制,我看大佬们画出来超好看,然后自己也尝试画了画,结果在导包过程中graphviz无限报错,到现在也没有整好,实在是非常的自闭了,希望有大佬看到后能帮忙处理一下啦!
我找网上其他巨巨的代码,是这样的(有点小修改):
def SKTree(dataSet):
# 特征
Xtrain = dataSet.iloc[:,:-1]
# 标签
Ytrain = dataSet.iloc[:,-1]
labels = Ytrain.unique().tolist()
Ytrain = Ytrain.apply(lambda x: labels.index(x)) #将本文转换为数字
# 绘制树模型
clf = DecisionTreeClassifier()
clf = clf.fit(Xtrain, Ytrain)
tree.export_graphviz(clf)
dot_data = tree.export_graphviz(clf, out_file=None)
graphviz.Source(dot_data)
# 给图形增加标签和颜色
dot_data = tree.export_graphviz(clf, out_file=None,
feature_names=['no surfacing', 'flippers'],
class_names=['fish', 'not fish'],
filled=True, rounded=True,
special_characters=True)
graph = pydotplus.graph_from_dot_data(dot_data)
graph.write_png('Tree.png') # 保存图像
Image(graph.create_png())
# graphviz.Source(dot_data)
# # 利用render方法生成图形
# graph = graphviz.Source(dot_data)
# graph.render("fish")
如果能帮俺解决,俺将十分感谢!!!!