欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

《Hands-On Machine Learning with Scikit-Learn & TensorFlow》读书笔记 第六章 决策树

程序员文章站 2022-03-31 22:38:02
...

第六章 决策树

CHAPTER 6

Decision Trees

和支持向量机一样, 决策树是一种多功能机器学习算法, 即可以执行分类任务也可以执行回归任务, 甚至包括多输出(multioutput)任务.

决策树也是随机森林的基本组成部分,而随机森林是当今最强大的机器学习算法之一。


鸢尾花数据集上进行一个决策树分类器训练

from sklearn.datasets import load_iris 
from sklearn.tree import DecisionTreeClassifier

iris = load_iris() 
X = iris.data[:, 2:] # petal length and width 
y = iris.target

tree_clf = DecisionTreeClassifier(max_depth=2) 
tree_clf.fit(X, y)

DecisionTreeClassifier类还有一些其他的参数用于限制树模型的形状:
min_samples_split(节点在被分裂之前必须具有的最小样本数),min_samples_leaf(叶节点必须具有的最小样本数),min_weight_fraction_leaf(和min_samples_leaf相同,但表示为加权总数的一小部分实例)
max_leaf_nodes(叶节点的最大数量)和max_features(在每个节点被评估是否分裂的时候,具有的最大特征数量)。
增加min_* hyperparameters或者减少max_* hyperparameters会使模型正则化。

可以利用graphviz package 中的dot命令行,将.dot文件转换成 PDF 或 PNG 等多种数据格式。

Graphviz是一款开源图形可视化软件包,http://www.graphviz.org/
$ dot -Tpng iris_tree.dot -o iris_tree.png

可视化图形

from sklearn.tree import export_graphviz
from IPython.display import Image  
import pydotplus 

dot_data = export_graphviz(tree_clf, out_file=None, 
                         feature_names=iris.feature_names[2:], 
                         class_names=iris.target_names,  
                         filled=True, # 颜色
                         rounded=True,)  #圆角
graph = pydotplus.graph_from_dot_data(dot_data)  
Image(graph.create_png()) 

《Hands-On Machine Learning with Scikit-Learn & TensorFlow》读书笔记 第六章 决策树

决策树的众多特性之一就是, 它不需要太多的数据预处理, 尤其是不需要进行特征的缩放或者归一化。

Scikit-Learn 用的是 CART 算法, CART 算法仅产生二叉树:每一个非叶节点总是只有两个子节点(只有是或否两个结果)。然而,像 ID3 这样的算法可以产生超过两个子节点的决策树模型。

绘制决策边界

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap

def plot_decision_boundary(clf, X, y, axes=[0, 7.5, 0, 3], iris=True, legend=False, plot_training=True):
    x1s = np.linspace(axes[0], axes[1], 100)
    x2s = np.linspace(axes[2], axes[3], 100)
    x1, x2 = np.meshgrid(x1s, x2s)
    X_new = np.c_[x1.ravel(), x2.ravel()]
    y_pred = clf.predict(X_new).reshape(x1.shape)
    custom_cmap = ListedColormap(['#fafab0','#9898ff','#a0faa0'])
    plt.contourf(x1, x2, y_pred, alpha=0.3, cmap=custom_cmap)  #轮廓,等高线
    if not iris:
        custom_cmap2 = ListedColormap(['#7d7d58','#4c4c7f','#507d50'])
        plt.contour(x1, x2, y_pred, cmap=custom_cmap2, alpha=0.8)
    if plot_training:
        plt.plot(X[:, 0][y==0], X[:, 1][y==0], "yo", label="Iris-Setosa")
        plt.plot(X[:, 0][y==1], X[:, 1][y==1], "bs", label="Iris-Versicolor")
        plt.plot(X[:, 0][y==2], X[:, 1][y==2], "g^", label="Iris-Virginica")
        plt.axis(axes)
    if iris:
        plt.xlabel("Petal length", fontsize=14)
        plt.ylabel("Petal width", fontsize=14)
    else:
        plt.xlabel(r"$x_1$", fontsize=18)
        plt.ylabel(r"$x_2$", fontsize=18, rotation=0)
    if legend:
        plt.legend(loc="lower right", fontsize=14)

