决策树算法-单棵树(上)

这次讲解机器学习里面非常经典的一个算法模型——分类树。由于篇幅比较长,所以特分为上下两篇讲解。本篇主要讲解决策树的原理,实际应用以及参数。

算法介绍

1.分类树原理

决策树是广泛应用于分类和回归任务的模型。本质上,它从一层层的if/else问题中进行学习,并得出结论。

想像一下,你想要区分下面四种动物:熊,鹰,企鹅和海豚。你的目标是通过提出尽可能少的if/else问题来得到正确答案。而这个提问过程可以表示为一棵决策树,如下图所示:

在这里插入图片描述

在这张图中,树的每个结点代表一个问题或一个包含答案的终结点(也叫叶结点)。树的边将问题的答案与将问的下一个问题连接起来。

用机器学习的语言来说就是,为了区分四类动物(鹰,企鹅,海豚和熊),我们利用三个特征(“有没有羽毛”,“会不会飞”和“有没有鳍”)来构建一个模型。我们利用监督学习从数据中学习模型,而无需人为构造模型。

2.构造分类树

学习分类树,就是学习一系列if/else问题,使我们能够以最快的速度得到正确答案。在机器学习中,这些问题叫做测试(PS:不要与测试集搞混,测试集是用来测试模型泛化性能的数据)。数据通常并不像动物的例子那样具有二元特性(是/否)的形式,而是表现为连续特征。而为了构造决策树,算法会搜遍所有可能的测试,找出对目标变量来说信息量最大的那个。
我们以一个测试为例,如下图所示:

在这里插入图片描述

将数据集在x[1]=0.0596处垂直划分可以得到最多信息,它在最大程度上将类别0中的点与类别1中的点进行区分。顶节点(也叫根结点)表示整个数据集,包含属于类别0的50个点和属于类别1的50个点。通过测试x[1]<=0.0596的真假来对数据集进行划分。

如果测试结果为真,那么将这个点分配给左结点,左结点里面包含属于类别0的2个点和属于类别1的32个点。否则将这个点分配给右结点,右结点里面包含属于类别0的48个点和属于类别1的18个点。

尽管第一次划分已经对两个类别做了很好的区分,但底部区域仍包含属于类别0的点,顶部区域也仍包含属于类别1的点。所以我们可以在两个区域中重复寻找最佳测试的过程,从而构建出更准确的模型。如下图所示:

在这里插入图片描述

开始时递归过程生成一棵二元决策树,其中的每个结点都包含一个测试。或者可以将每个测试看成沿着一条轴对当前数据进行划分。这是一种将算法看作分层划分的观点。

接下来对数据反复进行递归划分,直到划分后的每个区域(决策树的每个叶结点)只包含单一目标值(单一类别或单一回归值)。如果树中某个叶结点所包含数据点的目标值相同,那么这个叶结点就是纯的

要想对新数据点进行预测,首先要查看这个点位于特征空间划分的哪个区域,然后将该区域的多数目标值(如果是纯的叶结点,就是单一目标值)作为预测结果。从根结点开始对树进行遍历就可以找到这一区域,每一步向左还是向右取决于是否满足相应的测试。

决策树也可以用于回归任务,使用的方法相同。预测的方法是,基于每个结点的测试对树进行遍历,最终找到新数据点所属的叶结点。这一数据的输出即为此叶结点中所有训练点的平均目标值。

接下来,我们开始用真实数据进行建模操作。

scikit_learn的决策树在DecisionTreeRegressor类和DecisionTreeClassifier类中实现。

数据来源

红酒质量分类:https://www.kaggle.com/uciml/red-wine-quality-cortez-et-al-2009

在这里插入图片描述
该数据一共有1596条数据记录,包含挥发性酸度,柠檬酸,发酵后残留糖份等11个特征值,而我们所需要做的就是通过这些特征值来对红酒的质量(quality )进行分类,该数据中的质量以得分0到10之间来表示。

数据挖掘

