提示:最近系统性地学习推荐系统的课程。我们以小红书的场景为例,讲工业界的推荐系统。
我只讲工业界实际有用的技术。说实话,工业界的技术远远领先学术界,在公开渠道看到的书、论文跟工业界的实践有很大的gap,
看书学不到推荐系统的关键技术。
看书学不到推荐系统的关键技术。
看书学不到推荐系统的关键技术。
王树森娓娓道来**《小红书的推荐系统》**
GitHub资料连接:http://wangshusen.github.io/
B站视频合集:https://space.bilibili.com/1369507485/channel/seriesdetail?sid=2249610
基础知识:
【1】一文看懂推荐系统:概要01:推荐系统的基本概念
【2】一文看懂推荐系统:概要02:推荐系统的链路,从召回粗排,到精排,到重排,最终推荐展示给用户
【3】一文看懂推荐系统:召回01:基于物品的协同过滤(ItemCF),item-based Collaboration Filter的核心思想与推荐过程
【4】一文看懂推荐系统:召回02:Swing 模型,和itemCF很相似,区别在于计算相似度的方法不一样
【5】一文看懂推荐系统:召回03:基于用户的协同过滤(UserCF),要计算用户之间的相似度
【6】一文看懂推荐系统:召回04:离散特征处理,one-hot编码和embedding特征嵌入
提示:文章目录
最近这几篇都是详细讲解向量召回,
矩阵补充是向量召回最简单的一种方法,不过现在已经不太常用这种方法了。
我讲解矩阵补充是为了帮助大家理解下之后我要讲的的双塔模型
我会讲很多教科书里没有写 ,但是在工业界非常有用的知识点。
上一篇文章介绍了embedding,他可以把用户ID或者物品ID映射成向量。
我画的这个模型就是基于embedding做推荐,
模型的输入是一个用户ID和一个物品ID。
模型的输出是一个实数(两个向量的内积),是用户对物品兴趣的预估值rate,
这个数越大,表示用户对物品越感兴趣。
下面我们看一下模型的结构,左边的结构只有一个embedding层,
把一个用户ID映射到一个向量去做向量a,这个向量是对用户的表征。
回忆一下上一篇文章的内容,embedding层的参数是一个矩阵W,
矩阵中列的数量是用户数量,每一列都是图中a这么大的向量。
embedding层的参数数量等于用户数量乘以向量a的大小。
右边的结构是另一个embedding层,把一个物品ID映射到一个向量记作b,
大小跟向量a,这是乘一样的,向量b是对物品的表征。
embedding层的参数是一个矩阵输出的向量b是矩阵的一列矩阵,
中列的数量是物品的数量。
模型一共用了两个embedding层,它们不共享参数,
对向量a和b,求内积得到一个实数作为模型的输出,这个模型就是矩阵补充模型。
接下来我可以解释为什么这个模型叫做矩阵补充。
刚才定义了模型结构,接下来我要讲模型的训练。
首先讲训练的基本思路,用户embedding参数是一个矩阵记作大A,
用户对应矩阵的第u列记作向量au
物品embedding参数是另一个矩阵记作大B,
物品对应矩阵的第b列,记作做向量bi,
向量au和bi的内积是第u个用户和第i个物品兴趣的预估值rate,
内积越大说明用户u对物品i的兴趣越强。
训练模型的目的是学到矩阵A和B,使得预估值rate和真实观测(内积)的兴趣分数矩阵更接近。
A和B是embedding参数。
数据集是很多三元组的集合,每个三元组包含用户ID、物品ID、兴趣分数y,
三元组的意思是:该用户对该物品真实的兴趣分数。
在系统里有记录把数据集记作Ω,
它是这样三元组的集合,U和I分别是用户ID和物品ID,Y是真实观测的兴趣分数,
数据集中的兴趣分数是系统记录的。
这里举个例子,凡是有曝光的,系统都会记录兴趣分数,
曝光的意思是把物品展示给用户,
如果有曝光但是没有点击,说明不感兴趣,分数为零。
如果有点击、点赞、收藏、转发这些行为各算一分。
这些行为都说明用户对物品感兴趣。
那么兴趣分数最低是零分,最高是四分。
训练的目的就是让模型的输出一个兴趣分数
模型中embedding层可以把用户ID=和物品ID映射成向量
优化映射成向量au
第i号物品映射成向量bi
训练的时候要求解这样一个优化问题,优化变量是矩阵A和B,它们是embedding参数。
仔细看一下公式,这里的三元组UIY是训练集中的一条数据,意思是用户u对物品I的真实兴趣,分数是Y。
<内积>这一项是向量au和bi的内积,它是模型对性取分数的预估,
反应第u号用户有多喜欢第i号物品,
y这一项是真实性取分数(groundtruth),
然后求Y与预估值<内积>之间的差,
我们希望这个差越小越好,
我们取差的平方,差的平方越小,则预估值越接近真实值。
Y对每一条记录的差的平方求和作为优化的目标函数。【这就是典型的MSE误差损失函数】
对目标函数求最小化优化的变量是矩阵A和B,
求最小化损失函数,可以用随机梯度下降的算法,
每次更新矩阵A和B的一列,这样就可以学出矩阵A和B。
我来解释一下为什么这个模型叫做矩阵补充。
看一下这个矩阵,
矩阵每一行对应一个用户,
每一列对应一个物品矩阵中的每个元素,
表示一个用户对一个物品的真实性矩阵数。
系统里物品很多,一个用户看过的物品,只是系统中的极少数在矩阵中。
绿色位置表示曝光给用户的物品,
灰色位置表示没有曝光的物品,
只要把物品曝光给用户,我们就知道用户对物品是否感兴趣。
曝光了没点击说明不感兴趣,分数是零
曝光之后,客户可能会点击,点赞,收藏,转发每个都算一分,加起来最多有四分,
比如这个绿色位置表示,第3号用户对第2号物品的兴趣,分数等于4
矩阵中只有少数位置是绿色
大多数位置都是灰色,也就是没有曝光给用户的,我们并不知道用户对没曝光的物品是否感兴趣。
我们刚才拿绿色位置的数据训练出了模型,有了模型,
我们就可以预估出所有灰色位置的分数,
也就是把矩阵的元素给补全,这就是为什么模型叫做矩阵补充,
把矩阵元素补全之后,我们就可以做推荐。
给定一个用户,我们选出用户对应的行中分数较高的物品推荐给该用户。
okay,这本文介绍矩阵补充方法,只是为了帮助大家理解向量召回而已。
矩阵补充并不是工业技能work的方法,在实践中并不会用矩阵补充。
第一个缺点是矩阵补充,没有利用物品属性和用户属性
矩阵补充,仅仅用到了embedding,
用两个embedding层分别把物品ID和用户ID映射成两个向量,仅此而已。
在小红书的场景下,物品属性包括类目、关键词、地理位置、作者信息等等,
用户属性包括性别、年龄、地理定位,感兴趣的类目知道这些属性,召回可以做的更精准。
下一篇文章咱们介绍双塔模型
它可以看作矩阵补充模型的升级版,
双塔模型不单单使用物品ID和用户ID,
还会结合各种物品属性和用户属性,
双塔模型的实际表现非常好。
这里的样本指的是用户u和物品I的二元组记录即括号u i
训练向量召回模型需要正样本和负样本矩阵,
补充的正样本指的是物品给用户曝光之后有点击交互的行为,这样的正样本是OK的,没问题,工业界都这么做,
但是负样本的选取方式是完全错误的。
矩阵补充的负样本是曝光之后没有点击交互的物品,这是种想当然的做法。
学数据的人可能以为这样没错,但可惜这样在实践中不work。
后面会专门用一篇文章讲解正负样本怎么选……
第三个缺点是训练模型的方法不好,矩阵补充模型,用向量au和bi的内积作为兴趣分数的预估,
这样没错,可以work,但是效果不如余弦相似度。
工业界普遍使用余弦相似度,而不是用内积矩阵补充,用平方损失函数,也就是做回归,
让预估的兴趣分数以和真实的兴趣分数。
这样做的效果不如交叉商损失函数,也就是做分类。
工业界通常做分类,判定一个样本是正样本还是负样本,
在训练好模型之后,可以把模型用作配件系统中的召回通道,
比如在用户刷小红书的时候,快速找到这个用户可能感兴趣的一两百篇笔记,
做完训练之后再把模型存储在正确的地方。
便于做召回训练得到矩阵A和B,它们是以embedding参数
A的每一列对应一个用户,
B的每一列对应一个物品。
做推荐的时候要用到矩阵A和B,这两个矩阵可能会很大,
比如小红书有几亿用户,几亿篇笔记,
那么这两个矩阵的列数都是好几亿。
为了快速读取和快速查找,需要特殊的存储方式,
把矩阵a的列存储到一张key – value表(哈希表),存储的key值是用户的ID。
Value是矩阵a的一列,
给定用户ID在表中做查找,得到一个向量,也就是该用户的embedding。
另一个矩阵B也很大,B的存储和索引比较复杂,不能简单的用key value存储。
稍后我会解释原因以及解决方案。
在训练好模型,并且把以百里向量做存储之后,可以开始做线上服务。
某用户刷小红书的时候,小红书的后台会开始做召回,
把周围用户最可能感兴趣的K的物品作为召回结果,这叫做最近邻查找:nearest neighbor search
把第i个物品embedding向量记作bi,
计算用户向量a和物品向量bi的内积。
这是用户对第i号物品进取分数的预估。
返回内地最大的K的物品,比如K等于100,这些物品就是召回的结果。
这种最近邻查找有个严重的问题,如果逐一对比所有物品,
暴力方法的时间复杂度会正比于物品数量,这种巨大的计算量是不可接受的。
比如小红书有几亿篇比积,那么就有几亿个向量B逐一计算向量a和每个向量B的内积,
是不现实的根本做不到线上的实时计算,
避免暴力枚举有很多种算法加速
比如:快速最近邻查找,这些算法非常快,即使有几亿个物品,最多也只需要几万次内积。
这些算法的结果未必是最优的,但是不会比最优结果差又少。
比较有名的包括milvus,faiss,hnswlib等等,
做最近邻查找需要定义什么是最近邻,
也就是衡量最近邻的标准,
比如最近邻距离可以定义为欧式距离最小的最近邻,
也可以定义为向量内积最大的,这叫做内积相似度。
本文的矩阵补充用的都是内积相似度,
目前推荐系统最常用的是余弦相似度,其最近邻是向量夹角最小的。
有些系统不支持余弦相似度,但这很好解决。
如果你把所有向量都做归一化,让它们的二范数全都等于一,那么内积就等于余弦相似度。
这是个散点图,每个点是一个物品的embedding向量,embedding向量都是训练模型的时候计算出来的。
右边的五角星表示,一个用户的embedding向量记作a,
我们想要召回这个用户可能感兴趣的物品,
这就需要计算向量a与所有点的相似度。
如果用暴力枚举的话,计算量正比于点的数量,也就是物体的数量。
想要减少最近查找的计算量,必须要避免暴力枚举。
这里我介绍一种加速最近查找的算法,在做线上服务之前,先对数据做预处理,
把数据划分成很多区域,比如这样划分,
至于如何划分,取决于衡量最近邻的标准
如果是cos相似度,那么划分的结果就是这样的扇形,
如果是用欧式距离,那么划分的结果就是多边形划分,
之后每个区域用一个向量表示,
比如那个蓝色区域,用那蓝色向量表示,
比如那个黄色区域,用那个黄色向量表示,
这些向量长度都是一。
划分区域之后建立索引,把每个区域的向量作为key,把扇区区域中所有点的列表作为value,【这样就省掉了大量的key,用一个代表表示】
给定这个向量就能取回蓝色扇形区域中的所有的点。
划分区域之后,每个区域都用一个单位向量来表示,
假如有一个点,那么划分成1万个区域,索引上一共有1万个key值,
每个向量是一个区域的key值,
给定一个向量可以快速取回这个区域内所有的点。
有了这样一个索引,就可以在线上快速做召回了。
在线上给一个用户做推荐,这个用户的一个向量记作a。
我们首先把向量a跟索引中这些代表向量做对比(量很少),计算它们的相似度,
如果物品数量是几亿,索引中的向量数量也只有几万而已。
这一步的计算开销不大,
通过索引,我们找到这个区域内所有的点,每个点对应一个物品。
接下来我们计算点a跟区域内所有点的相似度,
如果一共有几亿个物品被划分到了几万个区域,
平均每个区域只有几万个点,所以这一步只需要计算几万次,相似度计算量也不大。
也就是夹角最小的三个点,他们会找到这三个对应三个物品,这三个物品就是最近邻查找的结果,
哪怕有几亿个物品,用这种加速算法做查找,也只需要计算几万次,
相似度比暴力枚举快1万倍。
这就是加速的最近邻算法查找?如何,够强吧!!!——说白了就先大范围搜索,再小范围精确对比
最后总结一下本文内容,主要介绍了矩阵补充模型,
矩阵补充的想法是把物品ID和用户ID做embedding映射成两个向量,
两个向量记作au和bi
两者内积作为用户u对物品I兴趣的预估值rate。
训练的时候,我们让au和bi的内积拟合真实观测到的兴趣分数,
用回归的方式学习模型中embedding的参数矩阵,
补充是个学术界的模型,有很多缺点导致效果不好。
工业界其实不用矩阵补充模型,
而是用更先进的双塔模型,
假如用矩阵补充在现场做召回也是可以的,尽管效果不好。
把用户向量a作为query查找,使得向量a和bi内积最大化的物品I
寻找这样内积最大的topK物品I作为召回。
在工业界的场景下,有上亿物品做暴力媒体的速度太慢了不可行,
实践中都用近似最近邻加速算法进行查找,不需要做枚举。
我已经讲了近似最近邻加速算法进行查找的做法,
工业界通常会用一些开源的向量数据库,比如milvus,faiss,hnswlib,
这些向量数据库都会支持近似最近邻加速算法进行查找。
完事!
提示:如何系统地学习推荐系统,本系列文章可以帮到你
(1)找工作投简历的话,你要将招聘单位的岗位需求和你的研究方向和工作内容对应起来,这样才能契合公司招聘需求,否则它直接把简历给你挂了
(2)你到底是要进公司做推荐系统方向?还是纯cv方向?还是NLP方向?还是语音方向?还是深度学习机器学习技术中台?还是硬件?还是前端开发?后端开发?测试开发?产品?人力?行政?这些你不可能啥都会,你需要找准一个方向,自己有积累,才能去投递,否则面试官跟你聊什么呢?
(3)今日推荐系统学习经验:模型的输出是一个实数(两个向量的内积),是用户对物品兴趣的预估值rate,这个数越大,表示用户对物品越感兴趣。加速的最近邻算法查找说白了就先大范围搜索,再小范围精确对比