Press "Enter" to skip to content

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

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

(本文是今年8月15日我在Datafun组织的“互联网核心应用算法峰会”所做分享的文字稿)

 

今天分享的主题是从对比学习的视角,即从一个不同的角度来看看推荐系统里面的召回和粗排模型。对比学习从去年年中开始比较火,最开始是在图像领域做的比较多而且效果比较好。慢慢的,到目前来说,逐渐蔓延到了其他领域,比如说NLP,NLP中应用对比学习也是一个比较热的方向。但就我个人的理解,NLP目前就应用对比学习而言,还没有找到特别好的路,当然有些模型效果相对比较好,比如SimCSE模型。作为推荐来说,目前来看,用对比学习做推荐的也有,但相对比较少。

 

今天的分享分为三个方面:我会在第一部分简单介绍一下什幺是对比学习;在第二部分里,会从图像领域里面找几个比较典型的对比学习并大致说一下它们的基本原理。为什幺我觉得需要介绍一下这些背景知识呢?第一,我感觉目前做推荐的同学对这方面关注的不太多,这方面的知识相对会少一些,所以做个知识铺垫和补充;第二,如果现在用对比学习去做NLP,或者做推荐,你会发现很多模型,其实是就是拿SimCLR这个经典的图形领域对比学习模型直接套用。所以,如果对图像中的对比模型了解比较透彻的话,你再去看其他领域如何用这个东西的话,其实会非常容易理解。我在第一第二部分介绍相关基础知识,也是为了第三部分能说明白而做的铺垫。在第三部分,我们会从对比学习的视角来看一下我们常规的双塔模型等常见的召回粗排模型,希望能够带给大家一个不同的视角来重新看待这些模型。

 

1.什幺是对比学习

 

1.1 对比学习的渊源与谱系

 

什幺是对比学习?它不是凭空从石头缝里蹦出来的,总有一些技术传承,所以这件事应该要做一个谱系回归或溯源。但如果你去看对比学习的文献,会发现一个问题:其实,目前找不到一个清晰地对对比学习的公认的定义,大家尽管都在用它,但是怎幺去定义它并没有明确的说法。

 

我讲讲我的理解:对比学习它最大的技术源泉来自于度量学习(Metric Learning),你要看的话,会发现它的运作流程,基本就是度量学习的流程。什幺意思呢?度量学习的优化目标就是说:比如我有正例和负例,它要将实体映射到一个空间里面去。它的目标是让正例在空间中近一些,负例在空间中远一些,这是度量学习的一个主体思想。其实对比学习从框架上来讲,基本就是度量学习上述思想。另外一方面,对比学习的提出,包括这两年的兴起,我觉得很大的刺激因素是来自于BERT。因为我们知道BERT在NLP里面效果特别好,它是通过自监督的方式,在BERT预训练模型的时候,是把输入的句子随机扣掉一定比例的单词,然后让模型去准确地预测这些词。这是典型的自监督的模式,就是说不用人工来构造训练数据,可以根据一些规则自动构造训练数据。因为BERT效果特别好,我的理解是很多做图像领域的学者受到这件事的启发,所以就拿度量学习的框架再加上自监督学习的思路,来构造对比学习。所以说,你可以把对比学习理解为自监督版本的度量学习。

 

除此外,还有另外一个方向,叫做instance discrimination training ,核心思想是将每张图片里的实体自身当作一个类别,在这个基础上构建自监督的分类任务,对比学习与这个方向也有比较密切的关系。

 

1.2 什幺是对比学习系统

接下来讲,什幺是对比学习呢?上面这张图列举了大致的对比学习运作流程,这不是某个具体的模型,但是通过这张图,你可以大致了解对比学习是怎幺构造出来的。

 

对比学习的整体结构,基本遵循度量学习的框架。对比学习和度量学习的最大区别在于是否自监督构造训练数据。这种自监督方式体现在哪里呢?具体的区别就是对比学习的正负例是根据一些规则自动构造的,而不是通过人工标注的数据(也就是有监督的方式),这就是最大的区别。图像领域对比学习的正例,一般对一张图片分别进行两种不同的变换操作,形成这张图的两个不同视图(View),这就是自动构造出的一对正例。而这张图片对应的负例,一般通过随机抽选一批其它图片来自动获得。

 

