课程
分类通常包含两个步骤:学习步骤和预测步骤。在学习步骤中,基于给定的训练数据构建模型;在预测步骤中,使用该模型对给定数据进行预测。决策树是最简单也最受欢迎的分类算法之一,便于理解与解释数据。它既可用于分类问题,也可用于回归问题。
若要轻松运行本教程中的所有示例代码,您可以免费创建一个 DataLab 工作簿,其中已预装 Python,并包含所有代码示例。F
决策树算法
决策树是一种类似流程图的树形结构,其中内部节点表示特征(或属性),分支表示决策规则,每个叶节点表示最终结果。
决策树中最顶端的节点称为根节点。它根据属性值学习如何进行划分,并以递归的方式对树进行分割,这一过程称为递归分区。这种类似流程图的结构有助于做出决策;它的可视化就像流程图,容易模仿人类的思考方式。因此,决策树易于理解和解释。

决策树算法。图片作者:Abid Ali Awan
决策树是一种白盒类型的机器学习算法。它会共享内部的决策逻辑,而这在诸如神经网络等黑盒算法中是不可见的。与神经网络算法相比,它的训练速度更快。
决策树的时间复杂度取决于给定数据中的记录数与属性数。决策树是分布无关或非参数的方法,不依赖于对数据底层概率分布的假设。决策树能够以良好的准确率处理高维数据。
决策树算法如何工作?
任何决策树算法的基本思想如下:
- 使用属性选择度量(ASM)选择最优属性来划分记录。
- 将该属性设为决策节点,并将数据集划分为更小的子集。
- 通过对每个子节点递归重复该过程开始构建树,直到满足以下任一条件:
- 所有元组都属于相同的属性取值。
- 不再有剩余的可用属性。
- 不再有样本实例。

属性选择度量
属性选择度量是一种启发式方法,用于选择能以最佳方式划分数据的分裂准则。它也被称为分裂规则,因为它帮助我们确定在给定节点上划分元组的断点。ASM 通过解释给定数据集,为每个特征(或属性)提供一个排序。得分最高的属性将被选为分裂属性。对于连续型属性,还需要为各分支定义分裂点。最常用的选择度量包括信息增益、增益率和基尼指数。
信息增益
Claude Shannon 提出了熵的概念,用于衡量输入集合的“杂质”。在物理和数学中,熵通常指系统的随机性或杂乱度;在信息论中,熵表示样本集合的纯度。信息增益是熵的降低量。信息增益计算在基于给定属性值进行划分前后的熵之差。ID3(迭代二分器)决策树算法使用信息增益。
其中,Pi 表示 D 中任意一个元组属于类别 Ci 的概率。


其中:
-
Info(D)表示识别D中一个元组的类别标签所需的平均信息量。 -
|Dj|/|D|作为第j个划分的权重。 -
InfoA(D)表示基于属性A的划分来对D中一个元组进行分类所需的期望信息量。
具有最高信息增益 Gain(A) 的属性 A 会被选为节点 N() 的分裂属性。
增益率
信息增益对具有众多取值结果的属性存在偏好。也就是说,它更倾向于拥有大量不同取值的属性。例如,考虑一个具有唯一标识符(如 customer_ID)的属性,由于产生纯划分,它使 info(D) 为零,从而最大化信息增益,但得到的是无用的划分。
C4.5(对 ID3 的改进)使用信息增益的扩展——增益率。增益率通过使用分裂信息(Split Info)对信息增益做归一化来处理这种偏差。C4.5 的 Java 实现称为 J48,可在 WEKA 数据挖掘工具中使用。

其中:
- |Dj|/|D| 作为第 j 个划分的权重。
- v 为属性 A 的离散取值个数。
增益率可定义为

具有最高增益率的属性会被选为分裂属性(来源)。
基尼指数
另一种决策树算法 CART(分类与回归树)使用基尼方法来创建分裂点。
其中 pi 表示 D 中一个元组属于类别 Ci 的概率。
基尼指数对每个属性考虑二元分裂。您可以计算每个划分不纯度的加权和。若对属性 A 进行二元分裂,将数据 D 划分为 D1 和 D2,则 D 的基尼指数为:

对于离散型属性,选择能使基尼指数最小的子集作为分裂属性。对于连续型属性,策略是将相邻取值对作为潜在分裂点,选择基尼指数更小的点作为最终分裂点。

