小红书的推荐系统

曝光→点击,停留几秒(说明不是误点)→阅读→点赞收藏转发评论

转化流程

image-20250218105011376

(抖音没有下滑和点击)

(短期)消费指标

image-20250218105125153

(不是衡量推荐系统好坏的根本指标)

北极星指标

image-20250218105516452

(最关键的指标,衡量好坏的根本标准)

通常来讲,点击率、点赞率与使用时长和阅读数量的涨跌是一致的,万一有冲突,以北极星指标为准。

希望推荐系统能激励作者发布,让我们的内容池变大,优质内容池是核心竞争力;激励发布通常是由冷启动负责(后面再讲冷启动)

实验流程

算法工程师的工作:对模型、特征、策略、系统做改进

image-20250218110355282

离线实验的结果有参考价值,能大致反映出算法的好坏;但是离线实验并没有线上实验可靠,想最终判断算法的好坏还是需要做线上实验。

北极星指标都是线上指标,只能通过线上实验获得,做离线实验无法得到。

具体做法是开小流量AB测试,把用户随机分为实验组和对照组,实验组用新策略,对照组用就策略;对比两者的业务指标,判断新策略是否会显著由于旧策略;如果新策略显著优于旧策略,可以加大流量,最终推全

推荐系统链路

image-20250218111405712

例如,小红书有上亿篇笔记,当刷新小红书的时候,系统会调用几十条召回通道,每条召回通道会取回几十到几百篇笔记,一共取回几千篇笔记。

做完召回之后,要从几千篇笔记中选出最感兴趣的。

粗排:用规模比较小的机器学习,给几千篇笔记逐一打分;按照分数做排序和截断,保留分数最高的几百篇笔记

精排:要用到大规模的深度神经网络,给几百篇笔记逐一打分;精排的分数反映出用户对笔记的兴趣。在精排之后,可以做截断,也可以不做截断(小红书的精排不做截断,所有几百篇笔记都带着分数进入重排)。

重排:会根据精排分数和多样性分数做随机抽样,得到几十篇笔记,然后把相似内容打散并且插入广告和运营内容。

推荐系统的目标:从物品的数据库中选出几十个物品展示给用户。

在小红书的场景下,物品就是笔记。一共有几亿篇笔记。

召回

image-20250218112714548

返回几千篇笔记,然后推荐系统会融合这些笔记,做去重和过滤。

过滤:排除掉作者不喜欢的笔记,不喜欢的话题

排序

用机器学习模型,预估用户对笔记的兴趣,保留分数最高的笔记

如果直接用大规模的神经网络,逐一对几千篇笔记打分,花费的代价会很大。为了解决计算量的问题,通常把排序分为粗排和精排两步。

粗排用比较简单的模型,快速给几千篇笔记打分,保留分数最高的几百篇笔记。

精排用较大的神经网络对几百篇笔记打分,不用做截断。

精排模型比粗排模型大很多,用的特征也更多。所以精排模型打分分数更可靠,但是精排的计算量很大——这就是为什么要先用粗排做筛选,然后用精排,这样做可以比较好的平衡计算量和准确性。

image-20250218113751470

做完粗排和精排会得到几百篇笔记,每篇笔记会有一个分数,反映了用户对这些笔记的兴趣有多高。

image-20250218204527808

重排

可以直接把笔记按照模型打的分数做排序,然后展示给用户,但此时的结果还存在不足,需要做一些调整,这一步叫做重排。

重排主要是考虑多样性,根据多样性做随机抽样,从几百篇笔记中选出几十篇,然后用规则将相似的笔记打散。

重排的结果就是最终展示给用户的物品。

image-20250218204159841

image-20250218204700791

总结

image-20250218205008986

A/B测试

A/B测试基本思想

A/B测试

image-20250218210707448

随机分桶

image-20250218210914858

image-20250218211145176

分层实验

分层实验的目标就是解决流量不够用的问题。