plt.figure(figsize=(8, 4))
plot_decision_boundary(tree_clf, X, y)
plt.plot([2.45, 2.45], [0, 3], "k-", linewidth=2)
plt.plot([2.45, 7.5], [1.75, 1.75], "k--", linewidth=2)
plt.plot([4.95, 4.95], [0, 1.75], "k:", linewidth=2)
plt.plot([4.85, 4.85], [1.75, 3], "k:", linewidth=2)
plt.text(1.40, 1.0, "Depth=0", fontsize=15)
plt.text(3.2, 1.80, "Depth=1", fontsize=13)
plt.text(4.05, 0.5, "(Depth=2)", fontsize=11)

plt.show()

《Hands-On Machine Learning with Scikit-Learn & TensorFlow》读书笔记 第六章 决策树

白盒与黑盒模型
正如我们看到的一样,决策树非常直观,他们的决定很容易被解释。这种模型通常被称为白盒模型。相反,随机森林或神经网络通常被认为是黑盒模型。他们能做出很好的预测,并且您可以轻松检查它们做出这些预测过程中计算的执行过程。然而,人们通常很难用简单的术语来解释为什么模型会做出这样的预测。例如,如果一个神经网络说一个特定的人出现在图片上,我们很难知道究竟是什么导致了这一个预测的出现:
模型是否认出了那个人的眼睛? 她的嘴? 她的鼻子?她的鞋?或者是否坐在沙发上? 相反,决策树提供良好的、简单的分类规则,甚至可以根据需要手动操作(例如鸢尾花分类)。

正则化超参数

决策树几乎不对训练数据做任何假设(于此相反的是线性回归等模型,这类模型通常会假设数据是符合线性关系的)。

如果不添加约束,树结构模型通常将根据训练数据调整自己,使自身能够很好的拟合数据,而这种情况下大多数会导致模型过拟合。

这一类的模型通常会被称为非参数模型,这不是因为它没有任何参数(通常也有很多),而是因为在训练之前没有确定参数的具体数量,所以模型结构可以根据数据的特性*生长。

于此相反的是,像线性回归这样的参数模型有事先设定好的参数数量,所以*度是受限的,这就减少了过拟合的风险(但是增加了欠拟合的风险)。

一些其他算法的工作原理是在没有任何约束条件下训练决策树模型,让模型*生长,然后再对不需要的节点进行剪枝。

当一个节点的全部子节点都是叶节点时,如果它对纯度的提升不具有统计学意义,我们就认为这个分支是不必要的。

标准的假设检验,例如卡方检测,通常会被用于评估一个概率值 – 即改进是否纯粹是偶然性的结果(也叫原假设)

如果 p 值比给定的阈值更高(通常设定为 5%,也就是 95% 置信度,通过超参数设置),那么节点就被认为是非必要的,它的子节点会被删除。

这种剪枝方式将会一直进行,直到所有的非必要节点都被删光。

对moons数据集进行训练生成的两个决策树模型,左侧的图形对应的决策树使用默认超参数生成(没有限制生长条件),右边的决策树模型设置为min_samples_leaf=4。很明显,左边的模型过拟合了,而右边的模型泛用性更好。

from sklearn.datasets import make_moons
from sklearn.tree import DecisionTreeClassifier
import matplotlib.pyplot as plt

Xm, ym = make_moons(n_samples=100, noise=0.25, random_state=53)

deep_tree_clf1 = DecisionTreeClassifier(random_state=42)
deep_tree_clf2 = DecisionTreeClassifier(min_samples_leaf=4, random_state=42)
deep_tree_clf1.fit(Xm, ym)
deep_tree_clf2.fit(Xm, ym)

plt.figure(figsize=(11, 4))
plt.subplot(121)
plot_decision_boundary(deep_tree_clf1, Xm, ym, axes=[-1.5, 2.5, -1, 1.5], iris=False)
plt.title("No restrictions", fontsize=16)
plt.subplot(122)
plot_decision_boundary(deep_tree_clf2, Xm, ym, axes=[-1.5, 2.5, -1, 1.5], iris=False)
plt.title("min_samples_leaf = {}".format(deep_tree_clf2.min_samples_leaf), fontsize=14)