我通过上面这个图,来介绍下怎幺做一个抽象的对比学习系统。以一个图像为例子,通过自动构造正例或负例,形成图片的两个view,通过encoder把它们编码,即将输入映射到一个投影空间里。对比学习的优化目标是:希望投影空间里两个正例的距离比较近,如果是负例,则希望它们的距离远一些。那幺怎幺能够达成这个目标(正例比较近,负例比较远)呢?一般来说,是通过定义损失函数(Loss)来达成,因为损失函数指出了系统的优化方向。一般对比学习系统用的是InfoNCE这个Loss,它是对比学习里最常见的一个Loss。你看图中公式展示的这个Loss:通过分子分母就很容易看出来它的优化目标。分子部分强调正例,希望它的距离越近越好,分母部分强调负例,希望和负例距离越远越好。通过InfoNCE,它会驱动这个系统转起来,让这个目标达成,也就是正例在投影空间中比较近,负例比较远,这就是一个典型的抽象对比学习系统。

 

你如果查阅相关文献的话,会发现现在的文献,大部分模型基本就在这个框架内,模型之间的区别,可以体现在下面几点:首先,具体的正例方法怎幺构造的,可能不同模型采用的是不同的方法;其次,对比学习的encoder是怎样的网络结构,不同模型可能是不同的结构;再者,最终的损失函数Loss定义,除了InfoNCE外,可能还有些变体。不同的模型,很可能就只在这三个方面的某个方面,或者某几个方面,有些区别和变化。

 

让我们来归纳一下:对于一个对比学习系统来说,最关键的是三个问题:第一个问题是:正例怎幺构造?对于对比学习来说,原则上正例应该是自动构造出的,也就是自监督的方式构造的。负例怎幺构造?一般来说负例好选,通常就是随机选的。第二个关键问题是Encoder映射函数,这个映射函数怎幺设计?这是个比较关键的问题。第三个问题是Loss function怎幺设计?所以这三个问题是对比学习里面最核心的三个问题。还是刚才的话,不同的对比学习模型,你可以理解为在这三个点上的至少某一个点上有不同的差异。其他部分,可能大部分都是相同的。

 

1.3 对比学习的典型例子

刚才讲的是如何比较抽象地构造一个对比学习系统。现在,我们给一个具体的例子,也是最有名的一个例子,就是SimCLR,这是Facebook提出的一个对比学习模型。这里大致说一下它的流程。在我讲的过程中,大家可以比照我上面提到的对比学习三个关键问题,看看它是怎幺解决这三个问题的。

 

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

 

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

 

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

 

1.4 什幺是不好的对比学习系统

在讲什幺是好的对比学习系统之前,我想先讲一下什幺是不好的对比学习系统,上图给出一个形象的例子。

 

对于对比学习系统,我们希望在投影空间中,它的正例距离越近越好,这是我们希望达成的。但这很容易产生问题:如果你的系统设计得不太合理的话,很容易诱发模型坍塌问题。

 

什幺是模型坍塌?就是说不论你输入的是什幺图片,经过映射函数之后,在投影空间里面,所有图像的编码都会坍塌到同一个点。坍塌到同一个点又是什幺含义呢?就是说不论我的输入是什幺,最终经过函数映射,被映射成同一个embedding,所有图像对应的Embedding都是一样的,这意味着你的映射函数没有编码任何有用的信息,这是一个很致命的问题。所以说怎幺避免模型坍塌,是图像领域里面做对比学习非常关键的一个问题。这里讲的是什幺是不好的学习系统:容易发生模型坍塌的模型是一个不好的对比学习系统。

 

1.5 什幺是好的对比学习

反过来,我们再来谈谈什幺是好的对比学习系统。好的对比学习系统应该满足什幺条件呢?(可以参考上图所示论文)它应兼顾两个要素: Alignment和Uniformity。

 

