线上线下分数不一致,元凶是target encoding泄漏

举报
yd_275379215 发表于 2026/10/17 16:51:37 2026/10/17
【摘要】 上线一个转化率预估模型前,离线AUC0.89,上线后线上真实AUC只有0.71,排查两天,元凶是targetencoding的泄漏。复盘记录一下,这类问题极具隐蔽性。背景:城市、类目这类高基数类别特征,one-hot维度爆炸,于是用了targetencoding,把每个类别值替换成该类别的历史转化率均值。离线编码时直接对全量训练集统计,问题就出在这里:每个样本的编码值里,包含了它自己的标签信...

上线一个转化率预估模型前,离线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被改动后时区处理变了,线上会晚八小时取数。泄漏问题的可怕在于它让你的离线指标说谎,防线必须建在流程里,靠人的自觉迟早再犯。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。