image-20250218211551789

线上只能同时开9个A/B测试,根本无法满足产品迭代的需求,解决方案就是分层实验。

image-20250218211903341

image-20250218212232447

同层互斥

image-20250218212529340

不同层正交

image-20250218212601111

互斥vs正交

image-20250218213402426

Holdout机制

image-20250218214013510

image-20250218214236303

image-20250218214400018

实验推全&反转实验

实验推全

image-20250218214750874

反转实验

image-20250218215004164

image-20250218215033089

总结

image-20250218224313465

基于物品的协同过滤(ItemCF)

ItemCF的原理

image-20250219124448906

用知识图谱,两本书的作者相同,所以两本书相似;

还可以基于全体用户的行为,判断两个物品的相似性,比如:

image-20250219124514738

ItemCF的实现

image-20250219124812904

image-20250219124927555

物品的相似度

可以从数据中挖掘出物品的相似度。

image-20250219125048237

两个物品不相似:

红色和绿色这两个物品的用户没有重合,这意味这两个物品不相似。image-20250219125248461

两个物品相似:

下面这两个物品的受众重合度非常高,判定为相似。

image-20250219125408059

计算物品相似度:

image-20250219125648531

考虑喜欢程度:

image-20250219125900517

小结

image-20250219130328515

ItemCF召回的完整流程

事先做离线计算

image-20250219133038943

“用户→物品”的索引

image-20250219133532627

“物品→物品”的索引

image-20250219133610055

线上做召回

image-20250219133808149

image-20250219133933058

image-20250219135220838

总结

ItemCF的原理

image-20250219135443548

ItemCF召回通道

image-20250219135630937

Swing召回通道

回顾ItemCF

ItemCF的原理

image-20250219140509391

ItemCF的物品相似度

image-20250219140652241

image-20250219140815046

ItemCF的不足之处

假如重合的用户是一个小圈子......

image-20250219141058015

左右两篇笔记没有什么相似之处,它们的受众差别很大,但是两篇笔记碰巧同时被分享的同一个微信群里,微信群里有人同时点开这篇笔记。这样会造成两篇笔记的受众完全不同,但是很多用户同时交互过两篇笔记,导致系统错误地判断两篇笔记的相似度很高。

想要解决这个问题就要降低小圈子用户的权重。我们希望两个物品重合的用户广泛而且多样,而不是集中在一个小圈子里。一个小圈子里的用户同时交互两个物品,不能说明两个物品相似;相反,如果大量不相关的用户同时交互两个物品,则说明两个物品有相同的受众。

Swing模型的原理就是给用户设置权重,解决小圈子问题。

Swing模型

image-20250219145547799

image-20250219145843351

用overlap可以降低小圈子对相似度的影响。

总结

image-20250219150301864

基于用户的协同过滤(UserCF)

UserCF的原理

image-20250219151009994

UserCF的实现

image-20250219151242645

image-20250219151252028

image-20250219151330984

用户的相似度

两个用户不相似

他们喜欢的物品没有重合

image-20250219151432157

两个用户相似

两个用户喜欢的物品重合度非常高

image-20250219151517179

计算用户相似度

image-20250219151554310

公式的不足之处:公式同等对待热门和冷门的物品,这是不对的。

降低热门物品权重

image-20250219151810819

《哈利波特》是非常热门的物品,无论是大学教授还是中学生,都喜欢看《哈利波特》。既然都喜欢看《哈利波特》,那么《哈利波特》对于计算用户相似度是没有价值的。

越热门的物品越无法反映用户最独特的兴趣,对计算相似度就没有用;相反,重合的物品越冷门,越能反映用户的兴趣。

如果两个人都很喜欢《deep learning》这本书,说明两个人很有可能是同行;如果两个人都喜欢更冷门一些的书,比如《深度学习在语音识别中的应用》,说明两个人是小同行。

为了更好地计算用户兴趣的相似度,我们需要降低热门物品的权重。

