Python 数据科学手册 5.8 决策树和随机森林

2022-12-01 17:27:16 浏览数 (2)

5.8 决策树和随机森林

原文:In-Depth: Decision Trees and Random Forests 译者:飞龙 协议:CC BY-NC-SA 4.0 译文没有得到原作者授权,不保证与原文的意思严格一致。

之前,我们深入研究了简单的生成分类器(见朴素贝叶斯分类)和强大的辨别分类器(参见支持向量机)。 这里我们来看看另一个强大的算法的动机 - 一种称为随机森林的非参数算法。 随机森林是组合方法的一个例子,这意味着它依赖于更简单估计器的整体聚合结果。 这种组合方法的结果令人惊讶,总和可以大于部分:即,多个估器中的多数表决最终可能比执行表决的任何个体的估计更好! 我们将在以下部分中看到这个例子。 我们从标准导入开始:

代码语言:javascript复制
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns; sns.set()

随机森林是一个例子,建立在决策树上的组合学习器。 因此,我们将首先讨论决策树本身。

决策树是分类或标注对象的非常直观的方法:您只需要询问一系列问题,它们为弄清楚分类而设计。 例如,如果您想建立一个决策树,来分类您在远足时遇到的动物,则可以构建如下所示的树:

二元分割使其非常有效:在一个结构良好的树中,每个问题都会将选项数量减少一半,即使在大量分类中也很快缩小选项。 当然,这个技巧是决定在每个步骤中要问哪些问题。 在决策树的机器学习实现中,问题通常采用数据中轴对齐分割的形式:即,树中的每个节点使用其中一个特征中的分割值将数据分成两组。 现在来看一个例子。

创建决策树

考虑以下二维数据,它拥有四个标签之一:

代码语言:javascript复制
from sklearn.datasets import make_blobs

X, y = make_blobs(n_samples=300, centers=4,
                  random_state=0, cluster_std=1.0)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='rainbow');

根据这些数据建立的一个简单的决策树,将根据一些定量标准,沿着一个或另一个轴线迭代地分割数据,并且在每个级别,根据其中的多数表决来分配新区域的标签。 该图显示了该数据的决策树分类器的前四个级别的可视化:

请注意,在第一次拆分之后,上部分支中的每个点保持不变,因此无需进一步细分此分支。 除了包含所有一种颜色的节点,在每个级别,每个区域再次沿着两个特征之一分裂。

将决策树拟合到我们的数据的这个过程,可以在 Scikit-Learn 中使用DecisionTreeClassifier估计器来完成:

代码语言:javascript复制
from sklearn.tree import DecisionTreeClassifier
tree = DecisionTreeClassifier().fit(X, y)

让我们写一个简单的辅助函数,帮助我们展示分类器的输出:

代码语言:javascript复制
def visualize_classifier(model, X, y, ax=None, cmap='rainbow'):
    ax = ax or plt.gca()

    # Plot the training points
    ax.scatter(X[:, 0], X[:, 1], c=y, s=30, cmap=cmap,
               clim=(y.min(), y.max()), zorder=3)
    ax.axis('tight')
    ax.axis('off')
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()

    # fit the estimator
    model.fit(X, y)
    xx, yy = np.meshgrid(np.linspace(*xlim, num=200),
                         np.linspace(*ylim, num=200))
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)

    # Create a color plot with the results
    n_classes = len(np.unique(y))
    contours = ax.contourf(xx, yy, Z, alpha=0.3,
                           levels=np.arange(n_classes   1) - 0.5,
                           cmap=cmap, clim=(y.min(), y.max()),
                           zorder=1)

    ax.set(xlim=xlim, ylim=ylim)

现在我们可以检测,决策树看起来是什么样子:

代码语言:javascript复制
visualize_classifier(DecisionTreeClassifier(), X, y)

如果您现在正在运行这个笔记,您可以使用“在线附录”中包含的帮助脚本,来启动决策树构建过程的交互式可视化:

代码语言:javascript复制
# helpers_05_08 is found in the online appendix
import helpers_05_08
helpers_05_08.plot_tree_interactive(X, y);

请注意,随着深度的增加,我们倾向于获得非常奇怪的分类区域; 例如,在第五层,黄色和蓝色区域之间有一个高而瘦的紫色区域。 很明显,这不是真实的,固有的数据分布结果,更多的是数据的特定采样或噪声属性的结果。 也就是说,这个决策树,即使只有五个层次的深度,显然对我们的数据过拟合了。

决策树和过拟合

这种过度拟合是决策树的一般属性:在树中很容易就走得太深,从而拟合特定数据的细节,而不是抽取它们分布的整体属性。 查看这种过拟合的另一种方法是,查看在不同数据子集上训练的模型 - 例如,在这个图中,我们训练两种不同的树,每种都是原始数据的一半:

很明显,在一些地方,两棵树产生一致的结果(例如在四个角落),而在其他地方,这两棵树给出非常不同的分类(例如,在任何两个簇之间的区域中)。 关键观察是,分类不太确定的地方,会发生不一致,因此通过使用这两种树的信息,我们可能会得到更好的结果!

如果您正在运行这个笔记,以下功能允许您交互显示树的拟合,在数据的随机子集上训练:

代码语言:javascript复制
# helpers_05_08 is found in the online appendix
import helpers_05_08
helpers_05_08.randomized_tree_interactive(X, y)

正如使用来自两棵树的信息,改善了我们的结果,我们可能希望使用来自许多树的信息,进一步改善我们的结果。

估计器的组合:随机森林