1.导入第三方库并读取文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd
import numpy as np
import winreg
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor#决策树回归
from sklearn.tree import DecisionTreeClassifier#决策树分类器
from sklearn.metrics import accuracy_score
###################
real_address = winreg.OpenKey(winreg.HKEY_CURRENT_USER,r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders',)
file_address=winreg.QueryValueEx(real_address, "Desktop")[0]
file_address+='\\'
file_origin=file_address+"\\源数据-分析\\winequality-red.csv"###https://www.kaggle.com/uciml/red-wine-quality-cortez-et-al-2009
red_wine=pd.read_csv(file_origin)
#设立桌面绝对路径,读取源数据文件,这样将数据直接下载到桌面上就可以了,省得还要去找
###################

老规矩,上来先依次导入建模需要的各个模块,并读取文件。

2.清洗数据

查找缺失值:

在这里插入图片描述

从上面的结果来看,数据中没有缺失值。

3.建模

1
2
3
4
5
6
7
8
train=red_wine.drop(["quality"],axis=1)
X_train,X_test,y_train,y_test=train_test_split(train,red_wine["quality"],random_state=1)
###考虑到接下来可能需要进行其他的操作,所以定了一个随机种子,保证接下来的train和test是同一组数
classifier=DecisionTreeClassifier(random_state=1)
train_prediction=classifier.fit(X_train,y_train).predict(X_train)
test_prediction=classifier.fit(X_train,y_train).predict(X_test)
print("决策树分类器训练模型评分:"+str(accuracy_score(y_train,train_prediction)))
print("决策树分类器待测模型评分:"+str(accuracy_score(y_test,test_prediction)))

划分列索引为特征值和预测值,并将数据划分成训练集和测试集。

引入决策树分类算法,并将算法中的参数设立好,进行建模后,对测试集进行精度评分,得到的结果如下:

在这里插入图片描述

可以看到,该模型的精度为63%左右。

4.参数-控制决策树的复杂度

random_state: sklearn可以选择建不同的树,在每次分枝时不使用全部特征,随机选取一部分特征,而random_state参数决定特征选择的随机性,类似random.seed()。

通常来说,构造决策树知道所有的叶结点都是纯的叶结点,这会导致模型非常复杂,并且对训练数据高度过拟合。纯叶结点的存在说明这颗树在训练集上的精度是100%(如上面图片结果所示)。训练集中的每个数据点都位于分类正确的叶结点中。

防止过拟合有两种常见策略:一种是及早停止树的生长,也叫预剪枝;另一种是先构造树,但随后删除或折叠信息量很少的结点,也叫后剪枝剪枝。预剪枝的限制条件可能包括限制树的最大深度,限制叶结点的最大数目,或者规定一个结点中数据点的最小数目来防止继续划分。

scikit-learn只实现了预剪枝,没有实现后剪枝。

我们固定一下树的random_state,限制树的深度max_depth,放在上面的数据集上重新建模,结果如下所示:

在这里插入图片描述

与之前的结果相比,可以看出如果我们不限制决策树的深度,它的深度和复杂度都可以变得特别大。因此,未剪枝的树容易过拟合,对新数据的泛化性能不佳。

如果我们尝试将剪枝应用在决策树上,这可以在完美拟合训练数据之前阻止树的展开。其中一种选择是在达到一定深度之后停止树的展开。这里我们设置了max_depth=13,这意味着只可以连续问13个问题(详见之前的算法介绍图片)。限制树的深度可以减少过拟合。这会降低训练集的精度,但可以提高测试集的精度,如上图结果所示。

小结

总的来说,决策树是一种非常经典的算法模型,它既可以用于分类问题,也可以用于回归问题。

下一篇我会讲一下如何分析决策树,以及决策树的优缺点。

有很多地方做的不是很好,欢迎网友来提出建议,也希望可以遇到些朋友来一起交流讨论。

PS:看过之前几篇文章的朋友应该已经发现了个现象,就是所有的机器学习算法建模的过程中, 除了算法不同,其它部分的代码都大致相同,这应该属于搬砖吧,是不是所有的机器学习或者说是数据挖掘都是这样的?ヾ( ̄ー ̄)