image-20250219152910266

物品越热门 n~l~ 就越大 ,也就是说物品的权重越小。

小结

image-20250219153627705

UserCF召回的完整流程

事先做离线计算

image-20250219153828160

“用户→物品”的索引

image-20250219153952242

“用户→用户”的索引

image-20250219154047447

线上做召回

image-20250219154134753

image-20250219154250509

如果取回的物品有重复的,就做去重,把分数加起来。

总结

UserCF的原理

image-20250219154719493

UserCF召回通道

image-20250219154806229

离散特征处理

前面几节课讲解了协同过滤的召回方法,后面几节课要介绍向量召回。

离散特征

image-20250301110458967

离散特征处理

image-20250301110552647

One-Hot 编码

例1:性别特征

image-20250301110710650

例2:国籍特征

image-20250301110757793

One-Hot编码的局限

image-20250301111431029

Embedding(嵌入)

例1:国籍的Embedding

image-20250301111912991

在训练神经网络时,会自动做反向传播,学习Embedding层的参数。

例2:物品ID的Embedding

image-20250301155244018

如果类别的数量不大,只有几百万,那么Embedding层的实现是比较容易的,TensorFlow和PyTorch都可以处理的很好;但是如果类别数量特别大,比如推荐系统中的物品数量有几十亿,那么Embedding层会特别大。

一个神经网络绝大多数参数都在Embedding层,所以工业界深度学习系统都会对Embedding层做很多优化,这是存储和计算效率的关键所在。

image-20250301155338815

Embedding = 参数矩阵 × One-Hot向量

image-20250301155945464

总结

image-20250301160027807

矩阵补充(Matrix Completion)

矩阵补充是向量召回最简单的一种方法,但是现在已经不太常用这种方法了,此处讲解矩阵补充是为了方便理解下节课的双塔模型。

image-20250301160826473

训练

基本想法

image-20250301161025019

数据集

image-20250301161141591

训练

image-20250301161631276

求最小化可以用随机梯度下降等算法,每次更新矩阵A和B的一列,这样就可以学出矩阵A和B。

image-20250301161849819

矩阵中只有少数位置是绿色,大多数位置都是灰色(也就是没有曝光给用户的),我们并不知道用户对没曝光的物品是否感兴趣。

我们刚才用绿色位置的数据训练出了模型,有了模型就可以预估出所有灰色位置的分数,也就是发矩阵的元素补全,这就是为什么模型叫做矩阵补充。

把矩阵元素补全之后就可以做推荐,给定一个用户,我们选出所对应的行中,分数较高的物品,推荐给该用户。

在实践中的效果不好……

image-20250301163137479

image-20250301163224873

image-20250301163315837

线上服务

在训练好模型之后,可以把模型用作推荐系统中的召回通道,比如在用户刷小红书的时候,快速找到这个用户可能感兴趣的一两百篇笔记。

模型存储

在做完训练之后,把模型存储在正确的地方,便于做召回。

image-20250301163948588

线上服务

image-20250301164336025

支持最近邻查找的系统

快速最近邻查找的算法已经被集成到很多向量数据库系统中。

image-20250301164642841

有些系统不支持余弦相似度,这也很好解决:把所有向量都做归一化,让他们的二范数全都等于1,那么内积就等于余弦相似度。

用一个直观的例子来演示最近邻查找:

image-20250301165020102

这是一个散点图,每个点是物品的Embedding向量(都是训练模型时训练出来的);右边的五角星是用户的Embedding向量,记做a。

image-20250301165205066

我们想要召回用户可能感兴趣的物品,这就需要计算这个向量和所有点的相似度。如果用暴力枚举的话,计算量正比于点的数量(也就是物品的数量)。想要减少最近邻查找的计算量必须要避免暴力枚举。

一种加速最近邻查找的算法:在做线上服务之前,先对数据做预处理,把数据划分成很多区域,比如以下划分(至于如何划分,取决于衡量最近邻的标准):

