决策树与随机森林
本章目录
学完后,你应能独立完成
- 手算所有候选阈值的叶均值与 SSE
- 解释树的容量、尺度与外推局限
- 推导平均方差并说明 OOB 的边界
改变一个条件,观察发生什么
浏览器内即时教学计算;Python 实验结果另列于代码区。图形与数值一起核对。
一个叶为什么预测均值
固定分组后,回归叶用常数 a 最小化 Σ(y_i−a)²;对 a 求导得叶均值。CART 回归比较分裂后的左右 SSE,分类比较加权 Gini 或熵。分类叶的概率是训练叶内类别比例,而不是回归均值的随意替代。
候选阈值放在相邻不同特征值之间。重复 x 不能按 y 人为分开;先按 x 排序,再扫描合法候选。贪心局部分裂不保证整棵树的全局最优。
分裂增益要与父节点比较
x=1…6、y=[0,0,1,4,5,5],父均值2.5,父 SSE=33.5。阈值3.5给左均值1/3、右均值14/3,子 SSE=4/3,增益约32.1667。只有看完所有合法阈值,才知道该刀优于其他候选。
分类用 Gini=1−Σp_c²或熵−Σp_c log p_c。左右不纯度按样本占比加权,不能直接平均。这个样本中若前后各三项是不同类别,3.5会分出纯叶。
容量与外推是结构带来的
深度限制最长路径,叶数限制区域数,叶最小样本控制局部估计的粗糙程度。深度3的二叉树最多8叶,但不一定有8叶。行数多不代表叶内独立日期多,金融面板需额外检查跨期覆盖。
精确顺序阈值分裂通常不依赖单位;正单调变换保留候选样本分组。直方图、精度、并列和缺失规则可能改变实现结果。分段常数叶不会像线性斜率那样向训练范围之外连续外推。
平均只减少非共同波动
设 B 个基预测各方差 σ²,两两同相关 ρ。这只是帮助理解的简化假设。平均的方差来自 B 项单树方差和 B(B−1) 项协方差;增加 B 无法消去共享成分。
- 平均预测是 Σf_b/B。
- 方差对 B² 归一化,协方差不能忽略。
- B→∞ 时仍留下 ρσ²。
- RF 的随机子特征有助去相关,也可能削弱单树,需要平衡而非最大随机化。
bootstrap 与 OOB 不等于未来验证
有放回抽 n 次,一行未抽中的概率 (1−1/n)^n趋于e⁻¹≈.368。OOB 用没抽到该行的树产生预测,在相应 iid 条件下可用于内部误差诊断。
它只排除某一行,仍可能见该日其他股票、相邻重叠标签或之后市场状态。主要研究结论应使用时间合法的外部验证。训练分裂重要性还偏向有很多候选切点的特征,相关列也会分摊贡献。
从算法到一次真实运行
代码在本地 Python 中执行,HTML 内展示参考实现和本轮运行结果。请先独立完成练习,再展开完整参考文件。
def best_split(x, y, min_leaf=1):
"""One-feature squared-error stump. None means no positive valid gain."""
x, y = np.asarray(x,float), np.asarray(y,float)
if x.ndim != 1 or y.shape != x.shape or min_leaf < 1:
raise ValueError("Expected matching 1D arrays and min_leaf>=1")
if len(x)==0 or not np.isfinite(x).all() or not np.isfinite(y).all():
raise ValueError("Expected nonempty finite data")
order = np.argsort(x,kind="stable"); xs, ys = x[order], y[order]
parent = np.sum((ys-ys.mean())**2)
best = None; best_sse = parent
for i in range(min_leaf, len(x)-min_leaf+1):
if xs[i-1] == xs[i]:
continue
left, right = ys[:i].mean(), ys[i:].mean()
sse = np.sum((ys[:i]-left)**2)+np.sum((ys[i:]-right)**2)
if sse < best_sse-1e-12:
best_sse = sse
best = (float((xs[i-1]+xs[i])/2), float(left), float(right), float(sse))
return best"""Standalone synthetic teaching example. Run locally with Python, not in the HTML."""
import json
import numpy as np
import pandas as pd
from scipy.special import expit
def clean(value):
if isinstance(value, dict): return {str(k):clean(v) for k,v in value.items()}
if isinstance(value,(list,tuple,np.ndarray)): return [clean(v) for v in value]
if isinstance(value,(np.integer,)): return int(value)
if isinstance(value,(float,np.floating)): return float(value) if np.isfinite(value) else None
if isinstance(value,(np.bool_,)): return bool(value)
return value
def print_result(result):
print(json.dumps(clean(result),ensure_ascii=False,allow_nan=False))
def best_split(x, y, min_leaf=1):
"""One-feature squared-error stump. None means no positive valid gain."""
x, y = np.asarray(x,float), np.asarray(y,float)
if x.ndim != 1 or y.shape != x.shape or min_leaf < 1:
raise ValueError("Expected matching 1D arrays and min_leaf>=1")
if len(x)==0 or not np.isfinite(x).all() or not np.isfinite(y).all():
raise ValueError("Expected nonempty finite data")
order = np.argsort(x,kind="stable"); xs, ys = x[order], y[order]
parent = np.sum((ys-ys.mean())**2)
best = None; best_sse = parent
for i in range(min_leaf, len(x)-min_leaf+1):
if xs[i-1] == xs[i]:
continue
left, right = ys[:i].mean(), ys[i:].mean()
sse = np.sum((ys[:i]-left)**2)+np.sum((ys[i:]-right)**2)
if sse < best_sse-1e-12:
best_sse = sse
best = (float((xs[i-1]+xs[i])/2), float(left), float(right), float(sse))
return best
if __name__ == "__main__":
x=np.arange(1,7); y=np.array([0.,0.,1.,4.,5.,5.])
split=best_split(x,y)
np.testing.assert_allclose(split,[3.5,1/3,14/3,4/3],atol=1e-12)
assert best_split(np.ones(3),np.array([1.,2.,3.])) is None
assert best_split(x,np.ones(6)) is None
assert best_split(np.array([1.,1.,2.,2.]),np.array([0.,0.,1.,1.]))[0]==1.5
print_result({"threshold":split[0],"left_mean":split[1],"right_mean":split[2],
"child_sse":split[3],"parent_sse":np.sum((y-y.mean())**2),
"averaging_variance_rho_0_8_B_100":.8+(1-.8)/100})
查看本轮本地运行记录 · 2026-10-12
{
"threshold": 3.5,
"left_mean": 0.3333333333333333,
"right_mean": 4.666666666666667,
"child_sse": 1.3333333333333335,
"parent_sse": 29.5,
"averaging_variance_rho_0_8_B_100": 0.802
}先回答,再看提示和答案
子 Gini=.2/.4、左右人数2/8,加权子不纯度多少?
一级提示
权重是节点内样本占比。
参考答案与条件
.2×.2+.8×.4=.36。不是(.2+.4)/2。
ρ=1 时增加树数能降低简化平均方差吗?
一级提示
先写出目标、信息集和所需条件。
参考答案与条件
不能,方差仍为 σ²。去掉非共享波动才是平均的收益。
树不需标准化,意味着预处理可以随便做吗?
一级提示
先写出目标、信息集和所需条件。
参考答案与条件
不意味着。时点、缺失、分箱及分类编码仍需检查;不缩放只是特定顺序分裂的性质。
把知识迁移到一个新任务
写单特征回归树桩,返回阈值、左右叶值、子 SSE。测试重复值、常数 x、常数 y、未排序输入和 min_leaf。再解释为什么 OOB 无法替代股票未来验证。
这里保存学习笔记,不在浏览器执行 Python。A/B/C 由你按独立完成程度填写。
参考与进一步阅读
在线文档可能比本地固定版本更新;API 行为以已验证代码和版本记录为准。
掌握状态与复习
关掉资料独立完成后记 A;提示后完成记 B;需要重学记 C。
记录尽量保存在当前浏览器,建议定期导出备份。