基尼指数最小的属性将被选为分裂属性。
在线运行和编辑此教程中的代码
运行代码在 Scikit-learn 中构建决策树分类器
导入必需的库
我们先加载所需的库。
# Load libraries
import pandas as pd
from sklearn.tree import DecisionTreeClassifier # Import Decision Tree Classifier
from sklearn.model_selection import train_test_split # Import train_test_split function
from sklearn import metrics #Import scikit-learn metrics module for accuracy calculation
加载数据
首先使用 pandas 的 CSV 读取函数加载所需的 Pima 印第安人糖尿病数据集。您可以下载该Kaggle 数据集进行跟练。
col_names = ['pregnant', 'glucose', 'bp', 'skin', 'insulin', 'bmi', 'pedigree', 'age', 'label']
# load dataset
pima = pd.read_csv("diabetes.csv", header=None, names=col_names)
pima.head()
| pregnant | glucose | bp | skin | insulin | bmi | pedigree | age | label | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 6 | 148 | 72 | 35 | 0 | 33.6 | 0.627 | 50 | 1 |
| 1 | 1 | 85 | 66 | 29 | 0 | 26.6 | 0.351 | 31 | 0 |
| 2 | 8 | 183 | 64 | 0 | 0 | 23.3 | 0.672 | 32 | 1 |
| 3 | 1 | 89 | 66 | 23 | 94 | 28.1 | 0.167 | 21 | 0 |
| 4 | 0 | 137 | 40 | 35 | 168 | 43.1 | 2.288 | 33 | 1 |
特征选择
此处需要将给定列划分为两类变量:依赖(或目标)变量与自变量(或特征变量)。
#split dataset in features and target variable
feature_cols = ['pregnant', 'insulin', 'bmi', 'age','glucose','bp','pedigree']
X = pima[feature_cols] # Features
y = pima.label # Target variable
划分数据
为更好地评估模型表现,将数据集划分为训练集与测试集是一种良好策略。
使用 train_test_split() 函数来划分数据集。您需要传入三个参数:特征、目标以及测试集大小。
# Split dataset into training set and test set
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1) # 70% training and 30% test
构建决策树模型
我们使用 Scikit-learn 创建一个决策树模型。
# Create Decision Tree classifer object
clf = DecisionTreeClassifier()
# Train Decision Tree Classifer
clf = clf.fit(X_train,y_train)
#Predict the response for test dataset
y_pred = clf.predict(X_test)
评估模型
我们来估计分类器或模型预测类型的准确程度。
准确率可以通过比较测试集真实值与预测值来计算。
# Model Accuracy, how often is the classifier correct?
print("Accuracy:",metrics.accuracy_score(y_test, y_pred))
Accuracy: 0.6753246753246753
我们得到的分类率为 67.53%,可视为不错的准确率。您可以通过调参来提升该准确率。
可视化决策树
您可以使用 Scikit-learn 的 export_graphviz() 函数在 Jupyter 笔记本中显示树结构。为绘制该树,还需要安装 graphviz 和 pydotplus。
pip install graphviz
pip install pydotplus
export_graphviz() 函数将决策树分类器转换为 dot 文件,pydotplus 会将该 dot 文件转换为 PNG 或在 Jupyter 中可显示的形式。
from sklearn.tree import export_graphviz
from sklearn.externals.six import StringIO
from IPython.display import Image
import pydotplus
dot_data = StringIO()
export_graphviz(clf, out_file=dot_data,
filled=True, rounded=True,
special_characters=True,feature_names = feature_cols,class_names=['0','1'])
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('diabetes.png')
Image(graph.create_png())

在决策树图中,每个内部节点都有一个用于划分数据的决策规则。Gini(基尼)用于衡量节点的不纯度。当一个节点中的所有记录都属于同一类别时,可称该节点为纯节点,也即叶节点。
此处得到的是一棵未剪枝的树。未剪枝的树较难解释且不易理解。下一节我们将通过剪枝来对其进行优化。
优化决策树性能
-
criterion:可选(默认值为“gini”),即属性选择度量。 该参数允许我们使用不同的属性选择度量。支持的准则有 “gini”(基尼指数)与 “entropy”(信息增益)。
-
splitter:字符串,可选(默认值为“best”),即分裂策略。 该参数允许我们选择分裂策略。支持 “best” 以选择最佳分裂,“random” 以选择随机的较优分裂。
-
max_depth:int 或 None,可选(默认 None),即树的最大深度。 树的最大深度。若为 None,则节点会扩展,直到所有叶子所含样本数小于 min_samples_split。最大深度设得过大易导致过拟合,过小则会欠拟合(来源)。
在 Scikit-learn 中,决策树分类器仅支持预剪枝方式进行优化。可将树的最大深度作为预剪枝的控制变量。下面的示例在相同数据上绘制一棵 max_depth=3 的决策树。除了预剪枝参数外,您也可以尝试其他属性选择度量,如 entropy。
# Create Decision Tree classifer object
clf = DecisionTreeClassifier(criterion="entropy", max_depth=3)
# Train Decision Tree Classifer
clf = clf.fit(X_train,y_train)
#Predict the response for test dataset
y_pred = clf.predict(X_test)
# Model Accuracy, how often is the classifier correct?
print("Accuracy:",metrics.accuracy_score(y_test, y_pred))
Accuracy: 0.7705627705627706
分类率提升到了 77.05%,比之前的模型更好。
让我们用以下代码让决策树更易于理解:
from six import StringIO
from IPython.display import Image
from sklearn.tree import export_graphviz
import pydotplus
dot_data = StringIO()
export_graphviz(clf, out_file=dot_data,
filled=True,
rounded=True,
special_characters=True,
feature_names=feature_cols,
class_names=['0','1'])
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('diabetes.png')
Image(graph.create_png())
这里,我们完成了以下步骤:
-
导入所需的库。
-
创建名为
dot_data的StringIO对象,用于保存决策树的文本表示。 -
使用
export_graphviz函数将决策树导出为dot格式,并将输出写入dot_data缓冲区。 -
从存储在
dot_data缓冲区中的dot表示创建一个pydotplus图对象。 -
将生成的图写入名为 "diabetes.png" 的 PNG 文件。
-
使用
IPython.display模块的Image对象显示生成的决策树 PNG 图片。