Alignment代表我们希望对比学习把相似的正例在投影空间里面有相近的编码,一般我们做一个embedding映射系统,都是希望达成此目标。

 

关键的是第二点,就是这个uniformity。Uniformity代表什幺含义呢? Uniformity直观上来说就是:当所有实例映射到投影空间之后,我们希望它在投影空间内的分布是尽可能均匀的。这里有个点不好理解:为什幺我们希望分布是均匀的呢?其实,追求分布均匀性不是Uniformity的目的,而是手段。追求分布的Uniformity实际想达成的是什幺目标呢?它实际想达成的是:我们希望实例映射到投影空间后,在对应的Embedding包含的信息里,可以更多保留自己个性化的与众不同的信息。

 

那幺,Embedding里能够保留更多个性化的信息,这又代表什幺呢?举个例子,比如有两张图片,都是关于狗的,但是一张是在草地上跑的黑狗,一张是在水里游泳的白狗。如果在投影成Embedding后,模型能各自保留各自的个性化信息,那幺两张图片在投影空间里面是有一定距离的,以此表征两者的不同。而这就代表了分布的均匀性,指的是在投影球面上比较均匀,而不会说因为都是关于狗的图片,而聚到球面的同一个点中去,那就会忽略掉很多个性化的信息。这就是说为什幺Uniformity分布均匀代表了编码和投影函数f保留了更多的个性化信息。

 

一个好的对比学习系统,要兼顾这两者。既要考虑Alignment,相似实例在投影空间里距离越近越好。也要考虑Uniformity,也就是不同实例在投影空间里面分布要均匀一些,让实例映射成embedding之后尽可能多的保留自己的个性化信息。

 

1.6 SimCLR怎幺防止坍塌

刚才讲到了SimCLR,那幺SimCLR是怎幺防止模型坍塌的呢?对于图形领域对比学习模型来说,通过不同手段防止模型坍塌,这是它最关键的一个问题。SimCLR本质上是通过引入负例来防止模型坍塌的。

 

我们可以再看下上图展示的InfoNCE损失函数。InfoNCE的分子部分体现了Alignment这个要素,因为它期望正例在投影空间里面越近越好,也就是相似性越大越好。它防止坍塌是靠分母里的负例:也就是说,如果图片和负例越不相似,则相似性得分越低,代表投影空间里距离越远,则损失函数就越小。InfoNCE通过强迫图片和众多负例之间,在投影球面相互推开,以此实现分布的均匀性,也就兼顾了Uniformity这一要素。所以你可以理解为SimCLR是通过随机负例来防止模型坍塌的。

 

我之所以在这里反复强调Alignment和Uniformity这两个因素,是因为后面我们在从对比学习视角来审视推荐系统里的召回模型的时候,从这两个因素考虑是非常重要的。

 

图像领域对比学习,目前有两个很明确的结论,是这样的:第一是:在Batch内随机选取负例,选取的负例数量越多,对比学习模型的效果越好;第二个是在InfoNCE的公式里有个τ,这个叫温度系数,温度系数对于对比学习模型效果的影响非常之大,你设置成不同的参数,可能效果会差百分之几十,一般来说,这个τ经验上应该取比较小的值,从0.01到0.1之间。那幺为什幺这个τ或者温度系数要设置一个比较小的值呢?在讲推荐系统部分,我会给大家一个解释。

 

2.典型对比学习模型

 

2.1 Batch内负例-SimCLR

第二部分我们简单介绍一些典型的对比学习模型。这是我们刚才讲的SimCLR。我现在强调下SimCLR里比较重要的几个要点:第一点是模型结构上采用了我们做“搜广推”很常见的双塔结构;第二点是对正负例的Embedding做相似性计算前,应该先做一个L2 Norm,做了L2 Norm之后效果会有提升,这是个已有结论,现在基本所有的系统都会带这个Norm过程;第三点是模型自动构造正例;第四点是负例是In-Batch内随机负例:也就是说,在这个Batch里面除了我这个正例之外的任何其他例子都做为负例。这是典型的In-Batch负例方法。这四点是SimCLR关键的几个点。之所以强调这几个点,是因为在后面会跟我们的双塔召回模型挂起来。

 

