🏠 总目录📚 本教程 06 · 数据漂移与概念漂移 ← →
📑 本页目录(点开跳转)

06 · 数据漂移与概念漂移

⏱ 38 分钟 | ⭐ 分清它们,才知道该修什么


🎯 一句话

数据漂移:进来的东西变了。 概念漂移:进来的东西没变,但它和答案的关系变了。 前者重训能修,后者重训未必能修 —— 而且它更难发现。

指标掉了输入分布变了吗?突变还是渐变?变了管道坏了先修,别重训数据漂移重训 / 加权突变渐变有标签的样本上准了吗?没变概念漂移重训+调窗口标签漂移重校准即可 ⭐掉了还准,只是概率偏了两个提问,把四种情况分干净 —— 而它们的处理方式完全不同💀 最贵的错误:管道坏了的时候去重训 —— 那会把错误数据永久固化进模型⭐ 最省力的情况:标签漂移只需重新校准概率,比重训便宜得多
两个提问就能把四种情况分干净,而它们的处理方式完全不同。💀 最贵的错误:管道坏了的时候去重训 —— 那会把错误数据永久固化进模型。⭐ 最省力的情况:标签漂移只需重新校准概率。

📐 一、用概率写清楚区别

模型学的是 $P(y \mid x)$,而你看到的数据由 $P(x, y) = P(y|x)\,P(x)$ 决定。

类型 变的是 直觉
数据漂移(covariate shift) $P(x)$ 变,$P(y\|x)$ 不变 来的人变了,但「什么样的人会买」这个规律没变
概念漂移(concept drift) $P(y\|x)$ 变了 ⭐ 同样的人,现在的行为和以前不一样了
标签漂移(prior shift) $P(y)$ 变 正样本整体比例变了(如欺诈率上升)

💡 一组对照例子(同一个信贷模型):

结果对照

数据漂移:
营销渠道换了→来的用户从 35 岁白领变成 22 岁学生
「收入高的人违约率低」这条规律【依然成立】
重新训练(用新人群的数据)就能修好 ✅
概念漂移:
经济环境变了→同样收入、同样年龄的人,违约率整体上升
「收入→违约率」这条【关系本身变了】
必须用新数据重训,而且【旧数据可能有害】⭐
标签漂移:
风控策略放松→通过率提高→坏账率从 2% 涨到 5%
模型排序可能还对,但【输出的概率不再校准】
修法可能只需要重新校准,不用重训 ⭐

⭐ 最后一行是个实用的省力点: 标签漂移常常只需要调整阈值或做一次概率重校准, 比重训模型便宜得多。先判断类型,再决定动作。


🔍 二、怎么检测数据漂移

方法 1:逐特征统计检验

特征类型 常用方法 说明
数值 KS 检验 比较两个分布,无需假设正态
数值 PSI(群体稳定性指数)⭐ 工业界最常用,有公认阈值
类别 卡方检验 / PSI

PSI 的公式和阈值(记住这个就够用):

$$\text{PSI} = \sum_i (p_i^{\text{线上}} - p_i^{\text{训练}}) \cdot \ln\frac{p_i^{\text{线上}}}{p_i^{\text{训练}}}$$

计算前两步

  • 把特征分成 10 个桶(按训练集的分位数切)
  • 算每个桶的占比变化
PSI 的阅读区间(作为排查线索)
PSI 范围原文判断
< 0.1稳定 ✅
0.1 ~ 0.25有变化 ⚠️ 关注
> 0.25显著漂移 🔴 该查了
import numpy as np

def psi(base, live, bins=10):
    """base/live 是同一个特征的两批取值。按 base 的分位数切桶。"""
    edges = np.quantile(base, np.linspace(0, 1, bins + 1))
    edges[0], edges[-1] = -np.inf, np.inf        # ⭐ 防止线上出现超界值被丢掉
    b = np.histogram(base, edges)[0] / len(base)
    l = np.histogram(live, edges)[0] / len(live)
    eps = 1e-6                                    # ⭐ 防 log(0)
    b, l = np.clip(b, eps, None), np.clip(l, eps, None)
    return float(np.sum((l - b) * np.log(l / b)))

