📑 本页目录(点开跳转)
16 · 重训练
⏱ 46 分钟 | ⭐ 什么时候训、用什么数据训
🎯 一句话
「定期重训」听起来天经地义,但它有三个必须先回答的问题: 什么时候训、用多长的数据训、以及 —— 训出来的怎么知道更好。
🚨 一、先问一句:这次真的该重训吗
⭐ 按第 6 章的四分法先定性:
① 管道坏了 → 【绝不重训】,先修管道 💀
② 数据漂移 → 重训有效 ✅
③ 概念漂移 → 重训有效,但要调训练窗口 ✅⭐
④ 标签漂移 → 可能只需重新校准,比重训便宜 ⭐
💀 再强调一次 ①:管道坏了时重训,会把错误数据学进模型, 把「临时故障」变成「永久损伤」。 重训流水线里应该有一道自动闸门:数据质量检查不通过就不许训。
⏰ 二、什么时候触发重训
| 触发方式 | 说明 | 适合 |
|---|---|---|
| 定时 | 每天/每周/每月固定训 | 简单,大多数场景够用 ⭐ |
| 性能触发 | 指标跌破阈值才训 | 需要有及时标签 |
| 漂移触发 | PSI 超阈值才训 | 不需要标签 ⭐ |
| 数据量触发 | 新增 N 条样本就训 | 冷启动期、数据稀疏时 |
⭐ 实践建议:定时为主 + 漂移触发为辅。 定时保证基线(不会忘),漂移触发处理突发情况。 纯性能触发的问题是:等指标掉下来时,损失已经发生了。
频率怎么定
⭐ 一个能算的判据:看【模型效果的衰减速度】
① 拿一个历史模型,冻结它
② 用它去预测之后 1、2、4、8 周的数据
③ 画出效果衰减曲线
④ 找到「掉到不可接受」的那个点 → 重训周期设成它的一半
例:8 周后 AUC 掉 0.02(可接受上限是 0.015)
→ 大约 6 周越线 → 重训周期设 3 周
from sklearn.metrics import roc_auc_score
import pandas as pd
def decay_curve(df, train_end_week, model_fn, feats, horizons=(1, 2, 4, 8, 12)):
"""冻结一个在 train_end_week 之前训好的模型,看它之后 k 周的效果衰减
df 需要有 week 列;model_fn(训练集) 返回一个已 fit 的模型"""
m = model_fn(df[df.week <= train_end_week]) # ⭐ 训完就冻住,之后不再更新
rows = []
for k in horizons:
seg = df[(df.week > train_end_week) & (df.week <= train_end_week + k)]
auc = roc_auc_score(seg.y, m.predict_proba(seg[feats])[:, 1])
rows.append({"上线后第几周": k, "AUC": round(auc, 4)})
return pd.DataFrame(rows)
# 典型输出:
# 上线后第几周 AUC
# 1 0.8410
# 2 0.8395
# 4 0.8352
# 8 0.8218 ← ⭐ 相对起点掉了 0.019,接近可接受上限
# 12 0.8079
💡 这个实验只需要历史数据,不用上线,半天就能做完 —— 但它能把「凭感觉每周训一次」变成有依据的决策。
⚠️ 一定要用至少三个不同的
train_end_week各跑一次: 单个起点的衰减速度可能是那段时间碰巧发生了什么。 三条曲线形状一致,结论才可信。
📅 三、用多长的数据训(训练窗口)
⭐ 这是重训里最被忽略、也最有价值的一个决策
窗口太短 → 样本少,方差大,学不到长期规律
窗口太长 → 混进了【已经过时】的规律(概念漂移)💀
三种策略:
| 策略 | 做法 | 适合 |
|---|---|---|
| 固定窗口 | 只用最近 N 天 | 概念漂移快(如时效性内容)⭐ |
| 全量累积 | 用所有历史数据 | 规律稳定(如 OCR、图像分类) |
| 加权衰减 ⭐ | 全都用,但老样本权重低 | 大多数业务场景,兼顾两者 |
def time_decay_weight(days_ago, half_life=30):
"""指数衰减:半衰期 30 天 → 30 天前的样本权重是今天的一半"""
return 0.5 ** (days_ago / half_life)
sample_weight = time_decay_weight(df["days_ago"].values, half_life=30)
model.fit(X, y, sample_weight=sample_weight) # ⭐ 大多数库都支持
⭐ 半衰期怎么定:用上面那个衰减实验的结果 —— 如果效果 6 周就明显退化,半衰期设 3~4 周比较合理。 这两个决策是同一个数据支撑的。
⚠️ 一个反直觉的发现
很多人默认「数据越多越好」,但在概念漂移的场景下:
用最近 3 个月 的数据训 → AUC 0.81
用全部 2 年 的数据训 → AUC 0.78 ⭐ 更差
→ 老数据不是没用,是【有害】
💡 一定要把「训练窗口」当成一个超参数去调,而不是默认用全量。
import pandas as pd
def tune_window(df, valid_start, model_fn, feats,
windows=(30, 60, 90, 180, 365, 10**9)):
"""把训练窗口当超参数调。⭐ 验证集必须在【时间上靠后】,不能随机切"""
va = df[df.day >= valid_start]
rows = []
for w in windows:
tr = df[(df.day < valid_start) & (df.day >= valid_start - w)]
m = model_fn(tr)
rows.append({"窗口(天)": w, "训练样本": len(tr),
"AUC": round(roc_auc_score(va.y, m.predict_proba(va[feats])[:, 1]), 4)})
return pd.DataFrame(rows)
# 典型输出(⭐ 注意 AUC 是【先涨后跌】的,最优窗口在中间):
# 窗口(天) 训练样本 AUC
# 30 62,000 0.7902 ← 样本太少,方差大
# 60 124,000 0.8071
# 90 186,000 0.8118 ← ⭐ 最优
# 180 371,000 0.8043
# 365 742,000 0.7961
# 全量 1,510,000 0.7834 ← 💀 默认做法,反而最差
⭐ 务必换 3–5 个
valid_start滚动跑一遍(滚动验证): 单个时间点选出来的"最优窗口"很可能是碰巧。 看的是"哪个窗口在多数时间点上都靠前",不是"哪个窗口拿了一次第一"。
✅ 四、训出来的怎么验证
重训不是「跑通就上」,它和首次上线一样要走完整流程:
[ ] 用【时间上更靠后】的数据做验证集(不能随机切)⭐
[ ] 和线上现役模型在【同一批数据】上比 ⭐⭐
[ ] 特征重要性排序有没有剧烈变化(剧变=数据可能有问题)⭐
[ ] 预测分布和现役模型差异有多大
[ ] 走影子 → 灰度(第 3 章),不要直接全量
⭐ 第 3 条是个很好用的早期警报: 如果重训后特征重要性排名大变,多半不是"模型进步了", 而是某个特征的数据出问题了。 值得在上线前查一下。
🚧 一定要有的兜底:模型回退基线
每次重训后,把【新模型】和【上一版】都保留
→ 新模型表现不如预期时,能立刻切回
⭐ 而且要保留【至少 3 个版本】——
因为问题可能是几周前那次重训引入的
🔁 五、自动重训流水线:五道闸门
数据 ──[闸1]──→ 训练 ──[闸2]──→ 评估 ──[闸3]──→ 影子 ──[闸4]──→ 灰度 ──[闸5]──→ 全量
闸1 数据质量:行数、缺失率、分布是否正常 ⭐ 挡住"管道坏了还硬训"
闸2 训练健康:loss 收敛、无 NaN、耗时正常
闸3 效果对比:在同一验证集上不差于现役模型 ⭐
闸4 一致性 :训练/推理特征比对通过(第 4 章)⭐⭐
闸5 灰度指标:小流量上业务指标不恶化
⭐ 全自动重训必须有这五道闸,否则它会自动地、定期地把问题推上线 —— 自动化会放大你的错误,而不只是放大你的效率。
闸 1 长什么样(大多数团队做得太浅):
def gate_data_quality(new, ref, feats, psi_max=0.25, row_drop_max=0.2):
"""闸1:⭐ 不能只看行数和缺失率 —— 那两项是最低标准,挡不住"单位变了"这类故障"""
issues = []
if len(new) < len(ref) * (1 - row_drop_max):
issues.append(f"行数骤降:{len(new)} vs 基准 {len(ref)}")
for f in feats:
miss = new[f].isna().mean()
if miss > ref[f].isna().mean() + 0.05:
issues.append(f"{f} 缺失率异常:{miss:.1%}")
p = psi(new[f], ref[f]) # 第 6 章的 PSI
if p > psi_max:
issues.append(f"{f} 分布突变:PSI={p:.2f}") # ⭐⭐ 真正挡住事故的是这一行
return (len(issues) == 0), issues
💀 一次自动重训,把 26 小时的脏数据变成三周的损失
系统:风控模型,每周一凌晨自动重训,训练窗口 90 天
周六:上游埋点发版,把 device_age_days 的单位从【天】改成【小时】
26 小时后被发现并回滚 —— 线上模型当时靠降级扛过去了 ✅
周一:自动重训照常跑
五道闸门为什么全没拦住:
| 闸 | 检查了什么 | 为什么没拦住 |
|---|---|---|
| 闸1 数据质量 | 行数 ✅、缺失率 ✅ | 没查分布 —— 那 26 小时的数据"完整而正常" 💀 |
| 闸2 训练健康 | loss 收敛 ✅ | 模型确实好好地学完了 —— 它学的是错的东西 |
| 闸3 效果对比 | 整体 AUC 0.842 → 0.836 | 只掉 0.006,通过了 −0.01 的阈值 ⭐ |
| 闸4 一致性 | 训练/推理特征比对 ✅ | 两边都用了同一批脏数据,当然一致 |
| 闸5 灰度指标 | 整体拦截率 ✅ | 整体没动 |
真正发生的事:
模型学到了「device_age_days 取到 1000+ 也是正常的」
→ 它降低了这个特征的整体权重
分人群看才看得见:
· 新设备用户的欺诈召回率:61% → 38% 💀
· 而新设备欺诈是低频场景,整体指标上几乎看不出
→ 三周后由业务方发现("最近新号薅羊毛的变多了")
⭐⭐ 这个案例最该记住的一句话: 脏数据占比小 ≠ 影响小。 那 26 小时在 90 天窗口里只占 1.2% —— 但它是系统性错误,不是随机噪声。 随机噪声会被平均掉,系统性错误会被认真地学进去。
三条改进(都很便宜):
① 闸1 加 PSI:拿本次训练数据和上次训练数据逐特征对比 ⭐⭐
② 闸3 加【分人群】对比:整体不差还不够,每个关键人群都不能明显差 ⭐
③ 维护一张【数据事故时间表】,重训时自动剔除或降权这些时间段的样本
💡 ③ 是很多团队没想到的:
线上事故修好了,但那段时间的数据【永远留在了训练集里】
🧊 六、几个特殊情况
| 情况 | 注意 |
|---|---|
| 冷启动期 | 数据少时频繁重训会剧烈震荡,先稳一段 |
| 有强季节性 | 别用去年同期以外的数据当窗口;或显式加季节特征 |
| 模型被反馈闭环污染 | 重训会强化偏差 → 需要随机流量数据纠偏(第 7 章)⭐ |
| 增量训练 vs 全量重训 | 增量快但会累积漂移,建议定期全量重训一次做基准 |
⭐ 第三行值得展开:在推荐/风控这类有反馈闭环的系统里, 无脑重训会让模型越来越窄 —— 因为训练数据是它自己筛出来的。 必须掺入那部分随机流量/随机放行的数据。 🔗 推荐算法 02、第 7 章、第 14 章的拒绝推断
增量训练 vs 全量重训,摊开说:
| 增量训练 | 全量重训 | |
|---|---|---|
| 单次耗时 | 分钟级 ⭐ | 小时级 |
| 对新分布的响应 | 快 | 慢(老数据稀释) |
| 误差累积 | 会,且很难察觉 💀 | 不会 |
| 可复现性 | 差(依赖历史每一步)⭐ | 好 |
| 出事时的回滚 | 难 —— 要回到哪一步? | 简单 |
⭐ 务实的组合:增量为主 + 定期(如每月)全量重训一次做基准。 每次全量重训后,把增量模型和全量模型在同一验证集上比一下 —— 两者差距如果在扩大,说明增量已经跑偏了。 这是一个几乎零成本的漂移探针。
⚠️ 七、一张坑表
| 坑 | 后果 | 怎么修 |
|---|---|---|
| 管道坏了还照常重训 💀 | 临时故障变成永久损伤 | 闸1 拦住;PSI 是关键,不是行数和缺失率 ⭐⭐ |
| 闸门只看整体指标 ⭐ | 就是那个 61%→38% 的案例 | 闸3 加分人群对比 |
| 事故时段的数据留在训练集里 | 修好了线上,却把错误学进了下个模型 | 维护数据事故时间表,重训自动剔除 |
| 默认用全量数据训 | 老数据有害,AUC 反而更低(0.7834 vs 0.8118) | 把窗口当超参数调,滚动验证 |
| 验证集随机切 | 泄漏未来信息,离线好线上崩 | 时间切分(ML基础 05) |
| 只和自己比,不和现役模型比 | "比上一版好"不等于"比线上那个好" | 闸3 必须在同一批数据上对比现役模型 |
| 纯性能触发重训 | 等指标掉下来,损失已经发生 | 定时为主 + 漂移触发为辅 |
| 重训完直接全量 | 没有影子和灰度,出事就是全量出事 | 走影子 → 灰度(第 3 章) |
| 只保留最新一个模型版本 | 问题可能是几周前那次重训引入的 | 至少保留 3 个版本 |
| 有反馈闭环还无脑重训 | 模型越来越窄,探索能力消失 | 掺入随机流量数据纠偏 ⭐ |
🔗 八、和站内其他章的关系
| 相关的地方 | 和这一章的关系 |
|---|---|
| 第 6 章四种漂移 | 决定该不该重训 |
| 第 3 章灰度 | 重训后的上线流程 |
| 第 4 章一致性 | 闸门 4 |
| 第 7 章随机流量 | 纠正反馈闭环 |
| ML基础 05时间切分 | 验证集不能随机切 |
✅ 检查点
- 四种漂移分别对应什么处理动作?哪种绝对不能重训?
- 重训触发方式有哪几种?实践建议是什么?纯性能触发的问题是什么?
- 怎么用数据算出该多久重训一次?这个实验有什么必须注意的地方?
- 训练窗口的三种策略?加权衰减的半衰期怎么定?调窗口时验证集有什么要求?
- 那个反直觉的发现是什么?说明了什么?
- 重训后验证清单里,哪一条是很好用的早期警报?为什么?
- 自动重训流水线的五道闸分别挡什么?闸1 只查行数和缺失率够吗?
- 那个「26 小时脏数据」的案例里,五道闸为什么全都没拦住?后果是什么?「脏数据占比小 ≠ 影响小」的道理是什么?该怎么改?
- 增量训练和全量重训各自的代价是什么?务实的组合是什么?有反馈闭环时无脑重训会怎样?
👀 答案
- ①管道坏了→绝不重训,先修管道 ②数据漂移→重训 ③概念漂移→重训+调训练窗口 ④标签漂移→可能只需重新校准。①绝对不能重训,会把错误数据学进去,临时故障变永久损伤。
- 定时、性能触发、漂移触发、数据量触发。建议定时为主+漂移触发为辅。纯性能触发的问题是:等指标掉下来时损失已经发生了。
- 冻结一个历史模型,用它预测之后 1/2/4/8/12 周的数据,画效果衰减曲线,找到"掉到不可接受"的点,重训周期设成它的一半。只需历史数据,半天能做完。注意:要换至少三个不同的起点各跑一次,单个起点的衰减速度可能是那段时间碰巧发生了什么,三条曲线形状一致结论才可信。
- 固定窗口(概念漂移快)、全量累积(规律稳定)、加权衰减(大多数场景)。半衰期用同一个衰减实验的结果定——效果 6 周退化则设 3~4 周。调窗口时验证集必须在时间上靠后,不能随机切;而且要换 3–5 个验证起点滚动跑,看的是"哪个窗口在多数时间点都靠前",不是"哪个拿了一次第一"。
- 用最近 3 个月训(AUC 0.81)比用全部 2 年训(0.78)更好 ——老数据不是没用,是有害。典型的窗口扫描结果是 AUC 先涨后跌、最优在中间(如 90 天 0.8118,而全量只有 0.7834)。说明训练窗口要当超参数调,不能默认全量。
- 特征重要性排序有没有剧烈变化。因为剧变多半不是"模型进步了",而是某个特征的数据出问题了。
- 闸1数据质量(挡住"管道坏了还硬训")、闸2训练健康、闸3效果对比、闸4训练/推理一致性、闸5灰度指标。闸1 只查行数和缺失率远远不够——那是最低标准,挡不住"单位变了"这类故障;真正管用的是逐特征 PSI,拿本次训练数据和上次训练数据对比。
- 埋点发版把
device_age_days的单位从天改成小时,26 小时后回滚。闸1 只查了行数和缺失率(那 26 小时的数据"完整而正常");闸2 loss 确实收敛了(模型好好地学完了,只是学的是错的东西);闸3 整体 AUC 0.842→0.836,只掉 0.006,通过了 −0.01 的阈值;闸4 两边用的是同一批脏数据,当然一致;闸5 整体拦截率没动。后果:新设备用户的欺诈召回率 61% → 38%,因为新设备欺诈是低频场景,整体指标上看不出来,三周后才由业务方发现。道理:那 26 小时在 90 天窗口里只占 1.2%,但它是系统性错误不是随机噪声——随机噪声会被平均掉,系统性错误会被认真地学进去。改法:①闸1 加 PSI ②闸3 加分人群对比 ③维护数据事故时间表,重训时自动剔除或降权那些时段的样本。 - 增量:快(分钟级)、响应新分布快,但会累积误差且很难察觉、可复现性差、出事难回滚。全量:慢,但无累积误差、可复现、好回滚。务实组合是增量为主 + 每月全量重训一次做基准,并且把增量模型和全量模型在同一验证集上比——差距在扩大就说明增量跑偏了,这是零成本的漂移探针。有反馈闭环时无脑重训会让模型越来越窄(训练数据是它自己筛出来的),要掺入随机流量/随机放行的数据纠偏。
🛑 可以停在这里
⚡ 走神救援
重训前先按第6章四分法定性:⭐管道坏了绝不重训(会把错误学进去,临时故障变永久损伤——重训流水线要有自动闸门)、数据漂移可重训、概念漂移重训+调窗口、标签漂移可能只需重新校准。触发:⭐定时为主+漂移触发为辅(纯性能触发的问题是等指标掉下来损失已经发生)。⭐频率能算出来:冻结一个历史模型看它在之后1/2/4/8周的效果衰减曲线,找到越线点,周期设成它的一半(半天能做完)。⭐⭐训练窗口是最被忽略也最有价值的决策:固定窗口/全量累积/加权衰减(半衰期用同一个衰减实验定);⭐反直觉发现:用最近3个月训(0.81) 比用全部2年训(0.78) 更好——老数据不是没用是有害,要把窗口当超参调。验证清单里⭐特征重要性排序剧变是个好用的早期警报(多半不是模型进步,是某特征数据出问题)。自动流水线五道闸:数据质量/训练健康/效果对比/训练推理一致性/灰度指标 —— ⭐自动化会放大你的错误而不只是效率。⚠️有反馈闭环时无脑重训会让模型越来越窄(训练数据是它自己筛的)→ 必须掺随机流量数据纠偏。💀真实案例(值得记一辈子):埋点发版把
device_age_days的单位从天改成小时,26 小时后回滚;周一自动重训照常跑,五道闸全部放行——闸1 只查行数和缺失率(那批数据"完整而正常")、闸2 loss 确实收敛(模型好好地学完了,只是学的是错的东西)、闸3 整体 AUC 0.842→0.836 只掉 0.006,通过了 −0.01 阈值、闸4 两边同一批脏数据当然一致、闸5 整体拦截率没动。真实后果:新设备用户的欺诈召回率 61%→38%,三周后才由业务方发现。⭐⭐那句该记住的话:脏数据占比小 ≠ 影响小——26 小时在 90 天窗口里只占 1.2%,但它是系统性错误不是随机噪声,随机噪声会被平均掉,系统性错误会被认真地学进去。三条便宜的改进:闸1 加逐特征 PSI(拿本次和上次训练数据比)、闸3 加分人群对比、维护一张数据事故时间表,重训自动剔除那些时段。衰减曲线和窗口扫描都要换 3–5 个起点滚动跑,单点结论可能是碰巧;窗口扫描的典型形状是 AUC 先涨后跌,90 天 0.8118 而全量只有 0.7834。增量 vs 全量:增量快但会累积误差且难察觉、可复现性差、难回滚 → ⭐增量为主 + 每月全量重训做基准,两者在同一验证集上比,差距扩大就说明增量跑偏了(零成本的漂移探针)。
下一节 👉 17-版本回溯与可复现.md