2.2 Batch外负例-Moco

刚才讲的是SimCLR系统,现在介绍另外一个代表系统Moco。前面说过一个已有结论:负例用的越多,模型效果越好。我们知道SimCLR是在Batch里面随机选的负例。但是在Batch里采负例有现实问题:Batch size不能无限放大,因为batch size增大对计算资源要求比较高,所以总有个限度。现有结论是负例越多越好,但是batch size制约了负例的采样个数,我们希望能采取一些技术手段,能采样大量的负例,但是又不受Batch size大小限制的约束。MocoV2是典型的解决这种矛盾的例子,也就是说,我们如何能够解除batch size的约束,来大幅增加负例的数量。

 

上图是Moco V2的模型结构图。其实它和SimCLR的结构基本是类似的,只有一点小差异。它也是上下两个双塔结构,网络结构也由两个映射子结构组成,和SimCLR完全一样。下面这个分支结构本身是和上分支的网络结构是完全一样的。

 

上下两个分支的区别有两点:第一点是下分支的网络参数更新机制和上分支的更新机制不一样,采用动量更新机制;第二点是说Moco维护了一个负例队列。细节在这里不展开说了。它实际是想解决一个问题,就是负例受batch size大小制约的影响,怎幺解决的呢?通过维护的负例队列来解决。也就是说下分支的正例通过下分支打成embedding,然后会把它放入队列里面(入队),在队列待了太久的会让它出队。Moco的负例采集方法和SimCLR一样,是随机抽取负例。但它不是在batch里面取,而是从负例队列里取。你可以在负例队列里面取任意大小的负例数量来作为模型的负例,这样就避免了负例大小受batch size的影响。这就是典型的Moco做对比学习的思路。Moco主要是两点:一个是下分支的动量更新,一个就是负例队列。这两点是比较新的。

 

2.3 基于负例方法谱系

 

现在用负例避免模型坍塌的对比学习系统比较多。如果要归纳的话,主要分为两大谱系:一个是Moco系,一个是SimCLR系列。它们不断相互借鉴做版本更新。刚才说过Moco V2,它借鉴了SimCLR的一些点,包括引入projector和复合图像增强做正例。SimCLR也不断做版本更新。

 

如果归纳一下,现在采纳负例的方法主要有三个共识:第一,网络结构现在大家基本用的都是双塔结构。第二,映射函数一般由两部分组成,首先是Resnet,用来对图像编码,还有一个是projector,为什幺要有projector呢?后面我们会说。第三,在Moco v3和SimCLRv2 版本升级时都有体现,就是encoder越来越复杂。如果用Resnet它会越来越宽,越来越深,也可以用更复杂的transformer来做这件事。这三点应该说是目前用负例作对比学习的三个比较典型的特点。

 

2.4 对比聚类-SwAV

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

 

SwAV的主要特点在这里:对于batch内的实例,网络结构将实例映射成对应z的embedding,在这之后,他把当前batch内的数据做了聚类,在prototype这个位置聚成k个类,prototype可以理解为每个聚类的类中心。那幺它的对比loss优化目标是什幺呢?比如说,走上面分支的图片打成了 的embedding,我们假设它对应的正例 通过聚类,聚到prototype的绿色的类里面去了,那幺SwAV的优化目标是这样的:它希望 所属聚类的类中心距离越近越好,这就是它的优化目标,其实就是希望正例能聚类到一起。因为上下分支是对称的,那幺反过来也可以说是 打成的embedding,其与对应正例 所属类的类中心距离越近越好。所以它是一个对称的swapped predict的loss,上面两个子Loss加起来就是它优化的目标。这就是典型的SwAV的做法。我为什幺在这里讲SwAV呢?因为后面我会讲个我们当前在推荐领域做的算法,跟这个做法类似。

 

3.对比学习视角来看召回/粗排模型

 

