个人项目 · 推荐评测
离线评测会不会把推荐模型的比较带偏
推荐的离线测试集来自线上日志,日志里的视频是旧策略挑过的。我用快手开源的两份数据当对照,KuaiRand 的随机曝光日志和 KuaiRec 近乎全观测的用户 × 视频矩阵,检验常规离线评测会不会改变“哪个模型更好”。
- 精排差距变化
- 0 / 21
- Holm 校正后检出显著的对数
- logQ 双塔对普通双塔
- 2.61×→1.23×
- Recall@100 之比,常规测试→随机曝光,仍排第一
- 常规测试目标在最热组
- 75.7%
- 随机曝光上只有 26.8%
- 语义 ID 对双塔
- +0.006→−0.032
- Recall@100 差,稠密测试→只在实际被推过的视频里算
问题:离线测试集本身被旧策略筛过
模型在常规离线测试上分高,可能只是更会猜旧策略推了什么,而不是更懂用户。KuaiRand-Pure 的常规测试里,有 72.8% 的行落在最热的两成视频上。
KuaiRand-Pure 同时有两份日志,27,285 个用户。
常规日志
线上推荐策略推出来的,143.7 万条交互、7,551 个视频。
随机曝光日志
推荐流里把一部分位置换成从候选池随机抽的视频,118.6 万条、7,583 个视频。没被策略筛过,可以当对照。池里都是同一时期上传的新视频。
所以问题可以直接检验。同一批模型换一份没被筛过的测试集,“谁比谁好”会不会变?
研究一的设计
同一批人比
两份测试集的用户不一样,各算各的会把“评的人不同”当成曝光偏差。精排只在两边都有正负样本的 10,965 人上比,召回是 14,786 人,每人等权。
主统计量是差距变化
不只看名次翻没翻,直接检验每对模型的差变了多少。精排 21 对一起做 Holm 校正,主指标是每人等权的 UAUC。
先定规则再训正式模型
判定规则在训练正式模型前写定,之前只跑过冒烟和只出标准误的试点,用来做功效检查。模型组冻结后测试集只开一次,开之前程序核对协议、代码和模型文件的哈希。
种子也要一致
LightGBM 和神经网络各训 3 个种子,成立要求至少 2 个同向。87 次训练在一张 RTX 3080 上 35 分钟跑完。
精排:差距没检出变化
7 个精排模型的 UAUC:常规测试和随机曝光
每行一个模型,两个点之间的线是同一个模型换测试集后的落差。悬停或点按看数值。
看数据表
| 模型 | 常规测试 | 随机曝光 | 随机 − 常规 |
|---|---|---|---|
| MMoE | 0.6065 | 0.5825 | −0.0240 |
| 精排双塔 | 0.6056 | 0.5831 | −0.0225 |
| DIN | 0.6049 | 0.5839 | −0.0210 |
| DCN-v2 | 0.6048 | 0.5816 | −0.0232 |
| 逻辑回归 | 0.6047 | 0.5817 | −0.0230 |
| LightGBM | 0.5957 | 0.5756 | −0.0201 |
| 热度 | 0.5746 | 0.5494 | −0.0252 |
7 个模型一起降了 0.020 到 0.025。拿随机曝光上的 UAUC 对常规测试做回归,斜率 1.06、R² 0.98,两两差距大体按原比例保留。21 对的差距变化没有一对在 Holm 校正后显著,也没有一对显著翻转。
两边都分得清的只有三档,热度最差,LightGBM 其次,其余五个更好。这五个彼此只差 0.002 左右,比检出门槛还小(差距变化的最小可检出差中位数 0.0065,真有这么大的变化约八成能检出)。所以只说“未检出变化”,不说“排序一致”。点估计的第一名从 MMoE 换成了 DIN,但这两者在两份测试集上的差都不显著。
召回:差距被压得很厉害,logQ 双塔仍是第一
常规测试 Recall@100
刻度 0 到 0.4
随机曝光 Recall@100
刻度 0 到 0.03,比左图小一个数量级
看数据表
| 模型 | 常规测试 | 随机曝光 |
|---|---|---|
| logQ 双塔 | 0.3902 | 0.0282 |
| ItemCF | 0.3260 | 0.0265 |
| 热度 | 0.2179 | 0.0228 |
| 普通双塔 | 0.1492 | 0.0230 |
常规测试上 4 个模型在 0.149 到 0.390 之间,到随机曝光全挤在 0.023 到 0.028。完全随机排序的期望约 0.013,模型只有它的 1.7 到 2.1 倍,绝对差缩小有一部分只是整体都被压低了。
logQ 双塔对普通双塔的领先从 0.241 缩到 0.005(95% 区间 0.004 到 0.007),在随机曝光上仍然显著第一。按比值看,从 2.61 倍降到 1.23 倍,相对优势也缩了,但还在。6 对的差距变化全部显著,没有显著翻转。
事先写的两条召回假设方向都反了
预期热度相对 logQ 双塔会更吃亏,实际热度的落后从 0.172 缩到 0.005;预期 logQ 的优势会扩大,实际从 0.241 缩到 0.005。按规则都不成立,照实报告。
logQ 校正是什么
双塔把同一批里其他行的视频当负样本,热门视频出现得多,会被过度打压。logQ 在训练时把 logit 减去视频在训练正样本里的对数频率。它校正的是批内采样偏差,不是曝光偏差。测试集打开后补了固定温度的对照,只算探索。按 Recall@100 看,0.241 几乎全来自 logQ,换温度的影响不到 0.01。
为什么常规测试把 logQ 的领先放大了
视频按训练期曝光数等分成五组,每组两成视频。常规测试约四分之三的目标在最热的一组。
召回目标在五个热度组里的占比
每组都是两成视频
看数据表
| 热度组 | 最冷 | 较冷 | 中间 | 较热 | 最热 |
|---|---|---|---|---|---|
| 常规测试 | 0.6% | 2.3% | 5.2% | 16.2% | 75.7% |
| 随机曝光 | 15.6% | 16.9% | 18.8% | 21.9% | 26.8% |
| logQ 双塔前 100 的占位 | 0.2% | 0.3% | 1.6% | 9.8% | 88.2% |
| 普通双塔前 100 的占位 | 16.0% | 18.3% | 20.7% | 22.5% | 22.5% |
logQ 双塔推的前 100 里有 88% 是最热组的视频,普通双塔各组差不多。分组看命中率,两份测试上 logQ 都只在最热组赢、其余四组都输。
常规测试:logQ 双塔 − 普通双塔
各热度组的组内 Recall@100 之差
随机曝光:logQ 双塔 − 普通双塔
同上,刻度约小五倍
看数据表
| 组内 Recall@100 | 最冷 | 较冷 | 中间 | 较热 | 最热 |
|---|---|---|---|---|---|
| logQ 双塔 · 常规 | 0.0033 | 0.0275 | 0.0568 | 0.1681 | 0.4750 |
| 普通双塔 · 常规 | 0.1982 | 0.2183 | 0.2263 | 0.2269 | 0.1248 |
| logQ 双塔 · 随机 | 0.0002 | 0.0006 | 0.0023 | 0.0107 | 0.0944 |
| 普通双塔 · 随机 | 0.0190 | 0.0231 | 0.0220 | 0.0272 | 0.0224 |
常规测试 75.7% 的目标在最热组,正好是 logQ 赢的地方,所以它的领先被放大。把常规测试按随机曝光的热度组成重新加权,logQ 的领先就没了,普通双塔反而高 0.014。
那它在随机曝光上为什么还是第一?重新加权用的还是常规测试的组内命中率。到了随机曝光,普通双塔领先的那四组,组内召回掉了八到十倍;logQ 领先的最热组只掉了五倍,最热组又占随机目标的四分之一多,所以 logQ 还剩 0.005 的领先。
这组分解是测试集打开后补的分析,只作描述,不是检验。
点击率校准和混训随机数据
点击率在随机曝光上整体高估事先写定,成立
训练期正样本率约 0.53,随机测试只有 0.18。学到的 6 个模型直接用时 LogLoss 是 0.73 到 0.75,比报一个常数(0.46)还差。越冷门高估越多,逻辑回归在最冷组的预测约为实际的 3.6 倍,最热组约 2.3 倍。
在随机曝光的校准集上,只调截距就修掉大头;Platt 同时调斜率还能再降,说明偏差不只是整体平移;按热度分 5 组各拟合 Platt、再向全局收缩,又降 0.0020 到 0.0034,7 个模型全部成立。
训练时混一点随机数据补充实验
LightGBM 训练轮数拉平后,往训练集里加相当于原训练集 5.5% 的随机曝光行,随机测试的 UAUC 从 0.576 提到 0.602;加同样多的更新的常规行,只高 0.0003,不显著。
训练行和测试来自同一份随机日志,只是日期不同,提升可能有一部分来自贴合测试分布;只试了一个模型、一种比例,不能当线上效果。
研究二:换到 KuaiRec,看生成式召回
研究一的随机曝光只去掉一种偏差,而且只在 7,583 个新视频的池子里成立。KuaiRec 有一块 1,411 个用户 × 3,327 个视频的小矩阵,约 99.6% 的格子有观看记录,可以直接看一个用户对全部候选的反应。它也不是纯随机曝光,快手先挑出彼此看过很多的用户和视频,再把没看过的格子补齐;视频文本的采集时点不明,可能晚于日志。我在上面实现了语义 ID 生成式召回,和 SASRec、带文本的 logQ 双塔、热度、打乱语义 ID 一起比。正样本是播放时长超过视频时长 2 倍。
语义 ID 生成式召回怎么做的
模型约 328 万参数,给 1,121 个测试用户各排一遍,GPU 上不到 1 秒。打乱语义 ID 用同一套码随机打乱,码的集合和前缀树形状不变,只去掉视频和文本的对应,用来看文本语义有没有用。
四种测试,前三种用同一份推荐列表
稠密测试
候选是用户在小矩阵里有记录的视频,每个正样本同权。1,121 人。
有偏测试
同样的目标,按曝光量计权,曝光量是训练期其他用户在这个视频上的行数。
借用曝光
给每个用户随机配一个别的用户,只把对方验证期实际看到的视频算作目标,抽 20 次取平均。1,117 人。
自然日志
另一批 4,872 个用户之后几天的正样本,另一套候选。
前三种用的是同一批用户的同一份前 100,只换目标和权重;第四种换了人。
5 个模型在四种测试上的名次(按 Recall@100)
从上到下第 1 到第 5 名,彩色是正文提到的三个模型
看数据表
| Recall@100 | 稠密 | 有偏 | 借用曝光 | 自然日志 |
|---|---|---|---|---|
| 热度 | 0.2571 | 0.3862 | 0.0495 | 0.0334 |
| SASRec | 0.2260 | 0.2815 | 0.1220 | 0.0764 |
| 文本语义 ID | 0.1996 | 0.2549 | 0.0811 | 0.0467 |
| 双塔 | 0.1946 | 0.2377 | 0.1127 | 0.0648 |
| 打乱语义 ID | 0.1210 | 0.1634 | 0.0495 | 0.0418 |
借用曝光上热度 0.049486、打乱语义 ID 0.049494,只差约 0.00001,未检出差异,图上按点估计排先后。
稠密测试和自然日志给出的排序对不上,10 对里 5 对方向相反,Kendall τ-b 是 0。按曝光量计权不改变稠密测试的排序,只把热度的领先再放大 0.074 到 0.087。同一批用户、同一份前 100,只换成在别人实际被推过的视频里算,排序就和自然日志一样。热度从第一掉到最后。
文本语义 ID − 双塔,同一批 1,117 人、同一份前 100
两边都显著,差距变化 3 个种子都同号
看数据表
| 1,117 人上的 Recall@100 | 稠密测试 | 借用曝光 |
|---|---|---|
| 文本语义 ID | 0.1983 | 0.0811 |
| 双塔 | 0.1927 | 0.1127 |
| 差 | +0.0057 | −0.0317 |
这批人是有借用目标的 1,117 人,比上面名次图的 1,121 人少 4 个,所以稠密测试的值略有不同。
语义 ID 在稠密测试上领先双塔 0.006,只在实际被推过的视频里算就落后 0.032。换成不剪枝的精确打分,方向也不变(领先约 0.031、落后约 0.020)。我的理解是,借用的目标是当时推荐流里实际在推的视频,双塔有视频编号向量,更容易记住训练期哪些视频常被一起看;语义 ID 用文本码表示视频,推荐会往文本相似的方向偏。这一条没单独检验。
研究二的其他发现
- 前 100 没赢 SASRec,但头部更准。按 Recall@100,文本语义 ID 在稠密测试上低 0.026、自然日志上低 0.030;稠密测试的 NDCG@20 反而高 0.034。
- 束搜索剪掉了不少好视频。文本语义 ID 在束宽 100 下的前 100 和精确打分的前 100,人均只重合 53% 到 64%,Recall@100 少了约 12% 到 13%。精确打分下它在稠密测试上和 SASRec 点估计接近、未检出差异(0.2259 对 0.2260),借用曝光和自然日志上仍落后,所以搜索只解释了一部分。测试集打开后补的
- 冷启动没看到优势。训练窗口里没有交互的 555 个视频,所有模型几乎都召不回,文本语义 ID 只召回 0.07%。
- 打乱对照踩过的坑。“文本好于打乱”按事先写定的规则成立,+0.079,但大头来自打乱模型被早停留在了第一个 epoch。两个语义 ID 模型至少训 5 个 epoch 再挑,只剩 +0.011。两个模型又是各自调参的,这 0.011 也不能全算到文本语义上。这是开测试集前试跑时发现的,判定规则没改,偏离记录在案。
- 种子之间的差比区间大一个数量级。用户 bootstrap 的区间不含训练随机性,所以成立还要求 3 个种子里至少 2 个同号。
能说什么,不能说什么
能说
- 在这批数据和模型上,常规离线评测没有把精排的比较带偏到能检出的程度。
- 常规离线评测会大幅放大召回的绝对差距。把常规测试按随机曝光的热度组成重新加权,logQ 双塔的领先就没了(事后补的描述)。
- 只限制哪些正样本能被观测到,就足以改变召回模型谁好谁差。
不能说
- 不能当线上效果。随机曝光仍是事后收集的离线数据,测不到新模型上线后的闭环、位置影响和留存时长。
- 精排“未检出”不等于等效,排除不了比门槛更小的变化。
- 热度分解、logQ × 温度、混训、束搜索误差都是测试集打开后补的,不改正式结论。
- 自然日志是另一批用户、另一套候选,τ = 0 不能全归到曝光方式上。
落到工程上
- 精排强模型之间离线分不出来,不为离线差的 0.001 选型,挑两三个进 A/B。
- 召回常规离线的绝对差距不能直接信,logQ 双塔的领先到随机曝光上从 0.241 缩到 0.005,不过它仍然第一。
- 点击率随机曝光上整体高估、越冷门高估越多,要用随机曝光样本按热度分组校准。长期留一小桶随机流量做无偏评估和校准,桶要小,也要限定在候选池里。
- 生成式召回束宽要和候选池、码表一起调,上线前拿一小批用户和精确打分对一次。
术语速查
- 召回、精排
- 推荐通常分几步:召回先从全部视频里粗筛出几百个候选,精排再给这些候选逐个打分排序。
- UAUC
- 每个用户内部算 AUC,再每人等权平均。推荐关心同一个用户内部的排序,全局 AUC 还会奖励把爱点的用户整体排前面。
- Recall@100
- 用户的目标视频里,有多少落进模型推的前 100 个。
- 差距变化 D
- 同一对模型换测试集后差距变了多少。研究一是随机曝光上的差减常规测试上的差,研究二是稠密测试上的差减有偏或借用曝光上的差。
- Holm 校正
- 多对比较一起做显著性检验时控制整体犯错率的方法,比 Bonferroni 稍宽松。
- logQ 校正
- 批内负采样时,按视频在训练正样本里的频率扣掉 logit,免得热门视频被过度打压。
- Kendall τ-b
- 两种排序有多一致,1 是完全一致。0 表示名次一致和相反的模型对一样多,互相抵消。
- 语义 ID
- 把视频的内容向量逐层量化成几个离散码,内容相近的视频共用前缀,生成模型逐位生成。
- 束搜索
- 逐位生成时每一步只留得分最高的若干个前缀;前缀下视频多时容易把好视频剪掉。
GitHub 仓库 · 代码、协议、结果文档和证据文件