决策树
使用C++
和python
实现ID3算法
的决策树模型,另有使用Pythonsklearn.tree
中的DecisionTreeClassifier
调包与可视化实例。
决策树原理
决策树作为一种机器学习算法,将 “特征a值小于x,特征b值小于y……=>类别1 “的逻辑规则流纳入树状数据结构。这种算法的优点是可解释性强。银行可以向客户解释他们被拒绝贷款的原因:例如,客户没有房子、收入低于5000元。
我们可以确保在前面的例子中建立的树是最佳的。在其他分割条件下,产生的树会更深,即需要更多的 “问题 “来达到答案。
决策树构建的流行算法,如ID3或C4.5,其核心是信息增益的贪婪最大化原则:在每一步,算法选择在分裂时提供最大信息增益的变量。然后递归地重复这个过程,直到熵为零(或某个小值以考虑过拟合的情况)。不同的算法使用不同的启发式 “早期停止 “或 “截止”,以避免构建一个过度拟合的树。
1 | def build(L): |
步骤如下:
- 自顶向下的贪婪搜索遍历可能的决策树空间构造决策树;
- 从“哪一个属性将在树的根节点被测试”开始;
- 使用统计测试来确定每一个实例属性单独分类训练样例的能力,分类能力最好的属性作为树的根结点测试。
- 然后为根结点属性的每个可能值产生一个分支,并把训练样例排列到适当的分支(也就是说,样例的该属性值对应的分支)之下。
- 重复这个过程,用每个分支结点关联的训练样例来选取在该点被测试的最佳属性。
熵
对于具有 N 个可能状态的系统,香农熵定义如下:
$$
\Large Entropy(S)= -\sum_{i=1}^{N}p_i \log_2{p_i},
$$
其中 $p_i$ 是使系统处于第 $i$ 个状态的概率。这是物理学、信息论和其他领域中使用的一个非常重要的概念。熵可以描述为系统中的混沌程度:熵越高,系统的有序性越低,反之亦然。这将帮助我们进行有效的数据拆分。
当只有两种样例时,定义可写为:
$$
\Large Entropy(S)= -p_{pos} \log_2{p_{pos}} -p_{neg} \log_2{p_{neg}}
$$
其C++实现如下:
1 | //根据具体属性和值来计算熵 |
信息增益
$$
\Large IG(Q) = S_O - \sum_{i=1}^{q}\frac{N_i}{N}S_i
$$
分类问题中的评判标准
我们讨论了熵,但还有其他的评判标准:
- 基尼不确定性 Gini uncertainty(基尼杂质Gini impurity): $G = 1 - \sum\limits_k (p_k)^2$. 最大化基尼不确定性可以最大化同一子树中同一类的对象对的数量。
- Misclassification error (误判误差): $E = 1 - \max\limits_k p_k$
实践中几乎从不使用误判误差,基尼不确定性和信息增益效果类似。
对于二元分类,熵和基尼不确定性采用以下形式:
$ S = -p_+ \log_2{p_+} -p_- \log_2{p_-} = -p_+ \log_2{p_+} -(1 - p_{+}) \log_2{(1 - p_{+})};$
$ G = 1 - p_+^2 - p_-^2 = 1 - p_+^2 - (1 - p_+)^2 = 2p_+(1-p_+).$
其中$p_+$ 是对象具有标签 +
的概率。
如果我们根据 $p_+$ 绘制这两个函数,就能发现看到熵是基尼不确定性的两倍。因此实践中这两个标准几乎相同。
1 | # 消除警告 |
优缺点
优点:
- 可解释性强
- 能被可视化
- 训练和预测速度快
- 模型参数少
- 支持数字特征和分类特征都支持
缺点:
- 对数据中的噪声敏感;稍微修改训练集(如删除一个特征,添加一些对象),整个模型可能会发生变化
- 由决策树构建的分离边界有其局限性——它由垂直于坐标轴之一的超平面组成,在实践中,其质量不如其他一些方法。
- 会过拟合
- 不稳定。数据的微小变化能显著改变决策树。这个问题通过决策树集成来解决(下一次讨论)。
- 最优决策树搜索问题是 NP 完备的。在实践中使用了如贪婪搜索具有最大信息增益的特征,但并不能保证找到全局最优树。
- 难以应对数据中的缺失值。
- 模型只能内插不能外推(随机森林和树增强也如此)。决策树对特征空间外的对象预测结果相同。
实例
分类问题
为人造数据拟合决策树。先生成两类服从不同均值的正态分布样本。
数据生成
1 | # 第一类 |
数据可视化
此分类问题是建立边界来分隔两个类(红点和黄点)。一条直线太简单,为每个红点蜿蜒的复杂曲线太复杂,会导致我们在新样本上出错。直观地说,平滑的边界或一条直线或超平面可以很好地处理新数据。
1 | plt.figure(figsize=(10, 8)) |
模型搭建与训练
训练Sklearn
决策树来区分这两个类,使用max_depth
限制树的深度并可视化生成的分离边界。
1 | from sklearn.tree import DecisionTreeClassifier |
1 | # 画出边界 |
树模型可视化
这个代码很难在kaggle
上跑通……直接放图
1 | # !pip install pydotplus |
1 | import pydotplus |

