1 矩阵补充(Matrix Completion)
矩阵补充是向量召回最简单的一种方法,不过现在已经不太常用这种方法了。
1.1 矩阵补充模型
模型的输出是一个实数,是用户对物品兴趣的预估值。这个数越大,表示用户对物品越感兴趣。
Embedding 层之间不共享参数。
1.2 训练
基本想法
-
用户 embedding 参数矩阵记作
。第
号用户对应矩阵第
列,记作向量
。
-
物品 embedding 参数矩阵记作
。第
号物品对应矩阵第
列,记作向量
。
-
内积
是第
号用户对第
号物品兴趣的预估值。
-
训练模型的目的是学习矩阵 A 和 B,使得预估值拟合真实观测的兴趣分数。
数据集
图片中的文字内容如下:
-
数据集:(用户ID,物品ID,兴趣分数)的集合,记作
。
-
数据集中的兴趣分数是系统记录的,比如:
-
曝光但是没有点击 → 0 分
-
点击、点赞、收藏、转发 → 各算 1 分
-
分数最低是 0,最高是 4。
-
训练
-
把用户ID、物品ID映射成向量。
-
第 u 号用户 → 向量 au。
-
第 i 号物品 → 向量 bi。
-
-
求解优化问题,得到参数 A 和 B。
:用户 u 对物品 i 的真实兴趣分数是 y。
:模型对兴趣分数的预估,反应第 u 号用户有多喜欢第 i 号物品。
:真实兴趣分数 y 与预估值之间的差,希望这个差越小越好。
:对每一条记录的差的平方求和,作为优化的目标函数。
:对目标函数求最小化,优化的变量是矩阵
和
。
求最小化可以用随机梯度下降等算法,每次更新矩阵
和
的一列,这样就可以学出矩阵
和
。
1.3 为什么模型叫矩阵补充?
绿色位置表示曝光给用户的物品;灰色位置表示没有曝光。
只要把物品曝光给用户,就知道用户对物品是否感兴趣。我们并不知道用户对没曝光的物品是否感兴趣。
拿绿色位置的数据训练出模型,有了模型就可以预估出所有灰色位置的分数,也就是把矩阵的元素给补全,这就是为什么模型叫矩阵补充。
把矩阵元素补全之后就可以做推荐。给定一个用户,选出用户对应的行中分数较高的物品推荐给该用户。
1.4 矩阵补充在实践中效果不好
-
缺点1:仅用ID embedding,没利用物品、用户属性。
-
物品属性:类别、关键词、地理位置、作者信息。
-
用户属性:性别、年龄、地理定位、感兴趣的类目。
-
双塔模型可以看做矩阵补充的升级版。
-
双塔模型(Two-Tower Model)是一种在深度学习领域,尤其是在推荐系统和信息检索等场景中常见的模型架构。这种模型的核心思想是将输入数据分成两个独立的部分,并通过两个独立的神经网络(即两个塔)分别处理这两个部分,然后在某个层次上将这两个部分数据的语义表示进行融合,以完成最终的任务。
-
缺点2:负样本的选取方式不对。
-
样本:用户—物品的二元组,记作
。
-
正样本:曝光之后,有点击、交互。(正确的做法)
-
负样本:曝光之后,没有点击、交互。(错误的做法)
-
-
缺点3:做训练的方法不好。
-
内积
不如余弦相似度。
-
用平方损失(回归),不如用交叉熵损失(分类)。
-
内积:
优点:直接计算两个向量的点积,简单直观。
缺点:对向量长度敏感,可能导致结果不准确,尤其是当向量长度差异较大时。
余弦相似度:
优点:只考虑向量的方向,忽略长度,适用于高维稀疏向量和对长度不敏感的场景。
缺点:忽略了向量的长度信息,可能无法捕捉到向量之间的绝对差异。
2 线上服务
2.1 模型存储
做完训练之后,要把模型存储在正确的地方,便于做召回。
-
训练得到矩阵 A 和 B。
A 的每一列对应一个用户。
B 的每一列对应一个物品。 -
把矩阵 A 的列存储到 key-value 表。
key 是用户 ID,value 是 A 的一列。
给定用户 ID,返回一个向量(用户的 embedding)。 -
矩阵 B 的存储和索引比较复杂。
2.2 线上服务
在训练好模型,并且把 Embedding 向量做存储之后,可以开始做线上服务。
-
把用户 ID 作为 key,查询 key-value 表,得到该用户的向量,记作
。
-
最近邻查找:查找用户最有可能感兴趣的
第个物品,作为召回结果。
号物品的 embedding 向量记作
。
内积是用户对第
号物品兴趣的预估。
返回内积最大的个物品。
这种最近邻查找有个严重的问题: 如果枚举所有物品,时间复杂度正比于物品数量。
如何加速最近邻查找,避免暴力枚举?
有很多种算法加速最近邻查找,这些算法非常快,即使有几亿个物品最多也只需要几万次内积。这些算法的结果未必是最优的,但是不会比最优结果差多少。
3 近似最近邻查找(Approximate Nearest Neighbor Search)
3.1 支持最近邻查找的系统
-
系统:Milvus、Faiss、HnswLib、等等。
-
衡量最近邻的标准:
-
欧式距离最小(L2距离)
-
向量内积最大(内积相似度)
-
向量夹角余弦最大(cosine相似度)
-
目前推荐系统最常用的是余弦相似度,即最近邻是向量夹角最小的。
有些系统不支持余弦相似度,但这很好解决。如果把所有向量都做归一化,让它们的二范数全都等于1,那么内积就等于余弦相似度。
3.2 展示最近邻查找
这是个散点图,每个点是物品的 Embedding 向量,Embedding 向量都是训练模型的时候计算出来的。
右边的五角星表示一个用户的 Embedding 向量,记作 。我们想要召回这个用户可能感兴趣的物品,这就需要计算向量
与所有点的相似度。如果用暴力枚举,计算量正比于点的数量,也就是物品的数量。
想要减少最近邻查找的计算量,必须要避免暴力枚举。
3.3 一种加速最近邻查找的算法
在做线上服务之前,先对数据做预处理,把数据划分成很多区域,如下图所示(以cosine相似度为标准)。至于如何划分,取决于衡量最近邻的标准。
划分之后,每个区域用一个向量表示。这些向量的长度都是1。划分区域之后,建立索引,把每个区域的向量作为 key,把区域中索引点的列表作为 value。
假如有一个点,那么划分成一万个区域,索引上一个有一万个 key 值。每个向量是一个区域的 key 值,给定一个向量,可以快速取回这个区域内所有的点。有了这样一个索引,就可以在线上快速做召回了。
在线上给用户做推荐,这个用户的 Embedding 向量记作 ,首先把向量
跟索引中这些向量做对比,计算它们的相似度。如果物品数量是几亿,索引中的向量数量也只有几万而已,这一步的计算开销不大。
通过计算之后,发现下图所示向量与 最相似。通过索引找到这个区域内所有的点,每个点对应一个物品。
接下来计算点 跟区域内所有点的相似度。如果一个有几亿个物品被划分到几万个区域,平均每个区域只有几万个点,所以这一步只需要计算几万次相似度,计算量也不大。
假如我们想要找向量 最相似的三个点,也就是夹角最小的三个点,那么会找到下图所示的三个,对应三个物品。这三个物品就是最近邻查找的结果。
哪怕有几亿个物品,用这种近似算法做查找,也只需要计算几万次相似度,比暴力枚举快一万倍。
4 总结
- 矩阵补充
-
把物品ID、用户ID做 embedding,映射成向量。
-
两个向量的内积
作为用户
对物品
兴趣的预估。
-
让
拟合真实观测的兴趣分数,学习模型的 embedding 层参数。
-
矩阵补充模型有很多缺点,效果不好。
-
-
线上召回
-
把用户向量
作为 query,查找使得
最大化的物品
。
-
暴力枚举速度太慢。实践中用近似最近邻查找。
-
Milvus、Faiss、HnswLib 等向量数据库支持近似最近邻查找。
-