《模型不玄学》第21章 稳定性与偏置审计

第21章 稳定性与偏置审计(数字会骗人,切片会说话)

小白一句话

前几章的评估都是"一个时间点、一个整体数字"。这一章把同一套评估放到多个时间点和多个子人群上重看:指标会不会漂,某些人是不是被系统性地高估或低估。

这一章在干嘛

整体 AUC 0.70、Brier 0.18 看起来还行,但这只是"某一天、全体人"的一个快照。本章做三种审计:

  1. 滚动起点:换个训练截止日、往后多测几个时间点,看指标是稳还是漂。
  2. 配对显著性:两个模型(或两个配置)差的那点数字,统计上是不是真的分得出来。
  3. 偏置切片:把人群切开,看"谁被高估、谁被低估"------整体好看不代表处处公平。

这三件事串起来就是本章的立场:整体数字好 ≠ 处处都好;单点数字 ≠ 稳定。前两章说"别信单点小样本数字",本章说"还要别信整体数字"。

1) 滚动起点:指标会漂吗

示例数据只有 3 个评分日,滚动评估能做两个点:

评估点 训练 测试 测试基准率 AUC Brier
点 1 07-08 07-15(56 行,49 正) 0.875 0.695 0.110
点 2 07-08 + 07-15 07-22(57 行,42 正) 0.737 0.698 0.178

AUC 几乎没动(0.695 → 0.698)------排序质量很稳 。但 Brier 从 0.110 涨到 0.178------概率刻度明显变差。为什么?

点 2 的测试集更难:基准率从 0.875 掉到 0.737,里面开始出现 U0009、U0025 这种"之前活跃、正在掉"的人(第16、17章的熟人)。人还在活跃的时候谁都能猜准,Brier 当然小;等有人开始凉了,校准就跟不上了。

结论:AUC 稳不代表 Brier 稳。不同指标对"任务变难"的敏感度不一样。正式上线前要多看几个滚动点,别拿单点数字当长期表现------这也是第29章"上线后监控再训练"要做的事的预演。

滚动前推(walk-forward):把"多看几个点"做成正式协议

上面只有 07-15、07-22 两个评估点,是快检。要更稳的评估,用滚动前推验证(walk-forward CV):把评分日排成一串,每个评估点用"它之前所有评分日的成熟样本"训练,评"下一个评分日"------训练窗口随评估点逐步扩展,测试永远在训练之后,绝不回头。

示例数据只有 30 天,取 8 个隔天评分日(07-08、07-10、...、07-22),凑出 7 个评估点(附件/06_评估篇/activity_walkforward.py):

评估点 训练评分日 训练行数 测试行数 基准率 PR-AUC AUC Brier
评 07-10 1 49 54 0.870 0.955 0.719 0.174
评 07-12 2 103 55 0.855 0.950 0.816 0.120
评 07-14 3 158 56 0.875 0.987 0.914 0.097
评 07-16 4 214 57 0.860 0.944 0.821 0.102
评 07-18 5 271 57 0.807 0.984 0.927 0.109
评 07-20 6 328 57 0.807 0.939 0.805 0.137
评 07-22 7 385 57 0.737 0.948 0.863 0.133

这 7 个点给出两条单点评估给不了的信息:

  • 训练评分日攒得越多,指标越稳 。评 07-22 是同一个测试集:只用 2 个评分日训(第 21 章滚动起点)AUC 0.698,用 7 个评分日训(walk-forward 末点)0.863。训练窗口每扩展一步,AUC 大体爬一截(0.719 → 0.816 → 0.914 → ...)。这就是第 8 章"多评分日样本"和第 29 章"重训前把历史攒够"的评估侧证据------样本量这道线,评估阶段同样看得见
  • 7 个点自己也在晃 。AUC 0.72~0.93、Brier 0.097~0.174,均值 ± 一个标准差是 0.124 ± 0.024。单看任何一个点都会高估或低估模型:点 1(训 49 行)0.719 偏悲观,点 5(训 271 行)0.927 偏乐观。上线前对指标的预期,应该用这个范围而不是某一次的数------第 29 章监控报警的参照线,就是这么来的。