⚠️ PSI 的两个坑:

坑 说明
分桶必须按训练集切,且边界要存下来 每次按各自分位数切,永远算不出漂移 💀
样本量小时不稳 少于 1000 条时 PSI 波动很大,别急着告警

方法 2:对抗验证(一次看全部特征)

流程图

① 训练集样本标成 0,线上样本标成 1
② 训一个分类器区分它们
③ 如果 AUC ≈ 0.5→两批数据分不开→没有漂移 ✅
如果 AUC 明显 > 0.5→有漂移 🔴
⭐ 更值钱的是第 ④ 步:
④ 看这个分类器的【特征重要性】—— 排在最前的就是漂移的元凶

🔗 这个方法你在 Kaggle 23 见过 (那里用它检测训练集和测试集的分布差异)。 同一个工具,换个场景:那里比的是训练集 vs 测试集,这里比的是训练集 vs 线上流量。

⭐ 它比逐特征检验强的地方:能发现「联合分布」的漂移 —— 每个特征单看都正常,但它们的组合变了(比如「高收入 + 年轻」这个组合突然变多)。


🕵️ 三、概念漂移:难在哪、怎么发现

对照

数据漂移:看输入就能发现 ← 不需要标签 ✅

概念漂移:必须知道【真实答案】才能发现 ← 需要标签 💀

这就是它难的根本原因:标签常常要等很久(第 7 章专门讲这个)。

三种形态

关键信息

⭐ ② 最危险的原因:任何单日的告警阈值都不会触发。 对策:除了日环比,一定要看「和 30 天前比」「和 90 天前比」。

一个便宜的检测手段:滑动窗口 AUC

把有标签的样本按时间排,算每周的 AUC

按时间排列后与基线比较
周AUC 与观察
周10.82
周20.81
周30.81
周40.79 ← 单看是正常波动
周50.78
周60.76 ← 和周1比已经掉了 0.06 🔴

看哪里

  • ⭐ 关键是【和基线比】,不是和上周比

🛠️ 四、发现了漂移,做什么

别急着重训。按这个顺序判断:

操作步骤

① 是数据管道坏了吗?
突变 + 只有个别特征异常 + 缺失率变化
【先修管道,绝不重训】⭐(重训会把错误固化,见第 1 章)
② 是数据漂移吗(P(x) 变)?
输入分布变了,但有标签的样本上模型还准
用新数据重训 / 加样本权重
③ 是概念漂移吗(P(y|x) 变)?
输入分布没怎么变,但预测准确率掉了
必须用新数据重训,且【要考虑丢掉太旧的数据】⭐
④ 是标签漂移吗(P(y) 变)?
排序还对,但概率不准了
可能只需重新校准,不用重训 ⭐ 最便宜
情况 该做的 不该做的
管道故障 修管道 ❌ 重训
数据漂移 重训 / 加权 / 扩样本 ❌ 只调阈值
概念漂移 重训 + 调整训练窗口 ❌ 无脑用全量历史
标签漂移 重校准 / 调阈值 ❌ 大动干戈重训

🔗 重训练的具体策略(窗口多长、多久一次、怎么验证)→ 第 16 章


💀 五、一个"PSI 全绿但模型已经烂了"的真事故

这是概念漂移最典型的样子,也是漂移监控最尴尬的时刻。

结果对照

某外卖平台的配送时长(ETA)预估模型
特征:距离、时段、天气、商家出餐历史、骑手密度……
某个月开始,用户投诉"预计 30 分钟实际 45 分钟"暴涨
查漂移监控:
· 全部 47 个特征的 PSI 都 < 0.06 ✅ 全绿
· 对抗验证 AUC = 0.52 ✅ 分不开
· 预测值分布:均值 31.2→31.5 分钟 ✅ 几乎没动
监控体系一个字都没说

