2022-01-16-对比学习视角:重新审视推荐系统的召回粗排模

2022-01-16  本文已影响0人  破阵子沙场秋点兵
 来自知乎专栏-张俊林老师-对比学习视角:重新审视推荐系统的召回粗排模型

原文链接:对比学习视角:重新审视推荐系统的召回粗排模型

对比学习视角:重新审视推荐系统的召回粗排模型.png

什么是对比学习

对比学习的渊源与谱系

对比学习它最大的技术源泉来自于度量学习(Metric Learning)。

什么对比学习系统

来自知乎张俊林

对于一个对比学习系统来说,最关键的是三个问题:

对比学习的典型例子

来自知乎张俊林

首先我们看正例是怎么构造的。比如说我们要训练一个模型,会先随机形成一个图像构成的batch,正例是怎么构造的?SimCLR会拿任意一张图片,对这个图片做一些变形,比如说翻转,抠出一部分,颜色变换一下,等等各种可能的变换。这样,每张图片会构造出它对应的两个正例。而负例,Batch内任意其它图片都可作为这张图片的负例。这是第一个问题的解决方案。

再看第二个问题:Encoder的网络结构。SimCLR从结构上来说,是由两个分支构成的。今天我们做搜索、广告、推荐的同学比较多,你看这个结构应该感到很亲切,这不就是典型的双塔结构么?上下两个塔结构就是映射函数,也就是投影函数f。这个投影函数可进一步分为两部分,第一部分是ResNet,ResNet先抽取图片中的特征,把图片打成embedding,这个Embedding来表征图片的内容。第二部分是个投影结构projector,一般由两到三层MLP连接构成,projector把Embeddding映射到某个投影空间里面,这也是一个embedding。同时,SimCLR上下分支也完全一样,包括参数也是共享的。这样做,它就把对应batch的正负例全部映射成对应的embedding了。这是第二个问题的解决方案。

最后来看第三个问题:损失函数问题。经过双塔结构,SimCLR已经将输入数据,映射到投影空间里了。我们可以对正负例做相似度计算,一般会用cosine,这个相似度其实就是正负例在投影空间里的距离远近的度量标准。做完相似度计算后,用对比损失函数InfoNCE来驱动,来达成对比学习的目标,也就是刚才讲的,要求在投影空间里面,用InfoNCE推动正例距离拉近一些,负例距离推远一些。

什么是不好的对比学习系统

来自知乎张俊林

对于对比学习系统,我们希望在投影空间中,它的正例距离越近越好,这是我们希望达成的。
但这很容易产生问题:如果你的系统设计得不太合理的话,很容易诱发模型坍塌问题。什么是模型坍塌?就是说不论你输入的是什么图片,经过映射函数之后,在投影空间里面,所有图像的编码都会坍塌到同一个点。坍塌到同一个点又是什么含义呢?就是说不论我的输入是什么,最终经过函数映射,被映射成同一个embedding,所有图像对应的Embedding都是一样的,这意味着你的映射函数没有编码任何有用的信息

什么是好的对比学习

来自知乎张俊林

好的对比学习系统应该满足什么条件呢?(可以参考上图所示论文)它应兼顾两个要素: Alignment和Uniformity。

SimCLR怎么防止坍塌

来自知乎张俊林

SimCLR本质上是通过引入负例来防止模型坍塌的。
InfoNCE的分子部分体现了Alignment这个要素,因为它期望正例在投影空间里面越近越好,也就是相似性越大越好。
它防止坍塌是靠分母里的负例:也就是说,如果图片和负例越不相似,则相似性得分越低,代表投影空间里距离越远,则损失函数就越小。

图像领域对比学习中的两个明确结论

典型对比学习模型

Batch内负例-SimCLR

来自知乎-张俊林

SimCLR比较重要的几个要点:

  1. 模型结构上采用了我们做“搜广推”很常见的双塔结构
  2. 对正负例的Embedding做相似性计算前,应该先做一个L2 Norm
  3. 模型自动构造正例
  4. 负例是In-Batch内随机负例

Batch外负例-Moco

来自知乎-张俊林

前面说过一个已有结论:负例用的越多,模型效果越好。因为batch size增大对计算资源要求比较高,所以总有个限度。MocoV2是典型的解决这种矛盾的例子,也就是说,我们如何能够解除batch size的约束,来大幅增加负例的数量。
Moco V2的模型结构图和SimCLR的基本是类似的,也是上下两个双塔结构,网络结构也由两个映射子结构组成,下分支结构本身是和上分支的网络结构是完全一样的。
上下两个分支的区别有两点:

  1. 下分支的网络参数更新机制和上分支的更新机制不一样,采用动量更新机制
  2. Moco维护了一个负例队列
    下分支的正例通过下分支打成embedding,然后会把它放入队列里面(入队),在队列待了太久的会让它出队。Moco的负例采集方法和SimCLR一样,是随机抽取负例。但它不是在batch里面取,而是从负例队列里取。