plt.show()

《Hands-On Machine Learning with Scikit-Learn & TensorFlow》读书笔记 第六章 决策树


回归

决策树也能够执行回归任务,让我们使用 Scikit-Learn 的DecisionTreeRegressor类构建一个回归树,让我们用max_depth = 2在具有噪声的二次项数据集上进行训练。

# Quadratic training set + noise
np.random.seed(42)
m = 200
X = np.random.rand(m, 1)
y = 4 * (X - 0.5) ** 2 + np.random.randn(m, 1) / 10

import matplotlib.pyplot as plt

plt.scatter(X,y,s=5)
plt.show()

《Hands-On Machine Learning with Scikit-Learn & TensorFlow》读书笔记 第六章 决策树

from sklearn.tree import DecisionTreeRegressor

tree_reg = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg.fit(X, y)

reg_dot_data = export_graphviz(tree_reg, out_file=None, 
                         filled=True, rounded=True,)  
reg_graph = pydotplus.graph_from_dot_data(reg_dot_data)  
Image(reg_graph.create_png()) 

《Hands-On Machine Learning with Scikit-Learn & TensorFlow》读书笔记 第六章 决策树

这棵树看起来非常类似于你之前建立的分类树,它的主要区别在于,它不是预测每个节点中的样本所属的分类,而是预测一个具体的数值。

from sklearn.tree import DecisionTreeRegressor

tree_reg1 = DecisionTreeRegressor(random_state=42, max_depth=2)
tree_reg2 = DecisionTreeRegressor(random_state=42, max_depth=3)
tree_reg1.fit(X, y)
tree_reg2.fit(X, y)

def plot_regression_predictions(tree_reg, X, y, axes=[0, 1, -0.2, 1], ylabel="$y$"):
    x1 = np.linspace(axes[0], axes[1], 500).reshape(-1, 1)
    y_pred = tree_reg.predict(x1)
    plt.axis(axes)
    plt.xlabel("$x_1$", fontsize=18)
    if ylabel:
        plt.ylabel(ylabel, fontsize=18, rotation=0)
    plt.plot(X, y, "b.")
    plt.plot(x1, y_pred, "r.-", linewidth=2, label=r"$\hat{y}$")

plt.figure(figsize=(18, 6))
plt.subplot(121)
plot_regression_predictions(tree_reg1, X, y)
for split, style in ((0.1973, "k-"), (0.0917, "k--"), (0.7718, "k--")):
    plt.plot([split, split], [-0.2, 1], style, linewidth=2)
plt.text(0.21, 0.65, "Depth=0", fontsize=15)
plt.text(0.01, 0.2, "Depth=1", fontsize=13)
plt.text(0.65, 0.8, "Depth=1", fontsize=13)
plt.legend(loc="upper center", fontsize=18)
plt.title("max_depth=2", fontsize=14)

plt.subplot(122)
plot_regression_predictions(tree_reg2, X, y, ylabel=None)
for split, style in ((0.1973, "k-"), (0.0917, "k--"), (0.7718, "k--")):
    plt.plot([split, split], [-0.2, 1], style, linewidth=2)
for split in (0.0458, 0.1298, 0.2873, 0.9040):
    plt.plot([split, split], [-0.2, 1], "k:", linewidth=1)
plt.text(0.3, 0.5, "Depth=2", fontsize=13)
plt.title("max_depth=3", fontsize=14)

plt.show()

《Hands-On Machine Learning with Scikit-Learn & TensorFlow》读书笔记 第六章 决策树

左侧显示的是模型的预测结果,如果你将max_depth=3设置为 3,模型就会如 右侧显示的那样.注意每个区域的预测值总是该区域中实例的平均目标值。算法以一种使大多数训练实例尽可能接近该预测值的方式分割每个区域。

和处理分类任务时一样,决策树在处理回归问题的时候也容易过拟合。如果不添加任何正则化(默认的超参数),你就会得到左侧的预测结果,显然,过度拟合的程度非常严重。而当我们设置了min_samples_leaf = 10,相对就会产生一个更加合适的模型了。