真正发生的事:

关键信息

平台调整了骑手的接单规则(一次最多并单从 3 单改成 5 单)
【输入特征一个都没变】:距离一样、时段一样、骑手密度一样
但【同样的输入,实际配送时长普遍多了 4 分钟】
⭐ 这就是教科书定义里的 P(y|x) 变了 —— 而且是外部规则造成的
检测手段 结果 为什么
PSI / KS ❌ 全绿 它们只看 $P(x)$
对抗验证 ❌ AUC 0.52 同上,只看输入
预测分布 ❌ 没动 模型的输入没变,输出自然没变
滑动窗口 MAE ✅ 6.1 → 9.8 分钟 ⭐ 唯一看了真实答案的
用户投诉率 ✅ 涨了 3 倍 最晚,也最贵

⭐ 这个事故给出的是本章最重要的结论: 漂移监控(PSI / 对抗验证)只能发现数据漂移,对概念漂移是完全失明的。 无论你的漂移看板多绿,都必须另外有一条"看真实答案"的线。

💡 这个案例还有一个附加教训:概念漂移的原因常常在你的系统之外 —— 政策、规则、竞品、天气、上下游的业务决策。 对策:让业务方的重大变更(规则调整、活动、定价)以事件的形式打到你的监控时间轴上, 排查时能一眼对上。🔗 第 11 章会讲这条"变更时间轴"。


🔬 六、对抗验证:完整可跑版

第二节说了思路,这里给能直接抄的实现。

import numpy as np, pandas as pd, lightgbm as lgb
from sklearn.model_selection import cross_val_predict
from sklearn.metrics import roc_auc_score

def adversarial_validation(train_df, live_df, n=50000, seed=0):
    """返回 (AUC, 每个特征的重要性)。AUC≈0.5 说明两批数据分不开。"""
    rng = np.random.default_rng(seed)
    a = train_df.sample(min(n, len(train_df)), random_state=seed)
    b = live_df.sample(min(n, len(live_df)), random_state=seed)
    X = pd.concat([a, b], ignore_index=True)
    y = np.r_[np.zeros(len(a)), np.ones(len(b))]          # ⭐ 训练集=0,线上=1

    # ⭐ 一定要打乱,否则按时间排列的数据在 CV 里会泄漏
    idx = rng.permutation(len(X)); X, y = X.iloc[idx], y[idx]

    clf = lgb.LGBMClassifier(n_estimators=200, learning_rate=.05,
                             num_leaves=31, verbose=-1)
    # ⭐ 用交叉验证的 out-of-fold 概率算 AUC,避免自己评自己
    oof = cross_val_predict(clf, X, y, cv=5, method="predict_proba")[:, 1]
    auc = roc_auc_score(y, oof)

    clf.fit(X, y)
    imp = pd.Series(clf.feature_importances_, index=X.columns) \
            .sort_values(ascending=False)
    return auc, imp
时间分类器:如何读区分能力
AUC 范围原文判断
≤ 0.55两批数据基本一致 ✅
0.55 ~ 0.70有可察觉的漂移 ⚠️ 看 imp 前几名
0.70 ~ 0.90明显漂移 🔴
> 0.90💀 几乎可以完美区分

—— 先怀疑【泄漏了时间类特征】(如 event_date、id 自增)

⚠️ AUC > 0.9 时的第一反应不该是"漂移好严重",而是 "我是不是把时间戳、自增 ID 这类特征喂进去了" —— 这类特征天然能完美区分新旧数据,会把真正的漂移信号淹掉。 规则:对抗验证前先剔除所有单调递增/时间相关的字段。

⚠️ 漂移检测的六个坑

