最新版学习笔记---Python机器学习基础教程(6)决策树---附完整代码

在这里插入图片描述
决策树是广泛用于分类和回归的任务模型。
下面看图片理解一下什么叫决策树:
在这里插入图片描述
在这张图中,树的每个节点代表一个问题或者一个包含答案的终结点(叶结点)。树的边将问题的答案与将问的下一个问题连接起来。用机器学习的语言来说就是,为了区分四类动物,我们利用三个特征来构建一个模型。

1. 构造决策树

我们在下图所示的二维数据集上构造决策树。
这个数据集是由2个半月形成的,每个类别都包含50个数据点。
在这里插入图片描述
为了构造决策树,算法搜遍所有可能的测试,找出对目标变量来说信息量最大的那一个。

下图显示了第一个测试,将数据集在 x[1]=0.0596 处垂直划分可以得到最多信息,它在最大程度上将类别 0 中的点与类别 1 中的点进行区分。右侧的决策树,通过测试 x[1] <=0.0596 的真假来对数据集进行划分,在图中表示为一条黑线。如果测试结果为真,那么将这个点分配给左结点,左结点里包含属于类别 0 的 2 个点和属于类别 1 的 32 个点。否则将这个点分配给右结点,右结点里包含属于类别 0 的 48 个点和属于类别 1 的 18 个点。
在这里插入图片描述

尽管第一次划分已经对两个类别做了很好的区分,但底部区域仍包含属于类别 0 的点,顶部区域也仍包含属于类别 1 的点。我们可以在两个区域中重复寻找最佳测试的过程,从而构建出更准确的模型。下图展示了信息量最大的下一次划分,这次划分是基于 x[0] 做出的,分为左右两个区域。
在这里插入图片描述

这一递归过程生成一棵二元决策树,其中每个结点都包含一个测试。或者你可以将每个测试看成沿着一条轴对当前数据进行划分。这是一种将算法看作分层划分的观点。由于每个测试仅关注一个特征,所以划分后的区域边界始终与坐标轴平行。
对数据反复进行递归划分,直到划分后的每个区域(决策树的每个叶结点)只包含单一目标值(单一类别或单一回归值)。如果树中某个叶结点所包含数据点的目标值都相同,那么这个叶结点就是的(pure)。这个数据集的最终划分结果见下图。
在这里插入图片描述
深度为9的决策树的决策树非常大,很难可视化。

  • 想要对新数据点进行预测,首先要查看这个点位于特征空间划分的哪个区域,然后将该区域的多数目标值(如果是纯的叶结点,就是单一目标值)作为预测结果。从根结点开始对树进行遍历就可以找到这一区域,每一步向左还是向右取决于是否满足相应的测试。
  • 决策树也可以用于回归任务,使用的方法完全相同。预测的方法是,基于每个结点的测试对树进行遍历,最终找到新数据点所属的叶结点。这一数据点的输出即为此叶结点中所有训练点的平均目标值。

2. 如何控制决策树的复杂度(深度)

为什么要控制决策树的复杂度,就是防止过拟合。
一般来将,构造决策树直到所有叶结点都是纯的叶结点,这会导致模型非常复杂,并且对训练数据高度过拟合。纯叶结点的存在说明这棵树在训练集上的精度是 100%。有的决策边界过于关注远离同类别其他点的单个异常点。

防止过拟合有两种常见的策略:

  • 预剪枝(pre-pruning),及早停止树的生长
  • 后剪枝(post-pruning)或剪枝(pruning),先构造树,但随后删除或折叠信息量很少的结点

注意:

  • scikit-learn 的决策树在 DecisionTreeRegressor 类和 DecisionTreeClassifier 类中实现。scikit-learn 只实现了预剪枝,没有实现后剪枝

下面我们在乳腺癌数据集上更详细地看一下预剪枝的效果。运行代码如下:

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
#加载数据
cancer = load_breast_cancer()
#分离数据,stratify作用为以分层方式分割数据,保持测试集与整个数据集里cancer.target的数据分类比例一致
#随机数种子为42
X_train, X_test, y_train, y_test = train_test_split(
    cancer.data, cancer.target, stratify=cancer.target, random_state=42)