tree_reg1 = DecisionTreeRegressor(random_state=42)
tree_reg2 = DecisionTreeRegressor(random_state=42, min_samples_leaf=10)
tree_reg1.fit(X, y)
tree_reg2.fit(X, y)

x1 = np.linspace(0, 1, 500).reshape(-1, 1)
y_pred1 = tree_reg1.predict(x1)
y_pred2 = tree_reg2.predict(x1)

plt.figure(figsize=(11, 4))

plt.subplot(121)
plt.plot(X, y, "b.")
plt.plot(x1, y_pred1, "r.-", linewidth=2, label=r"$\hat{y}$")
plt.axis([0, 1, -0.2, 1.1])
plt.xlabel("$x_1$", fontsize=18)
plt.ylabel("$y$", fontsize=18, rotation=0)
plt.legend(loc="upper center", fontsize=18)
plt.title("No restrictions", fontsize=14)

plt.subplot(122)
plt.plot(X, y, "b.")
plt.plot(x1, y_pred2, "r.-", linewidth=2, label=r"$\hat{y}$")
plt.axis([0, 1, -0.2, 1.1])
plt.xlabel("$x_1$", fontsize=18)
plt.title("min_samples_leaf={}".format(tree_reg2.min_samples_leaf), fontsize=14)
plt.show()

《Hands-On Machine Learning with Scikit-Learn & TensorFlow》读书笔记 第六章 决策树


不稳定性

np.random.seed(6)
Xs = np.random.rand(100, 2) - 0.5
ys = (Xs[:, 0] > 0).astype(np.float32) * 2

angle = np.pi / 4
rotation_matrix = np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]])
Xsr = Xs.dot(rotation_matrix)

tree_clf_s = DecisionTreeClassifier(random_state=42)
tree_clf_s.fit(Xs, ys)
tree_clf_sr = DecisionTreeClassifier(random_state=42)
tree_clf_sr.fit(Xsr, ys)

plt.figure(figsize=(11, 4))
plt.subplot(121)
plot_decision_boundary(tree_clf_s, Xs, ys, axes=[-0.7, 0.7, -0.7, 0.7], iris=False)
plt.subplot(122)
plot_decision_boundary(tree_clf_sr, Xsr, ys, axes=[-0.7, 0.7, -0.7, 0.7], iris=False)

plt.show()

《Hands-On Machine Learning with Scikit-Learn & TensorFlow》读书笔记 第六章 决策树

决策树很喜欢设定正交化的决策边界,(所有边界都是和某一个轴相垂直的),这使得它对训练数据集的旋转很敏感。在左图中,决策树可以轻易的将数据分隔开,但是在右图中,当我们把数据旋转了 45° 之后,决策树的边界看起来变的格外复杂。尽管两个决策树都完美的拟合了训练数据,右边模型的泛化能力很可能非常差。

解决这个难题的一种方式是使用 PCA 主成分分析,这样通常能使训练结果变得更好一些。


Q&A

1、100万实例的决策树,大概有多少层?
大概 有 log 2 (10^6) ≈ 20 层,会略多于20层,因为得到的决策树没有那么的平衡

2、父结点的GINI不纯度和子结点比,通常哪个更低?
答:通常子结点更低,即使子结点中的一个比父结点,但所有子结点加权后,往往是比父结点低的

3、如果决策树过拟合,尝试减少max_depth是个好主意吗?
答:是

4、如果决策树欠拟合,尝试缩放输入特征是个好主意吗?
答:不是,决策树不关心训练数据是否缩放或居中。

5、如果在一个包含100万个实例的训练集上训练一个决策树需要花费一个小时,那么在一个包含1000万个实例的训练集上训练另一个决策树需要多少时间?
答:10 × log(10m) / log(m). If m = 10^6 , then K ≈ 11.7

6、如果你的训练集包含100,000个实例,将设置presort =true. 能否加速训练?
答: 只有当数据集小于几千个实例时,预设训练集才能加速训练。如果它包含100,000个实例,则设置presort = True会显着减慢训练。