两点代价要认:一是多个滚动点意味着多训好几遍模型(每点一次全流程),评估成本上去了;二是评分日之间隔太近,相邻点的样本高度重复(同一个人的 07-10 和 07-12 特征几乎一样),信息增量递减------真实项目按周或按月滚动,比这个示例的隔天更合适。

2) 配对显著性:差异是真的吗

拿同一个测试集(07-22 的 57 人),比两个模型:

  • 全特征模型:前几章一直用的主角------10 个特征全用,第12~20章那个 AUC 0.698、Brier 0.178 就是它。
  • 去掉趋势特征的模型 :本章现造的消融变体------同一个 GBDT,只是输入里少掉 f_trend 一列,其余 9 列、超参、切分、校准、种子全部一样,前文没有这个模型。造它就是为了看"少一个特征,这点差别在统计上分不分得出"。

结果:

  • AUC:全特征 0.698 vs 去趋势 0.728(去趋势反而略高?)
  • Brier:0.178 vs 0.177(几乎一样)
  • 两模型判得不一样的人只有 3 个:全特征对而另一错 2 个,去趋势对而全特征错 1 个
  • McNemar 检验 p = 1.000

p=1.000 的意思是:在 57 个样本上,这点差异统计上完全分不出来。而且去趋势的 AUC 还略高,更说明这不是"趋势特征没用"的证据,就是小样本噪声------第12章明明说 f_trend 有 0.084 的重要性、能帮 U0009 加分,但在这 57 人身上根本体现不出来。

McNemar 检验把两个模型放到一张小表里看,逻辑是这样的:

  • 把 57 个样本按两个模型的判断分成四格:都对、都错、全特征对而另一错(b)、另一对而全特征错(c)。前两格两边表现一样,不提供"谁更好"的信息,只有 b 和 c 有用。
  • 如果两个模型其实一样好,b 和 c 应该大致相当(谁对谁错只是运气);如果 b 远大于 c,说明全特征模型真的更会判,反过来同理。
  • 检验就是算"b 和 c 偏成这样、靠运气发生的概率"------这个概率就是 p 值,通常 p<0.05 才算显著差异。

本例 b=2、c=1,几乎对称,p=1.000------运气完全能解释,分不出谁好。再补一句它的局限:McNemar 只看 0/1 判断的对错,看不见排序上的细微差异(比如 AUC 0.698 vs 0.728 这种);所以"没有显著差异"不等于"两个模型一模一样",只是"这点样本上分不出"。

结论:小样本上别拿微小差异当结论。真要比较两个配置,要么攒更多样本、多跑几个滚动点,要么上线做 A/B(第29章会提到)。这一节和"换种子就抖"(第20章)是一对兄弟:一个是数字会跳,一个是跳了你还分不出谁好。

换个招:同窗配对 + bootstrap,给"差多少"配个区间

McNemar 有个够不着的地方:它只数"判对/判错",AUC 0.698 和 0.728 这种排序上的细微差别它看不见。想比较排序指标,更常用的是 bootstrap:把评估样本反复有放回地抽,每次都重算两个模型的差值,抽上几千次,就能看出"这个差距大概在什么范围里晃"。

两个模型放在同一份评估样本上比(这叫同窗配对),比的是模型本身,不掺数据的差别。

具体做一遍。评估样本用第19章那套------把评分日撑密到 07-16 ~ 07-22,57 个人一共 399 行。两个模型还是上面那对(全特征 vs 去趋势),比的是 PR-AUC 的差值 Δ:

  • 全特征 PR-AUC 0.907
  • 去趋势 PR-AUC 0.923
  • Δ = 全特征 − 去趋势 = −0.016(这回是去趋势略好)