#调用函数
tree = DecisionTreeClassifier(random_state=0)
#训练模型
tree.fit(X_train, y_train)
print("Accuracy on training set: {:.3f}".format(tree.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(tree.score(X_test, y_test)))

输出结果:

Accuracy on training set: 1.000
Accuracy on test set: 0.937

训练集上的精度是 100%,这是因为叶结点都是的,树的深度很大,足以完美地记住训练数据的所有标签。
如果我们不限制决策树的深度,它的深度和复杂度都可以变得特别大。因此,未剪枝的树容易过拟合,对新数据的泛化性能不佳。

现在我们将预剪枝应用在决策树上,这可以在完美拟合训练数据之前阻止树的展开。一种选择是在到达一定深度后停止树的展开。这里我们设置 max_depth=5 ,也就是树的深度为5。限制树的深度可以减少过拟合。这会降低训练集的精度,但可以提高测试集的精度,运行代码入下:

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
#加载数据
cancer = load_breast_cancer()
#分离数据,stratify作用为以分层方式分割数据,保持测试集与整个数据集里cancer.target的数据分类比例一致
#随机数种子为42
X_train, X_test, y_train, y_test = train_test_split(
    cancer.data, cancer.target, stratify=cancer.target, random_state=42)
#调用函数,max_depth参数代表深度
tree = DecisionTreeClassifier(max_depth=4,random_state=0)
#训练模型
tree.fit(X_train, y_train)
print("Accuracy on training set: {:.3f}".format(tree.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(tree.score(X_test, y_test)))

输出结果:

Accuracy on training set: 0.995
Accuracy on test set: 0.951

可以看到,训练集精度有所降低,但是测试集的精度大大提高了。

3. 分析决策树

我们利用 graphviz 模块将上面生成的树进行可视化,在进行分析。
会生成两个文件,其中一个为我们想要的树的pdf图片,运行代码如下:

from sklearn import tree
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
import graphviz

#加载数据
cancer = load_breast_cancer()
#分离数据,stratify作用为以分层方式分割数据,保持测试集与整个数据集里cancer.target的数据分类比例一致
#随机数种子为42
X_train, X_test, y_train, y_test = train_test_split(
    cancer.data, cancer.target, stratify=cancer.target, random_state=42)
#调用函数,max_depth参数代表深度
clf=tree.DecisionTreeClassifier(max_depth=4,random_state=0)
#训练模型
clf.fit(X_train, y_train)
#参数filled填充节点分类颜色,参数impurity,杂质(默认值为True,当设置为True时,显示每个节点的杂质)
dot_data=tree.export_graphviz(clf, out_file=None, class_names=["malignant","benign"],
    feature_names=cancer.feature_names, impurity=False, filled=True)
graph = graphviz.Source(dot_data)
graph.render("tree")

效果图:
在这里插入图片描述
树的可视化有助于深入理解算法是如何进行预测的,也是易于向非专家解释的机器学习算法的优秀示例。不过,即使这里树的深度只有 4 层,也有点太大了。深度更大的树(深度为 10 并不罕见)更加难以理解。一种观察树的方法可能有用,就是找出大部分数据的实际路径。图中每个结点的 samples 给出了该结点中的样本个数, values 给出的是每个类别的样本个数。观察 worst radius <= 16.795 分支右侧的子结点,我们发现它只包含8 个良性样本,但有 134 个恶性样本。树的这一侧的其余分支只是利用一些更精细的区别将这 8 个良性样本分离出来。在第一次划分右侧的 142 个样本中,几乎所有样本(132 个)最后都进入最右侧的叶结点中。再来看一下根结点的左侧子结点,对于 worst radius > 16.795 ,我们得到 25 个恶性样本和 259 个良性样本。几乎所有良性样本最终都进入左数第二个叶结点中,大部分其他叶结点都只包含很少的样本。

4. 决策树的特征重要性

查看整个树可能非常费劲,除此之外,我还可以利用一些有用的属性来总结树的工作原理。其中最常用的是特征重要性(feature importance),它为每个特征对树的决策的重要性进行排序。
运行代码如下:

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
cancer = load_breast_cancer()
#分离数据,stratify作用为以分层方式分割数据,保持测试集与整个数据集里cancer.target的数据分类比例一致
#随机数种子为42
X_train, X_test, y_train, y_test = train_test_split(
    cancer.data, cancer.target, stratify=cancer.target, random_state=42)
#调用函数,max_depth参数代表深度
tree = DecisionTreeClassifier(max_depth=4,random_state=0)
#训练模型
tree.fit(X_train, y_train)
print("Feature importances:\n{}".format(tree.feature_importances_))

输出:

Feature importances:
[0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.01019737 0.04839825
 0.         0.         0.0024156  0.         0.         0.
 0.         0.         0.72682851 0.0458159  0.         0.
 0.0141577  0.         0.018188   0.1221132  0.01188548 0.        ]

从输出可以看出,对于每个特征来说,它都是一个介于 0 和 1 之间的数字,其中 0 表示“根本没用到”,1 表示“完美预测目标值”。特征重要性的求和始终为 1。

下面我们将其可视化,运行代码如下:

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np
cancer = load_breast_cancer()
#分离数据,stratify作用为以分层方式分割数据,保持测试集与整个数据集里cancer.target的数据分类比例一致
#随机数种子为42
X_train, X_test, y_train, y_test = train_test_split(
    cancer.data, cancer.target, stratify=cancer.target, random_state=42)
#调用函数,max_depth参数代表深度
tree = DecisionTreeClassifier(max_depth=4,random_state=0)
#训练模型
tree.fit(X_train, y_train)
def plot_feature_importance_cancer(model):
	#.shape[1]表示矩阵的第二维的长度,在这里就是特征的长度
    n_features = cancer.data.shape[1]
    plt.barh(range(n_features), model.feature_importances_, align='center')
    #前者代表y坐标轴的各个刻度,后者代表各个刻度位置的显示的lable
    plt.yticks(np.arange(n_features), cancer.feature_names)
    plt.xlabel("Feature importance")
    plt.ylabel("Feature")
    plt.show()
 
plot_feature_importance_cancer(tree)

效果图:
在这里插入图片描述
从图中我们可以看到,特征worst radius是最重要的特征。
这也证实了我们在分析树时的观察结论,即第一层划分已经将两个类别区分得很好。但是,如果某个特征的 feature_importance_ 很小,并不能说明这个特征没有提供任何信息。这只能说明该特征没有被树选中,可能是因为另一个特征也包含了同样的信息。

与线性模型的系数不同,特征重要性始终为正数,也不能说明该特征对应哪个类别。特征重要性告诉我们“worst radius”(最大半径)特征很重要,但并没有告诉我们半径大表示样本是良性还是恶性。事实上,在特征和类别之间可能没有这样简单的关系,你可以在下面的图片中可以看出这一点。
在这里插入图片描述

  • 左边是是一个二维数据集(y 轴上的特征与类别标签是非单调的关系)与决策树给出的决策边界‘
  • 右边是从左图的数据中学到的决策树

有两个特征和两个类别的数据集。这里所有信息都包含在 X[1] 中,没有用到X[0] 。但 X[1] 和输出类别之间并不是单调关系,即我们不能这么说:“较大的 X[1] 对应类别 0,较小的 X[1] 对应类别 1”(反之亦然)。

虽然我们主要讨论的是用于分类的决策树,但对用于回归的决策树来说,所有内容都是类似的,在 DecisionTreeRegressor 中实现。回归树的用法和分析与分类树非常类似。但在将基于树的模型用于回归时,我们想要指出它的一个特殊性质。 DecisionTreeRegressor(以及其他所有基于树的回归模型)不能外推加粗样式(extrapolate),也不能在训练数据范围之外进行预测。因为决策树用于回归很不常用,大家了解就行了,我在这里也就不写了。

5. 决策树的优点、缺点和参数

  • 优点:一是得到的模型很容易可视化,非专家也很容易理解(至少对于较小的树而言);二是算法完全不受数据缩放的影响,不需要特征预处理。
  • 缺点:即使做了预剪枝,它也经常会过拟合,泛化性能很差。因此,在大多数应用中,往往使用下面介绍的集成方法来替代单棵决策树。
  • 参数:控制决策树模型复杂度的参数是预剪枝参数,它在树完全展开之前停止树的构造。通常来说,选择一种预剪枝策略(设置 max_depth 、 max_leaf_nodes 或 min_samples_leaf )足以防止过拟合。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/zhu_rui/article/details/106134290