机器学习基础 Machine Learning
这一阶段的价值不在算法本身,而在于方法论:怎么定义问题、怎么切分数据、怎么定义「好」、怎么判断提升是否可信、以及怎么把「检索排序」这种真实系统的指标做对。这些直觉会一路沿用到 LLM 的 Evals 与 A/B 实验——很多 AI 项目死掉,不是模型不行,而是评估做错了。2026 年的真实战场里,GBDT 在表格数据上依然常胜神经网络,而「概率校准」「排序指标」这种被忽视的细节,恰恰是区分「能上线的模型」与「玩具」的分水岭。
阶段总览
- 能把一个模糊的业务问题翻译成「输入 / 输出 / 标签 / 指标 / 约束」的建模问题
- 掌握偏差-方差分解,能判断「该加数据还是该加容量」,并识别过拟合 / 欠拟合的判别信号
- 熟练使用 XGBoost / LightGBM / CatBoost 处理结构化数据,理解它们相对神经网络的真实优势与调参优先级
- 能设计正确的数据划分与交叉验证方案(含时间序列前向链、分组切分),识别并排查四类数据泄漏
- 会选评估指标,能解释 ROC-AUC 与 PR-AUC 的取舍,并做概率校准(为什么 LLM 的置信度不可信)
- 掌握检索与排序指标(Recall@k / MRR / MAP / NDCG),能搭建离线排序基线与指标体系(里程碑 M6)
- 清楚 2026 年经典 ML 的真实战场:结构化数据、风控、推荐、特征工程、排序基线与大模型护栏
| 主题 | 投入比重 | 为什么 |
|---|---|---|
| 评估与方法论(含排序指标) | 30% | 直接迁移到 LLM Evals 与 A/B 实验,M6 即在此 |
| 树模型 + 特征工程 | 25% | 结构化数据场景仍是工业界主力,性价比最高 |
| 偏差-方差 / 正则化 / 校准 | 20% | 决定模型能不能上线的底层直觉 |
| 线性模型 / 朴素贝叶斯 | 15% | 可解释、基线、风控与文本分类的大量应用 |
| 其他算法(SVM/KNN/聚类/降维) | 10% | 理解思想与边界,能判断何时该用 |
1. 学习范式与偏差-方差:方法论的地基
学习路径
- 读 1.1:记四种范式成本边界(监督 55–65%、自监督靠结构、半监督伪标签阈值 0.9–0.97)
- 跑内置伪标签循环,追踪每轮训练集增量与收敛并解释阈值作用
- 完成 1.4 自测:四种范式各举一个 2026 真实用法并说明为何如此组合
- 对接 M6:检索冷启动先用无监督聚类分桶 + 伪标签半监督扩标把基线跑起来
核心知识点详解
- 伪标签阈值是个旋钮:置信阈值取高(0.9–0.97)更安全但扩标慢,取低(0.7)扩张快却会把模型错误固化成标签。经验:纯自训练通常只涨 1–3 个点,边际收益服从长尾——标签 1k→5k 常涨 10–20 点,5k→50k 只涨 1–2 点,先标多少要算边际收益。
- 自监督绕开标注瓶颈:用数据自身结构造监督:文本走掩码(
MaskedLM/MAE),多模态走对比(CLIP/SimCLR)。2026 工业分布约 55–65% 监督、25–30% 自监督+微调,范式按数据可获得性组合而非二选一。 - 伪标签最常见的坑:伪标签只能在训练集内部生成,绝不写入验证折,否则等于泄漏、指标虚高。正确姿势是「置信度过滤 + 一致性正则」,实现见
for it in range(3): ...的循环。
学习路径
- 读 1.2:手写 E[(y−ŷ)²]=Bias²+Var+σ² 分解,背下不可约噪声 σ² 的语义
- 跑多项式度数 1/4/15 示例,记录三档的 MSE 与 5 折 std 判偏差/方差主导
- 用 learning_curve 跑一组,判断「加数据还有没有收益」的收敛判据
- 完成 1.4 自测:解释为什么换归纳偏置能同时降 Bias 与 Var
核心知识点详解
- 泛化误差三源分解:E[(y−ŷ)²]=Bias² + Var + σ²,σ² 是不可约噪声(数据带噪,再好的模型也去不掉)。降低 Bias 靠加容量/特征,降低 Var 靠加数据/正则/集成——两者随模型复杂度此消彼长,呈 U 形曲线。
- 定量诊断经验阈值:分类任务 train-val gap 超 8–15 个点即典型过拟合;回归因量纲不同要看相对 gap(gap/train_MSE)。用
learning_curve判「加数据」:val 还在降→有用;已成平台且 gap 小→转特征/模型族。 - 换归纳偏置是更高杠杆:固定算法族内才存在 B/V 此消彼长;切换到更好假设空间(树/Transformer 替换线性)可同时降两者。常见坑:先查泄漏再调容量,多数「train 低 val 高」根因是数据而非模型。
学习路径
- 读 1.3:背下四信号→四动作决策表,记住 train-val 分类 gap 8–15 点与相对 gap 0.15 阈值
- 跑 diagnose 函数对三组数字,逐一说出动作排序
- 完成 1.4 自测:对 train=0.99/val=0.62 给出三步动作及顺序(先查泄漏→扩验证→再调容量)
核心知识点详解
- 四信号→四动作表:train高val高=欠拟合→加容量/特征/升lr;train低val高=过拟合→加数据/正则/早停;两者都低=健康进误差分析;val 抖动大=验证集太小→扩验证/分层/重复K折。
- 加数据 vs 加容量决策树:①训练误差高→先加容量,加数据救不了欠拟合;②训练误差低验证高→先加数据/正则而非堆容量;③用相对 gap 阈值 0.15 量化:
diagnose(0.99,0.63)判定过拟合。 - 动作顺序常常被跳过(坑):看到 train 高 val 低,先查泄漏→扩验证→加数据→加正则→换模型族;跳过前两步直接降容量多半白调——数据少时加数据的收益远大于调正则。
核心知识点详解
- 度数 1/4/15 三档判据:deg=1 偏差主导(MSE 大但 std 小)、deg=15 方差主导(std 明显变大);用
cross_val_score同时看 MSE 的均值和 5 折 std 即可对号入座。 - 平台判据:learning_curve 里 val 已成平台且 gap 小→再加 10 倍数据通常只涨 1–2 个点,应转向特征/模型族;val 仍在降→加数据仍有收益。
- 四范式用例别漏组合逻辑:监督=表格风控、自监督=LLM 预训练、半监督=伪标签冷启动、无监督=向量召回聚类。坑:只背名单不答「按数据可获性分阶段组合」这一核心判断会减分。
1.1 四种学习范式在 2026 的占比与取舍
现代 ML 系统很少是「纯有监督」。理解四种范式的边界,是判断「该花多少钱标数据」的前提。2026 年的真实工业分布大致是:约 55–65% 的模型建立在有监督(含弱监督 / 远程监督)之上,25–30% 走「自监督预训练 + 有监督微调」两阶段,5–10% 用半监督或无监督处理冷启动与异常检测。
| 范式 | 标签需求 | 2026 典型用法 | 代表方法 |
|---|---|---|---|
| 监督学习 Supervised | 强 | 表格预测、分类、回归基线 | 逻辑回归、GBDT、NN |
| 自监督 Self-supervised | 零(用数据自身构造监督) | 大模型预训练、表征学习 | 掩码(BERT/MAE)、对比(SimCLR/MoCo/CLIP) |
| 半监督 Semi-supervised | 少量强标签 + 大量无标签 | 标签昂贵时的低成本方案 | 伪标签、一致性正则、FixMatch |
| 无监督 Unsupervised | 零 | 聚类、密度估计、异常、降维 | KMeans、GMM、DBSCAN、PCA |
- 自监督是 2026 的默认预训练范式:文本用掩码语言建模,视觉用 MAE,多模态用对比(CLIP)。它的本质是用数据自身的结构造监督信号,从而绕过「人工标注」这个瓶颈。
- 半监督的陷阱:伪标签(pseudo-labeling)会把模型自己的错误固化成「真相」。正确姿势是「置信度过滤 + 温度 sharpening + 一致性正则」,且伪标签只在训练集内部生成,绝不泄漏到验证集。
- 判断标准:拿不到标签时用自监督 / 半监督;标签便宜且干净时用监督;要可解释归因时优先线性 / GBDT。
python# 半监督伪标签循环:置信度过滤 + 每轮只加高置信样本,避免把错误固化成「真相」
import numpy as np
from sklearn.linear_model import LogisticRegression
X_L, y_L = X_labeled, y_labeled # 1000 条有标注
for it in range(3):
clf = LogisticRegression(max_iter=1000).fit(X_L, y_L)
p = clf.predict_proba(X_unlabeled)[:, 1]
conf = np.maximum(p, 1 - p) # 对「正 / 负」的把握
keep = conf > 0.95 # 高置信阈值(经验 0.9–0.97)
y_pseudo = (p[keep] > 0.5).astype(int)
X_L = np.vstack([X_L, X_unlabeled[keep]])
y_L = np.concatenate([y_L, y_pseudo])
print(f"iter {it}: 新增伪标签 {keep.sum()} 条, 当前训练集 {len(y_L)} 条")
# 预期输出(示意):iter 0 新增 ~1800 条 → iter 2 收敛到 ~4000 条
# 经验:纯自训练通常只涨 1–3 个点;阈值 0.9 → 0.97 涨幅变小但更安全
# 铁律:伪标签只在训练集内部生成,绝不写入验证折
半监督的收益服从长尾:标签从 1k 加到 5k 常能涨 10–20 个点,但从 5k 加到 50k 可能只涨 1–2 个点。所以「先标多少」要算边际收益,而不是盲目堆标注预算——这与 1.2 节的学习曲线诊断是同一件事。
1.2 偏差-方差分解:泛化误差从哪来
对任意模型 ŷ 与真实函数 f(x),在平方损失下,期望泛化误差可以严格分解为三项:
text设标签 y = f(x) + ε, ε ~ N(0, σ²) 为不可约噪声。
令 ŷ 为在训练集 D 上学到的模型预测,取期望 over D 与 ε:
E_D,y[(y − ŷ)²] = Bias²(ŷ) + Var(ŷ) + σ²
其中:
Bias(ŷ) = E_D[ŷ] − f(x) # 模型系统性偏离真相的平均量(假设太强→欠拟合)
Var(ŷ) = E_D[(ŷ − E_D[ŷ])²] # 模型对训练集扰动的敏感程度(容量太大→过拟合)
σ² = 不可约误差(数据本身带噪,再好的模型也去不掉)
注意:Bias² 与 Var 随模型复杂度(容量)呈“此消彼长”的 U 形关系。
| 模型复杂度 / 容量 | Bias² | Variance | 总误差 | 现象 |
|---|---|---|---|---|
| 过低(线性拟合正弦) | 高 | 低 | 高 | 欠拟合:train 与 val 都差 |
| 适中 | 中 | 中 | 低 | 甜区:泛化最好 |
| 过高(高阶多项式拟合带噪点) | 低 | 高 | 高 | 过拟合:train 极好、val 崩 |
关键推论:降低 Bias 靠加容量(更深 / 更宽 / 更多特征),降低 Variance 靠加数据、正则化、集成、早停。很多人只会一味加容量,结果 Bias 下去了 Variance 却爆了——这正是「train 降、val 升」的根源。
python# 用多项式拟合带噪数据,直观看到偏差-方差权衡
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import cross_val_score
np.random.seed(0)
x = np.linspace(0, 1, 30); X = x.reshape(-1, 1)
y = np.sin(2 * np.pi * x) + np.random.normal(0, 0.3, size=x.shape) # 真函数 + 噪声
for deg in [1, 4, 15]:
model = make_pipeline(PolynomialFeatures(deg), LinearRegression())
# 5 折交叉验证的得分方差越大,说明模型对训练集越敏感(方差高)
s = cross_val_score(model, X, y, cv=5, scoring="neg_mean_squared_error")
print(f"deg={deg:2d} MSE={ -s.mean():.4f} std={s.std():.4f}")
# deg=1 偏差主导(std 小但 MSE 大);deg=15 方差主导(std 明显变大)
python# 学习曲线:判断「加数据」还有没有收益
import numpy as np
from sklearn.model_selection import learning_curve
sizes, tr, va = learning_curve(
model, X, y, cv=5, scoring="neg_mean_squared_error",
train_sizes=np.linspace(0.1, 1.0, 5))
for n, a, b in zip(sizes, -tr.mean(1), -va.mean(1)):
print(f"n={int(n):5d} train_MSE={a:.3f} val_MSE={b:.3f} gap={b-a:.3f}")
# 预期输出(示意):
# n= 100 train_MSE=0.021 val_MSE=0.480 gap=0.459 <- 方差主导
# n= 1000 train_MSE=0.060 val_MSE=0.260 gap=0.200
# n=10000 train_MSE=0.150 val_MSE=0.190 gap=0.040 <- 收敛,加数据收益变小
# 判据:val 曲线仍在下降 → 加数据有用;已成平台且 gap 很小 → 该改特征 / 模型族
| 诊断 | train / val 距离 | 首选动作 |
|---|---|---|
| 高方差(过拟合) | gap 大、val 曲线还在降 | 加数据 / 正则 / 集成 |
| 高偏差(欠拟合) | gap 小、两条曲线都高 | 加容量 / 加特征 / 换模型族 |
| 已到不可约误差 | 两条曲线都贴近噪声下界 | 换更强标签或接受上限 |
经验量级:分类任务上 train-val 准确率 gap 超过 8–15 个点即典型过拟合;回归任务看相对 gap(gap / train_MSE)。这个阈值比「感觉有点过拟合」可靠得多,也是能否正确决定「加数据还是加容量」的量化依据。
1.3 过拟合 / 欠拟合的判别信号与决策依据
诊断先于治疗。把训练曲线和验证曲线并排看,四个典型信号直接对应四个动作:
| 现象 | 诊断 | 根因 | 优先动作 |
|---|---|---|---|
| train 高、val 高 | 欠拟合 | 容量不足 / 特征太弱 / 学习率太小 | 加容量、加特征、升学习率、换更强模型族 |
| train 很低、val 明显高 | 过拟合 | 容量过大 / 数据太少 / 噪声 | 加数据、正则化、早停、降容量、集成 |
| 两者都低且贴近 | 健康 | 在甜区 | 保持,开始做误差分析 |
| val 抖动大、不稳定 | 高方差 + 数据少 | 验证集太小 / 切分不稳 | 扩大验证集、用分层 / 重复 K 折、换指标 |
「该加数据还是该加容量」的决策树:先看训练误差。① 训练误差本身就高 → 模型没学好,问题在容量 / 特征 / 优化,先加容量、加特征、调学习率,别急着加数据(加数据也救不了欠拟合)。② 训练误差低但验证高 → 模型学过头了,先加数据 / 正则化 / 早停,而不是继续堆容量(堆容量只会让 gap 更大)。③ 数据真的加不动时,用正则化、Dropout、数据增强、集成来「虚拟扩充」降低方差。
python# 用 train/val gap 量化过拟合程度,给动作排序(比主观判断可靠)
def diagnose(train_metric, val_metric):
gap = train_metric - val_metric
ratio = gap / max(train_metric, 1e-9)
if train_metric < 0.70: # 训练集本身都没学好
return "欠拟合:加容量 / 特征、升 lr"
if ratio > 0.15: # 相对 gap 过大
return "过拟合:加数据 / 正则 / 早停"
return "健康:进入误差分析"
print(diagnose(0.62, 0.60)) # 欠拟合:加容量 / 特征、升 lr
print(diagnose(0.99, 0.63)) # 过拟合:加数据 / 正则 / 早停
print(diagnose(0.91, 0.89)) # 健康:进入误差分析
1.4 动手练习与自测
- 用 3 个多项式度数(1 / 4 / 15)在带噪正弦数据上跑 5 折 CV,记录 MSE 与 std,判断哪档方差主导。答案:deg=1 偏差主导(MSE 大但 std 小);deg=15 方差主导(std 明显变大)。
- 构造「train_acc=0.99、val_acc=0.62」的二分类结果,写出接下来三步动作及顺序。答案:先查泄漏与切分(同主体 / 时间是否跨折)→ 再看是否该扩验证集 → 最后才加正则 / 降容量;不要先加容量。
- 解释为什么「切换归纳偏置」能同时降 Bias 与 Var。判据:能说出模型族假设空间不同,好偏置让真函数更易被表达(降 Bias)且对采样扰动更不敏感(降 Var)。
- 用 learning_curve 画出一条 val 已平台的曲线,说明此时再标 10 倍数据的预期收益。答案:通常只涨 1–2 个点,应转向特征 / 模型族。
- 写出四种学习范式并各举一个 2026 真实用法。判据:监督=表格风控;自监督=LLM 预训练;半监督=伪标签冷启动;无监督=向量召回聚类。
- 说出「train-val gap 超过多少算过拟合」的经验阈值,并解释为什么回归要看相对 gap。答案:分类约 8–15 个点;回归因量纲不同,需用 gap / train_MSE 比较。
2. 机器学习方法论:问题定义、切分与基础指标
学习路径
- 读 2.1:回答建模五问,背下四种划分方式与适用场景
- 跑 ColumnTransformer + Pipeline 示例,验证 scaler/encoder 只 fit 训练折
- 对每个特征问一句「预测时刻是否已知」,标注候选泄漏并删除
- 完成 2.4 自测:写出错误代价矩阵并据此选指标与阈值
核心知识点详解
- 建模五问是开工前提:输入、输出、标签来源、指标、硬约束(延迟/成本/可解释/合规)五问答不上来就别写代码;先写清错误代价矩阵——一个漏报欺诈的代价可能是误报的 50–500 倍,这直接决定阈值与指标。
- 划分方式与适用:随机(i.i.d.)、按时间(train 时间 < test 时间)、按主体(
GroupKFold,同实体不跨折)、分层(Stratified保类别比例)。选错切分等于给自己造指标虚高。 - 预处理必须折内 fit:把
Scaler/OneHotEncoder包进Pipeline,CV 内部只 fit 训练折;对每个特征问一句「预测时刻我是否已知道」,答不上即候选泄漏,删掉。
学习路径
- 读 2.2:背下 K-fold / Stratified / TimeSeriesSplit / GroupKFold 的适用与坑
- 跑 TimeSeriesSplit 与 GroupKFold 断言脚本,验证「train 最大时间戳 < val 最小时间戳」「同组不跨折」
- 复现随机切分 vs 按组切分 AUC 虚高 10–30 点的差距
- 对接 M6:为检索标注数据集按 query 分组做 GroupKFold,杜绝同主体跨折
核心知识点详解
- 两个不变量断言:时间序列:train 最大时间戳 < val 最小时间戳(
assert tr.max() < va.min());按主体:同 group 不跨折(set.isdisjoint)。两个断言各一行,进 CI 即可防住最常见泄漏。 - 随机切分在时序/用户上的代价:同一主体的过去行为进训练、未来进验证,模型只要记住主体 ID 就能「预测」自己——离线 AUC 虚高 10–30 个点,上线暴跌。gap 越大说明「记住主体」的成分越多。
- 选错策略的典型坑:时序任务绝不能 shuffle(用
TimeSeriesSplit前向链);同用户/同文档任务用GroupKFold而非 KFold,否则同一实体跨折等于泄题。
学习路径
- 读 2.3:默写 P/R/F1 分母定义与 Youden J,分清误报/漏报归谁管
- 用混淆矩阵手算一组 P/R/F1,再用 sklearn 核对
- 扫描阈值找 F1 最优点,验证 0.5 并非最优(例 0.37 更优)
- 完成 2.4 自测:说明 macro / weighted / micro 何时该报哪个
核心知识点详解
- 四个格子三个指标分母:Precision=TP/(TP+FP)(管误报)、Recall=TP/(TP+FN)(管漏报)、F1=2PR/(P+R)(调和平均,偏小者)。分清 P/R 分母区别是面试高频坑。
- 阈值移动是免费调参:不重训、只在推理期把决策面从 0.5 挪到更优 F1 点(正类 1% 时典型 最优阈值 0.37 而非 0.5)。用
precision_recall_curve扫描thr[np.argmax(f1)]即得。 - 多分类平均方式:macro(各类等权,小类也管)、weighted(按样本加权,被大类主导)、micro(汇总格子再算)。类别极不平衡时报 macro 更能暴露小类崩坏。
- 只报一个数的报告不可信:可信评估至少给置信区间(多折均值±std)+ 正类比例 + 阈值;
Youden J=Recall+Specificity−1用于平衡选阈值。
核心知识点详解
- 随机 vs Group KFold 的 gap 判据:同主体数据上
cross_val_score用 cv=5 与cv=GroupKFold(5)对比,gap > 10 个点即典型分组泄漏,必须改用 GroupKFold;gap 接近 0 说明泛化是真实的。 - Scaler 移入 Pipeline 的意义:把
StandardScaler从「切分前全局 fit」移进Pipeline,CV 分数通常略降(更真实);差距越大说明全局 fit 泄漏越严重,这也是最隐蔽的预处理泄漏。 - 代价矩阵选指标的坑:先算漏报与误报的代价比,再选优化目标与阈值:漏报贵→重 Recall,误报贵→重 Precision;别只用默认 0.5 阈值,代价不对称时它几乎从不是最优。
2.1 问题定义与数据划分
一个可交付的建模问题必须能回答五个问题:输入是什么、输出是什么、标签从哪来、用什么指标衡量、有什么硬约束(延迟、成本、可解释性、合规)。答不上来就别急着写代码。
| 划分方式 | 适用场景 | 陷阱 |
|---|---|---|
| 随机划分 | 样本独立同分布 | 时间序列或同用户多记录时会造成泄漏 |
| 按时间划分 | 时序预测、推荐、风控 | 必须用「训练集时间 < 测试集时间」,否则指标虚高 |
| 按实体分组(GroupKFold) | 同一用户 / 同一文档有多条记录 | 同实体样本跨集会泄漏,指标严重乐观 |
| 层次化划分(Stratified) | 类别极度不平衡 | 否则某些小类可能整类落进测试集 |
python# 用 Pipeline 把「预处理」和「模型」绑定,交叉验证内部自动只 fit 训练折
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.linear_model import LogisticRegression
num = ["age", "income"]; cat = ["city", "channel"]
pre = ColumnTransformer([
("num", StandardScaler(), num),
("cat", OneHotEncoder(handle_unknown="ignore"), cat)])
clf = Pipeline([("pre", pre), ("lr", LogisticRegression(max_iter=1000))])
clf.fit(X_train, y_train) # scaler / encoder 只在 train 上 fit
print(clf.predict_proba(X_test)[:, 1][:3]) # 例: [0.87 0.12 0.64]
# 判断某特征是否可用一句话:把它写上「预测时刻我是否已知道」——答不上就是候选泄漏
「问题定义」还有一条常被跳过的一步:先写清楚「错误代价矩阵」。一个漏报欺诈的代价可能是误报的 50–500 倍,这个比值直接决定阈值与指标(是优化 F1 还是 Recall 在成本 0.02 处)。把它写下来,后面所有指标选择与阈值移动才有依据。
2.2 交叉验证与切分:随机切分为什么在时序 / 用户维度上是错的
交叉验证的核心是「用训练折估计参数、用验证折估计性能」,且验证折永远不能参与任何拟合。不同数据形态要选不同策略:
| 策略 | 做法 | 何时用 | 坑 |
|---|---|---|---|
| K-fold | 随机均分 K 份,轮流当验证 | i.i.d. 数据 | 时序 / 用户维度上泄题 |
| Stratified K-fold | 分层保持类别比例 | 类别不平衡的分类 | 仍假设样本独立 |
| Repeated K-fold | 重复多次不同随机种子取均值 | 数据少、要稳的方差估计 | 更慢 |
| 时间序列前向链(rolling / forward chaining) | 按时间排序,用 t| 任何带时间依赖的任务 | 不能用 shuffle | |
| GroupKFold / StratifiedGroupKFold | 同一 group 的样本必须全在同折 | 同用户 / 同文档 / 同会话有多条 | 分组后要重算基准 |
pythonfrom sklearn.model_selection import TimeSeriesSplit, GroupKFold
import numpy as np
# 时间序列前向链:第 i 折的训练集严格早于验证集,模拟「用过去预测未来」
tscv = TimeSeriesSplit(n_splits=5, test_size=2000)
X = np.arange(10000).reshape(-1, 1)
for i, (tr, va) in enumerate(tscv.split(X)):
assert tr.max() < va.min() # 关键不变量:训练时间窗 < 验证时间窗
print(f"fold {i}: train[{tr.min()}..{tr.max()}] valid[{va.min()}..{va.max()}]")
# 用户维度分组:同一 user_id 绝不跨折(否则该用户的行为同时出现在训练与验证)
gkf = GroupKFold(n_splits=5)
for tr, va in gkf.split(X, y, groups=user_ids):
assert set(user_ids[tr]).isdisjoint(set(user_ids[va])) # 分组不变量
python# 演示:随机切分在「同用户多条记录」上造成指标虚高
from sklearn.model_selection import cross_val_score, GroupKFold
import numpy as np
r = cross_val_score(clf, X, y, cv=5).mean() # 随机切分
g = cross_val_score(clf, X, y, cv=GroupKFold(5), groups=uid).mean()
print(f"random AUC={r:.3f} group AUC={g:.3f} gap={r - g:+.3f}")
# 典型结果:random=0.95, group=0.72 → 虚高 23 个点
# 读法:gap 越大,说明模型「记住主体」的成分越多;gap 接近 0 才说明泛化是真实的
时序任务的另一个不变量:训练集的最大时间戳必须严格小于验证集的最小时间戳。一旦 shuffle 了带时间的数据,模型就能「用未来预测过去」,离线指标会系统性虚高,而线上永远拿不到这个未来信息。风控、新闻推荐、销量预测都栽在这一条上。
2.3 混淆矩阵、Precision / Recall / F1
二分类的所有指标都从混淆矩阵的四个格子长出来。先定义清楚,再谈优化:
| 预测正 | 预测负 | |
|---|---|---|
| 实际正 | TP(真阳) | FN(假阴,漏报) |
| 实际负 | FP(假阳,误报) | TN(真阴) |
pythonfrom sklearn.metrics import confusion_matrix, precision_score, recall_score, f1_score, average_precision_score
# 定义(务必记住分母区别,这是面试高频坑):
# Precision = TP / (TP + FP) # 预测为正的里面,真的正占多少 → 管「误报」
# Recall = TP / (TP + FN) # 真正的正里,被抓住多少 → 管「漏报」
# F1 = 2 * P * R / (P + R) # 两者的调和平均(偏向较小值)
# Specificity = TN / (TN + FP) # 真负率
# FPR = FP / (FP + TN) # 假阳率(ROC 横轴)
# Youden's J = Recall + Specificity − 1 # 综合判别力,选阈值时用
y_true = [0,1,1,0,1,0,1,0]; y_pred = [0,1,0,0,1,1,1,0]
print(confusion_matrix(y_true, y_pred)) # 直接看四个格子
print("P=%.3f R=%.3f F1=%.3f" % (
precision_score(y_true, y_pred),
recall_score(y_true, y_pred),
f1_score(y_true, y_pred)))
- 选 Precision 还是 Recall:垃圾邮件拦截怕误删重要邮件 → 重 Precision;疾病筛查怕漏诊 → 重 Recall;风控反欺诈怕漏掉欺诈 → 重 Recall。代价相反时,用 F1 或代价敏感阈值(见 3.3)。
- 多分类:macro(各类等权平均,小类也管)、weighted(按样本数加权,被大类主导)、micro(先汇总所有格子再算)。类别极不平衡时 macro 更有意义。
- 阈值移动:把分类概率的 0.5 决策面挪到 0.3 或 0.8,就能在 P/R 之间换权衡,而不必重训模型——这是上线最常见的「免费调参」。
python# 扫描阈值找 F1 最优点(不重训,上线最常用的免费调参)
import numpy as np
from sklearn.metrics import precision_recall_curve
p, r, thr = precision_recall_curve(y_true, y_prob)
f1 = 2 * p * r / np.clip(p + r, 1e-9, None)
best_thr = thr[np.argmax(f1[:-1])]
print(f"best_thr={best_thr:.3f} F1={f1.max():.3f}")
# 典型:best_thr=0.37 F1=0.71;而默认 0.5 时 F1 只有 0.61
# 结论:正类稀少时 0.5 几乎从不是最优阈值,动阈值往往比换模型更划算
| 多分类平均方式 | 计算 | 谁主导 | 何时用 |
|---|---|---|---|
| macro | 各类指标等权平均 | 小类被放大 | 关心每个类都别太差 |
| weighted | 按类样本数加权 | 大类主导 | 关心整体样本表现 |
| micro | 汇总所有 TP/FP/FN 再算 | 被大类主导(≈准确率) | 总体吞吐 / 全局视角 |
2.4 动手练习与自测
- 构造一个「同用户多记录」的数据集,对比随机 KFold 与 GroupKFold 的 AUC,记录 gap。判据:gap > 10 个点即典型分组泄漏,必须改用 GroupKFold。
- 把 StandardScaler 从 Pipeline 外移到 Pipeline 内,观察 CV 分数变化方向。答案:Pipeline 内的分数通常略低(更真实),差距越大说明全局 fit 泄漏越严重。
- 给定某任务的错误代价矩阵,说明该优化 Precision 还是 Recall 并给出理由。判据:能说出漏报与误报代价比,并据此选指标与阈值。
- 为时序销量预测写出切分不变量,并解释 shuffle 为何是错的。答案:train 最大时间戳 < val 最小时间戳;shuffle 会让模型用未来信息预测过去。
- 用 P-R 曲线说明「阈值移动」为什么是免费的,并给出扫描阈值的代码。答案:决策阈值不改模型参数、不需重训,只在推理期换切点。
- 列出多分类 macro / weighted / micro 的区别,并指出类别极不平衡时该报哪个。答案:macro 更能暴露小类崩坏。
3. 评估进阶:ROC vs PR、概率校准、类别不平衡
学习路径
- 读 3.1:记 ROC-AUC = 排序概率、随机基线 = 正类先验,背下正类 <5% 用 PR-AUC
- 在正类 0.5% 数据上同时算 ROC 与 PR-AUC,对比二者对正类排序的灵敏度
- 完成 3.4 自测:解释为什么报 AUC 时必须同时报正类比例
核心知识点详解
- ROC-AUC 的排序含义:ROC-AUC = 随机抽一正一负、模型把正排在前面的概率,等价于 Mann-Whitney 统计量;与决策阈值无关,只衡量排序能力。
- 不平衡时为何改报 PR-AUC:正类稀少时 ROC 的 FPR 分母是巨量负例,几个 FP 看不出波动,曲线「好看」掩盖正类排序崩坏。经验:正类占比 < 5% 时报 PR-AUC/AP(随机基线=正类先验),欺诈/召回系统默认它。
- 报 AUC 必须报正类比例:正类从 50% 降到 0.1% 时,随机猜的 ROC-AUC 始终 0.5,而 PR-AUC 从 0.5 跌到 0.001。孤立报一个 ROC-AUC=0.90 在 2026 评审里会被直接质疑。
学习路径
- 读 3.2:实现 ECE,理解 Reliability diagram 与 Platt / Isotonic / 温度缩放
- 跑校准前/后 ECE 对比,验证 ECE 下降而 AUC(排序)不变
- 用 fit_temperature 在验证集上拟合 T,确认 T 常 1.2–2.5 代表过自信
- 完成 3.4 自测:说出 LLM 置信度不可信的两个机制原因
核心知识点详解
- ECE 是校准的核心度量:ECE 把预测概率分箱,对每箱加权求和 |经验准确率 − 平均置信|;ECE<0.05 可接受、>0.1 不能把概率当频率用。手写见
calibration_curve分桶后累加。 - 温度缩放是零成本后处理:只拟合一个标量 T(
p=softmax(z/T),LBFGS 优化),因是单调变换不改排序(AUC 不变)。典型 T=1.2–2.5,>1 说明模型过自信;校准后 ECE 常从 0.15+ 降到 0.03。 - LLM 置信度为何不可信(坑):softmax 概率①被高温长尾拉平、②对「没见过的幻觉」也高给概率、③被 RLHF 扭曲。别把 token 概率当把握;用温度缩放或自洽采样/候选一致性/显式拒识做决策(见
fit_temperature)。
学习路径
- 读 3.3:背下四类解法,记处理顺序「换指标→class_weight→阈值→Focal→SMOTE」
- 对比 class_weight 与 SMOTE 的 AP 提升,确认 SMOTE 必须折内做
- 跑阈值移动找 F1 最优点,完成不重训的「免费调参」
- 完成 3.4 自测:说明为什么 SMOTE 排在最后且必须折内
核心知识点详解
- 处理优先级(先易后险):①换指标(PR-AUC/F1@k)→②class_weight(零风险)→③阈值移动(零重训)→④Focal Loss→⑤SMOTE/重采样。前三步通常就拿走大部分收益,很多人一上来 SMOTE 反而既泄漏又过拟合。
- class_weight 的收益量级:按 n/n_k 反比加权等于代价敏感;正类 1% 时典型 AP 从 0.18 提到 0.24(+0.06),量级与 SMOTE 接近但最干净、零重采样。
- Focal Loss 的机制:
FL=−(1−p_t)^γ log p_t,γ=2 时让模型已很有把握的样本梯度骤降,逼模型盯难例(alpha=0.25常见)。 - SMOTE 必须在折内做(坑):重采样在切分之前对全集做,合成样本会同时落在训练与验证折→泄漏且虚高;只在每折训练部分做,验证折保持纯净(与
scaler折内 fit 同一条规则)。
核心知识点详解
- 双 AUC 对比的意义:正类 1% 上 ROC-AUC 可能 0.90 而 PR-AUC 只有 0.11(随机基线 0.01);用 PR-AUC 判断才有意义,ROC 只作辅助。
- 手写 ECE 的要点:按预测概率落桶 → 对每桶求 |平均置信−经验准确率| × 样本占比 → 累加;验证校准后 ECE 明显降且 AUC 不变(只改概率尺度)。
- 温度缩放为什么零成本:只拟合一个标量 T,
softmax(z/T)是单调变换故 argmax 不变;坑:温度缩放只在同分布验证集上有效,分布迁移后需重新拟合。
3.1 ROC-AUC 与 PR-AUC 的取舍
AUC-ROC 的直观含义是:随机抽一个正样本和一个负样本,模型把正样本排在前面的概率。它与决策阈值无关,衡量「排序能力」。数学上等价于 Wilcoxon-Mann-Whitney 统计量:AUC = (1/|P||N|) Σ_{i∈P,j∈N} 1[f_i > f_j]。
| 指标 | 横纵轴 | 类别不平衡时的行为 | 何时用 |
|---|---|---|---|
| ROC-AUC | FPR × TPR | 负样本极多时 FPR 分母大,曲线依然「好看」,掩盖差劲的正类排序 | 均衡数据、看整体排序 |
| PR-AUC (AP) | Recall × Precision | 只关注正类,正类稀少时任何漏抓都让曲线骤降,更敏感 | 极不平衡(欺诈 / 罕见病 / 长尾检索) |
| Average Precision | PR 曲线下面积(按召回加权) | 同 PR-AUC | 排序质量的标准离线指标 |
经验法则:正类占比 < 5% 时,PR-AUC 比 ROC-AUC 信息量大得多。因为 ROC 横轴 FPR 的分母是巨量负样本,几个 FP 看不出波动;而 PR 直接盯「预测为正的准确率」。欺诈检测、召回系统的离线评估默认报 PR-AUC / AP,而非 ROC-AUC。
pythonfrom sklearn.metrics import roc_auc_score, average_precision_score
# 正类占比 0.5% 的极端不平衡场景对比两个 AUC
print("ROC-AUC=%.3f PR-AUC=%.3f" % (
roc_auc_score(y, p), average_precision_score(y, p)))
# 典型结果:ROC-AUC=0.90(看着不错) PR-AUC=0.11
# 随机基线:PR-AUC 的先验上限 = 正类比例 = 0.005
# 判据:正类 < 5% 时以 PR-AUC 为主指标;ROC-AUC 只能当辅助
量化经验:正类从 50% 降到 0.1% 时,随机猜的 ROC-AUC 始终是 0.5,而 PR-AUC 从 0.5 一路跌到 0.001。这就是为什么「不平衡数据上报 ROC-AUC」在 2026 的评审里基本会被直接质疑——它掩盖了正类排序的真实崩坏。
3.2 概率校准:为什么 LLM 的置信度不可信
很多模型输出一个「概率」,但这个概率并不等于真实频率。校准要回答:当模型说「我有 70% 把握」时,是否真的在 100 次里有 70 次是对的?未校准的模型会在「0.9 置信」时实际只有 50% 正确——这在需要拿概率做决策(风控定价、拒识、医疗)时致命,也是LLM 置信度几乎不可信的根本原因(softmax 概率被长尾分布和温度放大,远离真实后验)。
| 诊断 / 工具 | 作用 | 解读 |
|---|---|---|
| Reliability diagram(可靠性图) | 把预测概率分箱,画「箱内经验频率」vs「预测均值」 | 对角线=完美校准;S 形=欠自信、反 S=过自信 |
| ECE(Expected Calibration Error) | 各箱加权 |准确率−置信| 的平均 | ECE<0.05 通常认为可接受 |
| Platt scaling | 用验证集拟合 sigmoid: p′=σ(a·p+b) | 1 个参数族,便宜、稳,适合小验证集 |
| Isotonic regression | 非参单调分段常数映射 | 更灵活但需更多验证数据,易过拟合 |
| Temperature scaling | p′=softmax(z/T),只调 1 个 T | 神经网络后处理标配,几乎零成本 |
pythonimport numpy as np
from sklearn.calibration import calibration_curve
def ece(y_true, y_prob, n_bins=10):
"""期望校准误差:各置信箱里 |经验准确率 − 平均置信| 的样本加权平均"""
bins = np.linspace(0, 1, n_bins + 1)
frac_pos, mean_pred = calibration_curve(y_true, y_prob, n_bins=n_bins)
# 手动算 ECE:按预测概率落桶
idx = np.clip(np.digitize(y_prob, bins) - 1, 0, n_bins - 1)
ece_val = 0.0
for b in range(n_bins):
m = idx == b
if m.sum() == 0: continue
conf = y_prob[m].mean()
acc = y_true[m].mean()
ece_val += abs(conf - acc) * m.sum() / len(y_true)
return ece_val
# 用法:先算未校准 ECE,再用 Platt / Temperature scaling 拟合后重算,对比下降
# 例:未校准 ECE=0.21 → 温度缩放后 ECE=0.04,决策阈值才能按字面意义理解
pythonimport torch, torch.nn.functional as F
# 温度缩放:在验证集上只拟合一个标量 T,让置信度贴近真实频率
def fit_temperature(logits, labels, iters=200):
T = torch.nn.Parameter(torch.ones(1))
opt = torch.optim.LBFGS([T], lr=0.01, max_iter=iters)
def closure():
opt.zero_grad()
loss = F.cross_entropy(logits / T, labels) # 只改 T,模型参数冻结
loss.backward(); return loss
opt.step(closure)
return float(T.detach())
# 经验:T 通常 1.2–2.5(T>1 说明模型过自信)
# 校准后 ECE 常从 0.15 降到 0.03,且不改排序(AUC 不变),是「零成本」后处理
# 但注意:温度缩放只在同分布验证集上有效,分布迁移后需重新拟合
校准的工程判据:ECE < 0.05 通常可接受,ECE > 0.1 就不能把概率当频率用。需要拿概率做决策的场景(拒识、定价、医疗、Agent 的工具调用置信度)必须先校准;只需排序的场景(推荐、检索)可以不校准。这个区分能省掉大量无用功。
3.3 类别不平衡:重采样、代价敏感、阈值移动、Focal Loss
正类稀少时,模型会「偷懒」把一切预测为负来最小化损失,因为负样本主导梯度。四类解法,按「改动离模型多远」排列:
| 手段 | 机制 | 代价 / 坑 |
|---|---|---|
| 随机过采样(复制正样本) | 增加正类出现频率 | 易过拟合,正样本被重复记忆 |
| 欠采样(删负样本) | 平衡类别 | 丢掉负样本信息,可能偏差 |
| SMOTE | 在少数类近邻间插值造合成样本 | 可能造出不合理样本,必须折内做 |
| 类别权重 class_weight | loss 按 n/n_k 加权 | 最干净,首选;等价于代价敏感 |
| 阈值移动 | 把决策面从 0.5 移到更优 F1 点 | 零重训,上线首选微调 |
| Focal Loss | FL=−(1−p_t)^γ log p_t 降权易分样本 | γ=2 常用,专注难例 |
pythonimport torch, torch.nn as nn
# 类别权重:把 loss 按类别频率反比加权(等价于对少数类多罚)
# 设正类占 1%,负类占 99%:
w = torch.tensor([1.0/0.99, 1.0/0.01]) # 负:正 ≈ [1.01, 100]
loss = nn.CrossEntropyLoss(weight=w)
# Focal Loss:γ=2 让「模型已很有把握」的样本梯度大幅缩小,逼模型盯难例
def focal_loss(logits, y, gamma=2.0, alpha=0.25):
ce = nn.functional.cross_entropy(logits, y, reduction="none")
pt = torch.exp(-ce) # 模型对真类的置信
return (alpha * (1 - pt) ** gamma * ce).mean()
# 阈值移动(不重训):用验证集 P-R 曲线找 F1 最大点作为新决策面
best_t = 0.5
for t in [0.1, 0.2, 0.3, 0.4, 0.5]:
pred = (probs[:, 1] >= t).astype(int)
# 选 F1 最大的 t 上线 —— 这是成本最低、最常被忽略的一招
pythonfrom sklearn.linear_model import LogisticRegression
from sklearn.metrics import average_precision_score
# 三种处理对比(同一不平衡数据,正类约 1%)
base = LogisticRegression(max_iter=1000).fit(Xtr, ytr)
w = LogisticRegression(max_iter=1000, class_weight="balanced").fit(Xtr, ytr)
print("base AP=%.3f balanced AP=%.3f" % (
average_precision_score(yva, base.predict_proba(Xva)[:, 1]),
average_precision_score(yva, w.predict_proba(Xva)[:, 1])))
# 典型:base AP=0.18 → balanced AP=0.24(+0.06)
# 经验:class_weight 常带来 +0.03–0.08 AP,且最干净、零重采样;SMOTE 量级相近但更易过拟合
3.4 动手练习与自测
- 在正类 1% 的数据上同时算 ROC-AUC 与 PR-AUC,解释两者差异。判据:能指出 PR-AUC 的随机基线 = 正类先验,故更敏感。
- 实现 ECE 函数并对比校准前后的值。判据:校准后 ECE 明显下降且 AUC 不变(只改概率尺度,不改排序)。
- 解释温度缩放为什么「零成本」且「不改排序」。答案:只拟合 1 个标量 T,且 softmax(z/T) 是单调变换,argmax 不变。
- 对同一数据比较 class_weight 与 SMOTE 的 AP 提升。判据:两者量级相近(多为 +0.03–0.08),但 SMOTE 必须折内做否则泄漏。
- 写出平衡处理的最优顺序,并说明为什么 SMOTE 排最后。答案:换指标→class_weight→阈值→Focal→SMOTE;SMOTE 风险最高收益未必最高。
- 为什么 LLM 的 token 概率不能直接当置信度?举两个机制原因。答案:高温长尾拉平 + RLHF 对齐扭曲 + 幻觉仍给高概率。
4. 正则化:控制方差的武器库
学习路径
- 读 4.1:默写 L2/ L1 / ElasticNet 惩罚项,理解 L1 球顶点在坐标轴产生稀疏
- 跑 Lasso / Ridge 系数路径,观察随 alpha 增大谁先塌到 0
- 先标准化再调 alpha,比较 L1 非零权重的特征选择效应
- 完成 4.3 自测:解释 AdamW 解耦权重衰减与 loss 里加 λw² 的差异
核心知识点详解
- L1 为什么产生稀疏:L1 惩罚 λΣ|w| 的等高线与约束「菱形」接触点常在坐标轴上(顶点),故部分 w 归 0;L2 是圆,接触点很少落在轴上,权重被收缩但不为 0。Lasso/Ridge 系数路径里 L1 随 alpha 增大更早塌到 0。
- 解耦权重衰减 AdamW:AdamW 的解耦权重衰减在自适应缩放之外更新,与「loss 里加 λw²」数值不同、更稳,这是它优于旧 Adam+L2 的原因;
LassoCV/RidgeCV可自动选 α。 - alpha 必须先标准化(坑):特征尺度直接改变惩罚强度——未标准化特征上谈 alpha 无意义。经验区间:Ridge α 在 1e-4~1e2、L1 在 1e-3~1.0;α 过大→欠拟合、过小→过拟合。
学习路径
- 读 4.2:区分 early stopping / Dropout(inverted 缩放)/ 数据增强
- 跑 nn.Dropout 的 train/eval 均值对比,验证 inverted scaling 与忘记 eval 的后果
- 实现带回滚最优权重的早停,说清 patience 与 approx L2 等价
- 完成 4.3 自测:解释为何 LLM 预训练常把 Dropout 设为 0
核心知识点详解
- 早停必须回滚最优权重:patience 通常 3–10 个 eval 周期;触发时当前权重已变差,必须
load_state_dict(best_state)回滚,否则用次优解上线。早停近似等价于 L2,常省 20–40% 无效训练。 - Inverted Dropout 的缩放:训练期
y = mask(x)/(1−p)(已缩放),推理期原样;nn.Dropout(p=0.1)用model.eval()自动关闭。坑:忘记model.eval()会让推理输出被随机置零且偏低、不稳定。 - LLM 预训练为何 Dropout=0:数据量极大、正则由权重衰减+规模承担,Dropout 反而拖慢收敛;它更多用于中等规模全连接/视觉网络。别误读成「Dropout 过时」。增强强度:Mixup α≈0.2、CutMix≈1.0 合理时可顶 10–30% 额外数据。
核心知识点详解
- 系数路径判读:
np.logspace(-3,1,30)扫 α,L1/Lasso 曲线先塌到 0(稀疏)、L2/Ridge 平滑收敛不为 0;把图画出来即可肉眼确认凸性。 - α 区间与标准化:必须在已标准化特征上选 α(Ridge 1e-4~1e2、L1 1e-3~1.0),否则尺度不同惩罚强度不可比;用
LassoCV(cv=5)自动选并统计非零权重数。 - 增强强度别拍脑袋:Mixup α≈0.2、CutMix≈1.0 是常用起手;增强太强会改标签语义反而伤精度——只增训不增验,验证集永远走原图。
4.1 L1 与 L2:稀疏、权重衰减、最大间隔
正则化在损失上额外加一个「参数惩罚项」,逼迫模型别把容量全用来记忆训练噪声:
| 正则 | 惩罚项 | 几何后果 | 效果 |
|---|---|---|---|
| L2(Ridge / 权重衰减) | λ·Σ w_i² | 最优解落在「球」内,远离坐标轴 | 收缩权重、降方差;不产生零解 |
| L1(Lasso) | λ·Σ |w_i| | 最优解落在「菱形(L1 球)」顶点,常在坐标轴上 | 产生稀疏解(部分 w=0),天然特征选择 |
| ElasticNet | λ(α·Σ|w| + (1−α)·Σw²) | 球与菱形折中 | 兼顾稀疏与稳定 |
pythonimport numpy as np
from sklearn.linear_model import Lasso, Ridge
import matplotlib.pyplot as plt
# L1 为什么产生零解:L1 球的顶点正好在坐标轴上,
# 等高线与 L1 约束的第一个接触点常常「压」在某个轴上 → 该维权重变 0。
# L2 球是圆的,接触点很少正好落在轴上 → 权重被收缩但不为 0。
coefs_l1, coefs_l2 = [], []
alphas = np.logspace(-3, 1, 30)
for a in alphas:
coefs_l1.append(Lasso(alpha=a, max_iter=5000).fit(X, y).coef_)
coefs_l2.append(Ridge(alpha=a).fit(X, y).coef_)
# 观察:L1 曲线随 alpha 增大更早「塌」到 0(稀疏);L2 平滑收敛到 0(无稀疏)
- L2 与最大间隔的关系:SVM 目标「最大化间隔」等价于在正确分类约束下的 L2 正则化。权重的 L2 范数越小,分类间隔越大——这是线性模型鲁棒性的几何来源。
- 权重衰减 ≠ 纯 L2(细节坑):在带动量的 SGD / AdamW 里,解耦权重衰减(decoupled weight decay)与在 loss 里加 λw² 数值上不同;AdamW 用的是解耦版,这也是它比旧 Adam+L2 训得好的原因之一。
- λ 的尺度:L2 的 λ 太大 → 欠拟合(权重全被压向 0);太小 → 没正则。用验证集选,不要拍脑袋。
pythonimport numpy as np
from sklearn.linear_model import LassoCV
# 让验证自动选 alpha,并看有多少特征被真正压到 0(L1 的特征选择效应)
m = LassoCV(cv=5, random_state=0).fit(X, y)
print("alpha*=%.4f 非零权重=%d/%d" % (m.alpha_, int(np.sum(m.coef_ != 0)), X.shape[1]))
# 经验取值(特征已标准化前提下):
# Ridge/L2 的 alpha 多在 1e-4 ~ 1e2;Lasso/L1 多在 1e-3 ~ 1.0
# 未标准化的特征上谈 alpha 无意义——尺度会直接改变惩罚强度
| 正则强度走向 | 训练现象 | 典型判据 |
|---|---|---|
| λ 过大 | 权重被压向 0,train/val 都差 | 欠拟合,val 曲线整体偏高 |
| λ 适中 | train 略升、val 最低 | val 取到最小点即为最优 λ |
| λ 过小 | train 极低、val 高 | 等价于没正则,重新出现过拟合 gap |
L2 与 L1 的差别不只是「稀疏」:L2 让解唯一且平滑(对共线特征会把权重平摊),L1 在共线特征里随机保留一个。所以「解释性」场景 L1 常给出更干净的稀疏解,但结果对随机种子敏感;「预测稳定性」场景 L2 更稳。ElasticNet 就是为兼顾这两点而生。
4.2 Early Stopping、Dropout 与数据增强
这三类是「不显式改 loss」的正则化,工程上最高频:
| 方法 | 机制 | 训练 / 推理差异 | 代价 |
|---|---|---|---|
| Early stopping | 监控 val loss,开始上升就停 | 只取最优 step 的权重 | 需频繁验证;近似等价于 L2 |
| Dropout | 以概率 p 随机置零神经元 | 训练期乘 mask 并缩放;推理期用「反转 / 期望」:y = (1−p)·Wx | 训练慢一点;大模型预训练常设 0 |
| Dropout 推理缩放 | inverted dropout:训练期已除 (1−p),推理期不缩放 | 训练期 y = mask(x)/(1−p),推理期原样 | 忘记缩放 → 推理输出整体偏小 |
| 数据增强 | 训练期对样本做保标签变换 | 只作用于训练;验证用原样 | 增强太强会改标签语义 |
pythonimport torch, torch.nn as nn
# 标准 Dropout(训练期随机置零 + inverted scaling;eval() 自动关闭)
drop = nn.Dropout(p=0.1)
x = torch.ones(1000)
drop.train(); print("train mean:", drop(x).mean().item()) # ≈ 1.0(已除 1-p 缩放)
drop.eval(); print("eval mean:", drop(x).mean().item()) # = 1.0(不丢弃)
# 常见坑:把 dropout 放在推理路径前却忘了 model.eval()
# → 推理输出被随机置零且未缩放,结果既不稳定又偏低
# 数据增强(图像):训练期在线变换,验证期走原图
tfm_train = nn.Sequential(
# 实际用 torchvision:RandomCrop / HorizontalFlip / ColorJitter / CutMix / Mixup
)
# 表格数据的「增强」等价于 SMOTE / 加噪声 / 特征掩码,逻辑相同:只增训不增验
pythonfrom copy import deepcopy
# 早停:patience 内 val loss 不降就停,并回滚到最优权重
best, wait, patience, best_state = float("inf"), 0, 5, None
for step in range(1000):
tr_loss = train_step() # 一次参数更新
va_loss = validate()
if va_loss < best - 1e-4:
best, wait = va_loss, 0
best_state = deepcopy(model.state_dict())
else:
wait += 1
if wait >= patience:
model.load_state_dict(best_state)
print(f"early stop @ step {step}, best val={best:.4f}")
break
# 经验:patience 取 3–10 个 eval 周期;早停近似等价于 L2,
# 且常省下 20–40% 的无效训练时间;忘记回滚最优权重是常见 bug(停在更差的点)
数据增强在 2026 有一批「强度换精度」的成熟配方:图像用 RandAugment / Mixup / CutMix(Mixup 的 α 常取 0.2,CutMix 常取 1.0),文本用回译 / 同义替换 / EDA,序列与多模态则流行课程学习(curriculum learning)——先易后难、按难度分桶逐步引入。经验上合理增强能顶 10–30% 的额外数据,但增强太强会改标签语义(把「猫」变成不可辨识的噪声),反而伤精度。
4.3 动手练习与自测
- 画 L1 / L2 的系数路径(coefficient path),说明随 alpha 增大谁先塌到 0。答案:L1(Lasso)先塌,产生稀疏;L2 平滑收缩不为 0。
- 解释「权重衰减」与「loss 里加 λ·w²」在 AdamW 里的差异。答案:解耦权重衰减不在自适应缩放里,故与自适应梯度范数解耦,更稳定。
- 给出你所在任务的 α 经验区间,并说明为何必须先标准化。判据:能指出未标准化时特征尺度会改变惩罚强度,α 不可比。
- 实现早停并说明「回滚最优权重」为什么必要。答案:patience 触发时当前权重已变差,不回滚会用次优解上线。
- 列出三种图像增强并给出常见强度参数。答案:Mixup α≈0.2、CutMix≈1.0、RandAugment 层数/幅度按任务调。
- 解释为什么 LLM 预训练常把 Dropout 设为 0。答案:数据量极大、正则由权重衰减与规模承担,Dropout 反而拖慢收敛。
5. 经典算法:理解边界而非死记公式
学习路径
- 读 5.1:默写正规方程与逻辑回归 log-odds 解读
- 在标准化数据上跑逻辑回归,用 OR=exp(系数) 解读特征影响力
- 对接 M6:用逻辑回归当护栏/意图路由小分类器基线,跑通检索排序的深度模型对照
核心知识点详解
- 逻辑回归是 log-odds 线性:
P(y=1|x)=σ(wᵀx),系数w_k即特征 k 每变动一单位对应的 log-odds 变化;OR=exp(w_k)如 +0.80→OR≈2.23,表示每增 1 个标准差事件几率变 2.23 倍——这就是风控/医疗仍坚持它的可归因理由。 - 正规方程是闭式解:线性回归最小二乘解析解
(XᵀX)⁻¹Xᵀy,几何上把 y 投影到特征张成的列空间;广义线性模型(泊松计数、Tweedie 零膨胀金额、Gamma 右偏)是保险/广告定价常识。 - 线性是必备基线(坑):低延迟(微秒级)、可解释、易监控漂移,是 LLM 时代护栏/路由层的首选。基线打不过就别上复杂模型;同时注意特征先标准化,系数才可直接比较。
学习路径
- 读 5.2:背下 XGBoost / LightGBM / CatBoost 关键改进与适用
- 用 GroupKFold + 早停跑 LightGBM,只以 OOF 预测算指标
- 跑 XGBoost 调参优先级(先 lr+轮数,再 num_leaves/min_data_in_leaf),记录 val AUC
- 对接 M6:用 LightGBM 做重排(rerank)基线,产出特征重要性分析
核心知识点详解
- GBDT 表格胜 NN 的三因:①对特征尺度不敏感(树的切分是 x>阈值,不用归一化);②轴对齐切分天然适配异构表格特征;③缺失值原生处理(XGBoost 自动学默认方向)。表格样本少、特征异构时 NN 的表示学习优势发挥不出。
- 核心机制一句话:每棵新树拟合损失在当前预测下的负梯度(回归即残差);XGBoost 用二阶泰勒展开统一成
g/h形式支持自定义目标;LightGBM 用直方图把分裂候选从特征值个数降到 255 箱,速度提升近一个数量级。 - 调参优先级:①learning_rate+轮数(配早停)→②num_leaves/max_depth→③min_data_in_leaf+lambda_l2→④feature/bagging_fraction。经验:eta=0.05 时 best_iter 常在 400–900,且
eta减半轮数约翻倍、AUC 略升。 - OOF 是唯一可信指标(坑):
GroupKFold+lgb.early_stopping,只用 out-of-fold 预测算 AUC;用训练集或 Cross-val 内泄漏得出的分数会乐观虚高。
学习路径
- 读 5.3:记住 SVM 核技巧、KNN 召回、KMeans/DBSCAN、PCA 的边界
- 用轮廓系数选 KMeans 的 k,确认无监督结果需外部标准验证
- 对 embedding 先 PCA 到 50 维再上 UMAP,理解可视化与降维入模的区别
核心知识点详解
- SVM 与核技巧:最大化间隔 + 核映射(RBF 隐式升维)
K(x,z)=exp(−‖x−z‖²/2σ²);小样本高维(文本/生物)仍有效,但大数据训练代价 O(n²)~O(n³)。理解核技巧即可,不必手推对偶。 - 聚类选 k:KMeans 用轮廓系数选最优 k(
max(range(2,11), key=silhouette));KMeans 假设球形簇、DBSCAN 能找任意形状并标噪声。坑:轮廓系数高只代表几何紧致,不代表业务分群有效,必须人工/下游验证。 - 降维的边界:PCA 保留 95% 方差作答(768 维句向量约需 120–200 维);t-SNE/UMAP 只能可视化(距离不可解释、随机种子敏感),不能替代 PCA 做「降维后入模」;先 PCA 到 50 维再上 UMAP 可提速又提质。
学习路径
- 读 5.4:背下 Multinomial / Bernoulli / Gaussian NB 的假设与用途
- 用 TF-IDF + MultinomialNB 跑文本分类,复用同一 vec(只 fit 训练)
- 扫描 alpha 找最优(常 0.1 附近),理解 Laplace 平滑作用
- 对接 M6:用 log 后验 margin 做廉价拒识/路由,验证小模型在前大模型在后的组合
核心知识点详解
- 条件独立假设换来的工程优势:
P(c|x) ∝ P(c)·Π_i P(x_i|c),假设几乎总不成立,但换来训练闭式、无需迭代、几百条样本也能用。Multinomial 配 TF-IDF 文本分类仍是很强基线,比深度学习快 ~100 倍、效果只差一点。 - Laplace 平滑与 alpha 扫描:
alpha防 unseen 特征概率为 0;典型最优在 0.1 附近(0.01 过拟合罕见词、5.0 欠拟合拉平),用cross_val_score扫描几个档位即可。 - log margin 做廉价路由(坑):log 后验两类差距小=模型没把握,可用这个 margin 做拒识/路由(小模型在前、LLM 在后);坑:NB 概率未校准(常过度自信),只可用「相对排序+margin」,别把概率当频率。
核心知识点详解
- OR 解读题:标准化逻辑回归系数 +0.80→
exp(0.80)=2.23,表示该特征每增 1 个标准差事件几率变为 2.23 倍;负值同理为缩小。 - LGBM vs MLP 对比判据:小表格数据上 GBDT 通常更快且 AUC 不落后于 MLP;记录训练时间与 AUC,若 GBDT 占优说明该场景应优先 GBDT 而非硬上 NN。
- 降维数量级:768 维句向量保留 95% 方差约需 120–200 维(
np.searchsorted在累积方差比上二分);做召回前降维可显著提速,精排用全维度。
5.1 线性模型:被低估的工作马
- 线性回归:最小二乘的闭式解为 (XᵀX)⁻¹Xᵀy(正规方程);几何意义是「把 y 投影到特征张成的列空间」。正则化版本 Ridge/Lasso/ElasticNet 分别加 L2/L1/混合惩罚。
- 逻辑回归:在 logit 空间线性,P(y=1|x)=σ(wᵀx);系数 w_k 即「特征 k 每变动一单位对应的 log-odds 变化」,可解释性极强,风控与医疗仍大量使用。
- 广义线性模型 GLM:泊松回归用于计数、Tweedie 用于零膨胀金额、Gamma 用于非负右偏——这是保险与广告定价的常识。
- 为什么还要学:极低延迟、可解释、易监控漂移、是任何复杂模型的必备基线。基线打不过,就不要上复杂模型。
| 模型 | 输出 | 可解释性 | 典型场景 |
|---|---|---|---|
| 线性回归 | 连续值 | 高(系数=边际效应) | 房价、销量、时延预测 |
| 逻辑回归 | 概率 | 高(系数=log-odds) | 风控打分、点击预估基线 |
| 泊松 / Tweedie | 计数 / 金额 | 中 | 理赔次数、零膨胀消费额 |
| SVM | 类别 / 间隔 | 中(支持向量) | 小样本高维、文本分类 |
pythonimport numpy as np
from sklearn.linear_model import LogisticRegression
# 逻辑回归系数 = 对数几率(log-odds),标准化后可直接比较影响力大小
m = LogisticRegression(max_iter=1000).fit(Xs, y) # Xs 已标准化
print("系数(log-odds):", np.round(m.coef_[0], 3))
print("优势比 OR :", np.round(np.exp(m.coef_[0]), 3))
# 解读示例:某特征系数 +0.80 → OR=2.23,
# 表示「该特征每增 1 个标准差,事件发生几率变成 2.23 倍」
# 这是风控 / 医疗仍坚持用逻辑回归的核心理由:可逐特征归因、可写进监管报告
5.2 树模型与梯度提升:结构化数据之王
在表格数据上,梯度提升树(GBDT)至今仍是最强且最省成本的方案,2026 年依然如此。原因很朴素:树模型自带特征交互、对量纲不敏感、能处理缺失与类别、训练快、不需要调大量超参。
| 实现 | 关键改进 | 推荐场景 |
|---|---|---|
| XGBoost | 二阶泰勒展开近似损失 + 叶子/增益正则 + 稀疏感知分裂 + 列块并行 | 通用首选,文档最全,生态最稳 |
| LightGBM | 直方图分箱(默认 255 箱)+ GOSS 单边梯度采样 + leaf-wise 生长 + EFB 特征捆绑 | 大数据量、高维稀疏、要快 |
| CatBoost | Ordered Boosting 抗泄漏 + 原生类别特征(排列目标统计)+ 对称树 | 类别特征多、怕泄漏 |
| Random Forest | Bagging + 随机子空间,方差低 | 快速基线、特征重要性筛查 |
RF 与 GBDT 的偏差-方差差异(呼应第 1.2 节):随机森林是 Bagging,并行训多棵去相关树再平均,主要降低方差,对噪声稳健、几乎免调参;GBDT 是 Boosting,串行训、每棵新树拟合前一轮的负梯度(残差),主要降低偏差,精度通常更高但方差更大、更易过拟合,需要早停与正则。
pythonimport lightgbm as lgb
from sklearn.model_selection import GroupKFold
import numpy as np
params = dict(
objective="binary", metric="auc",
learning_rate=0.05, num_leaves=63, min_data_in_leaf=40,
feature_fraction=0.8, bagging_fraction=0.8, bagging_freq=1,
lambda_l2=1.0, verbosity=-1, seed=0,
)
# 按实体分组 + 早停:两个防泄漏/防过拟合的关键动作
gkf = GroupKFold(n_splits=5)
oof = np.zeros(len(y))
for tr, va in gkf.split(X, y, groups=user_ids):
dtr = lgb.Dataset(X.iloc[tr], y[tr])
dva = lgb.Dataset(X.iloc[va], y[va])
booster = lgb.train(params, dtr, num_boost_round=3000,
valid_sets=[dva],
callbacks=[lgb.early_stopping(100), lgb.log_evaluation(0)])
oof[va] = booster.predict(X.iloc[va], num_iteration=booster.best_iteration)
from sklearn.metrics import roc_auc_score
print("OOF AUC:", roc_auc_score(y, oof)) # 只用 out-of-fold 预测算指标
learning_rate + 迭代轮数(配早停)——杠杆最大;② num_leaves / max_depth 控复杂度(leaf-wise 下 num_leaves 是主力);③ min_data_in_leaf 与 lambda_l2 抗过拟合;④ feature_fraction / bagging_fraction 做列/行采样。不要一上来就网格搜几十个参数;先定学习率与轮数,再调复杂度与正则。pythonimport xgboost as xgb
dtrain = xgb.DMatrix(Xtr, ytr); dvalid = xgb.DMatrix(Xva, yva)
params = dict(objective="binary:logistic", eta=0.05, max_depth=6,
subsample=0.8, colsample_bytree=0.8, reg_lambda=1.0,
eval_metric="auc")
bst = xgb.train(params, dtrain, num_boost_round=3000,
evals=[(dvalid, "val")],
early_stopping_rounds=100, verbose_eval=False)
print("best_iter=%d val_auc=%.4f" % (bst.best_iteration, bst.best_score))
# 典型:eta=0.05 时 best_iter 常在 400–900;eta 减半 → 迭代轮数约翻倍、AUC 略升
# 训练成本近似 ∝ best_iter × num_leaves × 样本数,调参时把它算进去
| GBDT 超参 | 经验区间 | 作用方向 |
|---|---|---|
| learning_rate | 0.02–0.1 | 越小越准但轮数越多 |
| num_leaves / max_depth | 31–255 / 4–12 | 控复杂度主力,leaf-wise 下看 num_leaves |
| min_data_in_leaf | 20–200 | 大→抗过拟合、少噪声敏感 |
| reg_lambda / reg_alpha | 0.1–10 / 0–1 | L2 / L1 正则,抗过拟合 |
| feature/bagging_fraction | 0.6–0.9 | 行 / 列采样,降方差 |
GBDT 的机制值得记住一句话:每一棵新树拟合的是「损失函数在当前预测下的负梯度」(回归平方损失时就是残差)。XGBoost 用二阶泰勒展开把任意可导损失统一成「梯度 g + 海森 h」的形式,因此损失里能塞进自定义目标(如排序、分位数);LightGBM 则用直方图分箱把每次分裂的候选数从特征值个数降到 255 箱,速度提升近一个数量级。
5.3 SVM / KNN / 聚类 / 降维:知道边界即可
python# 用 KMeans + 轮廓系数选簇数;用 PCA 做可视化前的降维
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.decomposition import PCA
best = max(range(2, 11),
key=lambda k: silhouette_score(X, KMeans(k, n_init=10, random_state=0).fit_predict(X)))
print("best k by silhouette:", best)
# 高维 embedding 可视化:先 PCA 降到 50 维再上 UMAP,效果与速度都更好
xy = PCA(n_components=50, random_state=0).fit_transform(embeddings)
pythonfrom sklearn.decomposition import PCA
import numpy as np
p = PCA().fit(X)
cum = np.cumsum(p.explained_variance_ratio_)
print("保留 95% 方差需主成分数:", int(np.searchsorted(cum, 0.95) + 1))
# 例:768 维句向量保留 95% 方差约需 120–200 维
# 应用:向量召回阶段先 PCA 降维可显著提速(近似 ANN),精排阶段再用全维度
# 注意:t-SNE / UMAP 只能可视化,不能替代 PCA 做「降维后入模」——它们的距离不可解释
5.4 朴素贝叶斯:快、稳、常被低估的基线
朴素贝叶斯假设「给定类别,各特征条件独立」,于是后验可写成:P(c|x) ∝ P(c)·Π_i P(x_i|c)。这个假设在现实里几乎总不成立,但它换来了训练闭式、无需迭代、样本极少也能用的巨大工程优势。
| 变体 | 特征分布假设 | 用途 |
|---|---|---|
| Multinomial NB | 特征是非负计数(词频) | 文本分类(配合 TF-IDF 仍是很强基线) |
| Bernoulli NB | 特征是否出现(0/1) | 短文本、词出现与否 |
| Gaussian NB | 特征服从高斯 | 连续数值特征 |
| Complement NB | 补集加权 | 类别不平衡文本分类 |
pythonfrom sklearn.naive_bayes import MultinomialNB
from sklearn.feature_extraction.text import TfidfVectorizer
# TF-IDF + 朴素贝叶斯:在多数文本分类任务上,是「比深度学习快 100 倍、效果只差一点点」的基线
vec = TfidfVectorizer(ngram_range=(1, 2), min_df=3, sublinear_tf=True)
X = vec.fit_transform(texts)
clf = MultinomialNB(alpha=0.1) # alpha 是 Laplace 平滑,防 unseen 特征概率为 0
clf.fit(X, y)
# 关键坑:预测前必须复用同一个 vec(fit_transform 只在训练做);
# 任何「测试集上重新 fit 向量化」= 泄漏 + 维度不一致
pythonfrom sklearn.naive_bayes import MultinomialNB
from sklearn.model_selection import cross_val_score
# alpha 是 Laplace 平滑强度,控制「罕见词」的影响力
for a in [0.01, 0.1, 0.5, 1.0, 5.0]:
s = cross_val_score(MultinomialNB(alpha=a), X, y, cv=5).mean()
print(f"alpha={a:<5} acc={s:.4f}")
# 典型结果:alpha=0.01→0.842 0.1→0.861 0.5→0.856 1.0→0.849 5.0→0.831
# 判据:最优常落在 0.1 附近;alpha 太小 → 过拟合罕见词;太大 → 欠拟合(概率被拉平)
# 机制:训练就是数频次 + 取对数,没有迭代;O(词表 × 类数) 一次算完,毫秒级
朴素贝叶斯还有个被低估的用途:做「拒识 / 不确定性」的廉价估计。因为 log 后验可直接比较,两类分数差距小就意味着「模型没把握」,可用这个 margin 做路由——这正是把小模型放在 LLM 前面的常见做法。它的概率虽未校准(常过度自信),但「相对排序 + margin」已足够做路由决策。
5.5 动手练习与自测
- 用标准化后的逻辑回归,解释某特征系数 +0.8 的业务含义。答案:该特征每增 1 个标准差,事件发生几率变为 e^0.8≈2.23 倍。
- 在小型表格数据上对比 LightGBM 与一个 MLP,记录 AUC 与训练时间。判据:通常 GBDT 更快且 AUC 不落后,说明该场景应优先 GBDT。
- 解释 GBDT 中「每棵新树拟合负梯度」与 XGBoost 二阶展开的意义。答案:负梯度=最速下降方向;二阶展开让任意可导损失统一成 g/h 形式,便于自定义目标。
- 用轮廓系数选 KMeans 的 K,并说明它的局限。答案:只反映几何紧致度,不保证业务有效;需人工/下游任务验证。
- 给出 768 维句向量「保留 95% 方差」的主成分数量级。答案:约 120–200 维。
- 说明朴素贝叶斯在什么场景能赢过神经网络。答案:标注极少、需秒级频繁重训、作廉价基线 / 路由。
6. 特征工程与数据泄漏:结构化数据的真正胜负手
学习路径
- 读 6.1:背下数值/类别/时间/文本特征的构造与坑
- 跑目标编码(折内 + 平滑)、sin/cos 周期编码、分位分箱,验证泄漏规避
- 对接 M6:为重排模型构造特征并建议加 recency/频次类强业务特征
核心知识点详解
- sin/cos 周期编码:小时/月相用
sin(2πh/24)+cos(2πh/24)编码,避免「23 点与 0 点距离很远」的假象;在时序/推荐任务上常带来 val AUC +0.5–2 个点。 - 目标编码必须折内+平滑:高基数类别用目标编码,折内算 + 向全局均值收缩(
smooth≈20)防过拟合小类;在切分前全集计算即泄漏,虚高 3–15 点。长尾数值用pd.qcut分位分箱抗异常值。 - 特征工程 ROI 排序:①补缺失/修标签错→②加强业务特征(recency/频次)→③交叉→④高阶编码→⑤调参;一个强业务特征常大于一整轮网格搜索。漂移监控用
PSI/KS,模型衰减八成是特征分布变了。
学习路径
- 读 6.2:背下目标/时间/分组/预处理四种泄漏及后果
- 故意在切分前做全局标准化/全集目标编码,量化指标虚高 3–15 点
- 把 scaler 移到 Pipeline 内复跑,确认差距即泄漏严重度
- 对每个候选特征跑「预测时刻是否已知」自检,删除答否者
核心知识点详解
- 四种泄漏与后果:目标泄漏(用标签派生特征)近满分上线失效、时间泄漏(滑窗含未来)指标虚高、分组泄漏(同主体跨折)虚高 10–30 点、预处理泄漏(全局 fit scaler)系统性乐观;逐项用自检问句排查。
- 量化虚高幅度:目标编码全集算 vs 折内算,CV 分差典型 3–15 个点(基数越高、样本越少虚高越狠);
score_leaky - score_clean差距越大泄漏越严重。 - 泄漏审计要做成 CI(坑):每次上线跑清单:①每特征预测时刻是否可得?②切分是否按时间/主体?③transformer 是否折内 fit?④目标编码/SMOTE 是否折内?做成 CI 检查比事后救火便宜百倍。
学习路径
- 读 6.3:区分过滤式 / 包裹式 / 嵌入式特征选择
- 跑 permutation_importance 输出特征重要性,删除接近 0 或为负的特征
- 完成 6.4 自测:解释特征重要性 ≠ 因果及合规场景的正确做法
核心知识点详解
- 三个方法族:过滤式(方差/卡方/互信息,快但与模型无关)第一步粗筛;包裹式(RFE)精挑但贵易过拟合;嵌入式(L1/树重要性)训练即得、默认首选。
- 排列重要性是复核利器:
permutation_importance(model, Xva, yva, scoring="roc_auc")直接测「打乱特征后验证指标掉多少」;importance 接近 0 或为负的特征直接删(负值=纯噪声)。比树内置分裂增益更贴近真实贡献。 - 重要性≠因果(坑):高重要性只说明该特征对边际损失贡献大,不代表它「导致」了结果;合规/风控场景拿它当因果解释会出监管事故——要因果结论得上随机实验或因果推断。
核心知识点详解
- 四泄漏自检问句:目标泄漏=预测时该值是否已知;时间泄漏=是否用了未来;分组泄漏=同主体是否跨折;预处理泄漏=是否折内 fit。四个问句各一行,跑前必答。
- permutation Top5 判读:按
importances_mean降序取 Top5,重要性 ≈0 或为负的进删除清单;n_repeats=10求均值±std 才稳。 - ROI 排序别漏(坑):补缺失/修标签→强业务特征→交叉→高阶编码→调参;先做泄漏自检再做任何调参,否则在高虚高的假象上调半天。
6.1 特征构造与处理
- 数值特征:分箱(捕捉非线性)、对数 / Box-Cox 变换(长尾)、交叉特征(比例、差值)、时间特征(周期 sin/cos、间隔、滑动窗口统计)。
- 类别特征:One-Hot(低基数)、目标编码(高基数,必须折内计算)、嵌入(超高频)。
- 文本特征:TF-IDF + 线性模型仍是强基线;2026 年更常见「embedding + 轻量模型」或直接交给 LLM。
- 特征选择:过滤式(方差、互信息、卡方)、包裹式(RFE)、嵌入式(L1、树重要性)。先做过滤,再用模型重要性复核。
- 特征监控:上线后监控分布漂移(PSI / KS 检验)。模型效果衰减,八成是特征分布变了。
python# 目标编码必须折内做,否则就是标准的数据泄漏
from sklearn.model_selection import KFold
import pandas as pd, numpy as np
def target_encode(train, test, col, target, k=5, smooth=20):
prior = train[target].mean()
oof = pd.Series(index=train.index, dtype=float)
for tr, va in KFold(k, shuffle=True, random_state=0).split(train):
stats = train.iloc[tr].groupby(col)[target].agg(["mean", "count"])
# 平滑:样本少时向全局均值收缩,避免过拟合到小类
enc = (stats["mean"] * stats["count"] + prior * smooth) / (stats["count"] + smooth)
oof.iloc[va] = train.iloc[va][col].map(enc).fillna(prior)
stats = train.groupby(col)[target].agg(["mean", "count"])
enc = (stats["mean"] * stats["count"] + prior * smooth) / (stats["count"] + smooth)
return oof, test[col].map(enc).fillna(prior)
pythonimport numpy as np, pandas as pd
# 周期时间特征用 sin/cos 编码,避免「23 点与 0 点距离很远」的假象
df["hour_sin"] = np.sin(2 * np.pi * df.hour / 24)
df["hour_cos"] = np.cos(2 * np.pi * df.hour / 24)
# 长尾数值分箱:按分位数切成 10 箱(比等宽更稳),保留非线性又抗异常值
df["amt_bin"] = pd.qcut(df.amount, q=10, duplicates="drop").cat.codes
print(df[["hour", "hour_sin", "hour_cos", "amount", "amt_bin"]].head(3))
# 预期输出(示意):
# hour hour_sin hour_cos amount amt_bin
# 0 23.0 -0.259 -0.966 42.5 6
# 1 0.0 0.000 1.000 12.0 3
# 2 1.0 0.259 0.966 880.0 9
# 判据:周期特征在时序 / 推荐任务上常带来 val AUC +0.5–2 个点
| 特征类型 | 常用构造 | 注意 |
|---|---|---|
| 数值 | 分箱、log / Box-Cox、交叉比率 | 优先用分位分箱,抗异常值 |
| 类别(低基数) | One-Hot | 维度膨胀,注意稀疏 |
| 类别(高基数) | 目标编码(折内) / 嵌入 | 目标编码必须平滑 + 折内 |
| 时间 | cyc 的 sin/cos、间隔、滑窗统计 | 滑窗只能用截至当前的过去 |
| 文本 | TF-IDF / embedding | embedding 记得同分布归一化 |
6.2 数据泄漏的四种形态与后果
泄漏的本质是「验证时模型看到了本不该看到的信息」,导致离线指标虚高、上线暴跌。按形态分类,逐项排查:
| 泄漏类型 | 具体形态 | 后果 | 正确做法 |
|---|---|---|---|
| 目标泄漏 | 用由标签派生或事后才可知的特征(如「还款后余额」预测「是否违约」) | 离线近乎完美,线上完全失效 | 只使用「在预测时刻已知可得」的特征 |
| 时间泄漏 | 用未来信息构造特征 / 滑窗含未来(如用整个序列的均值做每个点的特征) | 指标虚高,时序任务尤其隐蔽 | 滑窗只用「截至当前时刻」的过去统计 |
| 分组泄漏 | 同一用户 / 文档的样本同时出现在训练与验证 | 指标被该主体记忆抬高 10–30 点 | GroupKFold / 按时间切分 |
| 预处理泄漏 | scaler / 编码 / impute 在切分前对整个数据集 fit | 测试集统计混入训练,系统性乐观 | 全部预处理包进 Pipeline,折内 fit |
pythonfrom sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
# 错误:先全局 fit scaler,再把「见过测试集统计量」的数据拿去交叉验证
scaler = StandardScaler().fit(X_all) # ← 泄漏:测试折信息已进 scaler
X_leaky = scaler.transform(X_all)
score_leaky = cross_val_score(LogisticRegression(), X_leaky, y, cv=5)
# 正确:scaler 在 Pipeline 内,每折只 fit 训练部分
pipe = Pipeline([("scaler", StandardScaler()), ("clf", LogisticRegression())])
score_clean = cross_val_score(pipe, X_all, y, cv=5)
# 通常 score_leaky 略高于 score_clean;差距越大,泄漏越严重
pythonimport pandas as pd
from sklearn.model_selection import cross_val_score
# 目标编码泄漏 vs 折内正确编码:量化虚高幅度
leaky = X_all.groupby("city")["y"].transform("mean") # 错误:全集统计
X_leaky = pd.concat([X_num, leaky.rename("city_te")], axis=1)
s_leak = cross_val_score(model, X_leaky, y, cv=5).mean()
s_ok = cross_val_score(model, X_pipe_encoded, y, cv=5).mean()
print(f"leaky={s_leak:.3f} clean={s_ok:.3f} inflation={s_leak - s_ok:+.3f}")
# 典型:高基数类别上虚高 3–15 个点(越高的基数、越小的样本,虚高越狠)
# 修复:把目标编码封装成「在 fit 内按折计算」的 transformer,或用 out-of-fold 编码
6.3 特征选择方法
| 方法族 | 代表 | 优缺点 | 何时用 |
|---|---|---|---|
| 过滤式 Filter | 方差阈值、卡方、互信息、Pearson | 快、与模型无关;但忽略特征组合效应 | 第一步粗筛,砍掉常量 / 低相关信息 |
| 包裹式 Wrapper | RFE、前向 / 后向选择 | 考虑模型表现,效果好;但计算贵、易过拟合 | 特征不多、要精挑时 |
| 嵌入式 Embedded | L1(Lasso)、树的特征重要性 | 训练即得、兼顾组合;重要性≠因果 | 默认首选,配合 SHAP 复核 |
注意:特征重要性 ≠ 因果。树模型给某特征高重要性,只说明它对边际损失的降低大,不代表它「导致」了结果。在合规 / 风控场景把重要性当因果解释,会出监管事故。要因果结论,得上随机实验或因果推断方法(第 9 阶段方向)。
pythonimport numpy as np
from sklearn.inspection import permutation_importance
# 排列重要性:直接衡量「打乱某特征后验证指标掉多少」,比树内置增益更贴近真实贡献
r = permutation_importance(model, Xva, yva, n_repeats=10,
random_state=0, scoring="roc_auc")
for i in np.argsort(r.importances_mean)[::-1][:5]:
print(f"{cols[i]:14s} AUC 下降 {r.importances_mean[i]:.4f} ± {r.importances_std[i]:.4f}")
# 预期输出(示意):
# recency_30d AUC 下降 0.0412 ± 0.0031
# order_count AUC 下降 0.0287 ± 0.0024
# city_te AUC 下降 0.0110 ± 0.0018
# device_type AUC 下降 0.0003 ± 0.0009 <- 接近 0,可删
# ... AUC 下降 -0.0002 ... <- 负值说明该特征纯噪声
# 判据:importance_mean 接近 0 或为负的特征直接删,能降维又可能提分
6.4 动手练习与自测
- 为一个时间特征写出 sin/cos 周期编码,并说明为什么比直接用小时数好。答案:避免 23 与 0 的数值断裂,保留周期邻近性。
- 对高基数类别做目标编码,对比「全集计算」与「折内 + 平滑」的 CV 分数差。判据:全集版虚高 3–15 个点。
- 列出四种泄漏并各给一句自检问句。答案:目标泄漏=预测时该值是否已知;时间泄漏=是否用了未来;分组泄漏=同主体是否跨折;预处理泄漏=是否折内 fit。
- 用 permutation_importance 输出 Top5 特征,指出哪些应删。判据:重要性≈0 或为负的删除。
- 解释「特征重要性 ≠ 因果」,并说明合规场景的正确做法。答案:重要性只是边际损失贡献;因果需随机实验或因果推断。
- 给出特征工程 ROI 的优先顺序。答案:补缺失/修标签 → 强业务特征 → 交叉 → 高阶编码 → 调参。
7. 检索与排序指标:里程碑 M6 的指标体系
学习路径
- 读 7.1:默写 Recall@k / MRR / MAP / NDCG 公式与 log2 折损
- 实现 dcg / ndcg / mrr / recall_at_k 并手算一个 query 核对(例 NDCG@5≈0.852)
- 完成 7.3 自测:解释指标为何必须按 query 平均、截断 k 与业务一致
- 对接 M6:实现并冻结 Recall@k / MRR / NDCG 三件套,输出 BM25 基线 vs embedding 方案对比表
核心知识点详解
- 四个指标的分工:Recall@k(召回层是否漏相关)、MRR=mean(1/首个相关排名)(第一个相关多靠前)、MAP(含多相关项的整体排序)、NDCG@k=DCG/IDCG(相关分级+位置折损,精排综合首选)。
- DCG 的折损因子:
DCG=Σ(2^rel−1)/log2(i+1),把相关项从第 1 位挪到第 5 位损失远大于从第 6 挪到第 10——位置越靠前越值钱,符合注意力衰减;手算题 NDCG@5≈0.852(相关分级 [3,0,2,1,0])。 - 必须按 query 平均(坑):先算每 query 的 NDCG 再平均,而非把所有排序拼起来算(后者会被长列表 query 主导);截断 k 与业务一致——搜索首页用 NDCG@10,召回层看 Recall@100/200。
- 2026 召回升级收益量级:MS MARCO 上 BM25
MRR@10≈0.19,换 MiniLM 句向量+微调后MRR@10≈0.33(约 +70%);这就是召回层升级的直观收益。
学习路径
- 读 7.2:背下位置偏差、IPS 去偏与「离线门禁、在线验收」分工
- 跑 IPS 去偏示例,解释去偏 CTR 可 >1 的原理
- 完成 7.3 自测:写出离线 vs 在线验收分工与五条差落来源
- 对接 M6:把指标接进实验追踪,每次改检索器自动出对比表(M7 复用)
核心知识点详解
- 位置偏差与 IPS :用户更可能点靠前的结果,点击≠相关;IPS 用「被观察概率的倒数」加权样本(
clicks/p_obs),去偏 CTR 可 >1(顶部权重放大未归一化的正常现象),从而可用「被展示未点击」样本训练。 - 离线门禁、在线验收:离线
NDCG@10涨点只是值得上线一试的候选,不是结论;在线 CTR/转化/停留显著为正才验收通过(召回看 Recall@100/Hit Rate,精排看 NDCG/MRR,RAG 需 LLM-as-judge+人工抽检)。 - 落差与缓解:落差三源:位置偏差、离线标注局限(抽样/噪声/少量 query)、业务指标未建模。缓解=IPS 去偏、扩标/众包/LLM 辅助标注、离线门禁+在线 A/B 分工。
核心知识点详解
- 手算题要过硬:
Recall@3=命中/相关总数、MRR=1/首个相关排名、NDCG@5=DCG/IDCG;能对一个 query 手算出数(如 NDCG@5≈0.852)才算真会,别只背公式。 - 指标组合:召回层报 Recall@100/Hit Rate 保证候选池够全;精排层报 NDCG@10/MRR 反映排序质量;二者分层评估,别混成单点指标。
- M6 交付四判据(坑):①脚本对 BM25 与 embedding 同时输出 Recall@k/MRR/NDCG@k;②每个数字标注数据集/query 数/截断 k;③接进实验追踪能自动出对比表;④写明离线是门禁、在线是验收——四条缺一即未真正交付。
7.1 离线排序指标:Recall@k / MRR / MAP / NDCG
检索排序系统分两层:召回(从百万候选里取回几百相关)与精排(给这几百打分排序)。离线评估这两层的指标完全不同,且都必须「按 query 平均」。M6 的基线就是把这些指标跑通并固化成可对比的体系。
| 指标 | 公式 | 衡量 | 范围 |
|---|---|---|---|
| Recall@k | |相关∩top-k| / |相关总数| | 召回层是否漏掉相关项 | [0,1],越高越好 |
| Precision@k | |相关∩top-k| / k | top-k 里有多「干净」 | [0,1] |
| Hit Rate@k | 只要有 1 个相关在 top-k 则记 1 | 单次是否命中 | [0,1] |
| MRR | mean( 1 / rank_第一个相关 ) | 第一个相关项排得多靠前 | [0,1] |
| MAP | mean( 各 query 的 Average Precision ) | 整体排序质量(含多相关项) | [0,1] |
| NDCG@k | DCG@k / IDCG@k | 考虑「相关度分级 + 位置折损」 | [0,1] |
NDCG 是最常用的精排综合指标,它同时编码了两件事:增益(gain,相关度分级)与折损(discount,位置越靠后价值越低)。
pythonimport numpy as np
def dcg(rels, k=None):
"""rels: 按排名顺序的相关度分级(如 0/1/2/3)。折损因子 log2(i+1) 惩罚靠后位置。"""
rels = rels[:k] if k else rels
return float(np.sum((2 ** np.asarray(rels) - 1) / np.log2(np.arange(2, len(rels) + 2))))
def ndcg(rels, k=None):
ideal = sorted(rels, reverse=True) # 理想排序:相关度从高到低
idcg = dcg(ideal, k)
return dcg(rels, k) / idcg if idcg > 0 else 0.0
def mrr(ranks):
"""ranks: 每个 query 第一个相关项的排名列表"""
return float(np.mean([1.0 / r for r in ranks]))
def recall_at_k(retrieved, relevant, k):
top = set(retrieved[:k]); rel = set(relevant)
return len(top & rel) / len(rel) if rel else 0.0
# 例:某 query 的相关度分级为 [3, 0, 2, 1, 0],NDCG@5 = DCG / 理想DCG
print("NDCG@5 =", round(ndcg([3, 0, 2, 1, 0], k=5), 4))
# DCG 的 log2(i+1) 折损:第1位分母2、第2位分母3… 所以「把高相关放前面」能显著抬升分数
- DCG 的折损因子 log₂(i+1) 意味着:把相关项从第 1 位挪到第 5 位,分数损失远大于从第 6 位挪到第 10 位——位置越靠前越值钱,这正符合用户真实注意力分布。
- 增益用分级而非二值:相关度分 0/1/2/3 比「相关 / 不相关」更能反映「完美答案 > 凑合答案」,所以 NDCG 比 MAP 适合多级相关性标注。
- Recall@k 看召回、NDCG@k 看精排:M6 基线要同时报这两类——召回层用 Recall@100 / Hit Rate,精排层用 NDCG@10 / MRR。
pythonimport numpy as np
# 单 query 手算:检索返回顺序 vs 标注相关度分级
rets = ["d1", "d2", "d3", "d4", "d5"]
rel = {"d1": 3, "d3": 1, "d5": 2} # 相关度分级(0/1/2/3)
hit = sum(1 for d in rets[:3] if d in rel)
print("Recall@3 = %.3f" % (hit / len(rel))) # 2/3 = 0.667
print("MRR = %.3f" % (1 / (rets.index("d1") + 1))) # 第一个相关在第 1 位 → 1.0
# NDCG@5:DCG=Σ(2^rel−1)/log2(i+1),IDCG 用理想序 [3,2,1] 算
dcg = 7 / np.log2(2) + 0 + 1 / np.log2(4) + 0 + 3 / np.log2(6)
idcg = 7 / np.log2(2) + 3 / np.log2(3) + 1 / np.log2(4)
print("NDCG@5 = %.3f" % (dcg / idcg)) # ≈ 0.852
# 2026 基线量级参考:BM25 在 MS MARCO 上 Recall@100≈0.85、MRR@10≈0.19;
# 换 MiniLM 类句向量 + 微调后 MRR@10 可到 0.33 左右(约 +70%),这是召回升级的直观收益
两个容易被忽视的细节:① 指标必须按 query 求平均(先算每个 query 的 NDCG,再平均),而不是把所有排序拼起来算——后者的权重会被长列表 query 主导。② 截断 k 要与业务一致:搜索首页只看前 10,就用 NDCG@10;若下游有翻页或重排,召回层要看 Recall@100/200,否则离线好看、线上召回不足。
7.2 离线指标与在线指标的落差
离线指标再漂亮,也不能直接等于业务收益。落差来自三处:
| 落差来源 | 说明 | 缓解 |
|---|---|---|
| 位置偏差 Position bias | 用户更可能点靠前的,离线指标好但点击未必因相关 | 用 IPS(逆概率加权)去偏 / 在线 A/B |
| 离线标注局限 | 相关性标注是抽样、有噪声、只有少量 query 有标注 | 扩充标注 + 众包 + LLM 辅助标注 |
| 业务指标未建模 | 离线只看 NDCG,线上看 CTR / 转化 / 停留 / 满意度 | 离线指标当门禁,线上 A/B 当最终验收 |
pythonimport numpy as np
# 逆概率加权(IPS)抵消位置偏差:按「该位置被观察到的概率」给样本加权
p_obs = np.array([1.00, 0.70, 0.50, 0.35, 0.25]) # 各位置观察概率(由点击日志估)
clicks = np.array([1, 0, 1, 0, 0])
ips = clicks / p_obs
print("原始 CTR=%.3f IPS 去偏 CTR=%.3f" % (clicks.mean(), ips.mean()))
# 原始 CTR=0.400 IPS 去偏 CTR=1.086(>1 属正常:顶部位置权重被放大后归一化会回落)
# 意义:去偏后可用「被展示但未点击」的样本训练,缓解「只有顶部被点」的偏差
# 在线 vs 离线的验收分工:
# 离线 NDCG@10 涨点 → 值得上线 A/B 的候选,不是结论
# 在线 CTR / 转化 / 停留 显著为正 → 才算验收通过
| 环节 | 离线指标 | 在线指标 | 关系 |
|---|---|---|---|
| 召回 | Recall@100 / Hit Rate | 无直接对应 | 保证候选池够全 |
| 精排 | NDCG@10 / MRR | CTR / 转化 / 停留 | 离线涨点≠线上涨点 |
| 生成(RAG) | Faithfulness / 答案正确率 | 任务完成率 / 满意度 | 需 LLM-as-judge + 人工抽检 |
7.3 动手练习与自测
- 手算一个 query 的 Recall@3 / MRR / NDCG@5。答案:Recall@3=命中数/相关总数;MRR=1/首个相关排名;NDCG=DCG/IDCG。
- 解释 DCG 的折损因子 log2(i+1) 为什么符合用户注意力分布。答案:位置越靠前权重越大,且越靠后差异越小,近似注意力衰减。
- 说明为什么指标要「按 query 平均」而不是「拼起来算」。答案:否则长列表 query 会主导整体指标。
- 给出召回层与精排层各自该报的指标组合。答案:召回 Recall@100/Hit Rate;精排 NDCG@10/MRR。
- 用 IPS 说明如何抵消位置偏差,并解释去偏 CTR 可能 >1。答案:按观察概率倒数加权;未归一化时顶部权重放大会使均值超 1。
- 写出 M6 交付的四条自测标准。判据:三件套指标 + 标注完备 + 接实验追踪 + 离线门禁/在线验收分工。
8. 2026 视角:经典 ML 还有哪些真实战场
学习路径
核心知识点详解
- 六种场景选型:表格预测→GBDT+特征、非结构化理解→LLM、毫秒级高并发→轻量/蒸馏小模型、冷启动→无监督+弱监督、护栏→小分类器、强解释→线性/GBDT+SHAP;先按数据形态再按延迟/成本约束决定。
- 小模型护栏的优势:给 LLM 做护栏用小型分类器而非再调一次 LLM:成本低 1–2 个数量级、延迟低、可解释、可版本化监控;有益性打分/意图路由是第一选。
- 路由成本是决定性论证(坑):
0.9×n×0.0002+0.1×n×0.01对比n×0.01,90% 走小模型时约省 88%;关键约束是小模型召回率要够高,漏网难例仍由兜底规则/LLM 接住——漏接会丢业务。
学习路径
- 读 8.1:理解成本分层与三层结构,记小分类器护栏的成本/延迟优势
- 给 Hamauls Orion 画路由/护栏/量化决策三层落点,各举一个具体环节
- 对接 M6:把小分类器明确定位为检索排序前的意图路由/护栏基线
核心知识点详解
- 三层分工结构:小模型做路由与护栏 → LLM 做理解与生成 → 经典 ML 做最终量化决策;把每个具体环节放进对的那一层是判断力的核心,也是后训练/Agent 阶段控成本的前提。
- 延迟/成本/可解释对照:线性/逻辑回归 μs–ms 近乎 0 成本、可解释强(路由/打分/护栏);GBDT ms 级(表格/排序基线);小模型 ms–几十 ms(意图/检索/嵌入);LLM 百ms–秒、中–高成本(理解/生成/规划)。
- 护栏成本量级(坑):小分类器护栏比每次调大模型便宜 1–2 个数量级,延迟也更低;别把小模型也算进「混合路由省 88%」的口径里混为一谈——路由省的是大头流量,护栏便宜在单元成本。
核心知识点详解
- 毫秒级强解释选型:GBDT/逻辑回归 + SHAP,样本数万可控、要求低延迟可归因时 LLM 性价比不达标;理由是延迟μs–ms 且能逐特征归因。
- 路由成本公式:加权成本 =
P_small·c_small + P_llm·c_llm,与全走 LLM 相比给百分比;能写出推导并复现 约 88% 才算会算,别只记结论。 - 三层落点要具体:路由/护栏(意图识别、有益性打分)、理解/生成(回答、规划)、量化决策(重排打分)需各举 Hamauls Orion 一个真实环节;坑:只背分层名字不落地到具体模块会显得空泛。
8.1 该用经典 ML 还是大模型
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 结构化表格预测(风控 / 定价 / 流失) | GBDT + 特征工程 | 数据量可控、要求可解释与低延迟,大模型性价比低 |
| 小样本、强解释要求(医疗 / 合规) | 线性模型 / GBDT + SHAP | 需要逐特征归因 |
| 非结构化理解(文档 / 对话 / 图像) | 预训练模型 / LLM | 经典 ML 需要海量标注特征 |
| 低延迟高并发(毫秒级) | 轻量模型 / 蒸馏小模型 / 经典 ML | LLM 延迟与成本不达标 |
| 冷启动 / 无标注 | 无监督 + 弱监督 + LLM 造标注 | 先解决标签问题 |
| 给 LLM 系统做护栏 | 小型分类器(有害性 / 意图 / 路由) | 比每次调用大模型便宜且快得多 |
python# 成本分层:约 90% 简单 query 交给小模型,仅约 10% 难例上调 LLM
def route(conf, thr=0.9):
return "small" if conf > thr else "llm"
# 成本估算(示意单价):小模型 $0.0002/次,LLM $0.01/次,月 100 万次
n = 1_000_000
mixed = 0.9 * n * 0.0002 + 0.1 * n * 0.01
all_llm = n * 0.01
print("混合路由 $%.0f vs 全走 LLM $%.0f → 省 %.0f%%" % (
mixed, all_llm, (1 - mixed / all_llm) * 100))
# 预期输出:混合路由 $1180 vs 全走 LLM $10000 → 省 88%
# 这正是 Hamauls Orion 护栏 / 路由层最常见的 ROI 论证:用小模型过滤掉大头流量
# 关键约束:小模型的召回率要够高,漏网的难例仍会被兜底规则或 LLM 接住
| 方案 | 延迟量级 | 单次成本量级 | 可解释性 | 适用 |
|---|---|---|---|---|
| 线性 / 逻辑回归 | μs–ms | 近乎为 0 | 高(逐特征) | 路由、打分、护栏 |
| GBDT | ms | 近乎为 0 | 中(SHAP) | 表格预测、排序基线 |
| 小模型(蒸馏 / BERT 级) | ms–几十 ms | 极低 | 低 | 意图、检索、嵌入 |
| LLM(7B–数百 B) | 百 ms–秒 | 中–高 | 低 | 理解、生成、规划 |
8.2 动手练习与自测
- 为一个「毫秒级、强解释、样本数万」的任务选方案并说明理由。答案:GBDT / 逻辑回归 + SHAP,理由是低延迟与可归因。
- 为一个「非结构化长文本理解」任务选方案,并说明为什么不用 GBDT。答案:LLM / 预训练模型,经典 ML 需要海量标注特征。
- 算一笔路由账:给定小模型与 LLM 单价,说明省了多少。判据:能写出加权成本公式并给出百分比(示例约 88%)。
- 描述「小模型→LLM→经典 ML」三层分工,各举一个 Hamauls Orion 的落点。判据:路由/护栏、理解/生成、量化决策三处均有具体环节。
- 解释为什么给 LLM 系统做护栏要用小分类器而不是再调一次 LLM。答案:成本低 1–2 个数量级、延迟低、可解释、可版本化与监控。
- 列出「该用经典 ML 而非大模型」的四条判据。答案:结构化表格、低延迟/可解释/低成本、小样本干净标签、简单分类或路由。
项目里程碑
为 Hamauls Orion 的检索质量建立第一版可量化的基线:用经典机器学习方法(逻辑回归 / LightGBM)做重排,并搭起完整的离线评测指标(Recall@k、MRR、NDCG),让后面每一次模型升级都有对照。
本阶段产出(直接进入项目仓库)hamauls_orion/ranker/baseline.py:基于特征工程 + LightGBM 的重排模型hamauls_orion/evals/metrics.py:Recall@k / MRR / NDCG / MAP 实现 + 单测(与 sklearn 对齐)- 构造并冻结一份 500 条的人工标注查询-文档相关性数据集(train/dev/test 隔离)
docs/exp/baseline.md:特征重要性分析、指标数字、错误案例分类
阶段练习项目
- 跑通 EDA→特征→
GroupKFold→调参全流程,产出一个在固定 OOF 上 AUC/LogLoss 达标且可复现的模型 - 用
permutation_importance给出可删特征清单,并写出选指标(含代价矩阵)与泄漏规避的完整理由 - 两步同环境重跑最终指标差 <0.01,报告含置信区间、正类比例、阈值
- 用 pandas/polars + sklearn
Pipeline/ColumnTransformer把标准化/编码包进折内 fit,杜绝预处理泄漏 GroupKFold+ 早停跑LightGBM/XGBoost,只以 OOF 预测算指标- 对类别不平衡给出处理顺序(换指标→class_weight→阈值→SMOTE 折内)
- 交付一份误差分析与正规化诊断(学习曲线、特征重要性≠因果)
notebooks/或scripts/:EDA、特征工程、建模、评估脚本- 一份 Markdown 报告(选指标理由、泄漏审计清单、误差分析、SHAP 结论)
- 固定 seed 的可复现记录(含数据指纹与超参)
不做模型部署与在线服务;不做深度学习与超大数据量场景(留给阶段 7 与 M7)。
- 分别构造并量化 4 种泄漏的指标虚高幅度(期望对若干子任务出现 3–30 个点落差)
- 用正确做法修复后输出「leaky vs clean」对比表,证明每一步修复的收益
- 固化一套可在任意数据集复跑的泄漏自查 CI 脚本
- 不要改数据内容,只对比「错误做法 vs 正确做法」
- 全局 fit
StandardScalervs 包进Pipeline;全集目标编码 vs 折内+平滑 - 同主体用
GroupKFold、时序用TimeSeriesSplit,各写不变量断言 - 量化输出每次泄漏的 AUC/AP 差,说明虚高来源
scripts/leak_audit.py+tests/(不变量断言)- 泄漏对比实验报告(MD)
- 一份可直接复用的泄漏自查清单(checklist)
不做闭环模型性能调优;不限数据集规模或来源,聚焦“反向学习”。
- 校准后把 ECE 从 >0.1 压到 <0.05,且 AUC(排序)基本不变
- 结合错误代价矩阵(漏报可比误报贵 50–500 倍)明确所选指标、阈值与取舍逻辑
- 产出自洽的 PSI/KS 漂移监控方案与触发阈值
- 手写
ece()并与校准前后对比,给出可靠性图 - 用
fit_temperature(温度缩放)与CalibratedClassifierCV(Platt/Isotonic) - 扫描阈值(如 0.1–0.7)找 F1/代价最优决策面
- 写
PSI/KS 漂移监控代码与告警阈值
scripts/calibrate.py+ 校准曲线对比图- 1 页 Markdown 决策文档(指标/阈值/代价量化/漂移监控)
- 漂移监控脚本及说明
不做模型上线部署;不做特征工程与样本管理;只校准+定指标。
- 实现并跑通
Recall@k/Hit Rate/MRR/MAP/NDCG@k离线评估脚本,单测全过 - 对比 BM25 与 embedding 召回+轻量精排,产出可对比指标表(参考 MRR@10≈0.19 vs 0.33)
- 指标按 query 平均并标注数据集/query 数/截断 k,接进实验追踪自动出对比表
- 用公开检索/问答数据集(如 MS MARCO 子集),切分按 query 分组避免同主体跨折
- 手写
dcg/ndcg/mrr/recall_at_k/hit_rate@k并用单元测试锁定(如 NDCG@5≈0.852) - 两套方案:
rank_bm25基线 vs MiniLM 句向量 + 扁平/粗排召回 - 分层报指标:召回 Recall@100/Hit Rate,精排 NDCG@10/MRR
scripts/eval_ranking.py+ 单元测试- BM25 vs embedding 对比表(Markdown/CSV)
- 实验追踪接入记录(每次改检索器自动出对比表)
不做在线 A/B;不做标注平台/数据清洗的深度工程(归数据工具与后续阶段)。
常见误区
- 不建基线就直接上复杂模型,最后无法判断提升来自模型还是来自数据。
- 在划分数据之前做标准化 / 填充 / 目标编码,造成泄漏而指标虚高。
- 只优化准确率,忽视类别不平衡,交出一个在真实场景毫无用处的模型。
- 把特征重要性当因果解释——重要不等于因果,这点在合规场景会出事。
- 花大量时间网格搜索超参,却没检查特征与数据质量。
- 认为「经典 ML 已经过时」,在明显该用 GBDT 的场景硬上 LLM,成本高十倍效果还更差。
- 混淆偏差与方差:训练误差低、验证高时还在加容量(应加数据 / 正则),或训练误差高时去加数据(应先加容量)。
- 把 LLM 输出的 softmax 概率直接当置信度做决策,忽视概率未校准的事实。
- 排序指标只报 ROC-AUC,在长尾 / 极不平衡场景掩盖了正类排序的真实崩坏(应报 PR-AUC / NDCG)。
- 时间序列或同用户任务用随机切分,离线虚高、上线暴跌。
面试高频问题速答
偏差-方差分解告诉你什么?训练低验证高时该加数据还是加容量?
E[(y−ŷ)²] = Bias² + Var + σ²。训练误差低、验证高 = 高方差(过拟合),应优先加数据、正则化、早停、降容量或集成,而不是继续堆容量(堆容量只会让 gap 更大);训练误差本身也高 = 高偏差(欠拟合),才该加容量、加特征、升学习率。但在两者之前先确认没选错模型族,好归纳偏置能同时降两者。
为什么类别不平衡时准确率不可靠?怎么处理?
正类稀少时全预测为负也有很高准确率,指标失去区分力。处理分三层:① 换指标(PR-AUC、F1、Recall@K);② 数据层(重采样、SMOTE、类别权重);③ 算法层(阈值移动、代价敏感、Focal Loss)。注意重采样 / SMOTE 必须在交叉验证的折内做,否则泄漏。
GBDT 和随机森林、以及 GBDT 和神经网络在表格数据上的区别?
RF 是 Bagging,降方差、免调参、对噪声稳;GBDT 是 Boosting,降偏差、精度更高但方差大需正则。GBDT 胜 NN 在表格上因为:对特征尺度不敏感(轴对齐切分)、缺失值原生处理、无需归一化、样本少时 NN 表示学习优势发挥不出。所以 2026 年表格场景 GBDT 仍常胜。
ROC-AUC 和 PR-AUC 怎么选?
ROC-AUC 在类别不均衡时依然显得“不错”,因为 FPR 分母是巨量负例。当正类极稀少且更关心「抓正例的准确度」时用 PR-AUC / AP,它对正类表现变化更敏感,是欺诈、罕见病、长尾检索的主流选择。报 AUC 时必须同时报正类比例。
概率校准是什么?为什么 LLM 的置信度不可信?
校准要求“模型说 70% 把握时真有 70% 正确”。未校准模型常过度自信。LLM 的 token 概率经高温长尾、RLHF 对齐扭曲、且对幻觉也高概率输出,所以不能直接当置信度。工程上用验证集温度缩放,或用自洽采样 / 候选一致性 / 显式拒识替代裸概率做决策。
NDCG 为什么比 Precision@k 更适合排序评估?
NDCG 同时编码增益(相关度分级 0/1/2/3)和折损(log₂(i+1) 让靠前位置更值钱)。Precision@k 只数 top-k 里相关数、不分相关度高低、不惩罚相关项被排到后面。精排要反映“把最好的放最前”,NDCG 正是为此设计。
怎么判断模型上线的效果衰减是数据漂移还是概念漂移?
数据漂移是 P(X) 变了(特征分布监控可发现,用 PSI / KS);概念漂移是 P(Y|X) 变了(输入正常但标签关系变,只能靠线上指标)。前者可重训特征统计,后者需重新标注与建模。
什么时候你会选择不用大模型?
① 结构化表格且要求低延迟 / 可解释 / 低成本;② 数据小且标注干净,经典模型更稳;③ 需严格可审计的逐特征归因(合规);④ 任务其实是简单分类或路由,小模型成本低一个数量级。判断标准:在满足指标约束下选总拥有成本最低的方案。