如您所见,这个剪枝后的模型更简洁、可解释性更强,也更易理解。
决策树的优缺点
现在您已构建并优化了一个决策树分类器,我们不妨停下来从整体上评估一下该算法的优势与局限。理解这些权衡有助于您判断何时应选择决策树。
| 优势 | 劣势 |
|---|---|
| 易于解释与可视化 | 对噪声数据敏感且可能过拟合 |
| 可轻松刻画非线性模式 | 数据的微小变化可能导致截然不同的树 |
| 所需数据预处理最少(无需标准化列) | 在类别不平衡的数据集上存在偏差(建议进行平衡处理) |
| 有助于特征工程(预测缺失值、变量选择) | |
| 对数据分布无假设(非参数) |
请注意,一些缺点(如方差不稳定)可以通过集成方法来缓解,例如 装袋法(bagging) 和 提升法(boosting)算法。
决策树 vs. 决策树桩
虽然我们在整个教程中一直在使用完整的决策树,但了解一种更简单的变体——决策树桩(decision stump)——也很有价值。决策树桩本质上是一棵最大深度为 1 的决策树,即仅在根节点进行一次分裂,并有两个叶节点。
| 方面 | 决策树 | 决策树桩 |
|---|---|---|
| 深度 | 可为任意深度(由 max_depth 参数控制) |
始终为深度 1(仅一次分裂) |
| 复杂度 | 可建模复杂的非线性关系 | 仅能刻画简单的线性决策边界 |
| 使用场景 | 适用于复杂问题的独立分类器 | 主要作为集成方法中的弱学习器使用 |
| 准确率 | 作为独立模型通常具有更高的准确率 | 单独准确率较低,但组合后效果显著 |
| 可解释性 | 随深度增加而降低(正如未剪枝树所示) | 极其简单、易于解释 |
由于过于简单,决策树桩很少作为独立分类器使用。但它们在集成方法中能发挥作用,尤其是 AdaBoost 会将树桩作为弱学习器组合成强分类器,或在 梯度提升中作为提升过程的基学习器。
在 Scikit-learn 中,您只需将 max_depth=1,即可创建一个决策树桩:
# Create a Decision Stump
stump = DecisionTreeClassifier(max_depth=1)
stump = stump.fit(X_train, y_train)
总结
恭喜您读完本教程!
在本教程中,您深入了解了决策树的诸多细节:其工作原理、信息增益、增益率与基尼指数等属性选择度量,基于 Python 的 Scikit-learn 包进行的模型构建、可视化与对糖尿病数据集的评估。我们还讨论了它的优缺点,以及如何通过参数调优来优化决策树性能。
希望您现在可以将决策树算法应用到您自己的数据集分析中。
如果您想进一步学习 Python 中的机器学习,请查看我们的课程 Machine Learning with Tree-Based Models in Python。您也可以看看我们的 Kaggle 教程:您的第一个机器学习模型。
决策树分类常见问答
决策树分类有哪些常见用例?
决策树分类常见于多个领域,例如金融中的信用评分、医疗中的疾病诊断、营销中的客户细分等。它通过依据不同准则将数据划分为子集,从而辅助决策。
使用决策树时如何防止过拟合?
可以通过剪枝、设置最大深度、设置分裂内部节点所需的最小样本数以及叶节点所需的最小样本数来防止过拟合。还可通过交叉验证有效调参。
决策树与随机森林有什么区别?
决策树是用于分类或回归的单棵树结构;而随机森林是多棵决策树的集成,通过对多棵树的结果取平均来提升预测准确率并控制过拟合。
在使用决策树时,如何处理数据集中的缺失值?
决策树可以通过替代分裂(surrogate splits)来处理缺失值,或使用该特征的最频繁值、均值/众数进行填充。Scikit-learn 也提供了如 SimpleImputer 等方法,可在训练模型前填补缺失值。
使用 Scikit-learn 构建决策树模型有哪些优势?
Scikit-learn 为数据挖掘和数据分析提供了简洁高效的工具,包括决策树分类器。它具有易于集成、文档完善、支持多种评估指标与参数调优、以及提供决策树可视化方法等优势,因此深受机器学习实践者欢迎。