19.AI大模型开发:机器学习----决策树与CART算法

一句话讲完决策树:"像做选择题一样,一层一层往下问,直到得出答案。"

写在前面

今天讲一个特别直观、特别好懂 的算法------决策树(Decision Tree)

你平时生活里其实天天都在用决策树,只是你自己没意识到。比如:

  • 你去相亲,心里会过一遍:年龄合不合适?→ 长得帅不帅?→ 工作好不好?→ 见还是不见?

  • 你买西瓜,会敲一敲:声音闷不闷?→ 瓜蒂新不新鲜?→ 皮薄不薄?→ 买还是不买?

决策树就是把这种"一问一答"的决策过程,用计算机自动学出来。


第一部分:决策树是什么?

1.1 一个真实的小故事:银行柜员小张的烦恼

小张在银行工作,每天要审批信用卡申请。他的判断标准是:

python 复制代码
申请人月收入是否大于8000?
    ├── 是 → 是否有稳定工作(在职满1年)?
    │   ├── 是 → 信用分是否大于650?
    │   │   ├── 是 → ✅ 批卡(额度较高)
    │   │   └── 否 → ✅ 批卡(额度较低)
    │   └── 否 → ❌ 拒绝
    └── 否 → ❌ 拒绝

小张的经验,就是一棵决策树

1.2 决策树的三个核心概念

概念 通俗解释 对应上面的例子
内部节点 每个"判断条件" "月收入>8000?""在职满1年?"
分支 判断的结果 "是 / 否"
叶子节点 最终结论(不再继续分了) "✅批卡"或"❌拒绝"

1.3 决策树能解决两类问题

和KNN一样,决策树也是分类回归通吃

任务类型 预测内容 举例
分类树 预测"是哪一类" 信用卡批不批?(批/拒)
回归树 预测"是多少" 批多少额度?(连续数值:5000~10万)

第二部分:决策树的构建流程(4步走)

一颗决策树的"生长"过程分为4步:

步骤 做什么 白话解释
1 特征选择 从所有特征里挑一个"最有用"的,放在树根
2 分裂 根据这个特征把数据分成两组(或多组)
3 递归 对分出来的每一组,重复步骤1-2
4 停止 直到所有叶子节点都"纯"了,或者达到设定的停止条件

最关键的是第1步------怎么判断哪个特征"最有用"? 这就是CART算法的核心。


第三部分:CART决策树(最主流的实现)

3.1 什么是CART?

CART = Classification And Regression Tree(分类与回归树)。

它是目前最主流的决策树实现,也是随机森林、XGBoost、LightGBM这些高级算法的"地基"。

3.2 CART的三个核心规则

规则 说明 举例
规则1:强制二分 每次分裂只能分成两组 问"收入>8000?"→ 是/否,不能分成"高/中/低"三组
规则2:贪心选择 每一步都选当前"最有用"的特征 比较所有特征,选那个让数据分得最"干净"的
规则3:递归进行 一层一层往下分,直到满足停止条件 分完一组再分下一组,像剥洋葱

3.3 怎么判断特征"最有用"?(基尼系数)

CART分类树用的是**基尼系数(Gini Index)**来度量"纯度"。

公式

  • pk​ = 某组数据中第 k 类样本的比例

  • Gini越小 → 数据越"纯"(大家都是一个类)

  • Gini越大 → 数据越"混"(各类都有)

一个特征"有用" ,就是用它划分后,两组的Gini系数之和最小(分得最干净)。

3.4 CART回归树(预测数值)

如果我们要预测"批多少额度"(连续数字),CART也能做。区别在于:

对比 分类树 回归树
目标 预测类别 预测数值
"纯度"衡量 基尼系数(Gini) 均方误差(MSE)
叶子节点输出 投票选最多的类别 该组所有样本的平均值

回归树分裂时,找的是让MSE下降最多的那个分裂点


第四部分:决策树超参数(防过拟合的武器)

4.1 决策树容易过拟合

决策树有个特点:如果不加限制,它可以一直往下分,直到每个叶子节点只剩一个样本 。这样的模型在训练集上100%准确,但在新数据上表现极差------这就是过拟合

4.2 常用超参数(限制树的大小)

参数 作用 建议值
max_depth 树的最大深度(层数) 3~10,太深容易过拟合
min_samples_split 内部节点再划分所需的最小样本数 默认2,可设5~20
min_samples_leaf 叶子节点最少样本数 默认1,可设3~10
max_features 每次分裂考虑的最大特征数 默认全部,可设 sqrt(n)

4.3 剪枝(Pruning)------更高级的防过拟合

剪枝就是砍掉一些没用的树枝,让树变简单。

两种剪枝方式

方式 时机 做法 优缺点
预剪枝 树生长过程中 每次分裂前评估:分了能提升效果吗?不能就停 省时间,但可能"错过好分支"
后剪枝 树完全长成后 从下往上检查:这个分支有用吗?没用就砍掉 效果好,但耗时更久