坑 后果 怎么修
分桶边界每次重算 永远算不出漂移(两边各自等分位)💀 边界随模型一起保存 ⭐
样本量太小就报警 1000 条样本下 PSI 天然波动 0.02~0.05 定最小样本量;小样本时看置信区间
对抗验证喂了时间类特征 AUC 虚高到 0.99,掩盖真信号 先剔除 id / 时间戳 ⭐
只看特征不看标签延迟后的指标 对概念漂移完全失明 💀💀 必须有滑动窗口的真实指标
把周期性当成漂移 每周一、每个月初都告警 用同比(第 8 章)而不是环比
发现漂移就自动重训 管道故障时把错误固化 💀 先分类再动作(第四节那张表)⭐

📐 一个能省很多误报的数字

PSI 阈值离不开样本量

10 桶、没有真实漂移时的经验随机波动
样本量PSI 的典型随机波动该怎么读
5000.05 ~ 0.12阈值 0.1 会天天报警
2,0000.02 ~ 0.05正常波动仍然不可忽略
10,0000.005 ~ 0.015经典阈值开始更可信
100,000< 0.0050.1 才是有意义的阈值

PSI 的经典阈值(0.1 / 0.25)默认每个格子有上万条样本;分人群、分渠道后,误报会显著增加。


🔗 七、和站内其他章的关系

相关的地方 和这一章的关系
第 1 章四种变差方式 这一章展开其中的 ②③
第 5 章数据层监控 漂移检测是它的一部分
Kaggle 23对抗验证 同一个工具,换了场景
数学原理 08 分布变了意味着测试分布≠训练分布

✅ 检查点

  1. 数据漂移、概念漂移、标签漂移分别是什么变了?
  2. 用信贷的例子说明三者的区别。
  3. 哪一种最省力?为什么?
  4. PSI 的三档阈值是什么?它有哪两个坑?
  5. 对抗验证怎么做?它比逐特征检验强在哪?
  6. 为什么概念漂移比数据漂移难发现?
  7. 概念漂移的三种形态里哪种最危险?对策是什么?
  8. 发现漂移后的判断顺序是什么?哪种情况绝不能重训?
  9. ETA 那个事故里,为什么 PSI、对抗验证、预测分布全都没报?唯一报出来的是什么?
  10. 这个事故给出的最重要结论是什么?附加教训呢?
  11. 对抗验证 AUC > 0.9 时,第一反应应该是什么?
  12. 漂移检测的六个坑里,哪两个会让你对概念漂移完全失明 / 把错误固化?
  13. PSI 的经典阈值默认你有多少样本?「拆细之后到处都在报警」的真正原因是什么?