基于负例方法谱系

用负例避免模型坍塌的对比学习系统比较多,要归纳的话,主要分为两大谱系:一个是Moco系,一个是SimCLR系列。
采纳负例的方法主要有三个共识

  1. 网络结构现在大家基本用的都是双塔结构
  2. 映射函数一般由两部分组成,首先是Resnet,用来对图像编码,还有一个是projector
  3. 在Moco v3和SimCLRv2 版本升级时都有体现,就是encoder越来越复杂

对比聚类-SwAV

来自知乎张俊林

SwAV是个典型的对比聚类的方法,是图像对比学习众多模型中效果最好的方法之一。关于正负例构造方法,SwAV和刚才提到的方法一样。关于模型结构,SwAV的双塔结构和两个映射函数,和SimCLR等模型一样,是上下对称的。
SwAV的主要特点:


来自知乎张俊林

对比学习视角来看召回/粗拍模型

重新审视召回/粗排模型

双塔模型概述

来自知乎张俊林

双塔模型的思路:它把user特征(包括context特征)和Item特征拆分,分别传给两个塔,通过DNN打成Embedding。左塔打出user embedding,表征用户兴趣。右塔通过DNN打成Item embedding,表征物品。然后对两者做相似度计算,一般会用内积或者Cosine。

在实践的时候,你会发现有几个决策点需要去考虑。有三个实践经验我在这里提一下

  1. 第一种选择就是in-batch负例
    我们在做排序(Rank环节)的时候,负例怎么构建呢?比如对于点击模型,一般把展示给用户曝光未点击样本做负例,就是曝光给用户了,不过他没点击,说明他不感兴趣,那这就是负例。但是,在做召回时,应该说不能完全应用这种做法。原因很简单,一般这个问题在学术上叫做selection bias:也就是说,因为召回用的双塔模型是排序的前置环节,它面临的候选集是整个物料集合,它不像rank阶段, rank阶段面临的候选集是召回或粗排筛完之后剩下的子集合,只是物料全集的一个子集。所以召回和排序的候选物料集合分布是有差异的。如果你用rank阶段做负例的方式,来给召回环节做负例的话,你会发现有很多未曝光的或低曝光的样本,召回模型从来没有见到过,所以容易误分。这实际是会有些问题。
  2. 第二种典型做法是全局随机抽样
  3. 混合一下,就是说负例一部分来自于in-batch负例,一部分来自于全局随机采样负例
  4. 考虑各种不同类型负例进行混合,召回模型负例有非常多种可能的做法,比如把in-batch随机负例+曝光未点击负例,两者按照一定比例混合。
    在这里,我假设在这个决策点,我们的负例策略用in-batch负例。

目前的双塔就是一种对比学习

来自知乎张俊林

今天的主题是重新审视召回/粗排模型。我们再看一下经典的DNN双塔召回和粗排模型。我们如果把刚才的三个决策点敲定的话,那么结构是这样的:左塔的user打成embedding,右塔的item打成embedding,然后对两个embedding做Norm,上完Norm之后做相似度计算,负例应该是in-batch负例,还应该带一个温度超参。

然后再对比刚才讲的SimCLR,我们再回顾一下SimCLR。其实仔细考虑一下的话,会发现把这几个因素考虑进来的话,我们现在在做的双塔模型就是一个典型的对比学习系统。我们把SimCLR逐步改造一下,就很容易改造出我们现在在用的双塔模型。可以这样改造SimCLR:首先,负例还是采用in-batch负例,把正例的方式换一下:图像是通过自动图像增强,对于推荐来说,我把用户行为过的Item作为正例,然后把正例方式换一下。第二点,我把上图中SimCLR的encoder拿掉,因为encoder用的是Resnet,是专门提取图像特征的,我们做推荐不需要这部分,但是我们保留projector, projector的实际做法也是MLP,跟我们推荐做双塔的DNN模型结构基本是一样的。然后,对两个embedding我再做Norm,两个embedding的正负例做Norm,然后引入InfoNCE loss,InfoNCE Loss可以看成推荐里常见的Pairwise Loss 比如BPR的一个拓展,我们这里仍然带着温度超参。经过这几个改造,就是我把正例的方式换一下,仍然采用in-batch随机负例,把encoder拿掉,BPR Loss拓展成InfoNCE,你会发现这就是现在典型的双塔模型的做法。所以说,从这个角度,我们可以把目前双塔方法理解为一个典型的对比学习方法。

