3、特征分布变化分析:单特征分布对比、多特征联合分布、异常特征定位

模型上线后识别率下降,最让人头疼的情况是什么?我个人的经验是——数据分布变了,但监控指标没报警。

你想想看,模型本身没动过,代码也没改过,突然某天线上识别率掉了5个点。这时候第一反应往往是「是不是模型坏了?」。但说实话,模型没那么容易坏。大概率是输入特征和训练时的分布对不上了。

这一章我们就来聊聊怎么分析特征分布的变化。我会从单特征对比讲起,再到多特征联合分布,最后告诉你如何快速定位到那个「捣乱」的特征。

3.1 单特征分布对比:从直方图看起

最简单的办法,就是把训练集和线上数据的同一个特征拿出来,画直方图对比。我在项目中遇到过好几次,光看均值和中位数都没问题,但一画图就发现分布形状完全变了。

核心思路: 用 KS 检验或 JS 散度量化分布差异,用直方图/KDE 图可视化差异。

3.1.1 数值型特征的对比方法

对于数值型特征,我习惯先做三件事:

  1. 算统计量:均值、方差、分位数(P5、P25、P50、P75、P95)
  2. 做KS检验:计算训练集和线上集的最大分布差异
  3. 画分布图:直方图或核密度估计图

举个例子,假设我们有个特征叫「用户活跃天数」,训练集均值是12.3,线上集均值是11.8,看起来差不多对吧?但KS统计量是0.23,p值小于0.001。画图一看,原来线上集多了一堆活跃天数为0的用户。这就是分布形状变了,均值没反映出来。

我的经验: KS统计量大于0.1就要警惕了。大于0.2基本可以确定分布发生了显著变化。但别只看p值——样本量大的时候p值很容易显著,要结合KS值本身判断。

3.1.2 类别型特征的对比方法

类别型特征更直观。我一般看两个东西:

  • 类别覆盖率:训练集有的类别,线上有没有?线上有没有出现新类别?
  • 类别占比变化:每个类别的样本比例变化了多少?

可以用一个简单的表格来对比:

特征取值 训练集占比 线上集占比 变化幅度
A 45.2% 38.7% -6.5%
B 30.1% 35.6% +5.5%
C 24.7% 25.7% +1.0%

变化幅度超过5%的,我建议重点关注。超过10%的,基本可以确定这个特征分布变了。

注意: 类别型特征出现「新类别」时,模型会直接报错或产生不可预测的输出。我曾经遇到过线上数据里突然出现了一个训练集从未见过的城市编码,模型直接给了个极端的预测值。所以新类别检测一定要做。

3.2 多特征联合分布:单特征看不出的问题

单特征分布没问题,不代表联合分布没问题。我举个真实的例子:

有个推荐模型,特征A(用户年龄)和特征B(商品价格)在单特征分布上都没变化。但联合分布一画,发现「年轻用户 + 高价商品」的组合在线上明显变多了。模型在训练时没见过这种组合,预测自然不准。

这就是多特征联合分布分析的价值。

3.2.1 二维散点图与密度图

对于两个数值型特征,我推荐用散点图或二维密度图。训练集和线上集各画一张,对比密度分布的区域变化。

具体做法:

  • 把两个特征分别作为x轴和y轴
  • 用颜色深浅表示样本密度
  • 对比两张图,看高密度区域是否发生了偏移

如果发现某个区域的密度在线上明显增加或减少,那这个区域对应的特征组合就是异常点。

3.2.2 条件分布对比

有时候我们关心的是「给定特征A,特征B的分布是否变化」。这叫做条件分布对比。

举个例子:

  • 训练集:在「用户等级=高」的条件下,「消费金额」的均值是500
  • 线上集:在「用户等级=高」的条件下,「消费金额」的均值是350

单看「用户等级」和「消费金额」的分布都没变,但条件分布变了。这说明用户的行为模式发生了变化。

关键点: 条件分布变化往往比边缘分布变化更隐蔽,但对模型的影响也更大。因为模型学到的就是特征之间的条件关系。

3.3 异常特征定位:找到那个「罪魁祸首」

当我们发现分布确实变了,下一步就是定位——到底是哪个特征或哪些特征导致的?

我有一套自己的排查流程,分享给你:

3.3.1 特征重要性加权分布差异

不是所有特征对模型的影响都一样。有些特征权重很大,分布稍微一变,模型输出就崩了。有些特征权重很小,分布变再多也没关系。

所以,我计算分布差异时会加上特征重要性权重:

# 伪代码示例
def weighted_distribution_shift(train_dist, online_dist, feature_importance):
    # 计算每个特征的JS散度
    js_divergence = compute_js_divergence(train_dist, online_dist)
    # 用特征重要性加权
    weighted_shift = js_divergence * feature_importance
    return weighted_shift

这样算出来的分数,能直接反映「这个特征的变化对模型输出的潜在影响有多大」。

3.3.2 逐特征消融对比

如果特征数量不多(比如几十个),我会用消融法:

  1. 把线上数据逐个特征替换回训练集的分布
  2. 看替换哪个特征后,模型输出最接近训练集的表现
  3. 那个特征就是主要「肇事者」

这个方法虽然计算量大,但定位非常准。我在一次金融风控模型的排查中,就是用这个方法找到了一个「用户收入」特征的分箱逻辑在线上被改了。

3.3.3 异常特征定位清单

总结一下,我每次排查异常特征时都会走一遍这个清单:

步骤 方法 输出
1 单特征KS检验 + 直方图对比 每个特征的分布差异分数
2 特征重要性加权 加权后的影响分数
3 Top-N特征联合分布检查 联合分布变化的热力图
4 消融对比验证 确认主要异常特征
我的习惯: 每次上线新模型前,我都会把训练集的特征分布快照保存下来。这样线上出问题时,可以直接拿快照对比,不用重新跑训练流程。这个习惯救过我很多次。

3.4 知识体系总览

下面这张图概括了特征分布变化分析的核心流程。你可以把它当作排查时的路线图:

特征分布变化分析流程 训练集 & 线上集特征 分布差异计算 单特征分布对比 KS检验 / JS散度 直方图 / KDE图 多特征联合分布 二维密度图 / 条件分布 联合分布热力图 异常特征定位 特征重要性加权 + 消融对比 定位结果 & 修复方案 单特征没问题 ≠ 联合分布没问题

这张图把整个分析流程串起来了。从输入特征开始,分两条路走——单特征和多特征,最后汇聚到异常定位,输出结果。我在实际排查时就是按这个顺序来的,很少走弯路。

再次提醒: 分布变化分析不是一次性工作。我建议把它做成自动化监控的一部分。每天跑一次,把分布差异分数记录下来,设置告警阈值。这样问题刚冒头就能发现,不用等到识别率掉下来才去排查。