二、召回
✨文章摘要(AI生成)
系统梳理召回体系:协同过滤、双塔、Deep Retrieval与曝光过滤。
召回是推荐系统链路中的第一个流程,目的是从几亿的item中选出几千item。进行召回的模型方法是多种多样,包括但不仅限于基于统计学的、基于规则的和基于神经网络的。本章先从最基础的协同过滤开始介绍。
基于物品的协同过滤(ItemCF)
- Item Based Collaborative Filtering, 基于物品的协同过滤 ItemCF的基本思想在于:如果用户喜欢物品
,而物品 与物品 相似,那么用户很可能喜欢物品 。根据该思想,ItemCF的实现需要基于以下步骤:
- 基于转化流程中的动作得到某用户对物品的兴趣分数
; - 离线计算得到的物品之间的相似度
; - 线上预估用户对候选物品的兴趣
。

物品相似度
一般来说,ItemCF的思想认为两个物品的受众重合度越高,两个物品越相似,否则认为两物品不相似。所以计算物品相似度是基于与两个物品交互的用户群体重合度的:
- 设喜欢物品
的用户群体为集合 ,喜欢物品 的用户群体为集合 ; - 定义这两个集合的交集
,那么根据余弦相似度公式简单计算两个物品的相似度为 ,该公式没有考虑用户对物品的喜欢程度。 3. 考虑用户对于不同物品的喜欢程度对相似度的影响:
ItemCF 召回的完整流程
在线上进行实时ItemCF召回之前,需要事先做离线计算,存储计算结果并建立索引:
- 建立”用户 → 物品“的索引:
(1)记录每个用户最近点击、交互过的物品ID; (2)给定任意用户ID,可以利用索引找到该用户近期感兴趣的物品列表。
- 建立”物品→ 物品“的索引:
(1)计算物品之间的两两相似度; (2)对于每个物品,索引该物品最相似的 个物品; (3)给定任意物品ID,可以利用索引快速找到该物品最相似的 个物品。
基于事先建立的索引,实现线上召回:
- 给定用户ID,通过”用户 → 物品“索引,找到用户近期感兴趣的物品列表
; - 对于
列表中的每个物品,通过”物品→ 物品“的索引,找到 相似的物品; - 对于所有取回的相似物品(最多有
个),用公式预估用户对物品的兴趣分数; - 返回分数最高的若干个物品作为召回结果。
线上召回时索引可避免暴力枚举所有物品。虽然离线计算量大,但有效减少了线上计算量。 
Swing 模型
物品相似度:如果喜欢
前面说明过,ItemCF计算物品相似度是基于与两个物品交互的用户群体重合度的,如果重合的用户是一个小圈子(比如微信群),受众完全不同的两个物品也容易被误判为相似度较高,实际落地应用效果较差。因此需要考虑用户之间的物品重合度大小,降低小圈子的权重。故此在ItemCF的基础上提出了Swing模型,通过给用户设置权重解决小圈子问题。
设用户
喜欢的物品集合为 ,用户 喜欢的物品集合为 ,喜欢物品 的用户群体为集合 ,喜欢物品 的用户群体为集合 ; 定义两个用户的重合度为:
,如果用户 和 的重合度高,则他们可能来自一个小圈子,计算物品相似度时需要降低他们的权重; 定义交集
,则两个物品的相似度计算为: 其中
为超参数。这一公式体现了用户群体的重合度越大时,小圈子对物品相似度的贡献越小。
基于用户的协同过滤(UserCF)
UserCF 的原理
如果用户
- 基于转化流程中的动作得到用户对某物品的兴趣分数
; - 离线计算得到的用户之间的相似度
; - 线上预估用户对候选物品的兴趣:
用户相似度
类似于ItemCF和物品相似度,UserCF认为两个用户喜欢的物品重合度越高,两个用户越相似。所以计算物品相似度的计算方式如下:
设用户
喜欢的物品集合为 ,用户 喜欢的物品集合为 。 定义这两个集合的交集
,那么简单计算两个用户的相似度为: 上述计算相似度时并没有区别对待冷门和热门物品,给予了它们同样的权重 1 来影响相似度。由于热门s物品只占所有物品的很小部分,而且很容易出现不同用户有相同的热门物品重合度,故越热门的物品越无法反映用户的独特的兴趣。
所以,为了降低热门物品的权重,用户的相似度计算公式更新为:
其中
是喜欢物品 的用户数量,能够反映物品的热门程度。(对数中+1是为了避免负数的出现,相似度的值域 )
UserCF 召回的完整流程
同样的,在线上实时UserCF召回之前,需要事先做离线计算,存储计算结果并建立索引:
- 建立”用户 → 物品“的索引:
(1)记录每个用户最近点击、交互过的物品ID;(2)给定任意用户ID,可以利用索引找到该用户近期感兴趣的物品列表。 - 建立”用户→ 用户“的索引:
(1)计算用户之间的两两相似度; (2)对于每个用户,索引该用户最相似的 个用户 ; (3)给定任意用户ID,可以利用索引快速找到该用户最相似的 个用户。
基于事先建立的索引,实现线上召回:
- 给定用户ID,通过”用户 → 用户“索引,找到
相似的用户; - 对于每个
相似用户,通过”用户→ 物品“索引找到用户近期感兴趣的物品列表 - 对于所有取回的相似物品(最多有
个),用公式预估用户对每个物品的兴趣分数; - 返回分数最高的若干个物品作为召回结果。
进行线上召回时,ItemCF先通过用户ID索引该用户的感兴趣物品,再利用物品ID索引所有相似物品;UserCF则先通过用户ID所有相似用户,再利用用户ID索引用户感兴趣的物品。两者的使用的召回索引有所区别,但最后都是利用公式计算兴趣分数排序得到召回结果。
离散特征处理
在推荐系统中做离散特征处理的常见方法:
步骤1. 建立字典:把离散特征的所有类别映射成序号。 步骤2. 向量化:把序号映射成向量,包括: 方法(1)one-hot编码:把序号映射成高维稀疏向量; 方法(2)embedding(嵌入):把序号映射成低维稠密向量。
类别数量较少时,可用one-hot编码将类别映射成稀疏向量,在前面通过ItemCF和UserCF进行召回时,在计算相似度时就是将用户和物品表示成了稀疏向量。但在实际应用中,用户和物品的数量是极其巨大的,过于稀疏的向量计算复杂、计算量过大、存储困难,所以将其映射为 <mark style="background: #BBFABBA6;">embedding(低维稠密向量)</mark>是业界常用的方式。TensorFlow、PyTorch等提供embedding层,参数以矩阵的形式保存,矩阵大小(参数量)是 <mark style="background: #BBFABBA6;">向量维度 X 类别数量 </mark>。
如果物品类别数量巨大,导致embedding层参数量巨大,成为神经网络和深度学习系统的瓶颈,需要做额外的优化,以提升存储和计算效率。
可以根据训练后embedding分布了解分类的含义和特征,形成类内聚合、类间分离的效果 
one-hot和embedding的关系:词嵌入(Embedding)的获取过程,<mark style="background: #BBFABBA6;">本质上可以视为对一个可学习的参数矩阵进行查表操作,而这一查找机制正是由One-Hot 向量实现的 </mark>。One-Hot 向量是一种稀疏表示,其长度与词汇表大小相等,且只有一个位置为 
矩阵补充
矩阵补全, 顾名思义就是将一个含有缺失值的矩阵通过一定的方法将其补全为一个完全的矩阵。那么在推荐系统里,要用什么方式补全何种矩阵? 如下所示是基于embedding做推荐的模型:输入用户ID、物品ID,输出用户堆物品兴趣的预估值。模型训练所用数据集为(用户ID,物品ID,兴趣分数)的集合,记作
- 用户embedding参数矩阵记为
,第 号用户对应矩阵第 列,用户ID通过embedding层映射为向量 ;物品embedding参数矩阵记为 ,第 号物品对应矩阵第 列,物品ID通过embedding层映射为向量 ; - 计算内积
即是第 号用户对第 号物品兴趣的预估值; - 训练模型的目的是学习矩阵
和 ,使得预估值拟合真实观测的兴趣分数,所以需要求解如下最优化问题,得到 和 的参数: 。
分析矩阵补充模型:该矩阵即是通过求内积得到的用户对物品的兴趣分数矩阵。因为系统曝光给用户的物品只有很小部分,所以这个矩阵中只有绿色位置有用户对物品的兴趣分数;而因为系统没有曝光给用户某些物品,对应灰色位置没有分数。矩阵补全就是利用绿色位置的数据集来训练模型,利用训练好的模型再来预估灰色位置的兴趣分数,以实现补全用户对物品的兴趣分数矩阵。换言之,系统想要预估用户对未曝光物品的兴趣分数,可以 <mark style="background: #BBFABBA6;">通过已有用户对曝光物品的兴趣分数数据集训练模型,利用这个模型预估用户对未曝光物品的兴趣从而做好推荐 </mark>。
但是,矩阵补全模型在实际落地效果并不理想,所以才有后续譬如双塔模型的提出。其主要问题在于:
1. 仅仅使用ID做embedding,没有考虑物品和用户的属性(多维特征) ,物品类别、关键词、地理位置、作者信息,用户性别、年龄、地理位置、感兴趣的类目都是能把推荐做得更精准的重要特征。双塔模型作为矩阵补充的升级版,利用各种关键特征,具有较好的推荐效果。 2. 负样本的选取方式不对。 样本:用户——物品二元组,记作
正样本:曝光之后存在点击交互的物品当作正样本 负样本:曝光之后没有点击交互的物品当作负样本(错误),在双塔模型里会详细讲解常用的正负样本选取方式。 3. 做训练的方法不对。使用内积不如计算余弦相似度;损失函数用平方损失(预估值拟合真实观测分数,回归问题)不如使用交叉熵损失(分类问题)。
最近邻查找
<mark style="background: #BBFABBA6;">for 线上服务 </mark> 在矩阵补充模型做完模型训练后,需要保存训练得到的矩阵
- 根据用户ID作为key查询key-value表,得到该用户的向量
; 2. 最近邻查找:查找用户最有可能感兴趣的 个物品,作为召回结果。 (1)第 号物品的embedding向量为 ; (2)内积 是用户对第 号物品兴趣的预估; (3)返回内积最大的 个物品。
最近邻查找能在可接受的时间尺度内返回接近最优的结果,Milvus、Faiss、HnswLib等常见系统支持最近邻查找,衡量最近邻查找的标准包括但不限于欧式距离最小、向量内积最小、向量夹角余弦最大。
在应用最近邻查找算法的过程中,为避免暴力枚举(时间复杂度正比于物品数量,在实际的落地很难应用,小红书的笔记数量以亿为单位),前人提出了诸多加速最近邻查找的算法。王老师在课程中以余弦相似度为例,说明了一种加速算法的思路:
近似最近邻查找的例子
- 有
个物品通过embedding得到物品向量,如下图所示,将其向量分布区域划分为 个小区域(余弦划分为扇形区域), 每个区域使用一个单位向量表示; - 以该区域的表示向量作为key,区域中所有物品(数量为
)的列表为value, 个区域就有 个索引; - 在线上快速做推荐,分别计算用户向量
与所有索引向量的相似度(时间复杂度为 ); - 找到相似度最高的索引,再分别计算用户向量
与该索引区域包含的所有 个物品向量的相似度(时间复杂度为 ); - 选取最相似的
个点,作为召回结果。 显然在计算过程中,使用暴力枚举的时间复杂度为 ;加速算法将 (几亿)个物品分别划分到 个(几万)区域,每个区域只有 个(几万)物品,时间复杂度为 ,计算量远远小于暴力枚举。