sklearn的DecisionTreeClassifier默认是预剪枝 ------通过max_depthmin_samples_split等参数限制。


第五部分:信用卡审批预测

5.1 生成数据

python 复制代码
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score, classification_report
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree

# 设置随机种子
np.random.seed(42)

# 生成2000条信用卡申请数据
n = 2000

# 特征:月收入(万)(0.5~15)、在职月数(1~120)、信用分(300~850)、负债率(0~0.9)
income = np.random.uniform(0.5, 15, n)
work_months = np.random.randint(1, 121, n)
credit_score = np.random.randint(300, 851, n)
debt_ratio = np.random.uniform(0, 0.9, n)

# 生成标签(批卡=1,拒卡=0)
# 业务逻辑:收入高+信用分高+负债低 → 批卡
score = (income * 0.6 + credit_score * 0.01 - debt_ratio * 3 + work_months * 0.01)
y = (score > 5).astype(int)  # 综合得分>5批卡

# 拼成DataFrame
data = pd.DataFrame({
    '月收入(万)': income,
    '在职月数': work_months,
    '信用分': credit_score,
    '负债率': debt_ratio,
    '是否批卡': y
})

print("数据前5行:")
print(data.head())
print(f"\n总样本数:{len(data)},批卡比例:{y.sum()/n*100:.1f}%")

5.2 划分训练集和测试集(决策树不需要标准化)

python 复制代码
# 特征和标签分开
X = data[['月收入(万)', '在职月数', '信用分', '负债率']]
y = data['是否批卡']

# 训练集70%,测试集30%
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)

print(f"训练集:{len(X_train)}条,测试集:{len(X_test)}条")

注意 :和KNN不同,决策树不需要做标准化!因为决策树是"按阈值比较",不是算距离,特征尺度不影响分裂结果。

5.3 训练决策树模型(分类)

python 复制代码
# 创建决策树分类器(限制深度防止过拟合)
model = DecisionTreeClassifier(
    max_depth=5,           # 最大深度5层
    min_samples_split=20,  # 节点至少20个样本才继续分
    min_samples_leaf=10,   # 叶子节点至少10个样本
    random_state=42
)

# 训练
model.fit(X_train, y_train)

# 预测
y_pred = model.predict(X_test)

# 准确率
acc = accuracy_score(y_test, y_pred)
print(f"测试集准确率:{acc * 100:.2f}%")

# 详细分类报告
print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=['拒卡', '批卡']))

5.4 特征重要性分析(决策树的隐藏技能)

决策树能告诉我们:哪个特征对决策最重要?

python 复制代码
# 获取特征重要性
importance = model.feature_importances_
feature_names = ['月收入(万)', '在职月数', '信用分', '负债率']

print("\n特征重要性(加起来=1):")
for name, imp in zip(feature_names, importance):
    print(f"  {name}:{imp:.3f}")

# 可视化特征重要性
plt.figure(figsize=(8, 5))
plt.barh(feature_names, importance, color='steelblue')
plt.xlabel('重要性')
plt.title('信用卡审批决策树-特征重要性')
plt.show()

输出示例

python 复制代码
特征重要性(加起来=1):
  月收入(万):0.452
  在职月数:0.098
  信用分:0.335
  负债率:0.115

说明:月收入和信用分是最关键的两个因素。

5.5 可视化决策树(画出来看看!)

python 复制代码
plt.figure(figsize=(25, 15))
plot_tree(
    model,
    feature_names=feature_names,
    class_names=['拒卡', '批卡'],
    filled=True,           # 用颜色表示类别
    rounded=True,          # 圆角框
    fontsize=12,
    max_depth=4            # 只画前4层,不然太密
)
plt.title('信用卡审批决策树')
plt.show()

5.6 决策树回归(预测授信额度)

如果把标签改成连续值(批多少额度),就是用回归树。

python 复制代码
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error

# 生成新的标签:授信额度(万),范围2~50
limit = 2 + income * 2.5 + credit_score * 0.02 - debt_ratio * 10 + np.random.normal(0, 2, n)
limit = np.clip(limit, 2, 50)

# 重新切分
X_train, X_test, y_train_reg, y_test_reg = train_test_split(
    X, limit, test_size=0.3, random_state=42
)

# 创建回归树
reg_model = DecisionTreeRegressor(
    max_depth=6,
    min_samples_split=20,
    min_samples_leaf=10,
    random_state=42
)

# 训练
reg_model.fit(X_train, y_train_reg)

# 预测
y_pred_reg = reg_model.predict(X_test)

# 评估
mse = mean_squared_error(y_test_reg, y_pred_reg)
mae = mean_absolute_error(y_test_reg, y_pred_reg)
print(f"回归树 - 均方误差(MSE):{mse:.2f}")
print(f"回归树 - 平均绝对误差(MAE):{mae:.2f}")
print(f"真实额度前5个:{y_test_reg[:5]}")
print(f"预测额度前5个:{y_pred_reg[:5]}")