image-20250301165645138

如果cosine相似度,那么划分的结果就是以上的扇形;如果使用欧氏距离,那么划分的结果就是多边形。划分之后每个区域用一个向量表示,比如以下蓝色,黄色区域,用向量表示:

image-20250301170011361

这些向量的长度都是1,划分区域之后建立索引,把每个区域的向量作为key,把区域中所有点的列表作为value。划分区域之后每个区域都用一个单位向量来表示。

image-20250301171215774

假如有一个点,划分成一万个区域,索引上一共有一万个key值,每个向量是一个区域的key值,给定一个向量,可以快速取回这个区域内所有的点。有了这样的索引就可以在线上快速做召回了。

image-20250301171316056

在线上给一个用户做推荐,这个用户的embedding向量记做a。首先把向量a和索引中的这些向量作对比,计算他们的相似度。如果物品数量是几亿,索引中的数量也只有几万而已,这一步的计算开销不大。

image-20250301172028526

计算相似度之后,会发现a下面最靠近a的向量和a最相似,通过索引找到这个区域内所有的点,每个点对应一个物品;接下来计算点a和区域内所有点的相似度,如果有几亿个物品被划分到了几万个区域平均每个区域只有几万个点,所以这一步只需要计算几万次相似度,计算量也不大。

image-20250301172313583

假如要找和向量a最相似的三个点,也就是夹角最小的三个点,就会找到圈住的这三个,对应三个物品,这三个物品就是最近邻查找的结果。

总结

矩阵补充

image-20250301172443711

线上召回

image-20250301172543846

双塔模型:模型和训练

矩阵补充模型

用内积来预估用户对物品的兴趣分数。这个模型比较弱,只用到了用户和物品的ID,没有用到用户和物品的属性。双塔模型可以看做是矩阵补充模型的升级。

image-20250302163927083

双塔模型

对于用户连续特征,最简单的是做归一化,让特征均值为0,标准差是1

image-20250302164440057

物品的特征也是用类似的方法:

image-20250302164527407

双塔模型

image-20250302164633842

余弦相似度,意思是两个向量夹角的余弦值,相当于先对两个向量做归一化。余弦相似度的大小介于-1到1之间。

双塔模型的训练

image-20250302170036831

Pointwise:把正负样本组成一个数据集,在数据集上做随机梯度下降训练双塔模型

Pairwise:每次取一个正负样本组成一个二元组,损失函数用Triplet hinge Loss, 或者Triplet Logistic Loss,可以参考Facebook发的论文。

Listwise:组成一个list,训练方式类似于多元分类,参考YouTube发的论文。

正负样本的选择

image-20250302170148960

Pointwise训练

image-20250302210210591

不知道为什么要控制正负样本比在1:2或者1:3,但是互联网大厂都是这么做的,可能是业内的经验。

pairwise训练

两个物品塔是相同的,里面的Embedding层和全连接层都用一样的参数。

image-20250302210435635

分别计算用户对两个物品的兴趣:

image-20250302210655739

image-20250302210921148

训练的时候每个训练样本都是个三元组,向量a是用户的表征,b+和b-分别是物品正负样本的表征。

我们希望损失函数越小越好,训练的过程就是对损失函数求最小化,用梯度更新双塔神经网络的参数。

Triplet hinge loss只是一种损失函数,Triplet logistic loss损失函数起到同样的作用。

image-20250302212215785

这里的σ是个大于0的超参数,控制损失函数的形状,σ需要手动设置

推导出Triplet hinge loss和Triplet logistic loss损失函数之后,可以通过最小化损失函数来训练双塔模型。训练样本都是三元组,其中一个用户,一个正样本物品,一个负样本物品。

Listwise训练

image-20250302212714212

image-20250302212845672

总结

双塔模型

image-20250302212955941

不适用于召回的模型

image-20250302213234921

双塔模型:正负样本