数据漂移检测:概念、统计方法与可视化工具

模型上线后识别率下降,十有八九是数据漂移在作祟。我见过太多团队花几周调模型结构,结果发现是上游特征分布早就变了——说白了,数据变了,模型还在用老眼光看问题。

这一章我们聚焦三个核心问题:什么是数据漂移?怎么量化它?用什么工具能一眼看出问题?

数据漂移的本质

数据漂移,就是模型训练时见到的数据分布,和线上实际遇到的数据分布不一样了。你想想看,模型学的是历史规律,如果线上数据变了,它自然就懵了。

我习惯把漂移分成两类:

  • 特征漂移(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。一旦超过告警线,自动触发排查流程。这样不用天天盯着看,省心很多。

数据漂移检测流程

下面这张图是我在实际项目中总结的检测流程,你可以直接拿来用:

数据漂移检测流程 线上数据采集 特征对齐与清洗 分布对比(PSI + KS) 是否超过阈值? 否 是 继续监控 触发告警 根因排查与修复 模型回滚 / 重训 说明: PSI > 0.25 或 KS > 0.1 触发告警,进入排查流程 PSI < 0.1 且 KS < 0.05 继续监控

代码实战:用 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 是两个最实用的量化指标,配合可视化工具,能帮你快速定位问题。记住:检测要快,定位要准,告警要及时。