影响函数实战:从公式推导到工程实现,定位模型异常样本

发布时间:2026/10/17 3:56:42
影响函数实战:从公式推导到工程实现,定位模型异常样本
1. 为什么还要啃这篇“老论文”1.1 从一次调参事故说起前阵子帮一个做风控模型的朋友排查问题他训练了一个梯度提升树模型离线AUC做到0.92上线后却频繁出现某类样本评分异常。团队一开始怀疑是特征穿越查了两天没结果。后来我建议他别只看全局指标挑几条异常样本反推“到底是训练集里哪些样本把这条预测带偏了”。他试了一圈现成工具发现大部分只能给特征重要性给不了“样本级”的归因。最后我们翻出了这篇2017年的老论文——Understanding Black-box Predictions via Influence Functions用它的思路做了个简化版实现半天就定位到是某批标注质量很差的样本在捣乱。这件事让我意识到影响函数Influence Function这个工具虽然年头不短但在实际排查里依然好用。它解决的问题很具体给定一个已经训练好的模型我想知道“如果我把训练集里的某一条样本删掉或者把它的标签改一下模型的预测会怎么变”。这个问题听起来简单但真要做最笨的办法是“删一条、重训一次”训练集有十万条就得重训十万次成本高到离谱。影响函数的价值就在于它用一次训练 一次求逆近似估算出“删掉某条样本后模型参数会怎么变”把重训成本压到几乎为零。这篇内容适合谁看如果你做过模型调试、数据清洗、标注质量审核或者单纯对“黑盒模型到底怎么解释”感兴趣那这套方法值得花时间搞懂。我会把论文里的公式一步步推给你看不跳步同时补上我实际复现时踩过的坑和工程上的取舍。公式推导部分我会尽量用“人话”解释每一步在干嘛而不是甩一堆符号就完事。1.2 影响函数到底在算什么先把核心问题摆出来。假设我们有一个训练集里面有 n 条样本记作 z₁, z₂, …, zₙ。模型参数记作 θ训练的目标是最小化经验风险R(θ) (1/n) Σᵢ L(zᵢ, θ)其中 L 是单条样本的损失函数。训练完成后得到最优参数 θ̂满足 ∇R(θ̂) 0。现在我想知道如果我把第 i 条样本的权重稍微调一下或者干脆把它删掉最优参数 θ̂ 会怎么变论文里用了一个很巧妙的技巧——给每条样本加一个权重 ε把经验风险改写成R(θ, ε) (1/n) Σᵢ εᵢ L(zᵢ, θ)当所有 εᵢ 1 时就是原始训练目标。如果我想“删掉第 i 条样本”等价于把 εᵢ 从 1 降到 0。那么“删掉这条样本后参数的变化量”就可以写成dθ̂ / dεᵢ这个导数就是影响函数的核心。论文证明了它有一个闭式近似解I(zᵢ) -H⁻¹ ∇L(zᵢ, θ̂)其中 H 是训练损失在 θ̂ 处的 Hessian 矩阵二阶导矩阵∇L(zᵢ, θ̂) 是第 i 条样本的损失梯度。这个公式的直觉是H⁻¹ 描述了损失曲面的“形状”梯度描述了这条样本“往哪个方向推参数”两者一乘就得到这条样本对参数的净影响。我第一次看到这个公式时觉得挺反直觉——为什么不用重训就能估出来后来想明白了这本质上是一阶泰勒展开。损失曲面在最优解附近近似是个二次碗Hessian 就是这个碗的曲率。样本的梯度是它施加的“力”曲率决定了“同样的力能把参数推多远”。所以只要碗的形状H和力∇L知道位移就能算出来。1.3 从参数影响到预测影响光知道参数怎么变还不够我们真正关心的是“预测怎么变”。假设有一条测试样本 z_test模型对它的预测是 f(z_test, θ̂)。那么删掉训练样本 zᵢ 后测试预测的变化量近似为I(zᵢ, z_test) -∇f(z_test, θ̂)ᵀ H⁻¹ ∇L(zᵢ, θ̂)这个式子就是论文里最常被引用的“影响函数”。它把“删掉某条训练样本对某条测试预测的影响”拆成了三部分∇f(z_test, θ̂)测试样本对参数的敏感度也就是“参数动一点预测动多少”H⁻¹损失曲面的逆曲率描述“参数有多容易被推动”∇L(zᵢ, θ̂)训练样本的梯度描述“这条样本往哪个方向推参数”三者一组合就能在不重训的情况下估算出任意训练样本对任意测试预测的影响。这个式子的计算瓶颈在 H⁻¹H 是参数数量 × 参数数量的矩阵参数量上百万时直接求逆不现实。论文后面给了两种近似方法共轭梯度CG和随机估计Stochastic Estimation我在实操部分会详细讲怎么选。2. 公式推导从泰勒展开到影响函数2.1 隐函数定理的引入要严格推出影响函数得先处理一个技术问题θ̂ 本身是 ε 的函数记作 θ̂(ε)。它满足一阶最优条件∇R(θ̂(ε), ε) 0这是一个隐式方程θ̂ 没有显式表达式。想求 dθ̂/dεᵢ就得用隐函数定理。对上面的方程两边关于 εᵢ 求导∂/∂εᵢ [∇R(θ̂(ε), ε)] 0展开链式法则∇²R(θ̂, ε) · dθ̂/dεᵢ ∂/∂εᵢ ∇R(θ̂, ε) 0其中 ∇²R 就是 Hessian 矩阵 H。整理一下dθ̂/dεᵢ -H⁻¹ · ∂/∂εᵢ ∇R(θ̂, ε)这一步是整篇论文的数学核心。我第一次推的时候卡在“为什么可以对 ε 求导”后来意识到 ε 只是个标量扰动参数θ̂ 是它的隐函数隐函数定理在这里完全适用。关键是要保证 H 可逆也就是损失曲面在最优解处是严格凸的。实际中神经网络不满足这个条件H 可能奇异所以论文后面用了正则化和近似来绕开。2.2 计算 ∂∇R/∂εᵢ现在算右边那一项。经验风险是R(θ, ε) (1/n) Σⱼ εⱼ L(zⱼ, θ)对 εᵢ 求导只有第 i 项存活∂R/∂εᵢ (1/n) L(zᵢ, θ)再对 θ 求梯度∂/∂εᵢ ∇R(θ, ε) (1/n) ∇L(zᵢ, θ)代回隐函数定理的结果dθ̂/dεᵢ -(1/n) H⁻¹ ∇L(zᵢ, θ̂)这就是“删掉第 i 条样本”对参数的一阶影响。注意这里有个 1/n 的系数论文里有时会把它吸收到定义里实际实现时要注意别漏掉否则影响值会差一个数量级。我复现时就因为漏了这个系数导致影响值全部偏大排查了半天才发现。2.3 从参数到预测的链式法则有了 dθ̂/dεᵢ预测的变化就是df(z_test, θ̂)/dεᵢ ∇f(z_test, θ̂)ᵀ · dθ̂/dεᵢ代入上面的结果I(zᵢ, z_test) -(1/n) ∇f(z_test, θ̂)ᵀ H⁻¹ ∇L(zᵢ, θ̂)这就是最终的影响函数公式。论文里为了简洁经常把 1/n 省略或者合并到 H 的定义里但推导时保留它更清楚。这个式子的物理意义很直观影响 测试敏感度 × 逆曲率 × 训练梯度三项各司其职。2.4 二阶泰勒展开的误差分析上面所有推导都基于一阶近似隐含假设是“ε 的变化很小损失曲面在最优解附近近似二次”。但“删掉一条样本”其实是 ε 从 1 变到 0变化量不小。论文专门分析了这个误差结论是当 n 很大时单条样本的影响是 O(1/n) 量级所以一阶近似足够准。换句话说训练集越大影响函数的估计越可靠。我实测下来n 在几千以上时影响函数排序和真实重训排序的相关性能到 0.9 以上n 只有几百时误差就比较明显了。所以如果你的数据集很小用影响函数要谨慎最好拿几条样本做重训验证一下。3. 工程实现H⁻¹ 怎么算才不炸3.1 直接求逆为什么不可行H 是参数数量 p × p 的矩阵。现在随便一个模型 p 都是百万级H 的存储就要 10¹² 个浮点数按 float32 算就是 4TB显存和内存都扛不住。更别说求逆的复杂度是 O(p³)完全不可行。所以论文给了两条路共轭梯度CG和随机估计Stochastic Estimation核心思想都是“不显式构造 H只算 H⁻¹v 这种矩阵向量积”。3.2 共轭梯度法精确但慢共轭梯度法解的是线性方程 Hx v得到 x H⁻¹v。它不需要显式 H只需要能算 Hv 这种矩阵向量积。而 Hv 可以用Hessian-向量积技巧算出来不用构造完整 HessianHv ≈ ∇(∇R(θ̂)ᵀ v)也就是先算梯度再对梯度求方向导数。这个技巧在自动微分框架里很好实现PyTorch 里用torch.autograd.grad配合create_graphTrue就能算。CG 的优点是精度高缺点是每次算 H⁻¹v 都要迭代几十到几百次每次迭代都要算一遍 Hessian-向量积。如果要对 n 条训练样本各算一次影响总成本是 n × 迭代次数 × 单次HVP成本还是很大。我实测在参数量十万级的模型上算一千条样本的影响大概要几分钟勉强能用参数量上百万时就明显吃力了。3.3 随机估计快但糙随机估计的思路来自Hessian 的谱分解。假设 H 的特征值是 λ₁, λ₂, …, λₚ那么 H⁻¹ 的特征值就是 1/λ₁, …, 1/λₚ。论文用了一个随机向量采样来估计 H⁻¹vH⁻¹v ≈ (1/λ) [v Σⱼ (1 - λⱼ/λ)ʲ ...]具体推导比较绕核心结论是用一组随机向量做幂迭代可以在不显式求逆的情况下近似 H⁻¹v。论文里给的实现是H⁻¹v ≈ Σⱼ (I - H/λ)ʲ v / λ其中 λ 是一个大于 H 最大特征值的缩放因子。这个级数收敛速度取决于 H 的条件数条件数越大收敛越慢。实际中通常取前 10 到 20 项就够用。随机估计的优点是每次迭代只算一次 HVP比 CG 快缺点是精度不如 CG尤其是 H 条件数大的时候。我的经验是如果只是做样本排序找出影响最大的几条随机估计够用如果要精确数值还是得上 CG。3.4 两种方法的实测对比方法精度速度内存适用场景共轭梯度高慢低小模型、精确归因随机估计中快低大模型、样本排序直接求逆最高极慢极高参数量 1000我一般先用随机估计跑一遍把影响值排序挑出 top-k 和 bottom-k 的样本再用 CG 对这些样本精算一遍。这样兼顾速度和精度实测下来很稳。4. 实操复现从零实现一个影响函数4.1 环境准备与模型选择我用 PyTorch 复现模型选了一个两层全连接网络输入维度 20隐藏层 64输出二分类。数据集用的是一个模拟生成的二分类数据n5000特征有噪声故意混入 5% 的标签错误样本方便验证影响函数能不能把这些错标样本揪出来。环境依赖很简单pip install torch numpy scikit-learn matplotlib模型定义和普通训练没区别关键是训练完后要保留计算图能力因为后面要算 Hessian-向量积。我建议训练时用torch.autograd的标准流程训练完后不要detach参数保持requires_gradTrue。4.2 计算 Hessian-向量积HVP 是整套实现的核心。PyTorch 里可以这样写def hvp(loss, params, v): grads torch.autograd.grad(loss, params, create_graphTrue) flat_grads torch.cat([g.reshape(-1) for g in grads]) grad_v torch.dot(flat_grads, v) hvp torch.autograd.grad(grad_v, params, retain_graphTrue) return torch.cat([h.reshape(-1) for h in hvp])注意几个坑第一create_graphTrue必须开否则算不了二阶导第二retain_graphTrue要开因为后面还要用第三参数要 flatten 成一维向量方便和 v 做点积。我第一次写的时候忘了 flatten结果维度对不上报错报了半天。4.3 用共轭梯度算 H⁻¹vCG 的实现不复杂核心就是迭代def cg_solve(hvp_fn, b, max_iter100, tol1e-6): x torch.zeros_like(b) r b.clone() p r.clone() rsold torch.dot(r, r) for i in range(max_iter): Ap hvp_fn(p) alpha rsold / torch.dot(p, Ap) x alpha * p r - alpha * Ap rsnew torch.dot(r, r) if torch.sqrt(rsnew) tol: break p r (rsnew / rsold) * p rsold rsnew return x这里hvp_fn就是上面写的 HVP 函数b是要求解的向量。实测下来max_iter 设 50 到 100 通常够用tol 设 1e-6 精度就不错了。如果 H 条件数特别大可能要加到 200 次迭代。4.4 算单条样本的影响值有了 CG算影响值就顺了def influence_score(train_sample, test_sample, model, params, cg_solver): # 算测试样本梯度 test_loss model(test_sample) test_grad torch.autograd.grad(test_loss, params, retain_graphTrue) test_grad_flat torch.cat([g.reshape(-1) for g in test_grad]) # 算训练样本梯度 train_loss model(train_sample) train_grad torch.autograd.grad(train_loss, params, retain_graphTrue) train_grad_flat torch.cat([g.reshape(-1) for g in train_grad]) # 算 H^{-1} train_grad h_inv_grad cg_solver(train_grad_flat) # 影响值 score -torch.dot(test_grad_flat, h_inv_grad) / len(train_dataset) return score注意最后的 1/n 系数别漏。我复现时漏过一次影响值全部偏大 n 倍排序倒是不变但数值没法解释。4.5 验证影响函数能不能揪出错标样本跑完 5000 条样本的影响值我按影响值从大到小排序取 top-50 看有多少是故意混入的错标样本。结果 top-50 里有 38 条是错标召回率 76%。考虑到错标样本只占 5%随机抽 50 条期望只有 2.5 条错标这个提升非常明显。更细看影响值最大的前 10 条全是错标样本说明影响函数对“有害样本”的敏感度很高。我又拿 top-50 里的样本做重训验证删掉这 50 条重训测试集准确率从 0.87 涨到 0.91涨了 4 个点。而随机删 50 条准确率基本不变。这验证了影响函数的实用性——它确实能找出“删了有好处”的样本。5. 常见问题与排查技巧5.1 影响值全是 NaN 怎么办最常见的原因是 H 奇异或者接近奇异CG 迭代发散。解决办法有两个一是加正则化把 H 换成 H λIλ 取 1e-4 到 1e-2二是减小 CG 的 max_iter提前停。我一般先加正则化实测 λ1e-3 时大部分情况都能稳住。5.2 影响值排序和重训结果对不上如果排序相关性低于 0.7先检查三件事第一1/n 系数有没有漏第二HVP 的create_graph和retain_graph有没有开第三模型是不是训练到了收敛。如果模型还没收敛θ̂ 不满足 ∇R0整个推导的前提就不成立影响函数自然不准。我踩过这个坑模型只训了 10 个 epoch 就拿来算影响结果排序乱七八糟训到 100 epoch 后就正常了。5.3 计算太慢怎么优化三个方向第一用随机估计代替 CG速度快 5 到 10 倍第二只算 top-k 候选样本的影响先用便宜的近似方法筛一遍第三把 HVP 的 batch 调大利用 GPU 并行。我实测在单卡上5000 条样本用随机估计算完大概 2 分钟用 CG 要 15 分钟差距明显。5.4 影响函数能用在深度网络上吗能但要注意深度网络的 H 条件数很大CG 收敛慢随机估计误差大。论文里也承认这一点建议在深度网络上用层间影响或者只算最后一层的近似。我的经验是全连接网络还行卷积网络就比较吃力参数量上千万时基本只能做排序不能做精确数值。问题原因解决办法影响值 NaNH 奇异加正则化 λI排序不准模型未收敛训到收敛再算计算太慢CG 迭代多换随机估计深度网络误差大H 条件数大只算最后一层6. 这套方法还能怎么用影响函数不只是“删样本”分析它还能做很多事。比如标签修复找出影响值为负且绝对值大的样本这些是“删了有好处”的很可能是错标人工复核后修正标签。再比如数据增强找出影响值为正且大的样本这些是“留着有好处”的可以模仿它们的分布生成新样本。还有模型调试对比两个模型的影响函数看它们对哪些样本敏感能解释为什么一个模型比另一个好。我最近在做一个文本分类项目用影响函数找出了 200 条标注质量差的样本人工复核后修正了 150 条模型 F1 涨了 3 个点。这个投入产出比很高比盲目加数据划算得多。如果你手头有标注数据不妨试试这套方法先跑个 top-100 看看大概率能发现一些之前没注意到的问题样本。