Error in user YAML: (<unknown>): found a tab character that violate indentation while scanning a plain scalar at line 3 column 3
---
- oeasy Python 0799
- 这是 oeasy 系统化 Python 教程,从基础一步步讲,扎实、完整、不跳步。愿意花时间学,就能真正学会。
- 本教程同步发布在:
- 个人网站: `https://oeasy.org`
- 蓝桥云课: `https://www.lanqiao.cn/courses/3584`
- GitHub: `https://github.com/overmind1980/oeasy-python-tutorial`
- Gitee: `https://gitee.com/overmind1980/oeasypython`
---- 上次 我们封装了
- 标准化器 变型后的数据
- 随机森林分类器
- 封装 成了 流水线对象
- 两个操作连锁完成
- 流水线可以直接 进行预测
- 啥是 随机森林决策树 数量?🤔
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import matplotlib.pyplot as plt
X = [[1.75,1.78,70],[1.65,1.68,55],[1.83,1.85,85],[1.70,1.73,60],[1.91,1.96,95],[1.88,1.93,82],[1.98,2.11,90],[2.03,2.21,102],[2.08,2.13,111],[2.16,2.27,116]]
y = [0,0,0,0,0,1,1,1,1,1]
pipe = Pipeline([('scaler',StandardScaler()),('dt',DecisionTreeClassifier(random_state=42))])
pipe.fit(X,y)
dt = pipe.named_steps['dt']
print(dt.feature_importances_)
plt.figure(figsize=(8,6))
plot_tree(dt,feature_names=['Height','ArmSpan','Weight'],class_names=['No','Yes'],filled=True)
plt.savefig('tree.png')
plt.close()
print('Saved: tree.png')
- 把 随机森林(RandomForestClassifier) 评估器
- 换成了 决策树(DecisionTreeClassifier)
- 第一刀 为什么 切在
- 臂展 -0.382
- 标准化后的特征矩阵
X_scale (10行3列):
[[-0.8923, -0.9458, -0.8634], # 样本1:身高、臂展、体重标准化值
[-0.8985, -1.4570, -1.6436], # 样本2
[-0.4123, -0.5879, -0.0832], # 样本3
[-1.2123, -1.1994, -1.3835], # 样本4
[ 0.0799, 0.0256, 0.4369], # 样本5
[-0.1046, -0.1789, -0.2393], # 样本6
[ 0.5107, 0.7413, 0.1768], # 样本7
[ 0.8185, 1.2526, 0.8010], # 样本8
[ 1.1262, 0.8436, 1.2691], # 样本9
[ 1.6184, 1.5593, 1.5292]] # 样本10
- 标准化之后的臂展特征
样本 | 标准化臂展值 | 标签(0=无潜质,1=有潜质)
1 | -0.9458 | 0
2 | -1.4570 | 0
3 | -0.5879 | 0
4 | -1.1994 | 0
5 | 0.0256 | 0
6 | -0.1789 | 1
7 | 0.7413 | 1
8 | 1.2526 | 1
9 | 0.8436 | 1
10 | 1.5593 | 1
- 对「标准化臂展值」
- 进行从小到大排序
- 决策树必须排序
- 进行从小到大排序
排序后:
值:-1.4570 → 标签0
值:-1.1994 → 标签0
值:-0.9458 → 标签0
值:-0.5879 → 标签0
值:-0.1789 → 标签1
值:0.0256 → 标签0
值:0.7413 → 标签1
值:0.8436 → 标签1
值:1.2526 → 标签1
值:1.5593 → 标签1
- 排序后
- 标准化臂展值 + 对应标签的完整排序结果
- 计算「所有相邻值的中点」
- 决策树的候选阈值池
- 决策树不会乱选阈值
- 只会选「排序后相邻两个值的平均值」作为候选阈值
候选阈值1:(-1.4570 + -1.1994)/2 = -1.3282
候选阈值2:(-1.1994 + -0.9458)/2 = -1.0726
候选阈值3:(-0.9458 + -0.5879)/2 = -0.7669
候选阈值4:(-0.5879 + -0.1789)/2 = -0.3834 ✅ 重点!就是这个值!
候选阈值5:(-0.1789 + 0.0256)/2 = -0.0767
候选阈值6:(0.0256 + 0.7413)/2 = 0.3835
候选阈值7:(0.7413 + 0.8436)/2 = 0.7925
候选阈值8:(0.8436 + 1.2526)/2 = 1.0481
候选阈值9:(1.2526 + 1.5593)/2 = 1.4060
- 因为只有这些中点
- 才能把样本切成左右两部分
- 得到 9 个候选阈值
- 为什么 选这个值?
- 第一刀之前
- 每一刀的目标 都是要
- 把节点的加权基尼系数降到最低
- 第一刀切出纯净组 切分后
- 左分支 = 4个样本
- 全部是标签 0(无潜质)
- Gini=0.0(绝对纯净,没有任何混杂)
- 这个切分的牛逼之处在于
- 一刀就把 4 个「无潜质」样本彻底提纯、完全分离出来了
- 这是所有候选阈值里
- 唯一能做到「单侧 Gini=0」的阈值
- 臂展达不到的
- 就一定没有球员潜质!
- 啥是基尼系数?
- 基尼 是 数学家
- 研究的 是 财富是否平均的系数
- 假设 群体有 100 人 总财富为 100 万元
| 基尼系数值 | 分配状态 | 具体特征示例 |
|---|---|---|
| 0 | 绝对平等 | 100人总财富100万元 → 每人1万元 累计10%人口拥有10%财富 累计50%人口拥有50%财富 |
| 0.6 | 高度不平等 | 100人总财富100万元 → 前10%人口拥有60万元(60%财富) 后50%人口仅拥有5万元(5%财富) |
| 0.8 | 极端不平等 | 100人总财富100万元 → 前5%人口拥有80万元(80%财富) 后80%人口仅拥有5万元(5%财富) |
| 1 | 绝对不平等 | 100人总财富100万元 → 1人独占全部100万元 其余99人财富为0 |
- 决策树算法 在设计 “节点纯度指标” 时
- 借用了基尼系数的数学核心逻辑
- 通过 “概率平方和” 衡量分布的不均匀性
- 但调整了公式以适配分类任务
- 因此被称为 基尼不纯度(Gini Impurity)
- 借用了基尼系数的数学核心逻辑
- 第二刀
- 为什么 还选
臂展 - 为什么 在0.278
- 为什么 还选
- 第二刀的目标 还是 要
- 把 总节点的 加权基尼系数 降到最低
- 第一刀切完后右分支剩下 6 个样本
- 只有臂展能继续切出
- 单侧基尼 = 0 的 纯节点
- 身高 / 体重依旧做不到
- 而且这是能让基尼系数下降幅度最大的唯一选择
- 没有其他特征能替代
- 只有臂展能继续切出
- 臂展 满足 某个条件
- 一定有潜质
- 衡量 总的加权基尼系数
- 第三刀的目标 还是 要
- 把 总节点的 加权基尼系数 降到最低
- 总节点的基尼系数
| 切分步骤 | 全局加权基尼系数 | 核心变化 |
|---|---|---|
| 切分前 | 0.5 | 初始最不纯状态 |
| 第1刀后 | 0.167 | 纯度大幅提升 |
| 第2刀后 | 0.100 | 纯度进一步提升 |
| 第3刀后 | 0 | 所有节点纯,分类完成 |
- 最后一刀 肯定能 一分为二
- 为什么 还选 臂展?
-
第二刀后 剩下的左子分支
- 共2个样本
- 标签是
[1,0]
- 标签是
- 共2个样本
-
这两个样本很容易混淆
| 指标 | 无潜质 | 有潜质 |
|---|---|---|
| 原始身高(m) | 1.91 | 1.88 |
| 原始体重(kg) | 95 | 82 |
| 原始臂展(m) | 1.96 | 1.93 |
- 身高高、体重大、臂展长 的人
- 反而没有潜质
- 那为什么选择臂展?
-
决策树的【特征选择优先级】
- 从头到尾只选同一个特征到底
- 决策树的 CART 算法有一个硬性规则
-
能一个特征解决的问题
- 绝对不用第二个特征
-
臂展是唯一在全局范围内
- 能稳定区分标签的特征
-
那么 总结出 这个决策树之后
- 如何评估呢?
- 决策过程像一棵树
- 不断向下 开枝散叶
player = [1.88, 1.93, 82]
from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np
# 1. 原始数据训练模型
X = [[1.75,1.78,70],[1.65,1.68,55],[1.83,1.85,85],
[1.70,1.73,60],[1.91,1.96,95],[1.88,1.93,82],
[1.98,2.11,90],[2.03,2.21,102],[2.08,2.13,111],[2.16,2.27,116]]
y = [0,0,0,0,0,1,1,1,1,1]
pipe = Pipeline([('scaler',StandardScaler()),('dt',DecisionTreeClassifier(random_state=42))])
pipe.fit(X, y)
scaler, dt = pipe.named_steps['scaler'], pipe.named_steps['dt']
# 2. 待观察球员数据
player = [1.88, 1.93, 82]
player_std = scaler.transform(np.array(player).reshape(1,-1))[0]
a_std = player_std[1] # 球员标准化臂展值
# 3. 核心分支判断推理
print("="*60)
print("✅ 球员分支判断全过程(仅臂展维度)")
print(f"球员标准化臂展值:{a_std:.6f}")
print("-"*60)
# 第1层判断
cond1 = a_std <= -0.381518
print(f"第1层:臂展 <= -0.381518 → {cond1} → 走右分支")
# 第2层判断
cond2 = a_std <= 0.356083
print(f"第2层:臂展 <= 0.356083 → {cond2} → 走左分支")
# 第3层判断
cond3 = a_std <= -0.101738
print(f"第3层:臂展 <= -0.101738 → {cond3} → 走左分支")
# 最终结果
pred = pipe.predict(np.array(player).reshape(1,-1))[0]
print("-"*60)
print(f"最终判定:{'有潜质(1)' if pred==1 else '无潜质(0)'}")
print("="*60)
- 结论
- 为啥会有决策树模型呢?
- 早期有专家系统
- 把专家的知识数字化
- 其实就是分类讨论
- 一堆if-else
- 但本质上还是人来决定具体参数
- 专家系统的最大痛点是规则不全、规则冲突
- 而决策树可以完美解决这个问题
- 用数据自动挖掘规则
- 补充和优化专家系统的知识库
| 对比维度 | 决策树 | 专家系统 |
|---|---|---|
| 核心驱动 | 数据驱动:从数据集里自动学习规则 | 知识驱动:由领域专家手动定义规则 |
| 规则来源 | 来自数据的统计规律,比如“身高≤1.855m”这个阈值是算法从数据中算出来的 | 来自专家的经验,比如“身高>2.0m臂展更长”是专家凭经验总结的 |
| 构建方式 | 无需人工干预,输入数据→算法训练→输出规则树,代码自动完成 | 需要大量人工成本:专家访谈→规则提炼→编码到知识库,过程繁琐 |
| 灵活性 | 数据更新后,重新训练就能生成新规则,适配新数据 | 规则需要手动修改,新情况出现时,若没有对应的规则就无法决策 |
| 可扩展性 | 数据量越大,规则可能越精准(但可能过拟合) | 规则数量过多时,容易出现规则冲突(比如两条规则条件重叠但结论不同) |
| 适用场景 | 有充足标注数据的场景,比如: 1. 身高体重预测臂展 2. 客户分类、风险评估 |
数据稀缺但专家经验丰富的场景,比如: 1. 早期医疗诊断(数据少,医生经验关键) 2. 工业设备故障排查(专家知道故障和症状的对应关系) |
- 机器学习中
- 具体参数 由数据决定
- 这次我们了解了决策树
- 独木不成林
- 决策树 又是 怎么形成 随机森林的呢?🤔
- 我们下次再说👋
- 本文来自 oeasy Python 系统教程。
- 想完整、扎实学 Python,
- 搜索 oeasy 即可。

