用对比学习解释经验做法

什么是一个好的对比学习系统。它要满足两个要素:一个是alignment,另一个是uniformity。Alignment就是InfoNCE里面的分子,就是相似性越高拉的越近。Uniformity的话,我们讲了SimCLR是通过引入负例达成的,也就是我引入负例后,可以让投影空间中的所有实例分布比较均匀,也就是说让实例映射到投影空间之后,尽量多保留自己个性化的信息,这就是uniformity。我们可以从这个角度来解释为什么双塔召回模型要引入in-batch随机负例。它的作用是什么?它的作用是防止双塔模型的模型坍塌,这是目的。效果体现在user特征,item特征,映射到投影空间后,它让投影空间中的embedding,不论是user还是item,在投影空间分布得更均匀。分布的更均匀代表的是每一个user embedding和item embedding保留了更多个性化信息。这应该是引入in-batch随机负例的作用。
进一步做个推论:因为基于负例的对比学习系统有这么一个结论:负例越多模型效果越好,那么我们可以得出推论:做in-batch随机负例的双塔模型召回,随着batch size逐渐放大,效果应该越来越好。

如果这种解释是成立的,那么可以进一步做些推论:召回模型用的是随机负例/in-batch负例,因为引入了大量的随机负例,大量简单负例信息含量不大,但是因为数量多,所以聚集起来的loss会淹没掉hard负例的作用,那么这种情况下,引入温度超参应该更重要,因为他可以让loss更聚焦在hard负例上去。

进一步,我们可以再做更深层的推论:如果引入温度超参,其实不用花大的精力去挖掘hard负例,现在很多工作是专门挖掘hard负例的,既然温度超参的作用就是把loss聚焦在hard负例上,意味着它自动会做hard负例的选择,那么你费劲力气去挖掘hard负例就没有太大必要性。你可能需要做的是放大负例数量,然后加入温度超参,让模型自己去找hard负例。

搞点新意思

来自知乎张俊林

在介绍杜比学习角度可能的改进之前,这里做一些我们做的其它工作的简介。前面说过,召回模型其实有一个很典型的问题:user/item embedding之间特征交互太晚,导致它的效果不是那么好。我们的一个实践结论是如果在user embedding和item embedding做MLP之前引入SENet,这么改造一下,应该是对召回模型的效果有提升的,我的想法是引入SENet抑制噪音特征,强化有效特征,能更有效表达user/item之间的特征交叉。大家感兴趣的话可以参考上面图片列出的FiBiNET文献。

我之前做Rank模型,其实是把重心放在feature embedding上的,我们的一系列改进,其实都是在feature embedding上做的文章,包括最初我们提出的FiBiNet里的SENet也好,以及后面针对FiBiNet进一步拓展出的MaskNET也好,ContextNET也好(具体做法可以参照上图列出的论文)。其实可以用一句话归纳一下:都是在特征embedding上面做的一些工作,因为我个人判断是feature embedding这一块可挖掘的空间比较大。

来自知乎张俊林

怎么用对比学习做召回模型。这里给一个我们正在做的一个例子。刚才讲了经典的双塔模型,我们可以从对比学习角度来理解它。刚才我介绍过SwAV模型,原则上可以拿它来做推荐的召回模型。上图列出的是我们正在做的叫做ConCAT的召回模型,其实就是推荐领域里面的SwAV模型。这里说一下它的具体做法,是参考SwAV对双塔模型的一个改进:就是用用户行为过的item作为正例,这些正例形成batch,经过user和item塔,形成user embedding和item embedding,类似SwAV,我们在形成embedding后加了一个聚类过程,就是对user embedding做了一个聚类,对item embedding也做了一个聚类。优化目标是什呢?对于某个user embedding,优化目标是我希望user embedding和对应的正例item所属聚类的类中心的embedding的距离越近越好,和其它的聚类类中心越远越好。这是左塔对应的loss。因为它是结构对称的,那么反过来,也可以希望item embedding和对应正例user所属的聚类类中心距离越近越好。这是右塔的loss。把这两个loss加起来就是模型优化的目标。这是一个用对比学习改造召回模型的具体例子,我个人认为这种方式可能做出一些比较新型的召回模型。

更纯正的对比学习

来自知乎张俊林