光看这一个数没意义,bootstrap 抽 2000 次看 Δ 的分布:

抽法 Δ 均值 95% 置信区间 区间宽度 P(Δ>0)
UID 精确抽样(按用户抽) −0.015 −0.052, +0.018 0.071 0.197
按行抽样(把每行当独立样本) −0.015 −0.041, +0.010 0.051 0.139

怎么读这两行

  • 置信区间跨过了 0 (−0.052 到 +0.018),意思是"这个差距和 0 分不开"------Δ 有时是正的、有时是负的,全看抽到哪拨人。换成人话:两个模型没分出高下
  • P(Δ>0) = 0.197,只有不到两成的抽样里全特征占优。要下"全特征更好"的结论,这个数通常得奔着 0.95 去。所以不支持。
  • 这个结论和上面 McNemar 的 p=1.000 对上了 ------两招路子不同,都指向同一件事:这份数据上,两个模型分不出谁好。 交叉验证过一遍,心里就踏实了。

抽样的单位很关键。 表里第二种抽法(按行抽)给出的区间比按用户抽窄了 28% 。看着更"精确",其实是假的:同一用户的 7 天被当成 7 个独立样本,等于凭空多出来许多"新信息",不确定性被系统性低估了。这和第19章那个"快照级指标虚高"是同一件事------抽样单位要和评估口径一致,评估按人(UID 级)就按人抽,别把一个人的多次快照拆开当多个人用。

再补一句 bootstrap 的边界:它估的是"这份评估样本上的不确定性",估不出"换一批人、换一段时间还一不一样"。后者得靠滚动起点(前面第 1 节)和换种子(第20章)来看。三种招数凑一起,才敢说一个结论稳不稳。

3) 偏置切片:谁被高估了

整体 Brier 0.178 好看,不代表每个子人群都被公平对待。按活跃度、新近度把测试集切开,看"模型平均预测 vs 实际回访率":

切片 人数 实际回访率 平均预测 偏差(正=高估)
高活跃(总领取 > 3.5) 43 0.860 0.917 +0.056
低活跃(总领取 ≤ 3.5) 14 0.357 0.588 +0.231
久没来(距上次 ≥ 4 天) 15 0.467 0.629 +0.163
近期还来(距上次 ≤ 2 天) 37 0.838 0.914 +0.076

审计结论很扎眼:模型系统性高估"低活跃、久没来"的人 ------低活跃那 14 人实际只有 36% 回来,模型平均给到 59%,高估了两成多。这不是随机抖动:它和第16章 U0009"满格误判"是同一件事在人群层面上的表现------对"看起来要凉的人",模型过分乐观

业务上的含义:如果运营拿"模型说 0.6"当成"六成会回",去决定一个久没来的人"不用管",会系统性错判。审计结果告诉我们要么对这类人单独设更低的阈值、更保守的分档,要么继续补特征(第12章说过,能抓 U0009 的是趋势这类更灵敏的特征,而不是校准)。

两个读表的坑,免得误读:

  • 片内 AUC 别当回事。高活跃片内几乎全是正样本(43 人 86% 都回),没得排序,AUC 只有 0.42------不是"模型在这片里差",是"片内太同质,AUC 失去意义"。切片审计主要看"预测 vs 实际"的偏差,不是片内 AUC。
  • 切片越小越抖 。14~15 人的片,偏差 0.16~0.23 这个方向可信,具体小数别抠。

它和前后章节的关系

  • 第19章讲指标、第20章讲两种评估视角,本章把"时间"(滚动起点)和"人群"(偏置切片)两个维度再切一刀。
  • 第29章上线后的监控再训练,就是滚动起点评估的自动化版本;偏置审计发现的问题(低活跃被高估),决定了上线前要不要调阈值、补特征。