👀 答案
  1. 数据漂移 P(x) 变、P(y|x) 不变;概念漂移 P(y|x) 变;标签漂移 P(y) 变。
  2. 数据漂移:渠道换了,来的人从 35 岁白领变 22 岁学生,但"收入高违约低"仍成立。概念漂移:经济变了,同样收入年龄的人违约率整体上升,关系本身变了。标签漂移:风控放松,坏账率 2%→5%,排序可能还对但概率不再校准。
  3. 标签漂移。常常只需要重新校准或调阈值,比重训便宜得多。
  4. PSI <0.1 稳定、0.1~0.25 关注、>0.25 显著漂移。两个坑:分桶必须按训练集切且边界存下来(各自切永远算不出漂移)、样本量小于 1000 时不稳。
  5. 训练集标 0、线上标 1,训分类器区分;AUC≈0.5 无漂移,明显>0.5 有漂移;更值钱的是看它的特征重要性找出元凶。强在能发现联合分布的漂移——每个特征单看正常但组合变了。
  6. 因为数据漂移看输入就能发现(不需要标签),概念漂移必须知道真实答案(需要标签),而标签常常要等很久。
  7. 渐变最危险——每天掉一点点,任何单日阈值都不触发,三个月后已掉 20%。对策:除了日环比,要看和 30 天前、90 天前比。
  8. ①管道坏了吗 ②数据漂移吗 ③概念漂移吗 ④标签漂移吗。管道故障时绝不能重训——会把错误固化。
  9. 因为平台改了骑手并单规则,输入特征一个都没变(距离、时段、骑手密度全一样),变的是"同样输入下实际配送时长多了 4 分钟",即 P(y|x)。PSI 全 <0.06、对抗验证 AUC 0.52、预测均值 31.2→31.5,它们全都只看 P(x)。唯一报出来的是滑动窗口 MAE:6.1 → 9.8 分钟(唯一看了真实答案的指标)。
  10. 漂移监控(PSI/对抗验证)只能发现数据漂移,对概念漂移是完全失明的——无论漂移看板多绿,都必须另外有一条"看真实答案"的线。附加教训:概念漂移的原因常常在你的系统之外(政策、规则、竞品、上下游业务决策),所以要让业务方的重大变更以事件的形式打到监控时间轴上。
  11. 不是"漂移好严重",而是"我是不是把时间戳、自增 ID 这类单调特征喂进去了"——它们天然能完美区分新旧数据,会把真正的漂移信号淹掉。对抗验证前先剔除所有时间相关字段。
  12. 只看特征不看标签延迟后的真实指标 → 对概念漂移完全失明;发现漂移就自动重训 → 管道故障时把错误固化。后者的修法是先分类再动作。
  13. 默认你有上万条样本(500 条时 PSI 的随机波动就有 0.05~0.12,阈值 0.1 会天天报警)。「拆细之后到处报警」的真正原因是分人群/分渠道后每个格子的样本量掉得很快,PSI 的随机波动随之变大。

🛑 可以停在这里

⚡ 走神救援

⭐ 三种漂移,分清才知道该做什么:数据漂移是来的人变了、规律没变;概念漂移是同样的人行为变了、关系本身变了;标签漂移是正例比例变了——⭐ 标签漂移最省力,常常只要重新校准或调阈值,不用重训。

检测数据漂移两把工具。PSI 的两个坑:分桶必须按训练集切且边界要存下来、⚠️ 样本少的时候它本身就不稳。⭐ 对抗验证更值钱的地方不是那个 AUC,是它的特征重要性——它直接告诉你元凶是谁,而且能发现逐特征看不出来的联合分布漂移。

⭐ 概念漂移难在必须有标签才能发现(数据漂移看输入就够)。三种形态里渐变最危险:⚠️ 每天掉一点点,任何单日阈值都不触发——所以要和一个月前、三个月前比,不能只看日环比。

发现漂移后的处置有顺序:先问管道是不是坏了(💀 管道坏了绝不能重训,那会把错误固化),再分数据漂移、概念漂移、标签漂移分别处理。

💀 那个「PSI 全绿但模型已经烂了」的事故是全章的支点:平台改了一条业务规则,输入特征一个都没变——PSI、对抗验证、预测均值全部正常,而同样的输入下真实结果已经偏了,唯一报出来的是那条看真实答案的误差曲线。

⭐⭐ 结论:PSI 和对抗验证只能发现数据漂移,对概念漂移完全失明——漂移看板再绿,也必须另有一条「看真实答案」的线。 ⭐ 附加教训:概念漂移的原因常常在你的系统之外(政策、规则、竞品)——把业务方的重大变更打到监控时间轴上。

⚠️ 两个反向的坑:对抗验证 AUC 高得离谱时,先怀疑自己喂了时间戳或自增 ID,而不是「漂移严重」;📐 PSI 的经典阈值默认你有上万条样本——样本少时随机波动就能超过阈值,⭐ 这才是「拆细之后到处都在报警」的真正原因。

下一节 👉 07-没有标签怎么发现退化.md ⭐

打卡记录保存在你的浏览器里,首页能看到总进度