第六部分:决策树 vs KNN(什么时候用谁?)

对比维度 决策树 KNN
是否需要标准化 ❌ 不需要 ✅ 必须做
可解释性 ✅ 超强(可以画出来看) ❌ 黑盒
训练速度 ✅ 快 ❌ 慢(要存所有数据)
预测速度 ✅ 极快(就几层判断) ❌ 慢(要算距离)
对缺失值处理 可以处理 无法处理
过拟合风险 高(需要剪枝) 低(调K值就好)
非线性数据 ✅ 能处理 ❌ 效果差

经验建议

  • 需要解释给老板/客户听 → 选决策树(可以画图展示)

  • 数据特征少、干净 → 选KNN

  • 数据有缺失值 → 选决策树

  • 追求预测速度(在线服务)→ 选决策树

  • 追求极致准确率 → 两个都试试,选好的


第七部分:CART的优缺点

7.1 优点(5个)

优点 说明
简单易懂 画出来一看就懂,非技术人员也能理解
功能强大 分类回归通吃
规则简洁 强制二分,逻辑清晰
鲁棒性好 自带剪枝,抗过拟合
应用广泛 是随机森林、XGBoost的基石

7.2 缺点(3个)

缺点 说明
容易过拟合 不加限制的话,可以完美拟合训练集
结果不稳定 数据稍微变一点,整棵树可能完全不一样
非全局最优 每一步只选当前最优(贪心),不保证整体最优

第八部分:剪枝详解(两种方式对比)

8.1 预剪枝(Pre-pruning)

在树生长过程中就决定"要不要继续分"。

python 复制代码
# 这就是预剪枝------通过参数限制树的生长
model = DecisionTreeClassifier(
    max_depth=5,           # 到5层就不分了
    min_samples_split=20,  # 样本少于20就不分了
    min_samples_leaf=10    # 叶子少于10就不分了
)
优点 缺点
省时间(不用长完整棵树) 可能"短视"------现在没用但后面有用的分支被误砍
防止过拟合 容易欠拟合

8.2 后剪枝(Post-pruning)

先让树完全长成,再从下往上检查:这个分支有用吗?没用就砍掉。

sklearn的DecisionTreeClassifierccp_alpha参数可以实现成本复杂度剪枝(一种后剪枝)。

python 复制代码
# 用ccp_alpha做后剪枝(值越大剪得越狠)
model_pruned = DecisionTreeClassifier(
    ccp_alpha=0.005,  # 剪枝系数,需要调参
    random_state=42
)
model_pruned.fit(X_train, y_train)
优点 缺点
比预剪枝效果好(保留了更多有用分支) 耗时(先要长完整棵树)

实战建议 :先用预剪枝(限制max_depth等),简单高效;如果效果还不够好,再尝试后剪枝。


第九部分:完整知识点脑图

python 复制代码
决策树(CART)
│
├── 核心思想
│   └── 像做选择题一样,一层层往下问,直到出答案
│
├── 三大规则
│   ├── 强制二分(每次只分两组)
│   ├── 贪心选择(每一步选最"纯"的特征)
│   └── 递归进行(一层一层往下分)
│
├── 两种任务
│   ├── 分类树(基尼系数衡量纯度)
│   └── 回归树(MSE衡量纯度,叶子输出平均值)
│
├── 防止过拟合
│   ├── 预剪枝(限深度、限样本数)← sklearn默认
│   └── 后剪枝(先长完整棵树再砍)← ccp_alpha
│
├── 优势
│   ├── 可解释性强(可以画图)
│   ├── 不需要标准化
│   └── 能处理缺失值
│
└── 劣势
    ├── 容易过拟合
    ├── 结果不稳定(数据一变树就变)
    └── 贪心策略不保证全局最优

第十部分:总结

模块 核心内容
决策树思想 一层一层做选择题,直到得出结论
CART算法 强制二分 + 基尼系数 + 递归分裂
分类 vs 回归 分类看基尼,回归看MSE
超参数 max_depth、min_samples_split等(防过拟合)
剪枝 预剪枝(生长时限制)和后剪枝(长完再砍)
对比KNN 决策树可解释性强、不需标准化、速度快
代码实战 信用卡审批预测(分类)+ 额度预测(回归)

给新手的3个建议

  1. 先理解"树是怎么长的":每次找一个特征分裂→让数据变纯→递归→停止。把这句话背下来,决策树的骨架你就有了。

  2. 重视max_depth这个参数 :它是防止过拟合最简单粗暴的手段。从max_depth=3开始试,慢慢加深。

  3. 画图看树plot_tree是你理解模型最好的工具。画出来一看,比看100行代码都管用。