线上线下分数不一致,元凶是target encoding泄漏
上线一个转化率预估模型前,离线AUC 0.89,上线后线上真实AUC只有0.71,排查两天,元凶是target encoding的泄漏。复盘记录一下,这类问题极具隐蔽性。
背景:城市、类目这类高基数类别特征,one-hot维度爆炸,于是用了target encoding,把每个类别值替换成该类别的历史转化率均值。离线编码时直接对全量训练集统计,问题就出在这里:每个样本的编码值里,包含了它自己的标签信息。
极端例子便于理解:某城市只有2条样本,1条转化1条未转化,编码后该值是0.5;如果两条都是正样本,编码值是1.0。模型离线能从编码值里"读"到标签,分数虚高。线上没有标签可用,编码只能查历史统计表,分数立刻回落。
正确做法有两层。第一,离线必须用带外估计,K折交叉编码:把训练集分5折,每折的编码值由其余4折统计,保证样本看不到自己的标签。第二,平滑处理防过拟合,小样本类别向全局先验收缩。
from sklearn.model_selection import KFold
import numpy as np
def target_encode(tr_x, tr_y, col, smooth=20):
global_mean = tr_y.mean()
enc = np.zeros(len(tr_x))
for tr_idx, val_idx in KFold(5).split(tr_x):
stats = tr_y.iloc[tr_idx].groupby(
tr_x[col].iloc[tr_idx]).agg(['mean', 'count'])
smoothing = stats['count'] / (stats['count'] + smooth)
mapping = (smoothing * stats['mean']
+ (1 - smoothing) * global_mean)
enc[val_idx] = tr_x[col].iloc[val_idx].map(mapping).fillna(global_mean)
return enc
修复后离线AUC降到0.79,和线上0.78基本一致。分数"降低"了,但模型真实了。这件事的教训:离线分数虚高不是占便宜,是定时炸弹;所有涉及标签的统计类特征,都要过一遍"这条特征在预测时点是否可能包含当前样本的标签"这道安检。
泄漏检测的自动化补一段,事后我们把它做进了CI。每次训练流水线跑完,自动执行两个检查:一是特征与标签的时点合法性扫描,遍历特征元数据里的数据源和快照口径,SQL里出现未经快照限制的聚合就报警;二是线下线上一致性探针,训练完成后抽取5%样本,把特征计算代码在模拟的线上时点重跑一遍,两边逐特征比对,均值差异超过阈值就阻断上线。第二个探针上线首月就拦下一次事故,某特征的SQL被改动后时区处理变了,线上会晚八小时取数。泄漏问题的可怕在于它让你的离线指标说谎,防线必须建在流程里,靠人的自觉迟早再犯。
- 点赞
- 收藏
- 关注作者
评论(0)