这个概念 - 多个过拟合估计器可以组合,来减少这种过拟合的影响 - 是一种称为装袋的组合方法。 这个方法使用了一组并行估计器,每个都对数据过拟合,并对结果进行平均以找到更好的分类。 随机决策树的一个组合被称为随机森林。

这种类型的装袋分类,可以使用 Scikit-Learn 的BaggingClassifier元估计器手动进行,如下所示:

代码语言:javascript复制
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier

tree = DecisionTreeClassifier()
bag = BaggingClassifier(tree, n_estimators=100, max_samples=0.8,
                        random_state=1)

bag.fit(X, y)
visualize_classifier(bag, X, y)

在这个例子中,我们将每个估计器拟合训练点的 80% 随机子集进行来随机化数据。 在实践中,通过在选择分割的方式中添加一些随机性,来更有效地随机化决策树:这样,所有数据每次都有助于拟合,但是拟合的结果仍然具有所需的随机性。 例如,当确定要分割的特征时,随机化树可以从前几个特征中选择。 您可以在 Scikit-Learn 文档中阅读这些随机策略的更多技术细节和参考。

在 Scikit-Learn 中,随机决策树的优化组合在RandomForestClassifier估计器中实现,它自动地处理所有的随机化。 所有你需要做的是选择一些估计器,它将很快(如果需要则并行)拟合树的组合:

代码语言:javascript复制
from sklearn.ensemble import RandomForestClassifier

model = RandomForestClassifier(n_estimators=100, random_state=0)
visualize_classifier(model, X, y);

我们看到,通过对超过 100 个随机扰动的模型取平均,我们最终得到一个整体模型,更接近我们关于如何分割参数空间的直觉。

随机森林回归

在上一节中,我们在分类范围内考虑了随机森林。 随机森林也可以用于回归(即连续而不是分类变量)。 用于此的估计器是RandomForestRegressor,并且语法与我们之前看到的非常相似。

考虑以下数据,由快速和慢速振荡的组合产生:

代码语言:javascript复制
rng = np.random.RandomState(42)
x = 10 * rng.rand(200)

def model(x, sigma=0.3):
    fast_oscillation = np.sin(5 * x)
    slow_oscillation = np.sin(0.5 * x)
    noise = sigma * rng.randn(len(x))

    return slow_oscillation   fast_oscillation   noise

y = model(x)
plt.errorbar(x, y, 0.3, fmt='o');

使用随机森林回归器,我们可以找到最佳拟合曲线,

这里的真实模型以灰色平滑曲线中显示,随机森林模型由红色锯齿曲线显示。 可以看出,非参数随机森林模型足够灵活,可以拟合多周期数据,而不需要指定多周期模型!

示例:随机森林数字分类

早些时候我们快速浏览了手写数字数据(参见 Scikit-Learn 介绍)。 让我们再次使用它,来看看如何在这个上下文中使用随机森林分类器。

代码语言:javascript复制
from sklearn.datasets import load_digits
digits = load_digits()
digits.keys()
# dict_keys(['target', 'data', 'target_names', 'DESCR', 'images'])

为了提醒我们,我们正在观察什么,我们展示前几个数据点。

代码语言:javascript复制
# set up the figure
fig = plt.figure(figsize=(6, 6))  # figure size in inches
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)

# plot the digits: each image is 8x8 pixels
for i in range(64):
    ax = fig.add_subplot(8, 8, i   1, xticks=[], yticks=[])
    ax.imshow(digits.images[i], cmap=plt.cm.binary, interpolation='nearest')

    # label the image with the target value
    ax.text(0, 7, str(digits.target[i]))

我们可以快速使用随机森林对这些数字分类,像这样:

代码语言:javascript复制
from sklearn.cross_validation import train_test_split

Xtrain, Xtest, ytrain, ytest = train_test_split(digits.data, digits.target,
                                                random_state=0)
model = RandomForestClassifier(n_estimators=1000)
model.fit(Xtrain, ytrain)
ypred = model.predict(Xtest)

我们可以看一看分类器的分类报告:

代码语言:javascript复制
from sklearn import metrics
print(metrics.classification_report(ypred, ytest))
代码语言:javascript复制
             precision    recall  f1-score   support

          0       1.00      0.97      0.99        38
          1       1.00      0.98      0.99        44
          2       0.95      1.00      0.98        42
          3       0.98      0.96      0.97        46
          4       0.97      1.00      0.99        37
          5       0.98      0.96      0.97        49
          6       1.00      1.00      1.00        52
          7       1.00      0.96      0.98        50
          8       0.94      0.98      0.96        46
          9       0.96      0.98      0.97        46

avg / total       0.98      0.98      0.98       450

为了更好的度量,绘制混淆矩阵:

代码语言:javascript复制
from sklearn.metrics import confusion_matrix
mat = confusion_matrix(ytest, ypred)
sns.heatmap(mat.T, square=True, annot=True, fmt='d', cbar=False)
plt.xlabel('true label')
plt.ylabel('predicted label');

我们发现,简单无调整的随机森林,产生了数据的非常准确的分类。

随机森林总结

本节简要介绍了组合估计器的概念,特别是随机森林 - 随机决策树的整体。 随机森林是一个强大的方法,具有几个优点:

  • 训练和预测都非常快,因为底层决策树简单。 此外,两个任务都可以直接并行化,因为各个树是完全独立的实体。
  • 多个树提供了概率分类:估计器之间的多数表决提供了概率估计(在 Scikit-Learn 中使用predict_proba()方法来访问)。
  • 非参数模型是非常灵活的,因此可以在其他估计器拟合不足的任务上表现良好。

随机森林的主要缺点是结果不容易解释:即如果要对分类模型的含义作出总结,随机森林可能不是最佳选择。

0 人点赞