从原理上理解 很容易发现CART算法是一种贪心算法 它会从最顶层开始搜索最优的分裂方式 然后对子集也进行同样的处理 多次分裂后 CART算法不会审视自己目前这样的分裂产出的不纯度是否全局最优的。
通常 贪心算法可以获得一个不错的解 但不能保证该解是最优解 为了便于理解 我再举一个例子 假设你要从广州去上海 你在每个节点上都选择最短的路 但这样选择下来的总路径可能不是最短 这便是贪心算法面临的情况。
决策边界可视化除了前文中提及的将决策树本身可视化外 还有另一种常见的可视化方式 那便是将决策树的决策边界可视化出来。当然 如果数据量很大 还需要对数据进行采样后在进行可视化处理。
编写一个用于可视化决策边界的函数
# To plot pretty figures %matplotlib inline import matplotlib as mpl import matplotlib.pyplot as plt mpl.rc( axes , labelsize 14) mpl.rc( xtick , labelsize 12) mpl.rc( ytick , labelsize 12) 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) # meshgrid函数:用两个坐标轴上的点在平面上画网格 其实返回的是矩阵 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)) X iris.data[:, 2:] # 花瓣的长与宽 y iris.target # 花的种类 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()
效果
决策树的问题容易过拟合
决策树的特点是 它极少对训练数据本身做出假设 对比看线性模型 如果你选择使用线性模型 其实你就假设了训练数据是线性变化的 否则线性模型不可能得出好的结果 而决策树不会有这样的假设 这个特点容易让决策树出现过拟合的问题。
以一个具体的例子来展示决策树过拟合的情况:
首先 我通过sklearn的make_moons方法生成半环形分布式的数据集 直观理解如下
from sklearn.datasets import make_moons plt.subplot(122) x1,y1 make_moons(n_samples 1000,noise 0.1) plt.title( make_moons function example ) plt.scatter(x1[:,0],x1[:,1],marker o ,c y1) plt.show()
上述代码效果如下图
有了半环形分布的数据后 训练分类决策树 代码如下
from sklearn.datasets import make_moons Xm, ym make_moons(n_samples 100, noise 0.25, random_state 53) # 分类决策树 tree_clf DecisionTreeClassifier(random_state 42) tree_clf.fit(Xm, ym) # 可视化 plt.figure(figsize (8, 4)) plot_decision_boundary(tree_clf, Xm, ym, axes [-1.5, 2.4, -1, 1.5], iris False) plt.title( overfit , fontsize 16) plt.show()
决策树对半环形分布的数据 其决策边界如下
从图可以看出 决策树对该数据有明显的过拟合情况。
解决方法也很简单 就是使用各种超参数对模型进行正则化调整 即限制模型的拟合能力 从而希望获得具有更好泛化的模型。sklearn对决策树提供了max_depth 最大深度 、min_samples_split 分裂前节点必须有的最小样本数 、min_samples_leaf 叶节点必须要有的最小样本数 等等超参数用于正则化。
这里我使用max_depth和min_samples_leaf对分类决策树做了相应的正则化。
from sklearn.datasets import make_moons Xm, ym make_moons(n_samples 100, noise 0.25, random_state 53) # 分类决策树 tree_clf DecisionTreeClassifier(random_state 42, max_depth 5, min_samples_leaf 4) tree_clf.fit(Xm, ym) # 可视化 plt.figure(figsize (8, 4)) plot_decision_boundary(tree_clf, Xm, ym, axes [-1.5, 2.4, -1, 1.5], iris False) plt.title( regularization , fontsize 16) plt.show()
可视化效果如图
过拟合的问题不只是在分类决策树上 在回归决策树上也会有 这里可视化的展示一下 让你有更直观的了解 如下图
上图中 左半部分 无疑是过拟合的 而右半部分 使用了min_smaples_leaf做正则化的限制 效果还可以。
此外 从右半部分的图也可以看出 因为回归决策树使用MSE来做节点分裂标准 所以决策树的预测值都是对应区域内实例的目标平均值。
不稳定性前文中 我们已经展示 决策树对多种数据类型的处理情况并可视化的展示出其决策边界 仔细观察会发现 无论是分类决策树还是回归决策树 其决策边界都喜欢垂直于X轴或Y轴 这使得他们对训练集数据的旋转产生的变化特别敏感 一个具体的例子
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) fig, axes plt.subplots(ncols 2, figsize (10, 4), sharey True) plt.sca(axes[0]) plot_decision_boundary(tree_clf_s, Xs, ys, axes [-0.7, 0.7, -0.7, 0.7], iris False) plt.sca(axes[1]) plot_decision_boundary(tree_clf_sr, Xsr, ys, axes [-0.7, 0.7, -0.7, 0.7], iris False) plt.ylabel( ) plt.show()
从上图可以看出 左边的图 决策树使用一条线就将数据做好了分类 但我们将数据旋转一下 获得右边的图 再使用决策树去处理 会发现 决策树需要绘制多条线才能将数据做好分类 即右边数据训练出的决策树模型 可能无法很好的泛化。
更概括的说 决策树对训练集中微小的数据变化都非常敏感 一个具体例子 如果我们直接使用鸢尾花数据集进行可视化 效果如图 前面展示过
但我们使用相同的数据、类似的代码 会得到与上图大为不同的效果
iris load_iris() X iris.data[:, 2:] # petal length and width y iris.target # 构建决策树时 使用了不同的random_state tree_clf_tweaked DecisionTreeClassifier(max_depth 2, random_state 40) tree_clf_tweaked.fit(X, y) plt.figure(figsize (8, 4)) plot_decision_boundary(tree_clf_tweaked, X, y, legend False) plt.plot([0, 7.5], [0.8, 0.8], k- , linewidth 2) plt.plot([0, 7.5], [1.75, 1.75], k-- , linewidth 2) plt.text(1.0, 0.9, Depth 0 , fontsize 15) plt.text(1.0, 1.80, Depth 1 , fontsize 13) plt.show()
上述代码可视化的效果如下
从上图可知 即便是相同的训练数据上 如果random_state不同 sklearn选择特征集的算法是随机的 通过random_state参数控制 获得的决策树模型也完全不同了。
随机森林可以通过对多个树进行平均预测来限制这种不稳定性 关于随机森林的内容 我们后面的文章会讨论。
本文涉及代码已提交到 https://github.com/ayuLiao/machine_learning_interstellar_journey 项目中。
我是二两 下篇文章见。



