本次实验运用决策树在分类任务上构建模型,并证对模型进行调优。
知识点
- 决策树
决策树是分类与回归问题中常用的方法之一。其实不仅是机器学习领域,在每天的日常决策中,我们都在使用决策树。流程图实际上就是决策树的可视化表示,例如,下面是俄罗斯国立高等经济研究大学(Higher School of Economics)提供的关于「如何在学院网站上发表论文」的流程图:
用机器学习的术语来说,可以把它看成一个简单的分类器,根据内容(书、小册子、论文)、新闻类型、原发表物类型(科学期刊、通讯)等来确定合适的发表类型(书、文章、书的章节、预印本、Higher School of Economics and the Media 稿件)。
决策树常常是专家经验的概括,是一种分享特定过程知识的方式。例如,在引入可扩展机器学习算法之前,银行业的信用评分任务是由专家解决的,能否放贷是基于一些直观(或经验)的规则,这些规则就可以表示为决策树的形式,如下图所示:

作为机器学习算法的决策树基本上和上图差不多,它合并一连串逻辑规则,使之成为一个树形的数据结构,这些规则的形式为「特征 a 的值小于 x,特征 b 的值小于 y … => 类别 1」。
下面,我们基于「年龄」、「房产」、「收入」、「教育」特征使用决策树解决一个二元分类问题,即「是否允许贷款」。
如何构建决策树
年龄、房产、收入、教育,这么多的特征首先应该关注哪个呢?
为了回答上述问题,先看一个简单的游戏,即「20 个问题」游戏,这个游戏是这样玩的:A 心里想着一个名人,B 问 A 20 个问题,A 只能回答「是」或「否」,20 个问题之后 B 要猜出 A 心里想的那个名人是谁。首先问一个可以最大程度压缩剩余选项数目的问题会使 B 占据极大优势,例如询问「是不是安吉丽娜·朱莉?」,最多剔除一个选项,而询问「这个名人是女人吗?」将消除大约一半的选项。就是说,「性别」特征相比「安吉丽娜·朱莉」、「西班牙人」、「喜欢足球」等其他特征更能区分名人数据集。这背后的道理与熵有关,下面介绍熵的概念。
熵
熵是一个在物理、信息论和其他领域中广泛应用的重要概念,可以衡量获得的信息量。对于具有 N 种可能状态的系统而言,熵的定义如下:

其中,pipi 是系统位于第 i 个状态的概率。熵可以描述为系统的混沌程度,熵越高,系统的有序性越差,反之亦然。熵将帮助我们高效的分割数据,类似帮助我们找出在「20 个问题」游戏中先问什么问题较好。
玩具示例
为了解释熵是如何有利于构建决策树模型的,让我们来看一个玩具示例,在这个示例中将基于球的位置预测它的颜色。


将球分为「位置小于等于 12、位置大于 12」这两组,如下图所示。



结果表明,根据「坐标小于或等于 12」将球分为两组带来了一个更有序的系统。让我们继续分组,直到每组中的球颜色都一样。

上图可见,右边那组只需根据「坐标小于或等于 18」再分割一次即可。而左边那组还需要三次分割。注意,若组内所有球的颜色都一样,那么这个组的熵为 0(log2 1=0)。
通过这个例子,我们成功构建了一个基于球的位置预测球颜色的决策树。但倘若我们再向里面增加一个球,这个决策树就可能无法很好地工作,因为它完全拟合了训练集(初始的 20 球)。如果希望提升它的泛用性,那么一棵具有更少分支(「问题」)的决策树将有更好的效果。
决策树构建算法
在之前的例子中构建的决策树是最优的:它只需提 5 个「问题」(基于变量 Q),就完全拟合了训练集。其他分割条件会使得到的树更深,即需要更多「问题」才能获得答案。
构建决策树的流行算法(如 ID3 或 C4.5)的核心,是贪婪最大化信息增益:在每一步,算法都会选择能在分割后给出最大信息增益的变量。接着递归重复这一流程,直到熵为零(或者,为了避免过拟合,直到熵为某个较小的值)。不同的算法使用不同的推断,通过「提前停止」或「截断」以避免构建出过拟合的树。
分类问题中其他的分割质量标准
上面我们讨论了熵是如何衡量树的分区的,但还有其他指标来衡量分割的好坏:

实践中几乎从不使用错分率,而基尼不确定性和信息增益的效果差不多。
二元分类问题的熵和基尼不确定性为:

其中 p+ 是对象具有标签 + 的概率。
以 p+ 为坐标,绘制上面两个函数的图像。
import warnings
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
sns.set()
warnings.filterwarnings('ignore')
plt.figure(figsize=(6, 4))
xx = np.linspace(0, 1, 50)
plt.plot(xx, [2 * x * (1-x) for x in xx], label='gini')
plt.plot(xx, [4 * x * (1-x) for x in xx], label='2*gini')
plt.plot(xx, [-x * np.log2(x) - (1-x) * np.log2(1 - x)
for x in xx], label='entropy')
plt.plot(xx, [1 - max(x, 1-x) for x in xx], label='missclass')
plt.plot(xx, [2 - 2 * max(x, 1-x) for x in xx], label='2*missclass')
plt.xlabel('p+')
plt.ylabel('criterion')
plt.title('Criteria of quality as a function of p+ (binary classification)')
plt.legend()

上图可见,熵的图像和两倍的基尼不确定性图像非常接近。因此,在实践中,这两个指标的效果基本上是一样的。
示例
下面用一棵决策树拟合一些合成数据。这些合成数据属于两个不同的类别,这两个类别的均值不同,但都呈现正态分布。
# 第一类
np.random.seed(17)
train_data = np.random.normal(size=(100, 2))
train_labels = np.zeros(100)
# 第二类
train_data = np.r_[train_data, np.random.normal(size=(100, 2), loc=2)]
train_labels = np.r_[train_labels, np.ones(100)]
下面绘制数据。通俗地讲,这种情况下的分类问题就是构造一个「边界」,能够较好的分开两个类别(红点和黄点)。这个「边界」若是一条直线的话可能太过简单,若是沿着每个红点画出的蛇形曲线又太过复杂(这将导致其在新数据上的表现很差)。从直觉上说,某种平滑的边界,在新数据上的效果会比较好。
plt.figure(figsize=(10, 8))
plt.scatter(train_data[:, 0], train_data[:, 1], c=train_labels, s=100,
cmap='autumn', edgecolors='black', linewidth=1.5)
plt.plot(range(-2, 5), range(4, -3, -1))
下面训练一棵 sklearn 决策树,区分这两类数据点。最后可视化所得的边界。
from sklearn.tree import DecisionTreeClassifier
# 编写一个辅助函数,返回之后的可视化网格
def get_grid(data):
x_min, x_max = data[:, 0].min() - 1, data[:, 0].max() + 1
y_min, y_max = data[:, 1].min() - 1, data[:, 1].max() + 1
return np.meshgrid(np.arange(x_min, x_max, 0.01), np.arange(y_min, y_max, 0.01))
# max_depth 参数限制决策树的深度
clf_tree = DecisionTreeClassifier(criterion='entropy', max_depth=3,
random_state=17)
# 训练决策树
clf_tree.fit(train_data, train_labels)
# 可视化
xx, yy = get_grid(train_data)
predicted = clf_tree.predict(np.c_[xx.ravel(),
yy.ravel()]).reshape(xx.shape)
plt.pcolormesh(xx, yy, predicted, cmap='autumn')
plt.scatter(train_data[:, 0], train_data[:, 1], c=train_labels, s=100,
cmap='autumn', edgecolors='black', linewidth=1.5)
树的关键参数
理论上讲,我们可以构建一个决策树,直到每个叶节点只有一个实例,但这样做容易过拟合,导致其在新数据上的表现不佳。如果你这么做,在树的最深处,可能会存在由无关紧要的特征组成的分区,例如根据「客户裤子的颜色」这一特征进行分区,这是我们不希望发生。
但在两种情况下,树可以被构建到最大深度(每个叶节点只有一个实例):
- 随机森林。它将构建为最大深度的单个树的响应进行平均(稍后我们将讨论为什么要这样做)。
- 决策树修剪。在这种方法中,树首先被构造成最大深度。然后,从底部开始,基于交叉验证来比较有分区/无分区情形下树的质量情况,进而移除树的一些节点。
下图是过拟合的决策树给出的分界。

