数据漂移检测:概念、统计方法与可视化工具
模型上线后识别率下降,十有八九是数据漂移在作祟。我见过太多团队花几周调模型结构,结果发现是上游特征分布早就变了——说白了,数据变了,模型还在用老眼光看问题。
这一章我们聚焦三个核心问题:什么是数据漂移?怎么量化它?用什么工具能一眼看出问题?
数据漂移的本质
数据漂移,就是模型训练时见到的数据分布,和线上实际遇到的数据分布不一样了。你想想看,模型学的是历史规律,如果线上数据变了,它自然就懵了。
我习惯把漂移分成两类:
- 特征漂移(Covariate Shift):输入特征 X 的分布变了。比如用户年龄分布从 20-30 岁变成了 30-40 岁。
- 标签漂移(Label Shift):标签 Y 的分布变了。比如正样本比例从 5% 变成了 20%。
还有一种叫概念漂移(Concept Drift),指的是 X 和 Y 的关系变了——同样的特征,含义不同了。这个比较隐蔽,我后面会专门讲。
核心原则:检测漂移不是目的,目的是找到「模型为什么不准」的根因。所以检测要快,定位要准。
量化漂移的统计方法
量化漂移,说白了就是算「两个分布差多少」。我常用的方法就两个:PSI 和 KS。它们各有侧重,配合使用效果最好。
PSI(群体稳定性指标)
PSI 是风控领域的标配,用来衡量两个样本在同一个特征上的分布差异。公式长这样:
PSI = Σ (P_i - Q_i) * ln(P_i / Q_i)
其中 P_i 是线上样本在第 i 个分箱的比例,Q_i 是训练集在第 i 个分箱的比例。分箱一般用等频或等距,我习惯用等频分 10 箱。
PSI 的判定标准:
| PSI 值 | 含义 | 建议操作 |
|---|---|---|
| < 0.1 | 分布稳定 | 无需处理 |
| 0.1 - 0.25 | 轻度漂移 | 关注,准备回滚或重训 |
| > 0.25 | 严重漂移 | 立即排查,考虑模型回滚 |
我的经验:PSI 对分箱方式敏感。有一次我用了等距分箱,结果线上数据有长尾,大部分样本挤在一个箱子里,PSI 算出来只有 0.05,但实际分布已经变了。后来我改用等频分箱,PSI 直接飙到 0.3。所以,分箱方式一定要根据特征分布来选。
KS 检验(Kolmogorov-Smirnov)
KS 检验不依赖分箱,它直接比较两个分布的累积分布函数(CDF)的最大差异。公式简单:
KS = max |F_train(x) - F_online(x)|
KS 值越大,说明两个分布差异越大。p 值小于 0.05 通常认为有显著差异。
PSI 和 KS 的对比:
| 维度 | PSI | KS |
|---|---|---|
| 依赖分箱 | 是 | 否 |
| 敏感区域 | 整体分布差异 | 局部最大差异 |
| 计算速度 | 快 | 快 |
| 适用场景 | 连续特征、离散特征 | 连续特征 |
| 阈值经验 | 0.1 / 0.25 | 0.1(经验值) |
避坑指南:我曾经在一个项目里只用 KS 检测漂移,结果 KS 值一直很低,但模型效果却在下降。后来发现,KS 只关注最大差异点,如果漂移是均匀的(比如整体均值偏移 0.5 个标准差),KS 反而不敏感。所以我现在都是 PSI + KS 一起看,互相补充。
可视化工具:一眼看出问题
光看数字不够直观。我习惯先画图,再算指标。图能告诉你「哪里变了」,指标能告诉你「变了多少」。
分布对比图
最基础也最有效。把训练集和线上集的特征分布画在一起:
- 直方图:适合连续特征,看形状变化。
- 核密度估计图:比直方图平滑,适合小样本。
- 箱线图:看中位数、四分位距的变化。
我个人习惯用直方图 + 核密度叠加,既能看细节又能看趋势。
PSI 热力图
当特征很多时,一张热力图能快速定位「哪些特征漂移了」。横轴是特征,纵轴是时间窗口,颜色深浅代表 PSI 值。一眼扫过去,深色区域就是问题所在。
时间序列监控图
把 PSI 或 KS 值按天/小时画成折线图。如果某天突然跳变,大概率是上游数据源变了或者策略调整了。
我的习惯:我会在监控图上加一条「预警线」和一条「告警线」。预警线设在 PSI=0.1,告警线设在 PSI=0.25。一旦超过告警线,自动触发排查流程。这样不用天天盯着看,省心很多。
数据漂移检测流程
下面这张图是我在实际项目中总结的检测流程,你可以直接拿来用:
代码实战:用 Python 实现漂移检测
理论说完了,上代码。下面是我常用的漂移检测函数,你直接复制就能用:
import numpy as np
import pandas as pd
from scipy.stats import ks_2samp
def calculate_psi(train, online, bins=10):
"""
计算单个特征的 PSI
train: 训练集数据(array-like)
online: 线上数据(array-like)
bins: 分箱数
"""
# 等频分箱
percentiles = np.linspace(0, 100, bins + 1)
bin_edges = np.percentile(train, percentiles)
# 分箱
train_bins = np.histogram(train, bins=bin_edges)[0] + 1e-6
online_bins = np.histogram(online, bins=bin_edges)[0] + 1e-6
# 计算比例
train_ratio = train_bins / train_bins.sum()
online_ratio = online_bins / online_bins.sum()
# PSI
psi = np.sum((train_ratio - online_ratio) * np.log(train_ratio / online_ratio))
return psi
def calculate_ks(train, online):
"""
计算 KS 统计量
"""
stat, p_value = ks_2samp(train, online)
return stat, p_value
# 使用示例
train_feature = np.random.normal(0, 1, 10000)
online_feature = np.random.normal(0.2, 1.1, 10000)
psi = calculate_psi(train_feature, online_feature)
ks_stat, ks_p = calculate_ks(train_feature, online_feature)
print(f"PSI: {psi:.4f}")
print(f"KS: {ks_stat:.4f}, p-value: {ks_p:.4f}")
我的建议:把这个函数封装成一个批处理脚本,每天定时跑一次。结果写入数据库,再配合 Grafana 画成监控图。这样漂移一出现,你就能第一时间知道。
总结
数据漂移检测不是锦上添花,而是模型运维的必修课。PSI 和 KS 是两个最实用的量化指标,配合可视化工具,能帮你快速定位问题。记住:检测要快,定位要准,告警要及时。