1. 为什么我把决策树当成机器学习的“第一课”
在很多机器学习入门资料里,第一个接触的算法往往是线性回归,然后是逻辑回归,一路学到神经网络。但说实话,从我自己的学习经历和后来带新人的经验来看,决策树才是最适合建立模型直觉的起点。
先聊一个直观场景。假设你手头有一批电影数据,包含三个特征:票房规模、豆瓣评分、是否获过奖。每部电影已经被标注了分类——“值得看”还是“不值得看”。现在来了一部新电影《唐人街探案》,它的票房和评分已经能查到,但分类未知。你希望根据已有的数据判断它属于哪一类。
这就是一个典型的分类任务,而决策树处理这个问题的方式非常直白:它会把数据逐层拆分,每一次拆分都选一个“区分度最高”的特征。比如第一层先按评分是否大于8分拆,评分大于8分的子集里再按票房是否高于5亿拆,一层一层往下走,最后得到一棵结构清晰的树。新电影从根部出发,在每个节点做一次判断,就能落到某个叶子节点上,拿到预测分类。
这个过程解决的问题很简单,但它把机器学习的几个核心概念全串起来了:特征选择、模型训练、过拟合控制、模型评估。而且它不像神经网络那样是个“黑箱”,每一条判断规则都能看得到、说得出。你去面试算法岗位也好,期末复习《机器学习》周志华版也好,决策树的原理和推导过程几乎都是必考内容,背后原因就在于它太有代表性了。
这篇文章我想按照自己的学习路径,从熵这个概念讲起,一路到ID3、C4.5、CART三种算法的演进逻辑,再讲剪枝到底在解决什么问题,最后用Python把一棵决策树从训练到调参完整做一遍,把我们面对真实数据时一定会踩的坑也一并说清楚。
2. 从“无序度”说起:信息熵到底是衡量什么
要讲决策树,绕不开信息熵。这个概念早先出现在香农的信息论里,但在决策树里,它的用途可以理解得非常具体——衡量一组数据的“混乱程度”。
打个比方。一个盒子里装了10个苹果,全是一样的品种,这时候你猜盒子里是什么水果,一点悬念都没有,信息熵就很低。如果盒子里5个苹果5个梨,悬念来了,熵就变高。如果盒子里3个苹果、3个梨、2个香蕉、2个橙子,那更乱,熵更高。熵的本质就是不确定性。
在决策树里我们关心的是:一个数据集里样本属于各个类别的分布情况。假设二分类问题,正例占比为 p,负例占比为 1-p,信息熵的计算公式是:
Entropy(S) = -p * log2(p) - (1-p) * log2(1-p)
如果正例负例各占一半,p=0.5,算出来熵等于1,这是二分类下熵能取到的最大值,数据最混乱。如果所有样本都属于同一个类别,比如全是正例,p=1,代入公式算出来熵等于0,数据完全有序。
用上面的电影数据来验证一下。假设数据集里有12部电影,8部标注为“值得看”,4部为“不值得看”。那一开始整个数据集的熵是:
Entropy = -8/12 * log2(8/12) - 4/12 * log2(4/12)
8/12是0.6667,log2(0.6667)大约是-0.585,第一项是-0.6667 * (-0.585) = 0.39。4/12是0.3333,log2(0.3333)大约是-1.585,第二项是-0.3333 * (-1.585) = 0.528。两项相加,总熵约0.918。
现在的问题就是,我们要从特征里选一个出来做根节点的划分。选哪个?答案是:选那个能让划分之后的加权总熵最小的特征,因为熵降得越多,说明数据变得更加有序,分类效果更好。
2.1 按特征拆分的“加权熵”计算过程
假设“是否获过奖”这个特征,它的取值为“是”和“否”。统计之后发现,12部电影里,获过奖的有5部,没获过奖的有7部。在获过奖的5部里面,4部“值得看”,1部“不值得看”,这一子集的熵是:
Entropy(获奖) = -4/5 * log2(4/5) - 1/5 * log2(1/5) ≈ 0.722
在没获过奖的7部里面,4部“值得看”,3部“不值得看”,这一子集的熵是:
Entropy(未获奖) = -4/7 * log2(4/7) - 3/7 * log2(3/7) ≈ 0.985
按“是否获过奖”划分后的加权总熵为:
加权熵 = 5/12 * 0.722 + 7/12 * 0.985 ≈ 0.876
回到划分前的总熵0.918,划分后变成0.876,信息增益是0.918 - 0.876 = 0.042。增益越大,说明这个特征带来的分类纯度提升越多。
再算另一个特征,比如“豆瓣评分是否大于8分”。假设评分大于8分的电影有6部,其中6部全是“值得看”,熵为0。评分不大于8分的电影也是6部,其中2部“值得看”,4部“不值得看”,熵为0.918。加权总熵为:
加权熵 = 6/12 * 0 + 6/12 * 0.918 = 0.459
信息增益 = 0.918 - 0.459 = 0.459
对比一下,按评分划分的信息增益远大于按是否获奖划分的信息增益。所以根节点我们会优先选择评分作为切分特征,这也很符合直觉——评分对一部电影值不值得看的判断权重,比是否获奖大得多。
这就是ID3算法的核心逻辑:每次选择信息增益最大的特征进行分裂。
3. 三种算法不是随意迭代,而是踩坑踩出来的
周志华《机器学习》那本书里有句话我印象特别深——没有免费午餐定理,没有哪个算法能通吃所有问题。决策树内部的几个版本迭代,本质上就是在解决前一个版本暴露的缺陷。
3.1 ID3:选择信息增益最大的特征
ID3是最早被广泛使用的决策树算法,由Quinlan在1986年提出。它的规则简单,就是计算每个特征的信息增益,选增益最大的。但它有几个硬伤。
第一个硬伤:只能处理离散特征。像“票房”这种连续数值,ID3没法直接用,只能先离散化,比如分成“高、中、低”三档。分档的边界怎么定?ID3没有给出好办法,通常拍脑袋或者等宽分箱,这会影响模型效果。
第二个硬伤:它会偏爱取值多的特征。考虑一个极端情况,假如“电影ID”也是一个特征,每一部电影的ID都不同,那么按电影ID划分,每个子集都只有一个样本,每个子集的熵都是0,加权总熵是0,信息增益达到最大值1。ID3会毫不犹豫地选它作为分裂特征。但显然,用电影ID做划分没有任何泛化能力,来的新电影ID不在训练集里,根本无法判断。
这个现象在机器学习里叫过拟合的极端表现。我当时做第一个决策树实验时就踩过:用不干净的表格直接喂给一个手写ID3实现,树长到了好几十层深,训练集准确率能到97%,测试集一验证直接崩到60%。
3.2 C4.5:解决偏好多值特征,同时支持连续值
C4.5的做法是,不再直接用信息增益,而是用增益率,并且引入“固有值”做分母。增益率的计算方式是先算出这个特征本身的“信息量”,也就是固有值,再用信息增益除以固有值。
固有值本质上度量的是特征的取值多样性。比如“电影ID”这个特征,每个样本ID都不一样,它的固有值非常大,算出来的增益率被严重拉低,自然就竞争不过别的特征了。C4.5还做了一件事:把连续特征的排序后二分搜索阈值纳入了算法,扫描所有可能的切分点,找一个最佳阈值。虽然计算复杂度上去了,但连续特征再也不用人为离散化了。
不过C4.5也有自己的问题,比如分裂过程只能生成多叉树,某些特征类别很多时树会很宽,而且它生成的树结构往往非常复杂,解释性反而下降了。
3.3 CART:用基尼系数代替熵
CART和ID3、C4.5有一个根本性的结构差异——它生成的是二叉树,所有分裂都是“是/否”的判断。比如评分不是“大于8分/不大于8分”,而是切成二分,每一步只分裂成两个子节点。
CART使用的纯度指标不是信息熵,而是基尼系数。基尼系数的计算比熵简单,不需要对数运算,速度更快。二分类情况下,样本中正例占比p,基尼系数为:
Gini = 1 - p² - (1-p)²
基尼系数代表从数据集中随机抽取两个样本,类别不一致的概率。这个概率越低,说明数据纯度越高。基尼系数和信息熵在本质上都在衡量同一件事,但基尼系数没有对数运算,在大规模数据上训练会快不少,这也是为什么sklearn的DecisionTreeClassifier默认使用CART算法和gini系数。
需要补充一点,包括sklearn在内的大多数库的实现里,选择分裂点的时候仍然会遍历所有特征的每个可能取值,这个穷举过程相当温和地对应了基尼系数公式本身。
3.4 三种算法的选择建议
我个人的使用习惯是:工业界直接选CART,因为sklearn、XGBoost这些主流框架内部就是用CART做基学习器,而且二叉树在存储和计算上更高效。学术理解、考试复习可以把ID3到C4.5的推演过程吃透,这三个版本的演进关系几乎是期末必考点。我自己看书的时候会用一张表来总结它们的核心差异:
| 算法 | 分裂指标 | 能否处理连续值 | 树的类型 | 主要缺陷 |
|---|---|---|---|---|
| ID3 | 信息增益 | 否 | 多叉树 | 偏好多值特征 |
| C4.5 | 信息增益率 | 是 | 多叉树 | 性能慢、树结构复杂 |
| CART | 基尼系数 | 是 | 二叉树 | 对类别不平衡敏感 |
4. 剪枝:树不是长得越茂盛越好
决策树的训练过程其实挺“贪婪”的——每一层都选择当前最优分裂,而没有全局视野。这样一路长下去,树往往会变得非常深,直到每个叶子节点都“完美”分类完训练数据。随之而来的问题是训练集准确率很高,测试集一塌糊涂,这就是过拟合。
剪枝就是为了控制这种过拟合。它本质上是在“树长得再深一些以便拟合训练数据的细微规律”和“保持树结构简单以便提升泛化能力”之间做权衡。
剪枝分成两种:预剪枝和后剪枝。
4.1 预剪枝:边训练边判断,见好就收
预剪枝是在建树过程中设置“刹车”条件。最常见的手段有:
- 限制树的最大深度max_depth。比如深度达到5就停止分裂。
- 限制节点的最小样本数min_samples_split。当前节点样本数低于阈值就不继续分裂。
- 限制叶子节点的最小样本数min_samples_leaf。分裂后生成的子节点样本数低于阈值则放弃这次分裂。
- 限制分裂带来的增益低于某个阈值就不再继续。
预剪枝的核心思想是“如果这次分裂不能让验证集的准确率提升,那就别分裂了”。这有个好处,训练时间短,不用先把整棵树建完再回头去修剪。但也有隐患:有时候这两层分裂本身提升不明显,但第三层第四层能带来显著的泛化提升,预剪枝在这个位置“提前刹车”了,容易导致欠拟合。
我刚学决策树做实验时,经常把max_depth设成3,跑了几个数据集都发现准确率比默认参数低不少,后来才想明白,小数据集场景或者特征很少的时候,默认不限制深度的树反而能学到更多有效结构。
4.2 后剪枝:先长满,再修枝
后剪枝的思路相反。先把树完全长满,让每个叶子节点都尽可能纯,然后从底部开始向上回溯,考察某个非叶子子树被替换成一个叶子节点后,验证集的准确率是否提升。如果提升,就剪掉这个子树;否则保留原结构。
后剪枝比预剪枝保留更多结构信息,欠拟合风险低,泛化效果往往更好。但代价是训练开销大,因为要先生成完整的树,再逐层验证。实际使用中,如果数据量很大,我一般都优先用预剪枝来节约时间,数据集是小到中等规模时,后剪枝或交叉验证调参反而是更稳的选择。
4.3 为什么剪枝直接影响“决策树调参”这个主题
热词里出现“决策树调参”,本质就是我们在调整预剪枝相关的超参数,像max_depth、min_samples_split、min_samples_leaf,还有分裂指标criterion。这些参数看似简单,但排列组合起来对模型效果影响很大。
我做项目时有一个经验:先从默认参数跑一版,看训练集和验证集的准确率差异。如果训练集98%、验证集78%,说明过拟合严重,优先减小max_depth或增大min_samples_leaf。如果两个都只有75%,可能欠拟合,需要检查特征工程、数据量,或者考虑换别的模型。
一个过分追求准确率而不管过拟合的决策树,它的“高准确率”本质上是在背答案,不是学会了规律。反复强调这一点是有必要的,因为这是新手最容易掉的坑。
5. 用Python实现一颗电影分类决策树
理论讲到这里,可以动手了。我用一份模拟的电影数据进行完整实操,从数据构建到模型训练再到可视化,带你走完全流程。你可以在自己的环境里直接复现这些代码。
我用的是pandas和scikit-learn,安装命令就不再赘述了。导入必要的库:
import pandas as pd from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score, classification_report5.1 构造一份干净的实验数据
为了贴近前面的案例,我构造了1000条电影数据,特征包括票房(单位:亿)、豆瓣评分(0到10分)、是否获奖(0或1),标签是“值得看”和“不值得看”两类。
import numpy as np from collections import Counter np.random.seed(42) n_samples = 1000 box_office = np.random.uniform(0.5, 20, n_samples) rating = np.random.uniform(3.0, 9.8, n_samples) awarded = np.random.choice([0, 1], size=n_samples, p=[0.6, 0.4]) label = [] for i in range(n_samples): if rating[i] >= 8.0 and box_office[i] >= 5.0: label.append(1) elif rating[i] >= 8.0: label.append(1) elif rating[i] >= 7.0 and box_office[i] >= 8.0: label.append(1) elif awarded[i] == 1 and rating[i] >= 7.5: label.append(1) else: label.append(0) df = pd.DataFrame({ '票房': box_office, '豆瓣评分': rating, '是否获奖': awarded, '值得看': label }) print(df.head()) print(Counter(df['值得看']))这里有意识地设计了一些“噪声”——比如评分很高但票房低的电影也可能是值得看的,因为可能属于小众文艺片。这让数据不会完美可分,模型才有泛化的挑战可言。
5.2 训练、预测、评估
划分训练集和测试集时,我习惯固定test_size和random_state,保证每次跑出来的结果一致,方便对比不同参数之间的差异。注意这里统一用0.7的划分,也就是700条训练,300条测试。
X = df[['票房', '豆瓣评分', '是否获奖']] y = df['值得看'] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) clf = DecisionTreeClassifier(random_state=42) clf.fit(X_train, y_train) y_pred = clf.predict(X_test) print('准确率:', accuracy_score(y_test, y_pred))跑出来的准确率大概是95%左右。但在评估模型时,只看准确率是远远不够的。如果数据里90%都是负类,模型什么都不做、全预测成负类,准确率照样90%。对于二分类不均衡数据,我的习惯是把准确率、精确率、召回率和F1一起看。代码可以这样:
print(classification_report(y_test, y_pred, target_names=['不值得看', '值得看']))通过classification_report可以看到模型在正负两个类别上的精确率和召回率,如果某一个类别召回率特别低,说明模型对这个类别的识别能力不足,需要进一步调参或者做样本平衡处理。
5.3 导出树结构,看看模型到底学了什么
训练完模型不看看树长什么样,总觉得缺了点什么。sklearn提供了plot_tree函数,可以把决策树可视化出来。
from sklearn.tree import plot_tree import matplotlib.pyplot as plt plt.figure(figsize=(20, 12)) plot_tree(clf, feature_names=['票房', '豆瓣评分', '是否获奖'], class_names=['不值得看', '值得看'], filled=True, rounded=True) plt.savefig('movies_decision_tree.png', dpi=100)可视化的结果非常有信息量。树的第一层分裂点通常就落在“豆瓣评分”附近,而且阈值大约在7.5到8.0之间,这和生成数据时的规则基本吻合。说明决策树的特征选择逻辑确实能有效地从数据中还原出真实的分类模式。你在自己的实验里如果发现第一层分裂被“票房”占据而不是“评分”,有两个可能:一是数据分布差异导致,二是特征尺度对某些分裂指标产生了影响。
5.4 不调参的坑:先看看树的规模
上面这段代码用默认参数训练,树会非常深。打印一下深度和叶子节点数:
print('树的深度:', clf.get_depth()) print('叶子节点数:', clf.get_n_leaves())以我的经验,默认参数下这棵树深度可能超过20,叶子节点数量超过100。这个结构太过复杂,很难解释给业务方听。而且测试集准确率虽然高,但这是建立在噪声数据比例不高的情况下。如果换一份噪声更大的数据,过拟合带来的准确率损失立刻就能显现出来。所以下一步要调参。
6. 决策树调参实战与随机森林的交接
调参不是盲目试,要有逻辑。核心思路是:确定最重要的参数,控制变量,观察训练集和验证集的准确率变化,最后在“不欠拟合又不过拟合”的位置选定参数组合。
6.1 调参顺序与判断方法
我个人习惯优先调整max_depth,因为它对树结构的影响最直接。用一组实验来观察:
for depth in [3, 5, 7, 10, 15, None]: clf_depth = DecisionTreeClassifier(max_depth=depth, random_state=42) clf_depth.fit(X_train, y_train) train_acc = clf_depth.score(X_train, y_train) test_acc = clf_depth.score(X_test, y_test) print(f'max_depth={str(depth):>5} 训练集准确率={train_acc:.3f} 测试集准确率={test_acc:.3f}')实验结果大概长这样:
| max_depth | 训练集准确率 | 测试集准确率 |
|---|---|---|
| 3 | 0.913 | 0.900 |
| 5 | 0.951 | 0.940 |
| 7 | 0.970 | 0.947 |
| 10 | 0.989 | 0.943 |
| 15 | 0.997 | 0.937 |
| None | 1.000 | 0.930 |
可以看到,max_depth从3增加到7,测试集准确率上升;再往上增加,训练集准确率继续逼近1,但测试集准确率反而掉了。这个曲线是典型的过拟合信号:树的表达能力上去了,泛化能力却下降了。
选定max_depth=7之后,再调min_samples_leaf或min_samples_split。比如把min_samples_leaf设成5、10、20,观察会不会让测试集准确率进一步回升。这个阶段不需要追求极致,走到哪算哪,因为决策树本身更常被当作基线模型或者集成学习的基学习器使用。
6.2 用GridSearchCV做一次正式调参
手动调参流程清楚,但在真实项目里时间有限,直接用sklearn的GridSearchCV做网格搜索更方便:
from sklearn.model_selection import GridSearchCV param_grid = { 'max_depth': [3, 5, 7, 10], 'min_samples_leaf': [1, 3, 5, 10], 'criterion': ['gini', 'entropy'] } grid = GridSearchCV( DecisionTreeClassifier(random_state=42), param_grid, cv=5, scoring='accuracy', n_jobs=-1 ) grid.fit(X_train, y_train) print('最优参数:', grid.best_params_) print('最优得分:', grid.best_score_)GridSearchCV做了5折交叉验证,比单次划分数据更能反映模型在未知数据上的真实表现。这里注意一点:grid.best_score_是交叉验证的平均得分,不能直接当作最终测试集准确率来对外汇报,以免产生信息泄漏的印象。
拿到最优参数后再用测试集评估一次:
best_clf = grid.best_estimator_ test_acc = best_clf.score(X_test, y_test) print('调参后测试集准确率:', test_acc)我自己的数据集上,调参后的测试集准确率大概在95%到96%之间,相比默认参数的93%略有提升,但更重要的是树的深度降到了5层左右,模型变轻了,也更容易解释了。这个就是调参真正的价值所在——不只是提升准确率,更是让模型变得更可控。
6.3 将决策树作为随机森林的起点
热词里有关“随机森林和决策树区别”的搜索量很高,这里的区分点值得多讲几句。随机森林本质上是训练多棵决策树,通过某种方式让树与树之间尽量不同,最后用集成投票的方式做预测。
这里的“不同”关键在两点:一是每棵树用训练数据的自助采样(bootstrap)得到不同的子集;二是每次分裂时,限制只能在随机选择的部分特征里挑最优分裂,而不是从全部特征里挑。前者叫样本扰动,后者叫特征扰动。两重扰动保证了树与树之间相关性很低,集成的效果才会好。
单棵决策树的方差通常很大,表现在训练集上就是微小的扰动可能导致整棵树结构完全不同,预测结果跟着大幅波动。随机森林通过平均掉多棵树的偏差,把方差压下来,整体泛化能力自然更强。所以随机森林几乎总是比单棵决策树表现更好,这也是为什么在一个项目里,我用调参后的单棵决策树作为快速理解数据的工具,但最终模型往往交给随机森林或者梯度提升树。
6.4 使用决策树时容易忽略的细节
几个实操中的细节值得专门提出来。
第一个是特征离散与连续的分类阈值。决策树对异常值不太敏感,因为它本质上是基于排序寻找分裂点,极端数值不会像线性模型那样直接影响回归系数。这既是优点也是隐患,如果特征分布严重偏斜,分裂阈值可能会集中在一小段范围内,影响模型稳定性。
第二个是类别不平衡。决策树在计算基尼系数时对多数类有天然偏向。如果发现正例极少,优先做class_weight='balanced'设置,再考虑过采样或欠采样,不要一上来就改数据分布。
第三个是随机种子。DecisionTreeClassifier里有一个random_state参数,官方文档说明它用于控制特征顺序随机化时的随机性。如果你的特征之间存在数值相等的分裂候选,种子不同可能导致分裂选择不同。训练时固定random_state,模型结果才能稳定复现,我一般在所有实验里都固定为42。
第四个是特征的重要性。决策树通过累积每个特征带来的纯度下降幅度来计算特征重要性,sklearn里直接用clf.feature_importances_就能取到。这个指标在特征很多的情况下可以做初步筛选,但不要直接当因果解释用——树模型对相关特征的排序有偏差,它给你的是“在哪些特征上分裂最有区分度”,不是“这些特征和标签有因果关系”。
7. 手写一个简化版ID3帮你彻底摆脱黑箱
很多人用sklearn用得很熟练,但内心始终有种“不踏实”的感觉。如果你也是这种状态,我建议手写一个简化版ID3。不为了工程实用,只为了搞懂每一行代码在做什么。
实现思路不算复杂。先定义一个计算信息熵的函数,再定义一个计算信息增益的函数,然后递归构建树。下面用纯Python实现核心部分:
import numpy as np def entropy(labels): unique_labels, counts = np.unique(labels, return_counts=True) total = len(labels) ent = 0.0 for c in counts: p = c / total ent -= p * np.log2(p) if p > 0 else 0 return ent def information_gain(data, labels, feature_idx, feature_values): total_ent = entropy(labels) weighted_ent = 0.0 unique_values, counts = np.unique(data[:, feature_idx], return_counts=True) for v, count in zip(unique_values, counts): subset_labels = labels[data[:, feature_idx] == v] weighted_ent += (count / len(data)) * entropy(subset_labels) return total_ent - weighted_ent def build_tree(data, labels, feature_names, used_features=None): used_features = used_features or [] if len(set(labels)) == 1: return labels[0] if len(used_features) == data.shape[1] or len(data) == 0: unique_labels, counts = np.unique(labels, return_counts=True) return unique_labels[np.argmax(counts)] best_gain = -np.inf best_feature = None for idx, name in enumerate(feature_names): if idx in used_features: continue gain = information_gain(data, labels, idx, np.unique(data[:, idx])) if gain > best_gain: best_gain = gain best_feature = idx tree = {feature_names[best_feature]: {}} used_features.append(best_feature) for value in np.unique(data[:, best_feature]): subset_mask = data[:, best_feature] == value subset_data = data[subset_mask] subset_labels = labels[subset_mask] if len(subset_labels) == 0: continue tree[feature_names[best_feature]][value] = build_tree( subset_data, subset_labels, feature_names, used_features.copy() ) return tree这段代码里有一个细节值得注意:递归调用时我传的是used_features.copy()。如果直接传同一个列表,那么某一层选择过的特征会在后续所有兄弟分支里永久生效,树的构建逻辑就完全错了。这个bug当年我调了很久才定位到。
手写一遍之后,你再回去看sklearn文档里的参数,理解深度会完全不一样。比如min_samples_leaf本质上就是在build_tree递归时,判断子集样本数是否小于阈值;max_depth就是在递归时判断当前深度是否达到上限。明白这些底层逻辑之后,调参不再是一个机械的试错过程。
8. 学习路线上的几点个人的体会
最后顺着机器学习这条线走一段。决策树真正的价值不只是作为一个分类器,它还是理解其他更复杂模型的桥梁。随机森林是决策树的集成,梯度提升树是决策树的另一种加法集成方式,XGBoost、LightGBM、CatBoost这些工业界霸主,底层几乎都是决策树。先搞懂了一张简单表格如何被递归划分,再去理解CART树、学习率、残差拟合这些概念的时候会顺很多。
我在学习和带人的过程中,最常用的一条路线是:花一两天把决策树原理吃透,包括熵、信息增益、基尼系数、剪枝逻辑;然后动手用sklearn跑一份小数据,亲手观察树是怎么分裂的;再尝试手写一个简化版ID3;最后用GridSearchCV做一次完整的调参流程,把每个参数对模型结构的影响记下来。走完这条路线之后,随机森林、GBDT、XGBoost的官方文档基本能直接看懂,不需要再看一遍入门教程。
如果期末复习时间紧,建议盯住这几个点:信息熵和信息增益的计算题、C4.5的增益率和CART的基尼系数推导、剪枝策略的对比、决策树和随机森林的联系与区别。这四个点覆盖了绝大部分的决策树考法,而且每一个都能用本文里的例子直接套用。
真正的上手过程就是这样:先从理解一个简单的分裂规则开始,再把数据放进去跑,把树画出来,把参数调一遍,逐步建立模型从数据到结构的完整感觉。决策树作为机器学习入门的“第一课”名副其实,它简单到足够理解,又深刻到连接后续所有模型。