查看全课大纲(第 30 / 35 节)
- 1.特征工程简介
- 2.特征工程的处理流程
- 3.数据与特征概述
- 4.数据统计、相似度与数据探索
- 5.数据探索工具 Pandas_profiling
- 6.工具应用:《泰坦尼克》分析报告
- 7.构造一个微型链家租房数据集用于演示
- 8.数据预处理综述
- 9.缺失值处理实操
- 10.异常值处理实操
- 11.连续值特征构建
- 12.类别型数据特征构建
- 13.特征幅度缩放
- 14.特征离散化
- 15.特征编码:让类别数据参与建模
- 16.特征变换
- 17.时间特征
- 18.特征衍生
- 19.自动化特征工具
- 20.特征选择综述
- 21.特征选择方法
- 22.过滤式特征选择方法实操
- 23.包裹式特征选择方法
- 24.嵌入式特征选择
- 25.混序法与混合法特征选择
- 26.特征降维综述
- 27.案例实操:特征降维
- 28.特征监控概述与指标
- 29.Iris 数据集特征漂移检测案例
- 30.特征监控实战:用分布对比检测数据漂移
- 31.实时特征监控
- 32.电商销量预测项目背景介绍
- 33.数据预处理和特征工程(一)
- 34.数据预处理和特征工程(二)
- 35.数据建模
特征监控实战:用分布对比检测数据漂移
约 9 分钟
小象实战讲义 · 特征工程精讲与实践
模型上线只是开始。线上数据一旦发生分布变化,模型效果会无声下滑——特征监控(feature monitoring)就是给模型装上的”心电图”。本节以经典的波士顿房价数据集为背景,亲手做一次分布对比:把数据切成”参照组”与”生产组”,看哪些特征漂移、哪些平稳,并辨析一个经典陷阱——切分方式本身造成的假漂移。
💡 核心导读
- 数据漂移(data drift):线上数据分布偏离训练数据,是模型效果衰减的头号静默杀手
- 分布对比三板斧:分组统计、偏移度量、类别字段的占比对比
- 顺序切分可能制造”假漂移”,乱序(shuffle)后重新切分即可验证
- 原课程使用的 Boston 数据集已从 scikit-learn 移除,本节用等形态微型样例复现
- 工业实践中特征监控有成熟工具(evidently),机制本节全部手写讲透
一、监控场景与 Boston 数据集
原课程演示用的是 1978 年统计收集的波士顿房价(Boston house price)数据集:每行描述一个城镇的房价相关指标,共 506 条记录、13 个预测特征(例如 CRIM(城镇人均犯罪率)、ZN(住宅用地占比)、INDUS(非住宅用地占比)、TAX(房产税率)、NOX(氮氧化物浓度)、B 等),标签为房价中位数 MEDV。部分字段是数值型,部分虽然以数字形态存储、本质却是等级或类别,这一点后面会专门处理。
先说一个版本时效问题:原课程通过 sklearn.datasets.load_boston() 加载这份数据,但该函数因数据集中 B 特征存在伦理争议,已在 scikit-learn 1.2 版本移除,新版环境直接调用会报错。本讲义改为代码内构造一组形态一致的微型样例(60 行、TAX/NOX/B 三个字段),复现原课”前段与后段分布对比”的完整流程:
import numpy as np
import pandas as pd
np.random.seed(42)
tax_low = np.random.uniform(200, 400, 30).round(1)
tax_high = np.random.uniform(550, 750, 30).round(1)
nox_front = np.random.uniform(0.45, 0.55, 30).round(3)
nox_back = np.random.uniform(0.55, 0.70, 30).round(3)
b_front = np.random.uniform(380, 400, 30).round(1)
b_back = np.random.uniform(385, 405, 30).round(1)
boston = pd.DataFrame({
'TAX': np.concatenate([tax_low, tax_high]),
'NOX': np.concatenate([nox_front, nox_back]),
'B': np.concatenate([b_front, b_back]),
})
reference = boston.iloc[:30].reset_index(drop=True)
production = boston.iloc[30:].reset_index(drop=True)
print('reference(前 30 条)与 production(后 30 条)分组统计:')
print(reference.describe().round(2).loc[['mean', '50%', 'std']])
print(production.describe().round(2).loc[['mean', '50%', 'std']])reference(前 30 条)与 production(后 30 条)分组统计:
TAX NOX B
mean 287.71 0.50 389.12
50% 280.65 0.49 387.80
std 56.48 0.03 5.91
TAX NOX B
mean 649.28 0.62 394.36
50% 651.50 0.63 395.00
std 65.82 0.04 5.72reference(参照组,对应训练数据)与 production(生产组,对应线上数据)的命名来自监控工具的通用术语:拿训练时的历史数据做”参照”,与线上新数据对比。分组统计已经能看出问题——TAX 在两组间均值 287.71 对 649.28,差别悬殊;NOX 从 0.50 抬到 0.62;而 B 的 389.12 对 394.36 基本平稳。原课程在完整 Boston 数据集上得到同样的形态:TAX 参照组分布高点在 250 至 350 一带,生产组高点到了 650 附近,报告明确提示 data drift detected;NOX 有可见差异;B 基本没有偏移。
二、漂移度量与 shuffle 验证
光”看得出差别”不够,监控需要可量化、可设定阈值的信号。最直接的度量是均值偏移,再除以参照组均值做相对化,得到一个跨字段可比的相对偏移率:
把这套度量连同”乱序验证”一起跑一遍:
import numpy as np
import pandas as pd
np.random.seed(42)
tax_low = np.random.uniform(200, 400, 30).round(1)
tax_high = np.random.uniform(550, 750, 30).round(1)
nox_front = np.random.uniform(0.45, 0.55, 30).round(3)
nox_back = np.random.uniform(0.55, 0.70, 30).round(3)
b_front = np.random.uniform(380, 400, 30).round(1)
b_back = np.random.uniform(385, 405, 30).round(1)
boston = pd.DataFrame({
'TAX': np.concatenate([tax_low, tax_high]),
'NOX': np.concatenate([nox_front, nox_back]),
'B': np.concatenate([b_front, b_back]),
})
reference, production = boston.iloc[:30], boston.iloc[30:]
def drift_score(ref, prod):
stats = {}
for col in ref.columns:
mean_shift = prod[col].mean() - ref[col].mean()
rel_shift = abs(mean_shift) / ref[col].mean() * 100
stats[col] = round(mean_shift, 2), round(rel_shift, 1)
return stats
print('顺序切分的漂移信号(均值偏移, 相对偏移率%):')
for col, (m, q) in drift_score(reference, production).items():
print(f' {col}: ({m}, {q})')
shuffled = boston.sample(frac=1, random_state=42).reset_index(drop=True)
ref2, prod2 = shuffled.iloc[:30], shuffled.iloc[30:]
print('乱序(shuffle)后重新切分的漂移信号:')
for col, (m, q) in drift_score(ref2, prod2).items():
print(f' {col}: ({m}, {q})')顺序切分的漂移信号(均值偏移, 相对偏移率%):
TAX: (361.56, 125.7)
NOX: (0.13, 25.2)
B: (5.24, 1.3)
乱序(shuffle)后重新切分的漂移信号:
TAX: (-28.09, 5.8)
NOX: (0.0, 0.0)
B: (-2.05, 0.5)两段输出的对比非常有信息量:
- 顺序切分下,TAX 相对偏移率高达 125.7%,NOX 达 25.2%,B 仅 1.3%。
- 乱序重切后,TAX 掉到 5.8%,NOX 归零,B 依旧平稳——前面看到的”巨大漂移”大部分是切分方式制造出来的假信号。
原因是这份数据前 30 行与后 30 行本来就是按不同分布构造的,顺序切分恰好把两段分布各自完整切开,任何按序存储、分段分布的数据都会复现这种假漂移。原课程讲师给出的建议正是把数据 shuffle 之后再做检测,“会好很多”。引申到工程实践:如果你怀疑监控信号是切分方式导致的,做一次乱序对照即可验证;而真实生产监控里,reference 与 production 本来就是”训练期历史”与”线上当下”两份数据,其分布差是真实的时间漂移,不可 shuffle 掉。
三、类别型字段与类型映射意识
第一个对比只处理了数值字段。真实数据集里有些字段虽以数字形态存储,本质是等级或类别(如波士顿数据集的 RAD 高速公路可达性指数,取值只有几个离散档位)——对这类字段,比较均值与分位数并不合适,正确做法是对比各取值的占比分布:
import pandas as pd
rad_front = ['low', 'low', 'high', 'low', 'high', 'high',
'low', 'high', 'low', 'low', 'high', 'low']
rad_back = ['high', 'high', 'high', 'low', 'high', 'high',
'high', 'low', 'high', 'high', 'low', 'high']
reference = pd.Series(rad_front, name='RAD')
production = pd.Series(rad_back, name='RAD')
ref_pct = reference.value_counts(normalize=True).sort_index()
prod_pct = production.value_counts(normalize=True).sort_index()
compare = pd.DataFrame({'reference 占比': ref_pct, 'production 占比': prod_pct})
compare['占比偏移'] = (compare['production 占比'] - compare['reference 占比']).round(3)
print(compare) reference 占比 production 占比 占比偏移
RAD
high 0.416667 0.75 0.333
low 0.583333 0.25 -0.333high 档占比从 41.7% 抬到 75%,偏移 33.3 个百分点——类别字段的漂移信号要用占比差来读。这就是”类型映射”的意义:原课程在生成报告前专门传入一个 column_mapping 映射字典,把数据集里的 datetime(日期型)、target(标签)、numerical features(数值型特征)、categorical features(类别型特征)分别声明,报告才能用对的方法(分位数对比 vs 占比对比)对待每个字段。手写监控时同样要先问一句:这个字段到底是什么类型?
四、带标签的完整监控与工具生态
最后一步是把标签(房价)一起纳入监控,形成完整的对比报告。原课程的 evidently 报告在带标签后新增了几块内容,思路值得记住:
- 标签分布对比:参照组与生产组的房价分布叠加展示,看目标本身是否漂移;
- 相关度对比:每个特征与标签的相关程度,在两组间并列对比——相关结构的变化意味着”哪些特征变得更重要”在变;
- 逐特征散点(behavior by feature):每个特征对标签的散点分布两组并排,肉眼可见关系的形态变化。
工业界的实际体感是:业务平稳时数据分布基本稳定,特殊节点(电商的 618、双十一这类大促)才出现明显抖动。阶段性的特征监控就落在这些工具流程上:定期取线上数据快照,与训练参照数据生成对比报告,人工或自动研判漂移字段。原课程使用 evidently 库(当时版本的 Dashboard 与 DriftTab 组件)生成 HTML 报告,报告形态即上述内容;该库后续版本已将接口演进为 Report 加预设(DataDriftPreset)的形态。本讲义不依赖该库,机制全部以 pandas 手写呈现——读者想体验完整交互式报告,可自行安装 evidently 对照使用。
📝 动手练一练
练习 1(概念辨析):顺序切分制造”假漂移”的原因是什么?真实生产监控中,reference 与 production 的分布差可以像本节实验这样 shuffle 掉吗?为什么?
👉 点击查看参考答案
假漂移的来源是数据按序存储且分段分布不同,顺序切分把不同分布整段分开,对比信号被切分方式放大。真实生产监控里不可以 shuffle:reference 是训练期的历史数据,production 是线上当下的新数据,两者之间的分布差正是我们要监控的”时间漂移”——shuffle 等于把要检测的信号抹掉。乱序对照只在验证”切分方式是否造成伪信号”时使用。
练习 2(动手实算):B 字段顺序切分时均值偏移为 5.24、参照组均值 389.12。请手算它的相对偏移率,并对照 shuffle 后的 0.5%,判断该字段在两次检测中是否应被判为”漂移”(经验阈值取 10%)。
👉 点击查看参考答案
相对偏移率 = 5.24 / 389.12 × 100% ≈ 1.3%,远低于 10% 的经验阈值;shuffle 后进一步降到 0.5%。两次检测都不应判为漂移——B 字段前后两段本来就来自几乎相同的分布,是本实验里”平稳对照字段”的角色。对照 TAX(125.7% → 5.8%)与 NOX(25.2% → 0.0%),可以体会到相对偏移率让不同量纲的字段变得可比。
本章小结
特征监控的实操骨架:把数据切成 reference 与 production,数值字段比分位数与均值(用相对偏移率做跨字段可比的量化),类别字段比占比分布,带标签时再看标签分布与特征相关度的变化。顺序切分可能制造假漂移,shuffle 对照是快速验证手段,而真实生产的时间漂移不可 shuffle 掉。原课的 Boston 数据集已从 scikit-learn 1.2 移除(B 特征伦理争议),evidently 等监控工具的接口也已演进,机制层面手写 pandas 即可完整复现。
📋 行动清单
- 对一份时间序列数据做”前段 vs 后段”的分组统计,算出各字段的相对偏移率
- 用 shuffle 对照验证你发现的漂移是真实信号还是切分伪影
- 挑一个以数字存储的类别字段,用占比对比代替均值比较,重新审视它的”漂移”
—— 小象教研组