课程链接

1 双塔模型:正负样本

1.1 正样本

  • 正样本:曝光而且有点击的用户—物品二元组。(用户对物品感兴趣)。

  • 问题:少部分物品占据大部分点击,导致正样本大多是热门物品。

  • 解决方案:过采样冷门物品,或降采样热门物品。

    • 过采样up-sampling):一个样本出现多次。

    • 降采样down-sampling):一些样本被抛弃。

1.2 如何选择负样本?

简单负样本:全体物品

  • 未被召回的物品,大概率是用户不感兴趣的。

  • 未被召回的物品 ≈ 全体物品

几亿个物品里边只有几千个被召回,也就是说几乎所有的物品都没有被召回。

  • 从全体物品中做抽样,作为负样本。

  • 均匀抽样 or 非均匀抽样?

均匀抽样:对冷门物品不公平

  • 正样本大多是热门物品。

  • 如果均匀抽样产生负样本,负样本大多是冷门物品。

非均匀抽样:目的是打压热门物品

  • 负样本抽样概率与热门程度(点击次数)正相关。

简单负样本:Batch 内负样本

正样本

负样本

  • 一个 batch 内有 n 个正样本。

  • 一个用户和 n−1 个物品组成负样本。

  • 这个 batch 内一共有 n(n-1) 个负样本。

  • 都是简单负样本。(因为第一个用户不喜欢第二个物品。

Batch 内负样本存在一个问题

  • 一个物品出现在 batch 内的概率 ∝ 点击次数

  • 物品成为负样本的概率本该是 ∝ (点击次数)^0.75,但在这里是 ∝ 点击次数

  • 热门物品成为负样本的概率过大。

一个物品成为负样本的概率越大,模型对这个物品打压就越狠。

对负样本应该打压,但这里打压的太狠了,这样会造成偏差。

参考文献:

  • Xinyang Yi et al. Sampling-Bias-Corrected Neural Modeling for Large Corpus Item Recommendations. In RecSys, 2019.

  • 该文献讲了如何修正偏差

如何修正偏差 

  • 物品 i 被抽样到的概率:p_i​∝ 点击次数
  • 预估用户对物品 i 的兴趣:\cos \left ( a,b_i \right )
  • 做训练的时候,调整为:\cos \left ( a,b_i \right )-\log p_i

这样可以纠偏,避免过分打压热门物品。

训练结束之后,在线上做召回的时候,还是用原本的余弦相似度 \cos \left ( a,b_i \right ),线上召回的时候不用做这种调整

困难负样本 

  • 困难负样本:

    • 被粗排淘汰的物品(比较困难)。

    • 精排分数靠后的物品(非常困难)。

  • 对正负样本做二元分类:

    • 全体物品(简单)分类准确率高。

    • 被粗排淘汰的物品(比较困难)容易分错。

    • 精排分数靠后的物品(非常困难)更容易分错。

训练数据

  • 混合几种负样本。

  • 50%的负样本是全体物品(简单负样本)。

  • 50%的负样本是没通过排序的物品(困难负样本)。

常见的错误

        很多人以为可以把曝光但是没有点击的物品作为负样本,其实这是错误的。

1.3 选择负样本的原理

召回的目标:快速找到用户可能感兴趣的物品。

  • 全体物品(easy):绝大多数是用户根本不感兴趣的。

  • 被排序淘汰(hard):用户可能感兴趣,但是不够感兴趣。

  • 有曝光没点击(没用):用户感兴趣,可能碰巧没有点击。

有曝光没点击可以作为排序的负样本,不能作为召回的负样本。

这是工业界的共识,是通过反复做实验得出的结果。

1.4 总结

  • 正样本:曝光而且有点击。

  • 简单负样本

    • 全体物品。

    • batch内负样本。

  • 困难负样本:被召回,但是被排序淘汰。

  • 错误:曝光、但是未点击的物品做召回的负样本。

2 双塔模型:线上召回和更新

2.1 线上召回

离线存储

        在训练好模型之后,开始线上服务之前,先用右边的物品塔提取物品的特征。

         向量数据库存储特征向量和物品ID的二元组,用作最近邻查找。

线上召回

        用户塔不要事先计算和存储向量,而是当用户发起推荐请求的时候,调用神经网络在线上现算一个特征向量 a,然后把向量 a 作为 query,去数据库中做检索查找最近邻。

总结:双塔模型的召回 

  • 离线存储:把物品向量 b 存入向量数据库。

    • 完成训练之后,用物品塔计算每个物品的特征向量 b

    • 把几亿个物品向量 b 存入向量数据库(比如 Milvus、Faiss、HnswLib)。

    • 向量数据库建索引,以便加速最近邻查找。

  • 线上召回:查找用户最感兴趣的 k 个物品。

    • 给定用户 ID 和画像,线上用神经网络算用户向量 a

    • 最近邻查找:

      • 把向量 a 作为 query,调用向量数据库做最近邻查找。

      • 返回余弦相似度最大的 k 个物品,作为召回结果。

接下来这些物品会跟 ItemCF、Swing、UserCF 等召回通道的结果融合,然后经过排序,最终展示给用户。

思考

        事先存储物品向量 b,线上现算用户向量 a,why?

  • 每做一次召回,用到一个用户向量 a,几亿物品向量 b。(线上算物品向量的代价过大。

  • 用户兴趣动态变化,而物品特征相对稳定。(可以离线存储用户向量,但不利于推荐效果。

2.2 模型更新

全量更新

全量更新:在每天今天凌晨,用前一天的数据训练模型。

  • 昨天模型参数的基础上做训练。(不是随机初始化

  • 用昨天的数据,训练 1 epoch,即每天数据只用一遍。

  • 发布新的用户塔神经网络和物品向量,供线上召回使用。

  • 全量更新对数据流、系统的要求比较低。

全量更新不需要实时的数据流,对生成训练数据的速度没有要求,延迟一两个小时也没有关系。

全量更新对系统的要求也很低,每天做一次全量更新,所以只需要把神经网络和物品向量每天发布一次就够了

增量更新

增量更新:做 online learning 更新模型参数。

  • 用户兴趣会随时发生变化。

  • 实时收集线上数据,做流式处理,生成 TFRecord 文件。

  • 对模型做 online learning,增量更新 ID Embedding 参数。(不更新神经网络其他部分的参数。

  • 发布用户 ID Embedding,供用户塔在线计算用户向量。

只有在做全量更新的时候才会更新全连接层的参数。

全量更新 vs 增量更新 

问题:能否只做增量更新,不做全量更新?

        只做增量更新当然要比既做全量又做增量简单,工程实现会更容易,训练消耗的机器资源也会更少。

        实验后发现只做增量效果不好,最好还是既做全量又做增量。

效果不好的原因

  • 小时级数据有偏;分钟级数据偏差更大。

  • 全量更新:random shuffle 一天的数据,做 1 epoch 训练。

  • 增量更新:按照数据从早到晚的顺序,做 1 epoch 训练。

  • 随机打乱优于按顺序排列数据全量训练优于增量训练

做全量训练的时候,要随机排列数据,也就是 random shuffle,这样是为了消除偏差。

3 总结

​​​​​​​双塔模型

  • 用户塔、物品塔各输出一个向量,两个向量的余弦相似度作为兴趣的预估值。

  • 三种训练的方式:pointwise、pairwise、listwise。

  • 正样本:用户点击过的物品。

  • 负样本:全体物品(简单)、被排序淘汰的物品(困难)。

召回

  • 做完训练,把物品向量存储到向量数据库,供线上最近邻查找。

  • 线上召回时,给定用户 ID、用户画像,调用用户塔现算用户向量 a

  • a 作为 query,查询向量数据库,找到余弦相似度最高的 k 个物品向量,返回 k 个物品 ID。

更新模型

  • 全量更新:今天凌晨,用昨天的数据训练整个神经网络,做 1 epoch 的随机梯度下降。

  • 增量更新:用实时数据训练神经网络,只更新 ID Embedding,锁住全连接层。

  • 实际的系统

    • 全量更新 & 增量更新相结合。

    • 每隔几十分钟,发布最新的用户 ID Embedding,供用户塔在线计算用户向量(这样的好处是,可以捕捉到用户最新的兴趣点)。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