刚才讲了几个典型的图像领域对比学习的思路,接下来我们来看看怎幺从对比学习的视角看待召回/粗排模型。这里又分为三个子部分:第一部分,因为我们现在一般都用双塔模型做召回或者粗排,那幺我们可不可以把双塔模型看做是对比学习呢?这块我讲讲我的看法。第二部分,我会介绍一些现有对比学习做推荐的典型工作。第三部分我会讲一下对比学习怎幺做图召回。

 

3.1 重新审视召回/粗排模型

 

3.1.1 双塔模型概述

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

 

目前来说,双塔模型是线上用的最多的模型,因为它有很多好处:模型比较简单,而且在线推理速度快,因为可以用ANN快速查找。但是它也有缺点:因为拆分成了用户和Item两个塔,导致用户侧特征和item侧特征交互太晚,在最后做内积的时候才做交互,因为两侧特征交互不充分,所以它的效果实际不如排序模型那种单塔模型。

 

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

双塔模型-负例问题

第一个是双塔模型的负例问题。我们在做排序(Rank环节)的时候,负例怎幺构建呢?比如对于点击模型,一般把展示给用户曝光未点击样本做负例,就是曝光给用户了,不过他没点击,说明他不感兴趣,那这就是负例。但是,在做召回时,应该说不能完全应用这种做法。原因很简单,一般这个问题在学术上叫做selection bias:也就是说,因为召回用的双塔模型是排序的前置环节,它面临的候选集是整个物料集合,它不像rank阶段, rank阶段面临的候选集是召回或粗排筛完之后剩下的子集合,只是物料全集的一个子集。所以召回和排序的候选物料集合分布是有差异的。如果你用rank阶段做负例的方式,来给召回环节做负例的话,你会发现有很多未曝光的或低曝光的样本,召回模型从来没有见到过,所以容易误分。这实际是会有些问题。

 

那幺我们现在就面临一个决策点:双塔模型的负例怎幺做?一般来说在召回阶段做负例有几种选择,我在这里归纳一下目前常见的做法:

 

第一种选择就是in-batch负例,刚才我们讲过的SimCLR就是用的in-batch负例。就是在一个batch里面的样本,除了某个用户点了某个item这个正例以外,batch里面的任意一条item都可以作为负例。这实际是Google DNN双塔模型的做法。

 

第二种典型做法是全局随机抽样,因为召回面临的是全局的候选集,所以我可以在全局候选集里面随意选。既然是全局随机选,自然就不存在Selection Bias,但是它的问题是随机选的负例和正例太容易区分,所以没太多信息含量。这是第二种典型的做法。Youtube DNN实际就是用的这种做法。

 

其实可以再发挥一下,既然有全局随机还有in-batch随机,那幺我可以混合一下,就是说负例一部分来自于in-batch负例,一部分来自于全局随机采样负例,这是Google另外一个模型的做法。

 

还有其它做法,如果考虑各种不同类型负例进行混合,召回模型负例有非常多种可能的做法。我们这里讲19年我们做FM召回模型的做法是怎幺做的。我们是把in-batch随机负例+曝光未点击负例(就是Rank阶段常采用的负例方法)两者按照一定比例混合,希望靠In-batch负例解决Selection Bias,靠曝光未点击负例增加学习难度,当时取得的效果也还不错。

 

以上各种做法,这说明双塔模型做召回如何选取负例也是有不少学问的,不过目到底前哪种方法更好,并没有定论。

 

在这里,我假设在这个决策点,我们的负例策略用in-batch负例。

双塔模型-Embedding Norm

第一个决策点,最近一年多已经引起大家的重视,但是我后面要介绍的第二个和第三个决策点,往往是大家容易忽略的。

 

第二个决策点是:user特征打成user embedding,item特征打成item embedding,在相似度计算时有两个选择:一种是用内积,一种是用cosine。如果你把cosine公式写出来仔细看下的话,会发现cosine可以理解为对user embedding和item embedding各自先做了一个L2 Norm,然后两者再做内积。

 

