diff --git a/app/docs/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.mdx b/app/docs/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.mdx index e51cb14..cd86d55 100644 --- a/app/docs/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.mdx +++ b/app/docs/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.mdx @@ -1,12 +1,12 @@ --- -title: '王树森推荐系统学习笔记_召回' +title: "王树森推荐系统学习笔记_召回" description: "" date: "2025-09-22" tags: - tag-one --- -# 王树森推荐系统学习笔记_召回 +# 王树森推荐系统学习笔记\_召回 ## 召回 @@ -54,7 +54,7 @@ $$ $$ sim(i_1, i_2) = \frac{\sum_{v \in \mathcal{V}} like(v, i_1) \cdot like(v, i_2)} -{\sqrt{\sum_{u_1 \in \mathcal{W}_1} like^2(u_1, i_1)} \cdot \sqrt{\sum_{u_2 \in \mathcal{W}_2} like^2(u_2, i_2)}}\ +{\sqrt{\sum_{u_1 \in \mathcal{W}_1} like^2(u_1, i_1)} \cdot \sqrt{\sum_{u_2 \in \mathcal{W}_2} like^2(u_2, i_2)}}\ $$ #### ItemCF 召回的完整流程 @@ -88,6 +88,7 @@ $$ 用户喜欢物品 $i_1$ ,那么用户喜欢与物品 $i_1$ 相似的物品 $i_2$。 物品相似度: + - 如果喜欢 $i_1$、$i_2$ 的用户有很大的重叠,那么 $i_1$ 与 $i_2$ 相似。 - 公式: @@ -99,10 +100,12 @@ $$ **ItemCF 召回通道** 维持两个索引: + - 用户 ➝ 物品列表:用户最近交互过的 $n$ 个物品。 - 物品 ➝ 物品列表:相似度最高的 $k$ 个物品。 线上做召回: + - 利用两个索引,每次取回 $nk$ 个物品。 - 预估用户对每个物品的兴趣分数: @@ -151,13 +154,11 @@ $$ - 对于 $\mathcal{V}$ 中的用户 $u_1$ 和 $u_2$,重合度记作 $\text{overlap}(u_1, u_2)$。 - 两个用户重合度大,则可能来自一个小圈子,权重降低。 - - ### 基于用户的协同过滤(UserCF) #### 基本思想 -如果用户 $user_1$ 跟用户 $user_2$ 相似,而且 $user_2$ 喜欢某物品, 那么用户 $user_1$ 也很可能喜欢该物品。 +如果用户 $user_1$ 跟用户 $user_2$ 相似,而且 $user_2$ 喜欢某物品, 那么用户 $user_1$ 也很可能喜欢该物品。 #### UserCF 的实现 @@ -273,6 +274,7 @@ $$ 字典:中国 ➔ 1,美国 ➔ 2,印度 ➔ 3,⋯ One-hot编码:用 200 维稀疏向量表示国籍。 + - 未知 ➔ 0 ➔ [0,0,0,0,⋯,0] - 中国 ➔ 1 ➔ [**1**,0,0,0,⋯,0] - 美国 ➔ 2 ➔ [0,**1**,0,0,⋯,0] @@ -283,11 +285,13 @@ One-hot编码:用 200 维稀疏向量表示国籍。 可以将独热编码映射为嵌入向量 参数数量:向量维度 × 类别数量。 + - 设 embedding 得到的向量都是 4 维的。 - 一共有 200 个国籍。 - 参数数量 = 4 × 200 = 800。 编程实现:TensorFlow、PyTorch 提供 embedding 层。 + - 参数以矩阵的形式保存,矩阵大小是 向量维度 × 类别数量。 - 输入是序号,比如 “美国”的序号是 2。 - 输出是向量,比如 “美国”对应参数矩阵的第 2 列。 @@ -299,12 +303,11 @@ One-hot编码:用 200 维稀疏向量表示国籍。 离散特征处理:one-hot 编码、embedding。 类别数量很大时,用 embedding。 + - Word embedding。 - 用户 ID embedding。 - 物品 ID embedding。 - - ### 矩阵补充 ![](images\2-5-1.png) @@ -325,10 +328,11 @@ embedding 层矩阵 B 输出的向量 b 是矩阵的一列,物品的数量等 #### 数据集 -数据集:($\text{用户ID}$,** $\text{物品ID}$, 兴趣分数) 的集合,记作 +数据集:($\text{用户ID}$,\*\* $\text{物品ID}$, 兴趣分数) 的集合,记作 $\Omega = \{(u, i, y)\}$。 数据集中的兴趣分数是系统记录的,例如: + - 曝光但是没有点击 ➔ 0 分 - 点击、点赞、收藏、转发 ➔ 各算 1 分 - 分数最低是 0,最高是 4 @@ -336,6 +340,7 @@ $\Omega = \{(u, i, y)\}$。 #### 训练 把用户 ID、物品 ID 映射成向量。 + - 第 $u$ 号用户 ➔ 向量 $\mathbf{a}_u$ - 第 $i$ 号物品 ➔ 向量 $\mathbf{b}_i$ @@ -378,7 +383,7 @@ $$ 2. 把矩阵 **A** 的列存储到 key-value 表。 - key 是用户 ID,value 是 **A** 的一列。 - - 给定用户 ID,返回一个向量(*用户的 embedding*)。 + - 给定用户 ID,返回一个向量(_用户的 embedding_)。 3. 矩阵 **B** 的存储和索引比较复杂。 @@ -430,8 +435,6 @@ a 代表某个用户的embedding向量,图中的散点代表物品的embedding - 暴力枚举速度太慢。实践中用近似最近邻查找。 - Milvus、Faiss、HnswLib 等向量数据库支持近似最近邻查找。 - - ### 双塔模型:模型和训练 #### 双塔模型 @@ -527,7 +530,7 @@ $$ ### 双塔模型:正负样本 -**正样本** +**正样本** - 正样本:曝光⽽且有点击的⽤户—物品⼆元组。 (⽤户对物品感兴趣) @@ -544,45 +547,45 @@ $$ #### 简单负样本 -**简单负样本:全体物品** +**简单负样本:全体物品** -- 未被召回的物品,大概率是用户不感兴趣的。 -- 未被召回的物品 ≈ 整体物品 -- 从整体物品中做抽样,作为负样本。 -- 均匀抽样 or 非均匀抽样? +- 未被召回的物品,大概率是用户不感兴趣的。 +- 未被召回的物品 ≈ 整体物品 +- 从整体物品中做抽样,作为负样本。 +- 均匀抽样 or 非均匀抽样? -**均匀抽样**:对冷门物品不公平 +**均匀抽样**:对冷门物品不公平 -- 正样本大多是热门物品。 -- 如果均匀抽样产生负样本,负样本大多是冷门物品。 +- 正样本大多是热门物品。 +- 如果均匀抽样产生负样本,负样本大多是冷门物品。 -**非均匀抽样**:目的是打压热门物品 +**非均匀抽样**:目的是打压热门物品 -- 负样本抽样概率与热门程度(*点击次数*)正相关。 -- $抽样概率\propto(\text{点击次数})^{0.75}$。 0.75是经验值。 +- 负样本抽样概率与热门程度(_点击次数_)正相关。 +- $抽样概率\propto(\text{点击次数})^{0.75}$。 0.75是经验值。 **简单负样本:Batch内负样本** -- 一个 batch 内有 $n$ 个正样本。 +- 一个 batch 内有 $n$ 个正样本。 -- 一个用户 $p$ 和 $n-1$ 个物品组成负样本。 +- 一个用户 $p$ 和 $n-1$ 个物品组成负样本。 -- 这个 batch 内一共有 $n(n-1)$ 个负样本。 +- 这个 batch 内一共有 $n(n-1)$ 个负样本。 -- 都是简单负样本。(因为第一个用户不喜欢第二个物品。) +- 都是简单负样本。(因为第一个用户不喜欢第二个物品。) -- 一个物品出现在 batch 内的概率 $\propto \text{点击次数}$。 物品越热门,出现在batch内的概率就越高。 +- 一个物品出现在 batch 内的概率 $\propto \text{点击次数}$。 物品越热门,出现在batch内的概率就越高。 -- 物品成为负样本的概率本该是 $\propto {\text{点击次数}}^{0.75}$,但在这里是 $\propto \text{点击次数}$。 +- 物品成为负样本的概率本该是 $\propto {\text{点击次数}}^{0.75}$,但在这里是 $\propto \text{点击次数}$。 - 热门物品成为负样本的概率过大。 -- 物品 *i* 被抽样到的概率: $ p_i \propto {\text{点击次数}} $ +- 物品 _i_ 被抽样到的概率: $ p_i \propto {\text{点击次数}} $ 物品越热门,被抽样到的概率就越高。 -- 预估用户对物品 *i* 的兴趣: $ \cos(\mathbf{a}, \mathbf{b}_i) $ +- 预估用户对物品 _i_ 的兴趣: $ \cos(\mathbf{a}, \mathbf{b}\_i) $ -- 做训练的时候,调整为: $ \cos(\mathbf{a}, \mathbf{b}_i) - \log p_i $ +- 做训练的时候,调整为: $ \cos(\mathbf{a}, \mathbf{b}\_i) - \log p_i $ 这样可以纠偏,避免打压热门物品 具体原理: $\cos(\mathbf{a}, \mathbf{b}_i) - \log p_i $ 作为训练用,物品越热门,这个值越小。当某热门物品与某冷门物品的 $\cos(\mathbf{a}, \mathbf{b}_i)$ 相同时,因为热门物品的 $\cos(\mathbf{a}, \mathbf{b}_i) - \log p_i $ 更小,其训练所用的目标值就更小。当模型训练完成后,输入相同的热门物品与冷门物品,热门物品的 $\cos(\mathbf{a}, \mathbf{b}_i)$ 就大于冷门物品。 @@ -590,28 +593,28 @@ $$ ![](images\2-7-3.png) -#### 困难负样本 +#### 困难负样本 **困难负样本** -- 被粗排淘汰的物品(比较困难)。 -- 精排分数靠后的物品(非常困难)。 +- 被粗排淘汰的物品(比较困难)。 +- 精排分数靠后的物品(非常困难)。 -**对正负样本做二元分类**: +**对正负样本做二元分类**: -- 整体物品(简单)分类准确率高。 -- 被粗排淘汰的物品(比较困难)容易分错。 -- 精排分数靠后的物品(非常困难)更容易分错。 +- 整体物品(简单)分类准确率高。 +- 被粗排淘汰的物品(比较困难)容易分错。 +- 精排分数靠后的物品(非常困难)更容易分错。 -**训练数据** +**训练数据** -- 混合几种负样本。 +- 混合几种负样本。 -- 50% 的负样本是整体物品(简单负样本)。 +- 50% 的负样本是整体物品(简单负样本)。 - 50% 的负样本是没通过排序的物品(困难负样本)。 -#### 常见的错误 +#### 常见的错误 训练找回模型不能用这类负样本 @@ -619,27 +622,26 @@ $$ ![](images\2-7-4.png) -#### **选择负样本的原理** +#### **选择负样本的原理** -**召回的目标**:快速找到用户可能感兴趣的物品。 +**召回的目标**:快速找到用户可能感兴趣的物品。 -- 整体物品(easy):绝大多数是用户根本不感兴趣的。 -- 被排序淘汰(hard):用户可能感兴趣,但是不够感兴趣。 -- 有曝光没点击(没用):用户感兴趣,可能碰巧没有点击。 - - 可以作为排序的负样本, 不能作为召回的负样本。 +- 整体物品(easy):绝大多数是用户根本不感兴趣的。 +- 被排序淘汰(hard):用户可能感兴趣,但是不够感兴趣。 +- 有曝光没点击(没用):用户感兴趣,可能碰巧没有点击。 + - 可以作为排序的负样本, 不能作为召回的负样本。 +#### 总结 -#### 总结 - -- **正样本**:曝光而且有点击。 +- **正样本**:曝光而且有点击。 -- **简单负样本**: - - 整体物品。 - - batch 内负样本。 +- **简单负样本**: + - 整体物品。 + - batch 内负样本。 -- **困难负样本**:被召回,但是被排序淘汰。 +- **困难负样本**:被召回,但是被排序淘汰。 -- **错误**:曝光,但是未点击的物品做召回的负样本。 +- **错误**:曝光,但是未点击的物品做召回的负样本。 ### 双塔模型:线上召回和更新 @@ -647,86 +649,86 @@ $$ ![](images\2-8-1.png) -**双塔模型的召回** +**双塔模型的召回** -**离线存储**:把物品向量 $\mathbf{b}$ 存入向量数据库。 +**离线存储**:把物品向量 $\mathbf{b}$ 存入向量数据库。 -1. 完成训练之后,用物品塔计算每个物品的特征向量 $\mathbf{b}$。 -2. 把几亿个物品向量 $\mathbf{b}$ 存入向量数据库(比如 Milvus、Faiss、HnswLib)。 -3. 向量数据库建索引,以便加速最近邻查找。 +1. 完成训练之后,用物品塔计算每个物品的特征向量 $\mathbf{b}$。 +2. 把几亿个物品向量 $\mathbf{b}$ 存入向量数据库(比如 Milvus、Faiss、HnswLib)。 +3. 向量数据库建索引,以便加速最近邻查找。 -**线上召回**:查找用户最感兴趣的 k 个物品。 +**线上召回**:查找用户最感兴趣的 k 个物品。 -1. 给定用户 ID 和画像,线上用神经网络算用户向量 $\mathbf{a}$。 +1. 给定用户 ID 和画像,线上用神经网络算用户向量 $\mathbf{a}$。 -2. 最近邻查找: - - 把向量 $\mathbf{a}$ 作为 query,调用向量数据库做最近邻查找。 - - 返回余弦相似度最大的 k 个物品,作为召回结果。 +2. 最近邻查找: + - 把向量 $\mathbf{a}$ 作为 query,调用向量数据库做最近邻查找。 + - 返回余弦相似度最大的 k 个物品,作为召回结果。 -事先存储物品向量 $\mathbf{b}$,线上现算用户向量 $\mathbf{a}$,why? +事先存储物品向量 $\mathbf{b}$,线上现算用户向量 $\mathbf{a}$,why? - 每做一次召回,用到一个用户向量 $\mathbf{a}$,几亿物品向量 $\mathbf{b}$。 - (线上计算物品向量的代价过大。) + (线上计算物品向量的代价过大。) - 用户兴趣动态变化,而物品特征相对稳定。 - (可以离线存储用户向量,但不利于推荐效果。) + (可以离线存储用户向量,但不利于推荐效果。) #### 模型更新 -**全量更新 vs 增量更新** +**全量更新 vs 增量更新** -**全量更新**:今天凌晨,用昨天全天的数据训练模型。 +**全量更新**:今天凌晨,用昨天全天的数据训练模型。 -- 在昨天模型参数的基础上做训练。(不是随机初始化) -- 用昨天的数据,训练 1 epoch,即每天数据只用一遍。 -- 发布新的 **用户塔神经网络** 和 **物品向量**,供线上召回使用。 -- 全量更新对数据流、系统的要求比较低。 +- 在昨天模型参数的基础上做训练。(不是随机初始化) +- 用昨天的数据,训练 1 epoch,即每天数据只用一遍。 +- 发布新的 **用户塔神经网络** 和 **物品向量**,供线上召回使用。 +- 全量更新对数据流、系统的要求比较低。 -**增量更新**:做 online learning 更新模型参数。 +**增量更新**:做 online learning 更新模型参数。 -- 用户兴趣会随时发生变化。 -- 实时收集线上数据,做流式处理,生成 TFRecord 文件。 -- 对模型做 online learning,增量更新 ID Embedding 参数。 -- 发布用户 ID Embedding,供用户塔在线上计算用户向量。 +- 用户兴趣会随时发生变化。 +- 实时收集线上数据,做流式处理,生成 TFRecord 文件。 +- 对模型做 online learning,增量更新 ID Embedding 参数。 +- 发布用户 ID Embedding,供用户塔在线上计算用户向量。 ![](images\2-8-2.png) -**问题**:能否只做增量更新,不做全量更新? +**问题**:能否只做增量更新,不做全量更新? -- 一天中每一时段用户的行为不同,小时级数据有偏;分钟级数据偏差更大。 -- 全量更新:random shuffle 一天的数据,做 1 epoch 训练。 -- 增量更新:按照数据从早到晚的顺序,做 1 epoch 训练。 -- 随机打乱 优于 按顺序排列数据,全量训练 优于 增量训练。 +- 一天中每一时段用户的行为不同,小时级数据有偏;分钟级数据偏差更大。 +- 全量更新:random shuffle 一天的数据,做 1 epoch 训练。 +- 增量更新:按照数据从早到晚的顺序,做 1 epoch 训练。 +- 随机打乱 优于 按顺序排列数据,全量训练 优于 增量训练。 #### 总结 **双塔模型** -- 用户塔、物品塔各输出一个向量,两个向量的余弦相似度作为兴趣的预估值。 +- 用户塔、物品塔各输出一个向量,两个向量的余弦相似度作为兴趣的预估值。 -- 三种训练的方式:pointwise、pairwise、listwise。 +- 三种训练的方式:pointwise、pairwise、listwise。 -- 正样本:用户点击过的物品。 +- 正样本:用户点击过的物品。 -- 负样本:整体物品(简单)、被排序淘汰的物品(困难)。 +- 负样本:整体物品(简单)、被排序淘汰的物品(困难)。 -**召回** +**召回** -- 做完训练,把物品向量存储到向量数据库,供线上最近邻查找。 +- 做完训练,把物品向量存储到向量数据库,供线上最近邻查找。 -- 线上召回时,给定用户 ID、用户画像,调用用户塔现算用户向量 $\mathbf{a}$。 +- 线上召回时,给定用户 ID、用户画像,调用用户塔现算用户向量 $\mathbf{a}$。 -- 把 $\mathbf{a}$ 作为 query,查询向量数据库,找到余弦相似度最高的 $k$ 个物品向量,返回 $k$ 个物品 ID。 +- 把 $\mathbf{a}$ 作为 query,查询向量数据库,找到余弦相似度最高的 $k$ 个物品向量,返回 $k$ 个物品 ID。 -**更新模型** +**更新模型** -- 全量更新:今天凌晨,用昨天的数据训练整个神经网络,做 1 epoch 的随机梯度下降。 +- 全量更新:今天凌晨,用昨天的数据训练整个神经网络,做 1 epoch 的随机梯度下降。 -- 增量更新:用实时数据训练神经网络,只更新 ID Embedding,锁住全连接层。 +- 增量更新:用实时数据训练神经网络,只更新 ID Embedding,锁住全连接层。 -- 实际的系统: - - 全量更新 & 增量更新 相结合。 - - 每隔几十分钟,发布最新的用户 ID Embedding,供用户塔在线上计算用户向量。 +- 实际的系统: + - 全量更新 & 增量更新 相结合。 + - 每隔几十分钟,发布最新的用户 ID Embedding,供用户塔在线上计算用户向量。 ### 双塔模型`+`自监督学习 @@ -736,24 +738,24 @@ $$ - 少部分物品占据大部分点击。 - 大部分物品的点击次数不高。 - 高点击物品的表征学得好,长尾物品的表征学得不好。 -- 自监督学习:做 *data augmentation*,更好地学习长尾物品的向量 +- 自监督学习:做 _data augmentation_,更好地学习长尾物品的向量 #### 复习双塔模型 **Batch内负样本** -- 一个 *batch* 内有 $n$ 对正样本。 -- 组成 $n$ 个 *list*,每个 *list* 中有 1 对正样本和 $n-1$ 对负样本。 +- 一个 _batch_ 内有 $n$ 对正样本。 +- 组成 $n$ 个 _list_,每个 _list_ 中有 1 对正样本和 $n-1$ 对负样本。 **Listwise 训练** -- 一个 *batch* 包含 $n$ 对正样本(*有点击*): +- 一个 _batch_ 包含 $n$ 对正样本(_有点击_): $$(\mathbf{a_1}, \mathbf{b_1}), (\mathbf{a_2}, \mathbf{b_2}), \dots, (\mathbf{a_n}, \mathbf{b_n}).$$ - 负样本: $ \{(\mathbf{a_i}, \mathbf{b_j})\}$,对于所有的 $i \neq j $。 -- 鼓励 $ \cos(\mathbf{a_i}, \mathbf{b_i}) $ 尽量大, $\cos(\mathbf{a_i}, \mathbf{b_j})$ 尽量小。 +- 鼓励 $ \cos(\mathbf{a_i}, \mathbf{b_i}) $ 尽量大, $\cos(\mathbf{a_i}, \mathbf{b_j})$ 尽量小。 **损失函数** @@ -773,7 +775,7 @@ $$ **训练双塔模型** -- 从点击数据中随机抽取 $n$ 个 *用户—物品* 二元组,组成一个 *batch*。 +- 从点击数据中随机抽取 $n$ 个 _用户—物品_ 二元组,组成一个 _batch_。 - 双塔模型的损失函数: @@ -805,28 +807,28 @@ $$ **特征变换:Random Mask** -- 随机选一些离散特征(比如 *类别* ),把它们遮住。 +- 随机选一些离散特征(比如 _类别_ ),把它们遮住。 - 例: - - 某物品的 *类别* 特征是 $\mathcal{U} = \{\text{数码}, \text{摄影}\}$。 - - *Mask* 后的 *类别* 特征是 $\mathcal{U'} = \{\text{default}\}$。为代表缺失的默认值。 - - *Mask* 代表把特征中的值都丢掉。 + - 某物品的 _类别_ 特征是 $\mathcal{U} = \{\text{数码}, \text{摄影}\}$。 + - _Mask_ 后的 _类别_ 特征是 $\mathcal{U'} = \{\text{default}\}$。为代表缺失的默认值。 + - _Mask_ 代表把特征中的值都丢掉。 - 比如数码的值为 1,摄影的值为 2,缺失的默认值设置为 0 。那么变换之前的类别特征值可能为 1.5,Mask变换后的就应该为 0 。 - Random mask 后不会把所有的特征都变为默认值,因为只是随机mask一些特征,不会mask所有特征。 **特征变换:Dropout (仅对多值离散特征生效)** -- 一个物品可以有多个 *类别*,那么 *类别* 是一个多值离散特征。 -- *Dropout*:随机丢弃特征中 50% 的值。 +- 一个物品可以有多个 _类别_,那么 _类别_ 是一个多值离散特征。 +- _Dropout_:随机丢弃特征中 50% 的值。 - 例: - - 某物品的 *类别* 特征是 $\mathcal{U} = \{\text{美妆}, \text{摄影}\}$。 - - *Dropout* 后的 *类别* 特征是 $\mathcal{U'} = \{\text{美妆}\}$。 + - 某物品的 _类别_ 特征是 $\mathcal{U} = \{\text{美妆}, \text{摄影}\}$。 + - _Dropout_ 后的 _类别_ 特征是 $\mathcal{U'} = \{\text{美妆}\}$。 - 比如美妆的值为 1,摄影的值为2 。那么变换之前的类别特征值可能为 1.5,变换后的就应该为 1 。 **特征变换:互补特征 (complementary)** - 假设物品一共有 4 种特征: - *ID*,*类别*,*关键词*,*城市* + _ID_,_类别_,_关键词_,_城市_ - 随机分成两组:$\{\text{ID}, \text{关键词}\}$ 和 $\{\text{类别}, \text{城市}\}$ @@ -839,30 +841,26 @@ $$ **特征变换:Mask 一组关联的特征** - 一组特征相关联 - - 受众性别: $\mathcal{U} = \{\text{男}, \text{女}, \text{中性}\}$ + - 受众性别: $\mathcal{U} = \{\text{男}, \text{女}, \text{中性}\}$ - - 类目: $\mathcal{V} = \{\text{美妆}, \text{数码}, \text{足球}, \text{摄影}, \text{科技}, \dots\}$ + - 类目: $\mathcal{V} = \{\text{美妆}, \text{数码}, \text{足球}, \text{摄影}, \text{科技}, \dots\}$ - $u = \text{女}$ 和 $v = \text{美妆}$ 同时出现的概率 $p(u, v)$ 大。 - $u = \text{女}$ 和 $v = \text{数码}$ 同时出现的概率 $p(u, v)$ 小。 - - $p(u)$:某特征取值为 $u$ 的概率。 - - $p(\text{男性}) = 20\%$ - $p(\text{女性}) = 30\%$ - $p(\text{中性}) = 50\%$ - -- $p(u, v)$:某特征取值为 $u$,另一个特征取值为 $v$,同时发生的概率。 +- $p(u, v)$:某特征取值为 $u$,另一个特征取值为 $v$,同时发生的概率。 - $p(\text{女性}, \text{美妆}) = 3\%$ - $p(\text{女性}, \text{数码}) = 0.1\%$ -- 离线计算特征两两之间的关联,用互信息 (*mutual information*) 衡量: +- 离线计算特征两两之间的关联,用互信息 (_mutual information_) 衡量: (如计算 类别 特征与 受众性别 特征的MI) - $$ MI(\mathcal{U}, \mathcal{V}) = \sum_{u \in \mathcal{U}} \sum_{v \in \mathcal{V}} p(u, v) \cdot \log \frac{p(u, v)}{p(u) \cdot p(v)} $$ @@ -871,20 +869,18 @@ $$ - 随机选一个特征作为种子,找到种子最相关的 $k/2$ 种特征。 -- *Mask* 种子及其相关的 $k/2$ 种特征,保留其余的 $k/2$ 种特征。 +- _Mask_ 种子及其相关的 $k/2$ 种特征,保留其余的 $k/2$ 种特征。 -- 比如某物品有四种特征 $\{\text{ID}, \text{类别}, \text{关键词}, \text{城市}\} \quad $ ,种子特征为关键词,与其最相关的特征为城市,那么mask后的特征就为 $\{\text{ID}, \text{类别}, \text{default}, \text{default}\} \quad $ +- 比如某物品有四种特征 $\{\text{ID}, \text{类别}, \text{关键词}, \text{城市}\} \quad $ ,种子特征为关键词,与其最相关的特征为城市,那么mask后的特征就为 $\{\text{ID}, \text{类别}, \text{default}, \text{default}\} \quad $ - 好处与坏处 - - - 好处:比 *random mask*、*dropout*、*互补特征* 等方法效果更好。 + - 好处:比 _random mask_、_dropout_、_互补特征_ 等方法效果更好。 - 坏处:方法复杂,实现的难度大,不容易维护。 - **训练模型** -- 从全体物品中均匀抽样,得到 $m$ 个物品,作为一个 *batch*。 +- 从全体物品中均匀抽样,得到 $m$ 个物品,作为一个 _batch_。 - 做两类特征变换,物品塔输出两组向量: @@ -905,7 +901,7 @@ $$ $$ L_{\text{self}}[i] = -\log \left( \frac{\exp(\cos(\mathbf{b'_i}, \mathbf{b''_i}))}{\sum_{j=1}^{m} \exp(\cos(\mathbf{b'_i}, \mathbf{b''_j}))} \right) $$ - + - 做梯度下降,减少自监督学习的损失: $$ @@ -918,14 +914,14 @@ $$ - 自监督学习: - 对物品做随机特征变换。 - - 特征向量 $\mathbf{b'_i}$ 和 $\mathbf{b''_i}$ 相似度高(*相同物品*)。 - - 特征向量 $\mathbf{b'_i}$ 和 $\mathbf{b''_j}$ 相似度低(*不同物品*)。 + - 特征向量 $\mathbf{b'_i}$ 和 $\mathbf{b''_i}$ 相似度高(_相同物品_)。 + - 特征向量 $\mathbf{b'_i}$ 和 $\mathbf{b''_j}$ 相似度低(_不同物品_)。 - 实验效果:低曝光物品、新物品的推荐变得更准。 -- 对点击做随机抽样,得到 $n$ 对 *用户—物品* 二元组,作为一个 *batch*。 +- 对点击做随机抽样,得到 $n$ 对 _用户—物品_ 二元组,作为一个 _batch_。 -- 从全体 *物品* 中均匀抽样,得到 $m$ 个 *物品*,作为一个 *batch*。 +- 从全体 _物品_ 中均匀抽样,得到 $m$ 个 _物品_,作为一个 _batch_。 - 做梯度下降,使得损失减少: @@ -933,21 +929,19 @@ $$ \frac{1}{n} \sum_{i=1}^{n} L_{\text{main}}[i] + \alpha \cdot \frac{1}{m} \sum_{j=1}^{m} L_{\text{self}}[j]. $$ - - ### Deep Retrieval 召回 - 经典的双塔模型把用户、物品表示为向量,线上做最近邻查找。 -- *Deep Retrieval* 把物品表征为路径 (*path*),线上查找用户最匹配的路径。 +- _Deep Retrieval_ 把物品表征为路径 (_path_),线上查找用户最匹配的路径。 -- *Deep Retrieval* 类似于阿里的 *TDM*。 +- _Deep Retrieval_ 类似于阿里的 _TDM_。 **Outline** 1. 索引: - - 路径 $\rightarrow$ List<物品> - - 物品 $\rightarrow$ List<路径> + - 路径 $\rightarrow$ List<物品> + - 物品 $\rightarrow$ List<路径> 2. 预估模型:神经网络预估用户对路径的兴趣。 @@ -961,11 +955,11 @@ $$ **物品表征为路径** -- 深度:*depth* = 3。也就是层数。 +- 深度:_depth_ = 3。也就是层数。 -- 宽度:*width* = K。 +- 宽度:_width_ = K。 -- 一个物品表示为一条路径 (*path*),比如 $\mathbf{[2,4,1]}$。 +- 一个物品表示为一条路径 (_path_),比如 $\mathbf{[2,4,1]}$。 - 一个物品可以表示为多条路径,比如 $\{\mathbf{[2,4,1]}, \mathbf{[4,1,1]}\}$。 @@ -1000,10 +994,6 @@ $$ p(a, b, c | \mathbf{x}) = p_1(a | \mathbf{x}) \times p_2(b | a; \mathbf{x}) \times p_3(c | a, b; \mathbf{x}) $$ - - - - ![](images\2-10-2.png) 用户的特征向量 $\mathbf{x}$ 经过神经网络,再经过 softmax 激活函数,输出 $p_1$ 向量。$p_1$ 向量代表的是神经网络给 L1 层 k 个节点打的兴趣分数,分数越高越有可能被选中。若L1 层有 k 个节点,则 $p_1$ 为 k 维向量。根据 $p_1$ 从L1层的 k 个节点选出一个节点 a。 @@ -1020,7 +1010,7 @@ $$ **召回**:用户 $\rightarrow$ 路径 $\rightarrow$ 物品 -- **第一步**:给定用户特征,用 *beam search* 召回一批路径。 +- **第一步**:给定用户特征,用 _beam search_ 召回一批路径。 - **第二步**:利用索引 “path $\rightarrow$ List⟨item⟩”,召回一批物品。 @@ -1048,7 +1038,7 @@ $$ p(a,b | \mathbf{x})= p_1(a | \mathbf{x}) \times p_2(b | a; \mathbf{x}) $$ -算出 $4 \times K$ 个分数,每个分数对应一条路径,选出分数 *top 4* 路径。 +算出 $4 \times K$ 个分数,每个分数对应一条路径,选出分数 _top 4_ 路径。 ![](images\2-10-7.png) @@ -1058,7 +1048,7 @@ $$ p(a,b,c | \mathbf{x})= p(a,b | \mathbf{x}) \times p_3(c | a,b; \mathbf{x}) $$ -再算出 $4 \times K$ 个分数,每个分数对应一条路径,选出分数 *top 4* 路径。 +再算出 $4 \times K$ 个分数,每个分数对应一条路径,选出分数 _top 4_ 路径。 **Beam Search** @@ -1074,11 +1064,11 @@ $$ [a^\star, b^\star, c^\star] = \arg\max\limits_{a, b, c} p(a, b, c | \mathbf{x}) $$ -- 贪心算法(*beam size* = 1)选中的路径 $[a, b, c]$ 未必是最优的路径。 +- 贪心算法(_beam size_ = 1)选中的路径 $[a, b, c]$ 未必是最优的路径。 **线上召回** -- **第一步**:给定用户特征,用神经网络做预估,用 *beam search* 召回一批路径。 +- **第一步**:给定用户特征,用神经网络做预估,用 _beam search_ 召回一批路径。 - **第二步**:利用索引,召回一批物品。 - 查看索引 path $\rightarrow$ List⟨item⟩。 @@ -1114,40 +1104,40 @@ $$ - 损失函数: 对 $J$ 条路径的兴趣的加和越大,损失函数就越小。 - + $$ \text{loss} = -\log \left( \sum_{j=1}^{J} p(a_j, b_j, c_j \mid \mathbf{x}) \right) $$ **学习物品表征** -- 用户 *user* 对路径 *path* = $[a, b, c]$ 的兴趣记作: +- 用户 _user_ 对路径 _path_ = $[a, b, c]$ 的兴趣记作: $$ p(\text{path} \mid \text{user}) = p(a, b, c \mid \mathbf{x}) $$ -- 物品 *item* 与路径 *path* 的相关性: +- 物品 _item_ 与路径 _path_ 的相关性: $$ \text{score}(\text{item}, \text{path}) = \sum_{\text{user}} p(\text{path} \mid \text{user}) \times \text{click}(\text{user}, \text{item}) $$ - $\text{click}(\text{user}, \text{item})$ 代表用户是否点击过物品,点击过就是 1 ,没点击过就是 0 。 +$\text{click}(\text{user}, \text{item})$ 代表用户是否点击过物品,点击过就是 1 ,没点击过就是 0 。 -- 根据 $ \text{score}(\text{item}, \text{path}) $ 选出 $J$ 条路径作为 *item* 的表征。 +- 根据 $ \text{score}(\text{item}, \text{path}) $ 选出 $J$ 条路径作为 _item_ 的表征。 ![](images\2-10-8.png) - 选出 $J$ 条路径 $\Pi = \{\text{path}_1, \dots, \text{path}_J\}$,作为物品的表征。 -- **损失函数**(选择与 *item* 高度相关的 *path*): +- **损失函数**(选择与 _item_ 高度相关的 _path_): $$ \text{loss}(\text{item}, \Pi) = -\log \left( \sum_{j=1}^{J} \text{score}(\text{item}, \text{path}_j) \right) $$ -- **正则项**(避免过多的 *item* 集中在一条 *path* 上): +- **正则项**(避免过多的 _item_ 集中在一条 _path_ 上): $$ \text{reg}(\text{path}_j) = (\text{number of items on path}_j)^4. @@ -1171,11 +1161,11 @@ $$ **更新神经网络** - 神经网络判断用户对路径的兴趣: - + $$ p(\text{path} \mid \mathbf{x}) $$ - + - 训练所需的数据: 1. “物品 → 路径”的索引, 2. 用户点击过的物品。 @@ -1209,7 +1199,7 @@ $$ - 一个物品被表征为 $J$ 条路径: $\text{path}_1, \dots, \text{path}_J$ 。 - 如果用户点击过物品,则更新神经网络参数,使分数增大: - + $$ \sum_{j=1}^{J} p(\text{path}_j \mid \mathbf{x}). $$ @@ -1226,13 +1216,13 @@ GeoHash召回 - 用户可能对附近发生的事感兴趣。 - GeoHash:对经纬度的编码,地图上一个长方形区域。 -- 索引:GeoHash ➝ 优质笔记列表 *(按时间倒排)*。 +- 索引:GeoHash ➝ 优质笔记列表 _(按时间倒排)_。 - 这条召回通道没有个性化。 **同城召回** - 用户可能对同城发生的事感兴趣。 -- 索引:城市 ➝ 优质笔记列表 *(按时间倒排)*。 +- 索引:城市 ➝ 优质笔记列表 _(按时间倒排)_。 - 这条召回通道没有个性化。 ##### 作者召回 @@ -1242,39 +1232,39 @@ GeoHash召回 - 用户对关注的作者发布的笔记感兴趣。 - 索引: - + 用户 ➝ 关注的作者 - 作者 ➝ 发布的笔记 + 作者 ➝ 发布的笔记 - 召回: - - 用户 ➝ 关注的作者 ➝ 最新的笔记 + + 用户 ➝ 关注的作者 ➝ 最新的笔记 **有交互的作者召回** -- 如果用户对某笔记感兴趣 *(点赞、收藏、转发)*,那么用户可能对该作者的其他笔记感兴趣。 +- 如果用户对某笔记感兴趣 _(点赞、收藏、转发)_,那么用户可能对该作者的其他笔记感兴趣。 - 索引: - 用户 ➝ 有交互的作者 + 用户 ➝ 有交互的作者 - 召回: - 用户 ➝ 有交互的作者 ➝ 最新的笔记 + 用户 ➝ 有交互的作者 ➝ 最新的笔记 **相似作者召回** - 如果用户喜欢某作者,那么用户喜欢相似的作者。 - 索引: - 作者 ➝ 相似作者 *(k 个作者)* + 作者 ➝ 相似作者 _(k 个作者)_ - 召回: - 用户 ➝ 感兴趣的作者 *(n 个作者)* ➝ 相似作者 *(nk 个作者)* ➝ 最新的笔记 *(nk 篇笔记)* + 用户 ➝ 感兴趣的作者 _(n 个作者)_ ➝ 相似作者 _(nk 个作者)_ ➝ 最新的笔记 _(nk 篇笔记)_ #### 缓存召回 **缓存召回** -想法:复用前 *n* 次推荐精排的结果。 +想法:复用前 _n_ 次推荐精排的结果。 - 背景: - 精排输出几百篇笔记,送入重排。 @@ -1297,10 +1287,9 @@ GeoHash召回 - 如果用户看过某个物品,则不再把该物品曝光给该用户。 - 对于每个用户,记录已经曝光给他的物品。(小红书只召回 1 个月以内的笔记,因此只需要记录每个用户最近 1 个月的曝光历史。) - - 对于每个召回的物品,判断它是否已经给该用户曝光过,排除掉曾经曝光过的物品。 -- 一位用户看过 *n* 个物品,本次召回 *r* 个物品,如果暴力对比, 需要 $O(nr)$ 的时间。 +- 一位用户看过 _n_ 个物品,本次召回 _r_ 个物品,如果暴力对比, 需要 $O(nr)$ 的时间。 #### Bloom Filter @@ -1308,7 +1297,7 @@ GeoHash召回 - 如果判断为 no,那么该物品一定不在集合中。 -- 如果判断为 yes,那么该物品很可能在集合中。 (可能误伤,错误判断未曝光物品为已曝光,将其过滤掉。) +- 如果判断为 yes,那么该物品很可能在集合中。 (可能误伤,错误判断未曝光物品为已曝光,将其过滤掉。) - Bloom filter 把物品集合表征为一个 $m$ 维二进制向量。 - 每个用户有一个曝光物品的集合,表征为一个向量,需要 $m$ bit 的存储。 @@ -1323,7 +1312,6 @@ GeoHash召回 - 曝光物品集合大小为 $n$,二进制向量维度为 $m$,使用 $k$ 个哈希函数。 - Bloom filter 误伤的概率为 $\delta \approx \left( 1 - \exp \left( -\frac{kn}{m} \right) \right)^{k}$。 - - $n$ 越大,向量中的 1 越多,误伤概率越大。(未曝光物品的 $k$ 个位置恰好都是 1 的概率大。) - $m$ 越大,向量越长,越不容易发生哈希碰撞。 @@ -1338,8 +1326,6 @@ $$ ![](images\2-12-3.png) - - **Bloom Filter的缺点** - Bloom filter 把物品的集合表示成一个二进制向量。