动手

  1. 附件/06_评估篇/activity_audit.py,对照上面三块输出:滚动表、McNemar、切片表。
  2. 把第 2 部分的"去趋势"改成"去新近度特征"(把脚本里 FEATS_NO_TREND 换成去掉 f_recency 的那份)再跑,看这次能不能分出差异------新近度可能是比趋势更"大"的特征,样本小也可能更明显。
  3. 附件/06_评估篇/activity_bootstrap.py,看那张 bootstrap 表:Δ 的 95% 置信区间是不是跨过 0、P(Δ>0) 离 0.95 还差多远;再把按行抽样和 UID 精确抽样两个区间宽度对比一下,看前者窄了多少。
  4. activity_bootstrap.py 里的 boot_by_uid 换成按行抽(或者反过来把行数改成只取 07-22 一天),看置信区间怎么变------评估样本越小、每人票数越少,区间就越宽,这是"样本量决定你能下多细的结论"。
  5. 低活跃人群被高估 0.23,运营怎么接?别用全局阈值;对低活跃/久没来的人单独设低阈值或用更保守的分档,或继续补特征。
  6. 如果滚动评估发现 Brier 一路恶化、AUC 也在掉,先查三样:人群结构是不是变了、特征口径是不是漂了、标签定义是不是改了------这三样变了,模型没变也是废的。
  7. 附件/06_评估篇/activity_walkforward.py,对照上面那张 7 点表。把评分日间隔从 2 天改成 1 天(AS_OF_LIST 那行的 2 * k 改成 k),重跑看评估点数变多少、相邻点数字是不是更接近------评分日越密,重复信息越多。
  8. 从 walk-forward 的 7 个点里只看最后一个(评 07-22)当"模型表现",再和均值 ± 一个标准差比------想想上线前报哪个数、监控阈值拿什么当参照线。

本章配套脚本 附件/06_评估篇/activity_audit.py(滚动起点评估 + 全特征 vs 去趋势的 McNemar 配对检验 + 按活跃度/新近度的偏置切片)、附件/06_评估篇/activity_bootstrap.py(同窗配对 + UID 精确 bootstrap:ΔPR-AUC 的 95% 置信区间、P(Δ>0)、按行抽样与按用户抽样的对比、换种子复现)、附件/06_评估篇/activity_walkforward.py(8 个隔天评分日、7 个扩展窗口评估点的 walk-forward 全协议 + 指标轨迹汇总)。用到的 numpy、scikit-learn、scipy 已在第4章 附件/00_公共/requirements.txt 列出,环境搭建见第4章。训练表由 附件/05_活跃度预测篇/activity_train_table.py 生成,口径一致。

相关推荐
用户5274675614211 小时前
Agent 提交 PR 后别急着下班:把“可合并”做成一等状态
人工智能
小阿鑫1 小时前
AI 越来越强,为什么打工人反而越来越累、越来越内耗了?
人工智能·ai·程序员·职业发展·agi·工作效率
宋哥转AI1 小时前
深入理解 AI Agent · Agent 安全威胁全景与四层纵深防御
人工智能·agent·ai编程
甲维斯1 小时前
“O哥”手把手教学,复刻GPT6酷炫动效!
人工智能
外域速览1 小时前
GPT-6 Astra 开放、标普罕见拉响 AI 基建信用警报、三星 Arm 联手 2nm 端侧芯片:算力下半场的三条主线
arm开发·人工智能·gpt
yunwei371 小时前
ACRFence:防止 AI Agent 检查点恢复中的语义回滚攻击
人工智能·安全·架构
思考着亮1 小时前
9.Wiki 和 Rag 如何如何选择?
人工智能
luobing43651 小时前
新书推荐-《AI辅助芯片制造:算法与工程化落地》
人工智能·制造
deepseek231 小时前
Anthropic开源Commerce Agents:购物与商户智能体如何把审批写进工具链
人工智能·ai agent·mcp