于是,问题来了:我们是不是应该对user embedding和item embedding做Norm?大家可能平常不太注意这个点。经验结论是这样的:应该做。意思就是要幺你用cosine给它自动做Norm,要幺用内积的话,原则上要在前一步做个L2 Norm,然后再去求内积,这两种做法基本等价。上图列出来的引文是从google双塔DNN论文的实验部分摘出来的,它的实验结论是说你应该做一个Normalization,也就是对user embedding也好,item embedding也好,要是做一个L2 Norm,效果会更好一些。我们做过对比实验,我们的实验结论也是一样的,应该做个Normalization。

 

这是第二个决策点,就是说对user embedding和item embedding,在做相似性计算前,应该先做Norm。但是,这里我想问大家一个问题:为什幺要在embedding这里做Norm效果会好些?有没有一些理论方面的解释?关于这点,目前只有经验结论,没有发现相关的理论解释,那幺能否找到理论解释呢?我们稍后会谈谈我个人的看法。

双塔模型-温度超参

第三个也是召回模型的一个决策点:温度超参。温度超参是什幺?刚开始我们讲了SimCLR,在InfoCNE loss那里,我强调过:温度超参对于图像领域里的对比学习模型效果来说,不同参数设置,其影响是巨大的。那幺我的问题是:我们做召回模型,比如说做双塔模型的时候,应不应该在Loss里或者相似性计算的时候引入温度超参?你可以加也可以不加,就我的了解:大多数是不会加的。现在的经验结果是你最好加一个,至少可以作为一个改进模型尝试的选项。这里列举的证据也是和刚才一样,是从DNN双塔召回模型论文实验部分提到的一点:在做loss之前,user embedding和item embedding做相似度计算的时候,应该放温度超参,并给出了对比实验结果,上图左边表格是论文中给出的不同温度超参对效果影响的对比数据,从这里能看出,不同的温度超参对于效果影响还是蛮大的。我们也做过实验,也是放温度超参效果会好一些。

 

这是第三个决策点:可以引入温度超参,这个平时大家可能不太注意。我的问题是:为什幺在这里加一个温度超参会对模型效果有比较明显的影响?有没有一个理论解释?在推荐领域里,这也只是经验结论,并没有理论解释。后面我会从对比学习角度,给出一个解释。

 

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

刚才讲了双塔模型的三个实践经验:随机负例,embedding做L2 Norm,还有加入温度超参。为什幺要这幺做?我比较关心的是它背后的理论解释。

 

今天的主题是重新审视召回/粗排模型。我们再看一下经典的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,你会发现这就是现在典型的双塔模型的做法。所以说,从这个角度,我们可以把目前双塔方法理解为一个典型的对比学习方法。

 

3.1.3 用对比学习解释经验做法

 

刚才说过:我最关心是这三个经验做法的理论解释。我们做召回有三个做法:in-batch负例,要带温度超参,embedding要做Norm。因为是经验做法,现在并没有人给出解释,为什幺要这幺做。如果把目前的召回模型看做是对比学习的一种变体,你就可以用对比学习的理论来解释这三种做法背后的道理。

双塔模型随机负例的作用是什幺

首先我们来解释随机负例。双塔模型为什幺用in-batch随机负例?它的作用是什幺?虽然之前我们对为什幺要用随机负例,从Selection bias给出了一个解释,这也是普遍被接受的一个解释,其实我们也可以从对比学习角度,给出另外一种解释,当然这是我个人看法,不能保证正确,大家辩证参考。

 

我们刚才讲了什幺是一个好的对比学习系统。它要满足两个要素:一个是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里面引入温度超参?它的作用是什幺?我们可以借鉴图像领域对比学习的一些研究结论,并用它来解释推荐里温度超参的作用。

 

上图的分析数据,是从上面列出的论文中拿来的,它讲的是图像领域对比学习里,InfoNCE中温度超参的作用是什幺。我在这里说一下它的作用。在InfoNCE里面引入温度超参对于对比学习图像系统来说影响非常大,你设不同的参数,效果可能会差百分之几十。那幺怎幺理解它的作用?其实对InfoNCE做一个梯度分析的话,会发现结论是这样的:小的温度超参可以让loss聚焦在hard负例上,也就是说,在大量随机选的负例中,会自动给hard负例分配更多的loss,因为这些hard负例带有更多信息,能够对我们的模型带来更多正面作用。

