- 笔记摘自王树森老师的推荐系统公开课:基于小红书的场景讲解工业界真实的推荐系统
- 评论区有位同学分享了该课程的详细笔记,可供参考:笔记-工业界的推荐系统
- 写博客主要是记录自己认为重要的或没完全掌握的知识点
1 基于物品的协同过滤(ItemCF)
ItemCF的基本思想
- 如果用户喜欢物品1,而且物品1和物品2相似,则用户很可能喜欢物品2
- 预估用户对候选物品的兴趣: ∑ j l i k e ( u s e r , i t e m j ) × s i m ( i t e m j , i t e m ) \sum_jlike(user, item_j)\times sim(item_j,item) ∑jlike(user,itemj)×sim(itemj,item)
计算物品相似度
- 基本思想:两个物品的受众重合度越高,越相似
- 不考虑喜欢程度,即喜欢就是1,不喜欢就是0的情况下,相似度计算方法为:
- 喜欢物品 i 1 i_1 i1 的用户记作 W 1 \mathcal{W}_1 W1
- 喜欢物品 i 2 i_2 i2 的用户记作 W 2 \mathcal{W}_2 W2
- 定义交集 V = W 1 ∩ W 2 \mathcal{V}=\mathcal{W}_1\cap\mathcal{W}_2 V=W1∩W2
- 两个物品的相似度: s i m ( i 1 , i 2 ) = ∣ V ∣ ∣ W 1 ∣ ⋅ ∣ W 2 ∣ sim(i_1,i_2)=\frac{|\mathcal{V}|}{\sqrt{|\mathcal{W}_1|\cdot|\mathcal{W}_2|}} sim(i1,i2)=∣W1∣⋅∣W2∣∣V∣
- 进一步考虑用户对物品的喜欢程度
- 把每个物品表示为一个稀疏向量,向量的每个元素对应一个用户,元素的值为用户对该物品的兴趣分数,则物品相似度可以用余弦相似度表示:
s i m ( i 1 , i 2 ) = ∑ v ∈ V l i k e ( v , i 1 ) ⋅ l i k e ( v , i 2 ) ∑ u 1 ∈ W 1 l i k e 2 ( u 1 , i 1 ) ⋅ ∑ u 2 ∈ W 2 l i k e 2 ( u 2 , i 2 ) 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})}} sim(i1,i2)=∑u1∈W1like2(u1,i1)⋅∑u2∈W2like2(u2,i2)∑v∈Vlike(v,i1)⋅like(v,i2)
- 把每个物品表示为一个稀疏向量,向量的每个元素对应一个用户,元素的值为用户对该物品的兴趣分数,则物品相似度可以用余弦相似度表示:
ItemCF召回的流程
- 维护两个索引
- “用户->物品”的索引:用户最近交互过的n个物品,表示为
(物品ID,兴趣分数)
列表; - “物品->物品”的索引:相似度最高的k个物品,表示为
(物品ID,相似度)
列表。
- “用户->物品”的索引:用户最近交互过的n个物品,表示为
- 线上做召回
- 给定用户ID,通过“用户->物品”索引,找到用户最近感兴趣的物品列表(last-n)。
- 对于last-n列表中的每个物品,通过“物品->物品”的索引,找到top-k相似物品。
- 对于取回的相似物品(最多nk个),用公式预估用户对物品的兴趣分数。
- 兴趣分数计算公式: ∑ j l i k e ( u s e r , i t e m j ) × s i m ( i t e m j , i t e m ) \sum_jlike(user, item_j)\times sim(item_j,item) ∑jlike(user,itemj)×sim(itemj,item)
- 如果物品ID有重复,分数相加,去重
- 返回分数最高的100个物品,作为推荐结果。
2 Swing召回通道
ItemCF的不足之处:如果两个物品不太相似,但恰巧某个小圈子中的人都交互过这两个物品(例如一篇推文被分享到了一个微信群),按照ItemCF的计算方法会认为这两个物品相似度高
Swing模型
- 计算用户重合度
- 用户 u 1 u_1 u1 喜欢的物品记作集合 J 1 \mathcal{J}_1 J1
- 用户 u 2 u_2 u2 喜欢的物品记作集合 J 2 \mathcal{J}_2 J2
- 定义两个用户的重合度: o v e r l a p ( u 1 , u 2 ) = ∣ J 1 ∩ J 2 ∣ overlap(u_1,u_2)=|\mathcal{J}_1\cap\mathcal{J}_2| overlap(u1,u2)=∣J1∩J2∣
- 用户 u 1 u_1 u1 和 u 2 u_2 u2 的重合度高,则他们可能来自一个小圈子,要降低他们的权重
- 计算物品相似度
- 喜欢物品 i 1 i_1 i1 的用户记作集合 W 1 \mathcal{W}_1 W1
- 喜欢物品 i 2 i_2 i2 的用户记作集合 W 2 \mathcal{W}_2 W2
- 定义交集 V = W 1 ∩ W 2 \mathcal{V}=\mathcal{W}_1\cap\mathcal{W}_2 V=W1∩W2
- 两个物品的相似度:
s
i
m
(
i
1
,
i
2
)
=
∑
u
1
∈
V
∑
u
2
∈
V
1
α
+
o
v
e
r
l
a
p
(
u
1
,
u
2
)
sim(i_1,i_2)=\sum_{u_1\in \mathcal{V}}\sum_{u_2\in \mathcal{V}}\frac{1}{α + overlap(u_1,u_2)}
sim(i1,i2)=∑u1∈V∑u2∈Vα+overlap(u1,u2)1
- α α α 是超参数
-
o
v
e
r
l
a
p
(
u
1
,
u
2
)
overlap(u_1,u_2)
overlap(u1,u2) 表示两个用户的重合度
- 重合度越小,说明两个用户的兴趣差异越大,越能说明物品相似
3 基于用户的协同过滤(UserCF)
- 计算用户相似度
- 用户 u 1 u_1 u1 喜欢的物品记作集合 J 1 \mathcal{J}_1 J1
- 用户 u 2 u_2 u2 喜欢的物品记作集合 J 2 \mathcal{J}_2 J2
- 定义交集 I = J 1 ∩ J 2 I=\mathcal{J}_1\cap\mathcal{J}_2 I=J1∩J2
- 两个用户的相似度: s i m ( u 1 , u 2 ) = ∣ I ∣ ∣ J 1 ∣ ⋅ ∣ J 2 ∣ sim(u_1,u_2)=\frac{|I|}{\sqrt{|\mathcal{J}_1|·|\mathcal{J}_2|}} sim(u1,u2)=∣J1∣⋅∣J2∣∣I∣
- 也可以理解为把用户表示为一个稀疏向量,向量每个元素对应一个物品,若用户喜欢这个物品则元素值为1,不喜欢则值为0,相似度就是两个向量的余弦相似度
- 降低热门物品权重
- 热门物品无法反应用户独特的兴趣,在计算相似度时降低权重
- 设
n
l
n_l
nl 为喜欢物品
l
l
l 的用户数量,反映物品的热门程度,则相似度计算公式更新为:
s i m ( u 1 , u 2 ) = ∑ l ∈ I 1 log ( 1 + n l ) ∣ J 1 ∣ ⋅ ∣ J 2 ∣ sim(u_1,u_2)=\frac{{\sum_{l\in{I}} \frac{1}{\log{(1+n_l)}}}}{\sqrt{|\mathcal{J}_1|·|\mathcal{J}_2|}} sim(u1,u2)=∣J1∣⋅∣J2∣∑l∈Ilog(1+nl)1
UserCF召回流程
- 维护两个索引
- 用户->用户列表:相似度最高的k个用户,表示为
(用户ID,相似度)
。 - 用户->物品列表:用户最近交互的n个物品,表示为
(物品ID,兴趣分数)
。
- 用户->用户列表:相似度最高的k个用户,表示为
- 线上做召回
- 利用两个索引,每次取回nk个物品
- 预估用户对每个物品的兴趣分数: ∑ j s i m ( u s e r , u s e r j ) × l i k e ( u s e r j , i t e m ) \sum_jsim(user,user_j)×like(user_j,item) ∑jsim(user,userj)×like(userj,item)
- 返回分数最高的 100 个物品,作为召回结果
4 离散特征值处理
- 离散特征值例如国籍、性别、用户ID、物品ID
- 处理方法
- 建立字典:把类别映射成序号
- 向量化:把序号映射成向量
- One-hot 编码:把序号映射成高维稀疏向量
- Embedding:把序号映射成低维稠密向量
- One-Hot编码
- 举例:国籍有中国、美国、印度等 200 种类别
- 建立字典:中国 → 1,美国 → 2,印度 → 3, …
- One-hot 编码:用 200 维稀疏向量表示国籍
- 未知 → 0 → [0, 0, 0, 0, ⋯ , 0]
- 中国 → 1 → [1, 0, 0, 0, ⋯ , 0]
- 美国 → 2 → [0, 1, 0, 0, ⋯ , 0]
- 印度 → 3 → [0, 0, 1, 0, ⋯ , 0]
- One-Hot编码的局限性:类别数量太大时,维度很大,不适用(例如常见英文单词有几万个)
- 举例:国籍有中国、美国、印度等 200 种类别
- Embedding
- 参数以矩阵的形式保存,矩阵大小是
向量维度 x 类别数量
- 相似物品的 Embedding 距离更近
- Embedding = 参数矩阵 x One-Hot 向量
- 参数以矩阵的形式保存,矩阵大小是
矩阵补充(Matrix Completion)
- 数据集:(用户 ID,物品 ID,真实兴趣分数)的集合,记作 Ω = { ( u , i , y ) } \Omega=\{(u,i,y) \} Ω={(u,i,y)}
- 模型训练
- 用户 embedding 参数矩阵记作 A \bold{A} A,物品 embedding 参数矩阵记作 B \bold{B} B
- 把用户 ID、物品 ID 映射成向量:第 u u u 号用户 → 向量 a u \bold{a}_u au,第 i i i 号物品 → 向量 b i \bold{b}_i bi
- 内积 ⟨ a u , b i ⟩ \left\langle \bold{a}_u,\bold{b}_i \right\rangle ⟨au,bi⟩ 是第 u u u 号用户对第 i i i 号物品兴趣的预估值
- 训练模型的目的是学习矩阵
A
\bold{A}
A 和
B
\bold{B}
B,使得预估值拟合真实观测的兴趣分数
- 求解优化问题,得到参数 A \bold{A} A 和 B \bold{B} B: min A , B ∑ ( u , i , y ) ∈ Ω ( y − ⟨ a u , b i ⟩ ) 2 \min_{\bold{A},\bold{B}}\sum_{(u,i,y)\in\Omega}(y- \left\langle \bold{a}_u,\bold{b}_i \right\rangle)^2 minA,B∑(u,i,y)∈Ω(y−⟨au,bi⟩)2
- 矩阵补充的局限性
- 仅用 ID embedding,没利用物品、用户属性
- 负样本的选取方式不对
- 曝光之后,有点击、交互的物品确实为正样本
- 但曝光之后,没有点击、交互的物品不一定为负样本
- 做训练的方法不好
- 内积 ⟨ a u , b i ⟩ \left\langle \bold{a}_u,\bold{b}_i \right\rangle ⟨au,bi⟩ 不如余弦相似度
- 工业界普遍使用 余弦相似度 而不是 内积
- 用平方损失(回归),不如用交叉熵损失(分类)
- 矩阵补充的线上召回
- 模型存储
- 把矩阵 A \bold{A} A 的列存储到 key-value 表。key 是用户 ID;value 是 A \bold{A} A 的一列,表示一个用户embedding向量
- 矩阵 B \bold{B} B 的存储和索引比较复杂
- 线上服务
- 把用户 ID 作为 key,查询 key-value 表,得到该用户的 embedding 向量,记作 a \bold{a} a
- 最近邻查找:查找用户最有可能感兴趣的
k
k
k 个物品,作为召回结果
- 第 𝑖 号物品的 embedding 向量记作 b i \bold{b}_i bi
- 内积 ⟨ a u , b i ⟩ \left\langle \bold{a}_u,\bold{b}_i \right\rangle ⟨au,bi⟩ 是用户对第 𝑖 号物品兴趣的预估
- 返回内积最大的 k k k 个物品
- 如果枚举所有物品,时间复杂度正比于物品数量,开销太大 -> 使用近似最近邻查找
- 模型存储
近似最近邻查找
- Milvus、Faiss、HnswLib 等向量数据库支持近似最近邻查找
- 衡量最近邻的标准
- 欧式距离最小(L2 距离)
- 向量内积最大(内积相似度)
- 向量夹角余弦最大(cosine 相似度)
- 最常用
- 对于不支持的系统:把所有向量作归一化(让它们的二范数等于 1),此时内积就等于余弦相似度
- 近似最近邻查找
- 图中每个点表示一个物品的 Embedding
- 右边的 ★ 表示用户 a \bold{a} a
- 数据预处理:把数据据划分为多个区域,每个区域用一个单位向量表示(称为索引向量)
- 实际推荐时,先把用户向量与所有的索引向量做对比,找到最相似的索引向量
- 通过索引向量,我们找到索引对应区域中的所有物品,然后再计算该区域中所有物品与 a \bold{a} a 的相似度
双塔模型
- TODO