双塔模型
模型介绍
作为矩阵补充模型的升级,双塔模型左右分别是用户塔和物品塔,分别用于生成表示用户和物品的特征向量;充分使用了用户和物品的多维特征属性;使用余弦相似度替代内积计算预估用户对物品的兴趣分数。如下图所示(以用户塔为例),这两个塔的结构一致,仅在特征变换的方法和神经网络的参数有所差异。
以用户塔为例,把用户ID通过embedding层处理得到特征向量,针对不同离散特征要经过不同的embedding层处理得到对应特征向量,连续特征则通常经过归一化(均值为0,方差为1)、分桶(长尾分布)等处理。将这些特征向量拼接输入神经网络,给出用户表征。物品塔的处理方式类似。 
如下所示,双塔模型总体与矩阵补充类似,区别在于应用更多特征属性,并使用余弦相似度计算用户对物品的兴趣。 
模型训练
| 训练方法 | 选取样本 | 基本思想 (训练目标) | 损失函数 |
|---|---|---|---|
| Pointwise | 独立看待每个正负样本,每次选一个用户和一个物品(可正可负) | 1. 把召回看作二元分类任务;2. 鼓励用户与正样本的余弦相似度接近 +1;3. 鼓励用户与负样本的余弦相似度接近 -1;可将正负样本的比例控制在1:2或者1:3 | 交叉熵损失 |
| Pairwise | 每次取一个正样本、一个负样本以及用户特征三元组 | 鼓励用户与正样本的余弦相似度大于鼓励用户与负样本的余弦相似度。 | 合页损失* |
| Listwise | 用户特征 | 1. 鼓励用户与正样本的余弦相似度尽量大;2. 鼓励用户与每个负样本的余弦相似度尽量小。 | Sampled Softmax Loss** |
| 这里在解释其中*和 ** 的注释内容前,先做如下定义: |
- 某个用户的特征向量,记作
; - 某个物品正样本的特征向量,记作
;某个物品负样本的特征向量,记作 ; 3. 个物品负样本的特征向量分别记作 ; 4. 函数用于计算用户和物品余弦相似度。
现分析注释内容:
根据Pairwise的思想 ,希望实现
。如果 时,没有损失;否则损失等于 。由此推理的合页损失为Triplet Hinge Loss,公式表达为: 其中
是用户和物品样本之间的差距(margin)。 另有一种被称为Triplet Logistic Loss的损失函数也可以实现 ,公式如下,其中 超参数用于控制损失函数的形状:
如下图所示,实际上Listwise也是将召回看成二分类任务。
是模型输出的余弦相似度通过Softmax激活函数转化的结果; 是样本的真实标签,正样本标签为1,负样本标签为0。推理得到的损失函数为:
使用梯度下降法,减小损失函数。
区分召回模型和排序模型
如下所示,将用户特征和物品特征在输入神经网络之前做拼接称为前期融合模型,无法支持最近邻查找等加速算法,计算效率较差,通常用作排序模型
召回任务面对的是海量物品库,必须依赖向量检索(如最近邻查找)技术来瞬间筛选候选集,这要求用户和物品向量能独立生成并预先建立索引;而图中架构强制两者必须结合后才能通过神经网络计算,意味着必须对全库百万级商品逐一进行高成本运算,效率极低。反之,这种架构非常适合排序模型,因为在排序阶段候选数量已大幅减少,该架构能让特征在网络中进行深度的非线性交互,牺牲计算速度来换取更高的预估精度。
不适合召回:无法使用加速算法(核心原因): 图片文字明确提到:“无法支持最近邻查找等加速算法”。 在召回中,我们通常希望使用 向量检索(如 Faiss, ANN) 技术。这要求用户向量和物品向量是独立生成的,这样我们才能提前把所有物品向量算好并建立索引。 但在图片所示的架构中,用户特征和物品特征在网络的最底层就“纠缠”在一起了。这意味着,对于每一个物品,你都必须把它的特征和当前用户的特征拼起来,过一遍完整的神经网络才能得到分数。
适合排序:特征交互更充分(精度高): 这种“前期融合”的架构允许神经网络在底层就开始捕捉用户特征和物品特征之间的非线性交叉关系(Cross features)。相比于简单的向量点积,这种深度的交互能更精准地预估点击率(CTR)。计算量可控: 因为只需要对几百个候选物品进行打分,即使网络结构复杂一点,计算压力也是服务器可以承受的。
正负样本
<mark style="background: #BBFABBA6;">正样本 </mark>:在推荐系统中,一般将 <mark style="background: #BBFABBA6;">曝光而且有点击的“用户-物品”二元组 </mark>作为训练的正样本(用户对物品感兴趣)。但是存在一个实际问题,少部分物品受到了用户的大部分点击(热门物品),导致了正样本大多都是热门物品。所以,在选择正样本时,需要过采样(重复采样)冷门物品,或者降采样(抛弃一些样本)热门物品。
<mark style="background: #BBFABBA6;">负样本 </mark>:但是负样本的选取需要合理且正确,错误的负样本选取会影响模型召回效果。负样本主要根据其分类难度,被分为简单负样本和困难负样本。如下表所示:
| 负样本类型 | 负样本来源 | 选取思想 |
|---|---|---|
| 简单负样本 | 全体物品 | 1. 未被召回的物品,大概率是用户不感兴趣的,分类准确度较高(简单)。因为被召回的物品只是很小一部分,所以未被召回的物品近似等于全体物品。 |
| 2. 非均匀抽样,打压热门物品。抽样概率正比为 | ||
| 简单负样本 | Batch 内负样本 | 1. 一个 Batch 内有 |
| 2. 物品出现在 Batch 内的概率正比于点击次数(而不是 | ||
| 困难负样本 | 被排序淘汰的物品 | 用户感兴趣但是兴趣不够强烈的物品,分类比较困难。 |
| 困难负样本 | 精排分数靠后的物品 | 用户非常感兴趣但是排名靠后的物品,分类非常困难。 |
| 无用负样本 (排序可用) | 曝光但没有点击的物品 | 该物品作为负样本训练召回是错误的,但可作为训练排序的负样本。 |
- Batch内负样本示意图

纠偏*:因为在Batch内物品
被抽样的概率 正比于点击次数,需要通过纠偏来降低热门物品成为负样本的概率。设预估用户对物品 的兴趣分数为 ,则仅在训练时,将其调整为 ,在线上召回的时候不用减去 ,抵消掉它因为高频出现而带来的过度曝光压力,避免过分打压热门物品。
在进行训练时,常见的作法是混合以上几种简单和困难的负样本,比如:
- 50%的负样本是全体物品(简单负样本);
- 50%的负样本是就没有通过排序的物品(困难负样本)。
负样本的选择原理:召回的目标是快速找到用户可能感兴趣的物品,然后交给后续的排序模型逐一甄别。召回只需要区分用户不可能感兴趣或者可能感兴趣的物品,而不需要区分用户对物品感兴趣的程度(比较感兴趣/非常感兴趣)。
- 全体物品:绝大多数是用户根本不感兴趣的
- 被排序淘汰:用户可能感兴趣,但程度不够
- 有曝光但没有点击(每用):用户感兴趣但碰巧没点,可作为排序的负样本,不能作为召回的负样本
线上召回和更新
线上召回
与前面介绍的其他模型相同,在进行线上召回之前,双塔模型需要先事先进行计算、建立索引,利用数据库离线存储物品向量:
- 完成训练后,用物品塔计算每个物品的特征向量
; - 把几亿个物品向量
<mark style="background: #BBFABBA6;">离线存入</mark>向量数据库;- 向量数据库建立索引,以便加速最近邻查找。
需要线上召回给用户推荐物品时,查找用户最感兴趣的
- 给定用户ID和画像(用户多维特征属性),用户塔
<mark style="background: #BBFABBA6;">在线实时</mark>计算用户向量; - 最近邻查找: (1)把向量
作为query,调用向量数据库做最近邻查找; (2)返回余弦相似度最大的 个物品,作为召回结果。
这里需要提前计算存储物品向量
- 每做一次召回所用到的用户向量数量为1,而物品向量数量为几亿,线上计算物品向量的代价太大。
- 用户的兴趣是动态变化的,而物品特征相对稳定。虽然可以离线存储用户向量,但是不利于实时的推荐结果。
全量更新和增量更新
部署好模型后并非一劳永逸了,还需要根据前一段时间的数据结果做模型的更新,更新主要有全量更新和增量更新两种:
| 更新类型 | 全量更新 | 增量更新 |
|---|---|---|
| 基本思想 | 今天凌晨,用昨天全体的数据,在昨天模型参数的基础上(不是随机初始化,也不是昨天后续增量更新的模型参数)做模型训练。 | 用户兴趣会随时变化,几十分钟就可能发生变化。故需要做online learning更新模型参数。 |
| 基本流程 | 1. 用昨天的数据训练1个epoch,每天数据只用一遍;<br>2. 发布新的用户塔神经网络和物品向量(存入数据库),供线上使用; | 1. 实时收集线上数据,做流式处理,生成TFRecord文件;<br>2. 对模型做online learning,增量更新用户ID embedding参数;<br>3. 发布用户ID embedding,供用户塔在线上计算用户向量。 |
| 注意事项 | 1. 基于使用昨天的全体的数据以及昨天的全量模型,不是随机初始化模型,也不是昨天最后的增量模型。<br>2. 全量更新对数据流、系统的要求比较低。因为不需要实时数据流,所以对生成训练数据的速度没有要求。只需要把每天的数据落表,在凌晨做批处理将数据打包成tf record格式文件即可,神经网络和物品向量每天只需重新发布一次 | 1. 增量更新只更新用户ID embedding层参数,不更新神经网络其他部分参数。<br>2. 只有做全量更新时才会更新全连接层参数,主要原因在于工程实践困难 |
那么可以只做增量更新而不做全量更新吗?显然不能: 
- 增量更新不同时间段的用户行为不一样,从统计学的角度出发,以天为总体,小时级的数据是有偏的(跟全天级数据差别很大,用户在不同时间段的行为有差异),分钟级别的数据偏差更大;
- 全量更新random shuffle了一天的数据,而增量更新按照数据从早到晚的顺序做训练。一般来说,训练时,随机打乱优于按顺序排列的数据。故全量训练往往优于增量训练。
既要全量训练得到效果更好的模型参数,也要增量训练实时捕捉用户兴趣的动态变化。
自监督学习
用于改进双塔模型中物品塔的训练效果
回顾双塔模型的训练
基于双塔模型的推荐系统存在严重的头部效应:少部分物品占据大部分点击次数,大部分的物品点击次数不多,所以推荐系统对高点击物品的表征学的好,而对于长尾物品的表征学习困难。所以,引用自监督学习的思想,做data argumentation,可以更好地学习长尾物品的表征。
假设双塔模型原本使用listwise训练方法,使用batch内负样本。 一个batch内
做训练时,鼓励
纠偏:之前讲到物品
从点击数据中随机抽取
对所有用户的损失函数求平均做梯度下降:
自监督学习
上述listwise方法同时训练用户塔和物品塔,下面介绍使用自监督学习仅训练物品塔。
召回中的自监督学习,更像是设立一个辅助任务,帮助系统学到更多已有数据的监督信息(调整塔的参数),有效解决长尾问题;同时应用不同的特征变化能得到更多的向量表征,起到了数据增强或扩充数据集的作用。如下图所示,
- 希望物品
的两个向量表征 和 有较高的相似度,鼓励 尽量大; - 希望物品
和物品 的任意向量表征(如 和 )都有较低的相似度,鼓励 尽量小。
特征变换
| 特征变换 | 基本思想 | 示例 |
|---|---|---|
| Random Mask | 随机挑选一些离散特征,把它们遮住。 | 处理前物品的特征集合u={数码,摄影}; 处理后u={default},全部丢弃。 |
| Dropout | 随机丢弃特征中50%的值(仅对多值离散特征生效)。 | 处理前某特征的向量a=[1,2]; 处理后a=[1, 0],丢弃50%。 |
| 互补特征 | 将若干个特征随机分为两组,两组都是物品表征且特征互不重复。因为表示的都是同一物品,所以鼓励这两个特征向量相似。 | 某物品具有ID、类目、关键词、城市4个特征,随机分成 {ID, default, 关键词, default} 和 {default, 类目, default, 城市} 这两种物品表征。 |
| Mask一组关联的特征 | 1. 使用互信息* (MI, mutual information) 衡量关联度,离线计算特征两两之间的关联矩阵,根据关联矩阵进行特征的随机Mask,把关联的特征全都遮住。<br><br>2. 它比以上三种方法效果都要好,但是比较复杂,实现难度大且不容易维护。 | 1. 设一共有k种特征,离线计算特征两两之间的MI,得到k×k的矩阵;<br><br>2. 随机选一个特征作为seed,找到该seed最相关的k/2种特征;<br><br>3. Mask该seed及其最相关的k/2种特征,保留其余k/2种特征。 |
*:设特征1的类别集合为
,特征2的类别集合为 , 为某特征取值为 的概率, 为某特征取值为 、另一个特征取值为 的概率。则互信息计算公式:
自监督学习训练双塔模型
结合前文描述,自监督学习的训练流程如下:
- 从全体物品中均匀抽样,得到
个物品,作为一个batch; - 做两类特征变换,物品塔输出两组向量
和 ; - 第
个物品的损失函数为: - 做梯度下降,减小自监督学习的损失:
。
为了解决长尾问题,现将自监督学习加入到双塔模型的训练之中:
对点击物品做随机采样,得到
对“用户-物品”二元组,作为一个batch; 从全体物品中均匀抽样,得到
个物品,作为一个batch; 做梯度下降,使得损失(双塔模型损失+自监督学习损失)减小:
其中
是超参数,用于调整自监督学习的影响程度。
自监督学习训练物品塔的 <mark style="background: #BBFABBA6;">实验效果 </mark>:低曝光物品和新物品的推荐变得更加准确
Deep Retrieval 召回
<mark style="background: #BBFABBA6;">与双塔模型召回的区别 </mark>:双塔把 <mark style="background: #BBFABBA6;">向量表征 </mark>作为用户和物品的中介,Deep Retrieval把 <mark style="background: #BBFABBA6;">路径 </mark>作为用户和物品的中介。
经典的双塔模型把用户、物品表示为向量,线上做最近邻查找。Deep Retrieval(字节)把物品表征为路径,线上查找与用户最匹配的路径,类似于阿里的 TDM。
- Outline
- 索引
- 预估模型:神经网络预估用户对路径的兴趣
- 线上召回:
- 训练
- 学习神经网络参数
- 学习
的表征
- 索引
索引
- 用于把物品和路径关联起来
- 用若干节点组成一条路径
- 一个物品可以对应多条路径
- 一条路径也能对应多个物品

预估模型
为了预估用户对路径的兴趣,假设用3个节点表示一条路径
下图中的三个神经网络不共享参数

线上召回
- 给定
<mark style="background: #BBFABBA6;">用户</mark>特征 ,用神经网络预估用户对路径 的兴趣,分数记作 - 用beam search寻找分数
最高的 条path,召回一批<mark style="background: #BBFABBA6;">路径</mark> - 利用索引
召回每条路径上的 个<mark style="background: #BBFABBA6;">物品</mark> - 一共召回
个物品,对物品做初步排序,返回分数最高的若干物品
- beam search:假设有三层,每层有
个节点,那么一共有 条路径,用神经网络给这 路径打分的计算量太大,可以用beam search减小计算量。需要设置超参数beam size,该参数越大则计算量越大,search效果越好。这里设置beam size=4,每次计算 条路径的分数,选出分数最高的四个路径,并删除没有出度的节点。
离线训练
需要同时学习神经网络的模型参数和物品表征(学习物品和路径的对应关系,建立起物品到路径,路径到物品两个索引)。训练过程中只使用由用户和物品组成二元组的正样本(存在点击交互),不使用负样本。
<mark style="background: #BBFABBA6;">神经网络的模型参数 </mark>:把每个物品表征为
<mark style="background: #BBFABBA6;">
物品表征
</mark>:用户 user 对路径
用户作为物品和路径之间的中介,可据此计算物品item与路径path的相关性:
根据
即选择与物品相关性最高的
利用贪心算法更新路径:假设已经把物品表征为
从而确保选中的路径具有较高的分数
其它召回通道
在小红书的推荐系统中,还有如下表所示的几种简单有效、不太重要的召回通道:
| 召回通道 | 原理 | 索引 | 召回流程 |
|---|---|---|---|
| GeoHash召回* | 用户可能对附近发生的事感兴趣,根据用户定位的GeoHash取回该地点最新的若干篇笔记。 | GeoHash→优质笔记列表(按时间倒排) | 无个性化召回(所以选择推荐优质笔记) |
| 同城召回 | 用户可能对同城(曾经生活过的城市)发生的事情感兴趣。 | 城市→优质笔记列表(按时间倒排) | 无个性化召回 |
| 作者召回 | 用户对关注的作者发布的笔记感兴趣。 | 用户→关注的作者 <br>作者→发布的笔记(按时间倒排) | 用户→关注的作者→最新的笔记 |
| 有交互的作者召回 | 如果用户对某笔记感兴趣(点赞、收藏、转发),那么用户可能对该作者的其他笔记也感兴趣。 | 1. 用户→有交互的作者 <br>2. 作者→发布的笔记(按时间倒排) | 用户→有交互的作者→最新的笔记 |
| 相似作者召回 | 如果用户喜欢(关注、交互)某作者,那么用户喜欢相似的作者(类似ItemCF)。 | 1. 用户→感兴趣的作者 <br>2. 作者→相似作者 <br>3. 作者→发布的笔记(按时间倒排) | 用户→感兴趣的作者→相似作者→最新的笔记 |
*:GeoHash索引:为方便检索,把经纬度编码成二进制,表示地图上一个长方形的区域
除此之外,还有一种被称为 <mark style="background: #BBFABBA6;">缓存召回 </mark>的召回方法。在精排到重排的过程中,从几百篇笔记中只筛选了几十篇作为推荐结果,大部分精排结果并没有被曝光。因此,缓存召回希望复用前
- 一旦笔记成功曝光,就从缓存退场;
- 如果超过缓存大小,就移除最先进入缓存的笔记;
- 笔记最多被召回若干次,达到这个次数就退场;
- 每篇笔记最多保存若干天,达到这个天数就退场;
- 想让低曝光笔记缓存更长时间,基于曝光次数设置退场规则。
曝光过滤 & Bloom Filter
在小红书和抖音的推荐系统中,为了避免重复推荐物品,如果用户看过某个物品,则系统不再把该物品曝光给该用户,这个思想称之为曝光过滤。一般来说,曝光过滤是在召回层实现的,它的基本流程如下:
- 对于每个用户,记录已经曝光给他的物品。(小红书只召回1个月以内的笔记,因此只需要记录每个用户最近1个月的曝光历史)
- 对于每个召回的物品,判断它是否已经给该用户曝光过,排除掉曾经曝光过的物品。 长视频平台(如Youtube)可能不做曝光过滤
App前端存在埋点,记录所有曝光的物品,将曝光物品落表的速度要足够快(用户刷新间隔通常较短,几秒钟到几分钟之间),如果没有在下一刷之前将曝光物品写入Bloom Filter,很可能导致下一刷出现重复曝光的物品。实践中将这些曝光物品写入Kafka消息队列,使用Flink实时读取消息队列,计算哈希值并将结果写入Bloom Filter。在完成上一次召回之后,下一次召回请求曝光过滤服务,获取该用户的二进制向量, 召回服务器利用Bloom Filter计算召回物品的哈希值,并与用户的二进制向量做对比,把已经曝光过的物品过滤掉,剩余未曝光的物品发送给排序服务器。
假设以为用户看过
- 如果判断为no,那么该物品一定不在集合中;
- 如果判断为yes,那么该物品很可能在集合中(可能误伤,错误判断未曝光物品为已曝光,将其过滤掉)。
如下所示是Bloom Filter 数据结构实现曝光过滤的原理:
- Bloom Filter 把物品集合表征为一个
维的二进制向量; - 每个用户有一个曝光物品的集合,表征为一个向量,需要
bit的存储空间; - Bloom filter 有
个哈希函数,每个哈希函数把物品ID映射成介于 和 之间的整数; - 以下图(
)为例,已曝光物品ID通过哈希函数映射进了二进制向量中对应的位置,如果该位置为0则调整为1,若为1无需调整; - 对于召回的物品ID,通过哈希函数映射,若对应位置
<mark style="background: #BBFABBA6;">全为1</mark>,则说明该物品已曝光,否则未曝光。
根据前面的描述,Bloom Filter 明显会产生误判。设曝光物品集合大小为
1.
越大,向量中的1越多,误伤概率越大(未曝光物品的位置恰好都是1的概率大); 2. 越大, 向量越长,越不容易发生哈希碰撞; 3. 太大、太小都不好, 有最优取值; 4. 若人为设定可容忍的误伤概率为 ,那么最优参数为: 。这里的推导可参考一位大佬的CSDN文章。
除了会产生误判外,Bloom Filter 还有一个缺点。在每次往集合内添加一个物品,只需要把向量
本章小结
召回的目的在于快速从几亿的物品中初步筛选出几千物品。无论是基于统计学、基于规则还是基于神经网络的召回方法都是行之有效的手段。协同过滤就是一种基础和经典的模型思想。
本章首先是对ItemCF和UserCF这两种协同过滤的介绍,它们使用物品和用户的ID计算相似度,结合用户对物品的交互信息,来计算用户对物品的兴趣分数进行排序作为召回结果。但在工业实践中初始物品数量都以亿为单位,协同过滤使用的稀疏向量过于庞大和稀疏,计算复杂度过高。
因此,引入了使用embedding处理离散特征(比如物品类别)的矩阵补全模型。但是矩阵补全模型仅使用ID做embedding、负样本选取错误而且求内积做训练的方式(回归任务)不对,在实际应用中效果不佳。 针对这些问题,前人在矩阵补全的基础上改进得到了双塔模型。双塔模型使用用户和物品的多维特征属性进行embedding后,通过特征变化再输入神经网络(后期融合)通过计算得到余弦相似度;在负样本选取上,考虑到对简单负样本和困难负样本的混合选取;并将训练任务转化为分类问题,根据不同的训练方式推理了对应的交叉熵损失或合页损失。
但是双塔模型也存在严重的头部效应,不能很好地处理长尾数据,只对高点击物品的表征学的好。所以引入自监督学习的思想,使用多种不同的特征变换(Mask、Dropout、互补等),更好地处理长尾数据、更充分地学习用户和物品的表征信息。
同时,除了基于用户和物品的协同过滤外,本章还介绍了一些基于地理位置、作者的召回通道。另外介绍了一种缓存召回,通过设置退场机制,可以重新利用被精排过滤的召回结果。
以上都是对模型和训练思想的介绍,而在实际中训练完模型要将模型推全上线。进行线上计算的时候,需要使用最近邻查找进行召回结果的输出。原始的最近邻查找的计算代价太大,前人提出了多种加速最近邻查找的方法。除此之外,为了降低线上模型的计算复杂度和时间,需要做线下计算存储物品的表征,并建立多种索引方便快速查找。而在模型上线后,使用全量更新和增量更新进行模型的更新迭代。
最后,在推荐系统中,为了避免出现给用户重复推荐物品的问题,需要在召回层进行曝光过滤。Bloom Filter 是一种常用的方法,但是它只支持添加物品、不支持删除物品,而且存在一定的误伤概率。