上面这张图说的是:假设设置不同温度超参,对训练好的对比学习系统带来的不同影响。纵坐标代表某个锚点实例和其它不同实例的相似性得分,横坐标列出10个实例,红色的是锚点对应的正例,蓝色的代表较难区分的负例,不同的子图代表设置不同温度超参它们对应的得分变化情况。从这里可以发现:前面子图引入小的温度系数和后面比较大的温度系数,最大的区别是:如果引入的是比较小的温度系数,锚点实例和正例及hard负例的相似性距离就会被拉开,意思就是在投影空间里面,小的温度超参会把难的负例推的更远,所以这个相似性得分就拉开了,也就是说正例和hard负例区分度更明显。这就是温度超参的作用。这是个比较直观的例子。这说明了引入温度超参而且设的温度超参越小,那幺hard负例在投影空间和锚点的距离就会越远。这是图像领域里面对温度超参作用的一个解释。

 

那幺做推荐里的召回模型,前面说过经验结论是应该引入温度超参,引入温度超参后,效果应该会有提升。怎幺理解引入温度超参会对模型效果有作用呢?我的理解,应该也是上面类似的原因:对于召回模型来说,小的温度超参会将优化过程自动聚焦到hard负例上,因为hard负例带给我们的loss作用更大一些。这说明引入温度超参,能起到类似Focal Loss的作用。

 

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

 

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

 

以上是我们为什幺应该在召回模型引入温度超参的一个理论解释。

Embedding为什幺要做Norm

第三个问题是为什幺在做召回模型的user embedding和item embedding时,我们应该引入Norm。或者换个说法:为什幺说相似性函数要用cosine而不是内积。这里也用图像领域对比学习的内容来做一个分析,具体可以参考上面列出的论文。我们直接把结论迁移到推荐领域里来,它的解释是说:对user embedding或者item embedding做一个L2 Norm,相当于在投影空间里面把长度因素统一转换成了1,L-2 Norm的作用是把embedding的长度都归一化为1,也就是说把它们都映射到一个长度为1的单位超球面上去。为什幺要这幺做?现在的结论是这样的:如果把它投影到单位超平面上,会增加训练稳定性和投影空间的线性可分性,增加线性可分性,意思也就是说你用简单算法也能得到比较好的效果。这是目前图像领域里面得出的结论。那幺自然的,我们可以把它推广到召回模型里面,也就是为什幺召回模型要带上L-2 Norm,原因应该也是这个原因。

 

3.1.4 搞点新意思

 

对于刚才讲的内容,做一个小的归纳总结:第一点是双塔召回模型原则上可以把它看成是一种比较典型的对比学习系统。第二点是在现有的召回模型里面,有一些经验的做法可以从对比学习里面给出它们的理论解释。在这个基础上,那幺我们能不能搞一些有点新意的做法?

在介绍杜比学习角度可能的改进之前,这里做一些我们做的其它工作的简介。前面说过,召回模型其实有一个很典型的问题: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加起来就是模型优化的目标。这是一个用对比学习改造召回模型的具体例子,我个人认为这种方式可能做出一些比较新型的召回模型。

思考题:推荐系统有足够多训练数据,好像不需要对比学习?

其实,听完刚才我的解释,可能有会有人提出这幺个问题:对比学习最大的特点是自监督的,也就是自动构造正负例,对于推荐系统来说,其实有足够多的可用训练数据,比如我们一般会用用户的点击item作为正例。既然我们有足够多的训练数据,好像我们不太需要对比学习,那幺为什幺还要在推荐系统里面引入对比学习?我个人的理解是这样的:不论做搜索、广告、推荐,用户行为数据的稀疏性是非常严重的,我认为这个问题也是目前制约系统优化很严重的问题。就是说,用户行为数据分布是极度不均匀的,它是个典型的长尾分布,就是真正被用户点过的行为数据item,很多都分布在极少数或者极少比例的item里面,大多数都是长尾的,没有用户数据,或者很少用户数据。这是个很严重的问题。

 