这个论文中还引入了另一种正例的做法,叫feature mask。刚才的做法是说做正例时,在item特征引入dropout,随机抛一部分,item两个塔抛法不一样,通过这个方式制造一对正例。还有一种可能做法:因为item的特征很多,所以人工把它们分成两个子集合:子集合A和子集合B。通过这种方式,制作item两个不同的view,并要求这两个view在投影空间里面距离要近一些,In-batch随机负例与正例的距离越远越好。这是另外一种引入辅助的对比Loss的做法。

来自知乎张俊林

那么在item侧引入对比辅助Loss,有没有用呢?上图是它的实验结果。从结果中,我们能发现FD(dropout)、FM(mask)这两种做法相对baseline这种标准双塔模型来说,效果上还是有比较明显提升的。在什么场景下效果尤其好呢?就是对于数据稀疏场景,也就是低频的item或者user embedding较多的场景,它的效果尤其好。它这里做了个对比实验:就是在选择训练数据的时候,我只取原始数据10%比例作为训练数据,甚至1%的训练数据,意思是人为制造稀疏场景,即使里面有些中高频的,因为抽样只取1%,很多高频和中频也成了稀疏的了。结论是对于这种稀疏的数据集下,采用这种引入对比学习Loss,方法,效果提升更明显。

图模型召回

来自知乎张作霖

最后介绍一下怎么把对比学习引入到图召回模型里面。上图讲的是典型的如何用图模型做召回的思路:一般做召回模型时,我们把user和item特征分离,把user特征、item特征通过离线模型训练打出user embedding和item embedding。在线服务的时候,拿到用户user embedding然后从item库里去做ANN匹配,找出出得分最相似的item,作为召回结果。这就是典型的用模型做模型召回的框架。

那么怎么用图模型做召回?很简单,把打user和item embedding的模型换成一个典型的GNN模型就可以了。


来自知乎张俊林

上图展示了典型的图模型方法:把用户行为图,也就是把user对item的行为构建为一个二分图,也可以把user的属性和item的属性放进来,拓展成更复杂的图,然后在这上面做些图的迭代算法。这是典型的GNN模型的方式


来自知乎张俊林

那么,有了经典的GNN模型,首先一个问题是:我们怎么在GNN上采用对比学习呢?这里给一个在图计算里面引入对比学习的例子。想想我们刚才讲过的SimCLR和双塔召回模型,其实在GNN里引入对比学习,其过程是一样的:对于整个图中的一个子图,我们可以对子图做些操作,得到这个子图不同的视角view,以此来构造图的正例,常见的操作方法包括Node dropping和Edge Perturbation等。做完正例,然后把它通过一个模型映射到embedding空间里面去,要求两个正例在投影空间里面距离越近越好。负例可以采取In-batch随机选择负例,要求这些负例和正例在投影空间里面的距离越远越好。这是典型的SimCLR的做法,唯一的区别是这里的输入侧不再是个图像,或者不再是推荐里的user或者item,而是一个子图,就这么个区别。所以可理解为在套用SimCLR的做法


来自知乎张俊林

在对比学习里面最关键的是怎么构造正例,那么在图模型里面是怎么构造正例的呢?这里列出几种常见方法。一种叫Node Dropping,因为图由图节点和连接图节点的边构成,那随机drop掉一些节点,就可以够造出一个不同的子图view出来。这是一种基于图节点的做法。另外可以对边做一些工作,同样的,这么多边,可以随机删掉一些边,也可以随机增加一些,这样也可以构造不同的正例,这是基于边的做法。还可以有其他做法,我们讲过图节点可能是带属性的,那么我们可以随机mask掉图节点的一些属性,这也是一种构造图模型正例的方式。还可以在图上随机游走,利用不同游走结果来构造对比学习子图的view。所以这四种是典型的图模型里面构造正例的方式。现有的工作基本超不出这四种模式。


来自知乎张俊林

最后一个问题是:我们现在知道了GNN模型如何引入对比模型,在推荐里,对于图模型召回,怎么引入对比学习呢?只要把上面介绍的知识结合起来就行。可以利用用户行为构造用户行为图,然后像刚才讲的借用经典GNN的方法来构建一个图计算系统,之后可以参照上面GNN引入对比学习的思路,把对比学习系统引入GNN召回模型,这样对于稀疏的user和item数据,会打出更靠谱的user embedding和item embedding。在线服务的时候用user embedding拉对应的item embedding就可以了。这样,我们就构造出一个典型的基于对比学习的图召回模型。它对于借鉴冷启动应该是有帮助的。

上一篇 下一篇

猜你喜欢

热点阅读