一开始有200个样本,每类100个。初始状态的熵最大,$S=1$。然后,通过将 $x_2$ 的值与 1.211 进行比较,将样本第一次分成 2 组,左右组的熵都随之减少。在上面这个可视化图片中,第一类的样本越多橙色越深,第二类的样本越多蓝色越深。一开始,两个类的样本数量相等,所以树的根节点是白色的。
理论上您可以构建一棵决策树直到每个叶子都只有一个实例,但这在构建单棵树时并不常见,因为它会过拟合或泛化能力降低,树底部将是一些不重要的特征。
以下两种情况可将树构建到最大深度:
- 随机森林。随机森林(一组树)平均单个树的输出。
- 修剪枝叶 (Pruning trees)。首先将树构造到最大深度,然后自下而上地通过比较具有和不具有该节点的树的质量来删除树的一些节点(使用交叉验证比较)。
下图是在过拟合树中构建的分割边界示例。

回归问题
预测数值型变量时,标准会发生变化:
- Variance:
$$
\Large D = \frac{1}{\ell} \sum\limits_{i =1}^{\ell} (y_i - \frac{1}{\ell} \sum\limits_{j=1}^{\ell} y_j)^2,
$$
we look for features that divide the training set in such a way that the values of the target feature in each leaf are roughly equal.其中$\ell$是一个叶子的样本数,$y_i$是目标变量的值。简单地说,我们通过最小化方差寻找的特征是以这样一种方式划分训练集,即目标特征在每个叶子中的值大致相等。
让我们生成一些服从 $f(x) = e^{-x ^ 2} + 1.5 * e^{-(x - 2) ^ 2}$ 的数据,并加上一些噪声。然后我们将使用这些数据和树所做的预测来训练一棵树。
1 | n_train = 150 |
不难发现,决策树用分段常数函数逼近数据集
使用决策树进行 MNIST 手写数字识别
加载数据
1 | from sklearn.datasets import load_digits |
MNIST 可视化
1 | f, axes = plt.subplots(1, 4, sharey=True, figsize=(16, 6)) |
数据集划分
1 | X_train, X_holdout, y_train, y_holdout = train_test_split( |
模型建立与训练
1 | tree = DecisionTreeClassifier(max_depth=5, random_state=17) |
模型预测与评定
1 | tree_pred = tree.predict(X_holdout) |
调参
1 | tree_params = { |
显示最优参数:
1 | tree_grid.best_params_, tree_grid.best_score_ # ({'max_depth': 20, 'max_features': 64}, 0.844) |
交叉验证
1 | np.mean( |
调库
sklearn.tree.DecisionTreeClassifier
sklearn.tree.DecisionTreeClassifier
类的主要参数有:
1 | class sklearn.tree.DecisionTreeClassifier(*, criterion='gini', splitter='best', max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, class_weight=None, ccp_alpha=0.0) |
max_depth
– the maximum depth of the tree;max_features
- the maximum number of features with which to search for the best partition (this is necessary with a large number of features because it would be “expensive” to search for partitions for all features);min_samples_leaf
– the minimum number of samples in a leaf. This parameter prevents creating trees where any leaf would have only a few members.
Classifier可选参数
参数:
参数 | 说明 |
---|---|
criterion | {“gini”, “entropy”}, default=”gini” 这个参数是用来选择使用何种方法度量树的切分质量的。当criterion取值为“gini”时采用 基尼不纯度(Gini impurity)算法构造决策树,当criterion取值为 “entropy” 时采用信息增益( information gain)算法构造决策树. |
splitter | {“best”, “random”}, default=”best” 此参数决定了在每个节点上拆分策略的选择。支持的策略是“best” 选择“最佳拆分策略”, “random” 选择“最佳随机拆分策略”。 |
max_depth | int, default=None 树的最大深度。如果取值为None,则将所有节点展开,直到所有的叶子都是纯净的或者直到所有叶子都包含少于min_samples_split个样本。 |
min_samples_split | int or float, default=2 拆分内部节点所需的最少样本数: · 如果取值 int , 则将 min_samples_split 视为最小值。· 如果为float,则 min_samples_split 是一个分数,而ceil(min_samples_split * n_samples) 是每个拆分的最小样本数。-注释 在版本0.18中更改:增加了分数形式的浮点值。 |
min_samples_leaf | int or float, default=1 在叶节点处所需的最小样本数。 仅在任何深度的分裂点在左分支和右分支中的每个分支上至少留有 min_samples_leaf 个训练样本时,才考虑。 这可能具有平滑模型的效果,尤其是在回归中。· 如果为int,则将 min_samples_leaf 视为最小值· 如果为float,则 min_samples_leaf 是一个分数,而ceil(min_samples_leaf * n_samples) 是每个节点的最小样本数。- 注释: 在版本0.18中发生了更改:添加了分数形式的浮点值。 |
min_weight_fraction_leaf | float, default=0.0 在所有叶节点处(所有输入样本)的权重总和中的最小加权分数。 如果未提供 sample_weight ,则样本的权重相等。 |
max_features | int, float or {“auto”, “sqrt”, “log2”}, default=None 寻找最佳分割时要考虑的特征数量: - 如果为 int ,则在每次拆分时考虑max_features 功能。- 如果为 float ,则max_features 是一个分数,而int(max_features * n_features) 是每个分割处的特征数量。- 如果为 “auto” ,则max_features = sqrt(n_features) 。- 如果为 “sqrt” ,则max_features = sqrt(n_features) 。- 如果为 “log2” ,则max_features = log2(n_features) 。- 如果为 None ,则max_features = n_features 。注意:直到找到至少一个有效的节点样本分区,分割的搜索才会停止,即使它需要有效检查的特征数量多于 max_features 也是如此。 |
random_state | int, RandomState instance, default=None 此参数用来控制估计器的随机性。即使分割器设置为“最佳”,这些特征也总是在每个分割中随机排列。当 max_features <n_features 时,该算法将在每个拆分中随机选择max_features ,然后再在其中找到最佳拆分。但是,即使max_features = n_features ,找到的最佳分割也可能因不同的运行而有所不同。 就是这种情况,如果标准的改进对于几个拆分而言是相同的,并且必须随机选择一个拆分。 为了在拟合过程中获得确定性的行为,random_state 必须固定为整数。 有关详细信息,请参见词汇表。 |
max_leaf_nodes | int, default=None 优先以最佳方式生成带有 max_leaf_nodes 的树。 最佳节点定义为不纯度的相对减少。 如果为None,则叶节点数不受限制。 |
min_impurity_decrease | float, default=0.0 如果节点分裂会导致不纯度的减少大于或等于该值,则该节点将被分裂。 加权不纯度减少方程如下: N_t / N * (impurity - N_t_R / N_t * right_impurity - N_t_L / N_t * left_impurity) 其中 N 是样本总数,N_t 是当前节点上的样本数,N_t_L 是左子节点中的样本数,N_t_R 是右子节点中的样本数。如果给 sample_weight 传了值,则N , N_t , N_t_R 和 N_t_L 均指加权总和。在 0.19 版新增 。 |
min_impurity_split | float, default=0 树模型停止生长的阈值。如果节点的不纯度高于阈值,则该节点将分裂,否则为叶节点。 警告: 从版本0.19开始被弃用: min_impurity_split 在0.19中被弃用,转而支持min_impurity_decrease 。min_impurity_split 的默认值在0.23中从1e-7 更改为0 ,在0.25中将被删除。使用min_impurity_decrease 代替。 |
class_weight | dict, list of dict or “balanced”, default=None 以 {class_label: weight} 的形式表示与类别关联的权重。如果取值None,所有分类的权重为1。对于多输出问题,可以按照y的列的顺序提供一个字典列表。注意多输出(包括多标签) ,应在其自己的字典中为每一列的每个类别定义权重。例如:对于四分类多标签问题, 权重应为[{0:1、1:1:1],{0:1、1:5},{0:1、1:1:1},{0:1、1: 1}],而不是[{1:1},{2:5},{3:1},{4:1}]。 “平衡”模式使用y的值自动将权重与输入数据中的类频率成反比地调整为 n_samples /(n_classes * np.bincount(y)) 。对于多输出,y的每一列的权重将相乘。 请注意,如果指定了 sample_weight ,则这些权重将与sample_weight (通过fit 方法传递)相乘。 |
presort | deprecated, default=’deprecated’ 此参数已弃用,并将在v0.24中删除。 注意:从0.22版开始已弃用。 |
ccp_alpha | non-negative float, default=0.0 用于最小化成本复杂性修剪的复杂性参数。 将选择成本复杂度最大且小于ccp_alpha的子树。 默认情况下,不执行修剪。 有关详细信息,请参见最小成本复杂性修剪。 |
属性
属性 | 说明 |
---|---|
classes_ | ndarray of shape (n_classes,) or list of ndarray 类标签(单输出问题)或类标签数组的列表(多输出问题)。 |
feature_importances_ | ndarray of shape (n_features,) 返回特征重要程度数据。 |
max_features_ | intmax_features 的推断值。 |
n_classes_ | int or list of int 整数的类别数(单输出问题),或者一个包含所有类别数量的列表(多输出问题)。 |
n_features_ | int 执行模型拟合训练时的特征数量。 |
n_outputs_ | int 执行模型拟合训练时的输出数量。 |
tree_ | Tree 基础的Tree对象。请通过 help(sklearn.tree._tree.Tree) 查看Tree对象的属性,并了解决策树的结构以了解这些属性的基本用法。 |
Classifier使用示例
1 | from sklearn.datasets import load_iris |
sklearn.tree.DecisionTreeRegressor
1 | class sklearn.tree.DecisionTreeRegressor(*, criterion='mse', splitter='best', max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, presort='deprecated', ccp_alpha=0.0) |
criterion
{“squared_error”, “friedman_mse”, “absolute_error”, “poisson”}, default=”squared_error”
The function to measure the quality of a split. Supported criteria are “squared_error” for the mean squared error, which is equal to variance reduction as feature selection criterion and minimizes the L2 loss using the mean of each terminal node, “friedman_mse”, which uses mean squared error with Friedman’s improvement score for potential splits, “absolute_error” for the mean absolute error, which minimizes the L1 loss using the median of each terminal node, and “poisson” which uses reduction in Poisson deviance to find splits.
Regressor可选参数
参数:
参数 | 说明 |
---|---|
criterion | {“mse”, “friedman_mse”, “mae”}, default=”mse” 测量分割质量的函数。支持的标准是均方误差的“ mse”,等于方差减少作为特征选择标准,并使用每个终端节点的均值“ friedman_mse”来最小化L2损失,该方法使用均方误差和弗里德曼改进分数作为潜在值拆分,并使用“ mae”表示平均绝对误差,使用每个终端节点的中值将L1损失最小化。 版本0.18 中的新功能 :平均绝对误差(MAE)标准。 |
splitter | {“best”, “random”}, default=”best” 用于在每个节点上选择拆分的策略。支持的策略是“best”选择最佳拆分,“random”选择最佳随机拆分。 |
max_depth | int, default=None 树的最大深度。如果为None,则将节点展开,直到所有叶子都是纯净的,或者直到所有叶子都包含少于min_samples_split个样本。 |
min_samples_split | int or float, default=2 拆分内部节点所需的最少样本数: - 如果为int,则认为 min_samples_split 是最小值。- 如果为float, min_samples_split 则为分数, ceil(min_samples_split * n_samples) 是每个拆分的最小样本数。在版本0.18中更改 :添加了分数的浮点值。 |
min_samples_leaf | int or float, default=1 在叶节点处需要的最小样本数。任何深度的分割点只有在左、右分支中至少留下min_samples_leaf训练样本时才会被考虑。这可能具有平滑模型的效果,尤其是在回归中。 |
min_weight_fraction_leaf | float, default=0.0 在所有叶节点处(所有输入样本)的权重总和中的最小加权分数。如果未提供sample_weight,则样本的权重相等。 |
max_features | int, float or {“auto”, “sqrt”, “log2”}, default=None 寻找最佳分割时要考虑的特征数量: - 如果为int,则 max_features 在每个分割处考虑特征。- 如果为float, max_features 则为小数,并且int(max_features * n_features) 是在每次拆分时考虑要素。- 如果为“auto”,则为 max_features=n_features 。- 如果是“ sqrt”,则 max_features=sqrt(n_features) 。- 如果为“ log2”,则为 max_features=log2(n_features) 。- 如果为”None“,则 max_features=n_features 。注:直到找到至少一个有效的节点样本分区,分割的搜索才会停止,即使它需要有效检查多个 max_features 要素也是如此。 |
random_state | int, RandomState instance, default=None 控制估算器的随机性。即使 splitter 设置为"best" ,这些特性总是在每次拆分时随机排列。当max_features < n_features 时,算法将在每个分割处随机选择max_features 特征,然后在其中找到最佳分割。即使max_features=n_features ,但最佳分割可能因不同的运行而有所不同,这种情况下,如果标准的改进对于多个分割是相同的,必须随机选择一个分割。为了在拟合过程中获得确定性行为,必须将其固定为整数。有关详细信息,请参见词汇表。 |
max_leaf_nodes | int, default=None 以最好的方式种植一棵树,树上有 max_l 节点。最佳节点定义为杂质相对减少。如果没有,则叶节点的数量不受限制。 |
min_impurity_decrease | float, default=0.0 如果节点分裂会导致杂质的减少大于或等于该值,则该节点将被分裂。 加权杂质减少方程如下: N_t / N * (impurity - N_t_R / N_t * right_impurity N_t_L / N_t * left_impurity) 其中, N 是样本总数,N_t 是当前节点的样本数,N_t_L 是左子节点中的样本数,N_t_R 是右子节点中的样本数。N ,N_t ,N_t_R 和N_t_L 都指的是加权和,如果sample_weight 可以通过。版本0.19中的新功能。 |
min_impurity_split | float, (default=0) 尽早停止树的生长的阈值。如果节点的杂质高于阈值,则该节点将分裂,否则为叶。 从版本0.19 min_impurity_split 开始不推荐使用:在版本0.19中 不再推荐使用 min_impurity_decrease 。 min_impurity_split 的默认值在0.23中从1e-7更改为0,将会在0.25中删除。使用min_impurity_decrease 将其代替。 |
presort | deprecated, default=’deprecated’ 此参数已弃用,并将在v0.24中删除。 从0.22版开始不推荐使用。 |
ccp_alpha | non-negative float, default=0.0 用于最小代价复杂度修剪的复杂度参数。选择代价复杂度最大且小于 ccp_alpha 的子树。默认情况下,不执行修剪。有关详细信息,请参见 最小成本复杂性修剪。0.22版中的新功能。 |
属性:
属性 | 说明 |
---|---|
feature_importances_ | ndarray of shape (n_features,) 返特征的重要性。 |
max_features_ | int max_features的推断值。 |
n_features_ | intfit 执行时的特征数量。 |
n_outputs_ | intfit 执行时的输出数量。 |
tree_ | Tree 基础的Tree对象。有关树对象的属性请参阅 help(sklearn.tree._tree.Tree) , 有关这些属性的基本用法,请参阅 决策树结构。 |
Regressor使用示例
1 | from sklearn.datasets import load_diabetes |
实现
ID3 实现
1 | //计算信息增益,DFS构建决策树 |
1 | #coding=utf-8 |
C4.5
1 | Function C4.5(R:包含连续属性的无类别属性集合,C:类别属性,S:训练集) |
资源
- scikit-learn 决策树指南
- Decision trees and k Nearest Neighbors are covered practically in every ML book. We recommend “Pattern Recognition and Machine Learning” (C. Bishop) and “Machine Learning: A Probabilistic Perspective” (K. Murphy).
- The book “Machine Learning in Action” (P. Harrington) will walk you through implementations of classic ML algorithms in pure Python.
- Scipy 2017 scikit-learn tutorial by Alex Gramfort and Andreas Mueller.
- Implementations of many ML algorithms. Good to search for decision trees and k-NN.
参考
- mlcourse.ai Author: Yury Kashnitsky. Translated and edited by Christina Butsko, Gleb Filatov, and Yuanyuan Pao.
- CSDN上的博客
- Scikit-learn 文档
- 中文文档和中文教程