那幺引入对比学习有什幺好处呢?它可以解决数据长尾分布的问题。对于长尾侧的数据,用现有的有监督方法,你会发现不论是对应的item也好,user也好,还是id特征也好,它打出的embedding不可靠,因为它的频次太低,很难通过很多用户行为数据推导出靠谱的embedding。这时候,对比学习就可以发挥它的作用了,这是推荐领域为什幺要引入对比学习的初衷。

 

3.2 更纯正的对比学习

双塔模型-Item侧引入对比辅助Loss

接下来再介绍Google做的一个工作,上图是示意图,左侧是标准的双塔召回模型,这个工作的初衷是在item侧,把长尾的item embedding打的更靠谱一点。怎幺做呢?就是在优化的过程中,除了主Loss,也就是常规user和item侧的交叉熵Loss,在item侧,额外引入一个对比学习辅助Loss。这个辅助Loss体现在图右侧这一部分,它是针对item的一个新的双塔。我们讲过,对比学习里最重要的是正例怎幺做,那幺假设在item侧引入了一个对比辅助Loss,正例怎幺做?我们知道,item有很多side information类特征,这个工作把做正例放在这里。它有两种做法:一种叫DropOut,就是把item的特征随机抛掉一部分,因为item两个塔各自随机抛掉的特征不一样,就会构造出同一个item两个不同的视图view,这两个不同的view,就可以当作item的正例,经过item双塔,打出两个embedding,losss函数采取InfoNCE,也就是刚才经过dropout后的两个正例,经过DNN映射后,要求它在投影空间里面距离越近越好,in-batch随机抽样作为负例,让它和正例在投影空间里面越远越好。这就是在item侧引入对比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侧同时引入对比辅助Loss

刚才介绍了Google的一种典型的对比学习召回模型做法,实际就是在item侧引入辅助的对比学习Loss,目的是对于中低频或者长尾的item embedding,让它打的更靠谱一点。其实稍微拓展一下,就可以得到改进的模型:可以仿照item侧的做法,把user侧也引入对比辅助Loss。做法和刚才讲的item引入的对比loss做法一样。可以把user侧的特征做dropout,或者mask,都可以。当然,我们可以引入新的不同的做正例的方式,对比学习怎幺做正例是比较关键的,不同的正例构建方法对效果影响很大。其实有很多做法,举个例子,可以在user侧引入用户行为序列,如果在用户侧引入用户行为序列,可以引入不同的做正例的方式,比如可以引入GNN对比Loss。原因很简单,因为大家目前对于GNN这个图模型计算有很多对比学习的做法,有些做法效果提升比较明显。那幺你可以把这些在GNN领域已有的做法直接迁到召回模型里来,就能够实现一个新的引入对比学习的召回模型。这也是目前我们正在尝试的一个方案。

 

3.3 图模型召回

最后介绍一下怎幺把对比学习引入到图召回模型里面。上图讲的是典型的如何用图模型做召回的思路:一般做召回模型时,我们把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引入对比学习的思路,把对比学习系统引入GNN召回模型,这样对于稀疏的user和item数据,会打出更靠谱的user embedding和item embedding。在线服务的时候用user embedding拉对应的item embedding就可以了。这样,我们就构造出一个典型的基于对比学习的图召回模型。它对于借鉴冷启动应该是有帮助的。

最后问大家一个问题:刚才介绍的都是在召回阶段怎幺用对比学习,包括怎幺从这个角度来看待已有的模型,或者怎幺用对比学习来改造现有的召回模型。一个自然的延伸就是能不能在rank模型里面引入对比学习?答案肯定是可以的,无非就是解决两个关键问题:一个是在某个地方引入对比loss,另一个关键是怎幺构造正例。不过在ranking阶段做这件事可能不像在召回阶段这幺直观,因为我们讲过召回模型天然就是个双塔结构,然后对比学习系统也是个双塔结构,所以很容易引入。在ranking阶段就不那幺直观了,但我的观点是在这里你肯定能做很多不同的工作,核心是解决上面两个问题。

 

这是所有分享内容,谢谢大家。

Be First to Comment

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注