常见的解决决策树过拟合的方法为:
-
人工限制深度或叶节点的最少样本数。
-
对树进行剪枝。
sklearn.tree.DecisionTreeClassifier 类的主要参数为:
- max_depth 树的最大深度;
- max_features 搜索最佳分区时的最大特征数(特征很多时,设置这个参数很有必要,因为基于所有特征搜索分区会很「昂贵」);
- min_samples_leaf 叶节点的最少样本数。
树的参数需要根据输入数据设定,通常通过交叉验证可以确定参数范围,下文会具体讨论交叉验证。
当对数值变量进行预测时,我们构造决策树的思路和分类问题时所用的思路是一样的,但衡量决策树好坏的质量标准改变了,现在它的质量标准如下:
其中,ℓ 是叶节点中的样本数,yi 是目标变量的值。简单来说,通过最小化方差,使每个叶子中的目标特征的值大致相等,以此来划分训练集的特征。
示例
让我们基于以下函数生成一些带噪数据:
接着在生成的数据上训练一颗决策树,并进行预测,调用 plt 方法画出结果示意图。
from sklearn.tree import DecisionTreeRegressor
n_train = 150
n_test = 1000
noise = 0.1
def f(x):
x = x.ravel()
return np.exp(-x ** 2) + 1.5 * np.exp(-(x - 2) ** 2)
def generate(n_samples, noise):
X = np.random.rand(n_samples) * 10 - 5
X = np.sort(X).ravel()
y = np.exp(-X ** 2) + 1.5 * np.exp(-(X - 2) ** 2) + \
np.random.normal(0.0, noise, n_samples)
X = X.reshape((n_samples, 1))
return X, y
X_train, y_train = generate(n_samples=n_train, noise=noise)
X_test, y_test = generate(n_samples=n_test, noise=noise)
reg_tree = DecisionTreeRegressor(max_depth=5, random_state=17)
reg_tree.fit(X_train, y_train)
reg_tree_pred = reg_tree.predict(X_test)
plt.figure(figsize=(10, 6))
plt.plot(X_test, f(X_test), "b")
plt.scatter(X_train, y_train, c="b", s=20)
plt.plot(X_test, reg_tree_pred, "g", lw=2)
plt.xlim([-5, 5])
plt.title("Decision tree regressor, MSE = %.2f" %
(np.sum((y_test - reg_tree_pred) ** 2) / n_test))
plt.show()
上图表明,决策树使用分段的常数函数逼近数据。

决策树的优势和劣势
优势:
- 生成容易理解的分类规则,这一属性称为模型的可解释性。例如它生成的规则可能是「如果年龄不满 25 岁,并对摩托车感兴趣,那么就拒绝发放贷款」。
- 很容易可视化,即模型本身(树)和特定测试对象的预测(穿过树的路径)可以「被解释」。
- 训练和预测的速度快。
- 较少的参数数目。
- 支持数值和类别特征。
劣势:
- 决策树对输入数据中的噪声非常敏感,这削弱了模型的可解释性。
- 决策树构建的边界有其局限性:它由垂直于其中一个坐标轴的超平面组成,在实践中比其他方法的效果要差。
- 我们需要通过剪枝、设定叶节点的最小样本数、设定树的最大深度等方法避免过拟合。
- 不稳定性,数据的细微变动都会显著改变决策树。这一问题可通过决策树集成方法来处理(以后的实验会介绍)。
- 搜索最佳决策树是一个「NP 完全」(NP-Complete)问题。了解什么是 NP-Complete 请点击 这里。实践中使用的一些推断方法,比如基于最大信息增益进行贪婪搜索,并不能保证找到全局最优决策树。
- 倘若数据中出现缺失值,将难以创建决策树模型。Friedman 的 CART 算法中大约 50% 的代码是为了处理数据中的缺失值(现在 sklearn 实现了这一算法的改进版本)。
- 这一模型只能内插,不能外推。也就是说,倘若你预测的对象在训练集所设置的特征空间之外,那么决策树就只能做出常数预测。比如,在我们的黄球和蓝球的例子中,这意味着模型将对所有位于 >19 或 <0 的球做出同样的预测。
PS:
- “NP” 指它属于可在多项式时间内被验证的决策问题。
- “完全(Complete)” 指它是最难的问题之一:一旦它能在多项式时间内被求解,则所有 NP 问题都能在多项式时间内被求解。
本文我们通过决策树方法在简单示例上构建了分类模型,该方法常常作为机器学习模型的基线,熟悉它的用法是十分必要的。
&spm=1001.2101.3001.5002&articleId=144707308&d=1&t=3&u=f22e227577bc4beb8f023fdba67791c9)
3万+

被折叠的 条评论
为什么被折叠?



