闲鱼搜索召回升级:向量召回 & 个性化召回
涤生、志勇、远悠 闲鱼技术 稿
在搜索系统中,召回环节位于排序漏斗的最底层,决定了下游排序的空间大小,其重要程度毋庸置疑,在闲鱼搜索场景亦是如此。然而由于机器和人力资源的限制,长期以来闲鱼搜索的召回都是使用最简单的基于文本的召回方式,其优化迭代方式也只是在基础商品字段(标题、描述)之上,增加扩展字段。基于此,季度优化前,闲鱼主搜的召回整体方案如下:
- Query侧:通过Query改写,扩展Query语义,缓解用户侧搜索词表达不充分的问题,间接实现扩召回;
- Item侧:通过增加扩展字段,强化商品侧的表征,具体的拓展字段包括:
- 结构化信息,如类目、算法识别的CPV(属性值,如商品品牌、型号、颜色等);
- 商品图像识别的标签,如OCR识别出的商品图片中的描述字段;
- I2I商品信息迁移:通过swing等I2I技术,引入与trigger商品相似商品的基础信息作为文本召回字段;
- 同款、一键转卖商品信息迁移,同I2I,只不过扩展信息通过确定的关联商品得到;
- 其他商品预测Tag拓展;
虽然通过如上扩充索引字段的方式,有效提升了搜索的召回能力。但数据分析发现,召回不足的情况仍有较大的搜索PV占比,说明召回侧还有比较大的空间可挖(具体数据这里不做详细罗列)。而优化召回不足大体可以从两个方向发力:1)算法策略层面进行优化,提升召回能力;2)供给层面优化,引导增加商品供给,或使卖家优化商品供给描述。这里则讨论前者,首先是当前系统主要有以下不足之处:
- Query召回商品仍然使用纯文本方式召回,Term命中规则严格,缺少语义匹配能力。
- 当前召回个性化能力不足,或者说没有兼顾效率特征,召回截断后可能损失更具个性化的相关商品。
对于1,本季度我们增加基于语义的向量召回,缓解召回语义能力不足的问题;对于2则有很多思路,如考虑成交效率的向量召回、u2i、u2i2i等,这里做了一些尝试,发现有时常规的方案无法直接照搬到闲鱼场景,而最终本次优化我们优先采用了基于行为的I2I(准确说是Q2I2I),同时为了弥补长尾query召回仍然不足的问题,我们补充了基于多模态内容的I2I,从文本和视觉维度召回相关商品。
对于上述扩召回的候选,我们使用类目、核心词、语义相关性等维度保证相关性,召回升级后整体模块构成如下:
后面的章节,将依次分模块进行详细方案的介绍。
语义向量召回
建模目标
搜索向量召回的最理想结果是尽可能检索出“相关且高成交效率”的商品。由于闲鱼搜索之前没有向量召回链路,因此一期我们决定先从“相关性”目标出发,设计基于纯语义的向量召回,目的是弥补文本召回语义泛化能力弱的问题。其难点主要为闲鱼场景特色下Query和商品的语义表征建模,以及线上机制策略的兼容;而对于“成交效率”目标的兼顾,本季度也做了相应的实验,但是由于闲鱼场景的特殊性,暂时无法直接照搬常规方法,需要进一步探索,这点在本章结尾进行讨论。
模型设计
闲鱼搜索的语义向量模型同大多数的场景一样,使用DSSM架构,Baseline Encoder为预训练的Electra-Small模型(相对于Bert-base效果微跌,但模型大小由300M+缩小到47M,提升了运行效率)。为了丰富Query语义,弥补Query表达不充分的问题,我们增加了临近Query表征(基于行为的Q2Q),与集团ICBU、淘宝主搜通过多任务方式引入不同,这里直接增加Query和临近Query的self-attention模块,通过更为直接的融入信息,避免了多任务调餐的工作量及其不确定性。
对于无临近Query的Key Query,进行置空操作,此外对于有临近Query的Sample也会以一定几率置空,以适应新Query与超长尾Query缺少Q2Q的问题。
模型架构如下:
实现细节
- 数据构造
搜索语义召回向量的核心目标为相关性,因此其样本构造也是围绕此设计,这里方案参考闲鱼搜索相关性——体验与效率平衡的背后:
- 正样本:充足曝光下高点击ctr样本(如:ctr大于同query下商品点击率平均值)
- 负样本:
- Baseline随机构造负样本:为增加随机性,该部分实现可在训练时使用同Batch中其他样本做负样本,同时也可以引入经典的Batch Level Hard Sample机制。
- 同父类目的邻居子类目负采样。
- 高曝光低点击类目样本:同一个Query搜索下,根据全局点击商品的类目分布,取相对超低频类目样本作为负样本。
- 充足曝光情况下,低于相应Query平均曝光点击率一定百分比的样本做负样本。
- 基于Query核心Term替换构造负样本:如,对于“品牌A+品类”结构的Query,使用“品牌B+品类”结构的Query做其负样本。
- Graph Query Attention
Query Graph即上文提到的行为Q2Q,这里参考Swing I2I算法,构造Swing Q2Q结果。详细地。取Top 3 Query拼接后作为Key Query的补充信息,而后Key Query与Graph Querys通过[SEP]拼接作为模型Query塔的输入。为了使模型能够区分Key Query和Graph Querys的差异,不同的Query类型使用不同的Segment_id作为标识。对于Item-Title塔,其Segment_id与Key Query一致。
直接拼接的方式融合Graph Query信息可以从模型底层就与key Query进行相互的Attention交互,以Query-Title的匹配为目标优化可以更直接的引入临近Query的有用信息,缺点则是一定程度上损失了模型预测阶段对于无Graph Query的样本精度。这点则是通过随机丢弃Graph Query的方式缓解,实验证明该方法行之有效。
- Batch-Level Triple loss vs InfoNCE loss
早些时候,语义向量召回的优化目标loss为经典的Triple loss:
其中 d(a,p)表示向量a和p的距离,a和p表示query和doc的正样本对,a和n表示负样本对。Triple loss训练的另一个标配是Online hard negative mining,即在mini-batch内,每个正样本对之间互为负样本,选择其中最难的样本作为负样本(相似度打分最高的样本对)。
除了经典的Triple loss外,我们也借鉴了对比学习框架(如MOCO、SimCLR等)下常用的InfoNCE Loss,:,这里增加温度参数,进一步提升模型的泛化能力。
其中q为query向量,k+为商品正样本对,k-为负样本对,τ为temperature超参数。InfoNCE在自监督对比学习中十分有效,同样在有监督的对比学习框架下,也被验证效果。上式从代码实现来看更加直观,也比较巧妙:
loss_fun = torch.nn.CrossEntropyLoss()
# query_vecs : [batch, feat_dim]
# item_vecs : [batch, feat_dim]
# sim eg: cosine similarity,return dim: [batch, batch]
scores = sim(query_vecs, item_vecs) / temp
labels = torch.arange(scores.size(0)).long() # scores矩阵对角线为正样本
loss = loss_func(scores, labels)
- 商品侧引入其常搜query信息
该部分策略出发点,闲鱼搜索场景中很多标题也存在信息量不足的情况,如“便宜卖”,容易使模型学偏,这里使用商品历史有点击的Top3 Query作为商品的补充信息,与Query Graph一样的方式融合进网络(使用不同的Segment_id),但实验发现效果不尽如人意,原因分析可能是闲鱼商品在预测过程中,有不小占比的商品集合为新品,该部分商品往往补充信息不足。商品侧引入其高点击Query,根据模型《Shortcut Learning in Deep Neural Networks》的论述,这里模型容易走捷径,过分关注补充的Query信息,对于缺失补充特征的商品预测效果有偏。因此在离线HitRate指标表现不好,在线也存在类似分布不一致的问题。而换个思路补充相似商品信息,也会存在类似问题,因此该路径未继续深挖。
- 上述关键离线实验对比
策略 | AUC(相关性) | HitRate@1、5、10 |
---|---|---|
online baseline model+dim=128 | 0.780 | 51.0、73.3、80.7 |
query+难样本构造+dim=64 | 0.805 | 52.6、 76.8、 84.4 |
query+graph query+dim=64 | 0.790 | 50.5、 76.3、 84.4 |
query+graph query+难样本构造+dim=64 | 0.804 | 55.2、 79.6、 86.6 |
query+graph query+难样本构造+infonce loss+batch size=64 | 0.822 | 61.2、 83.9、 89.6 |
aph query+难样本构造+infonce loss+batchsize=128 | 0.824 | 62.6、 84.7、90.1 |
线上工程方案
- 向量引擎
得益于强大的Ha3引擎,使得向量引擎的搭建变得容易了许多。在构建向量索引和使用向量引擎对外提供服务的过程中,需要注意以下的问题:
- 商品状态的维护,包括商品的下线以及新商品的发布。闲鱼场景中的商品属于孤品,通常商品被卖出或者下架后,将不能再被展示出来,这就要求商品在下架的同时能够及时从索引中删除;同样,在闲鱼平台上,新商品的成交要比旧商品高,因此,新入库商品需要能够及时进入向量索引。
- 召回结果的过滤、统计、排序等操作。为了能提高向量召回结果的效率,需要将这些操作封装到引擎中,使得召回的结果符合相关性等要求。
为了能够同时满足如上的需求,我们设计了如下的向量召回索引流程:
-
索引构建阶段,分别搭建数据的批量和增量流程,并通过SARO(集团PB级数据处理能力以及秒级实时性能的大数据ETL平台)统一管理
- 每天例行通过ODPS构建T+1全量的索引,使得每天的在线数据能够及时同步;
- 每天的增量数据,如商品状态的改变(如商品下线,价格更改等),新增商品,都会通过消息流的形式传递给SARO,通过SARO对数据统一管理。
-
引擎查询阶段,得益于Ha3引擎的强大功能,通过scorer插件、function插件等对召回结果过滤、统计和排序
-
多路召回
在闲鱼搜索业务中,存在多个索引引擎,主要包括倒排引擎,即主引擎和优品引擎,在增加了向量引擎以及I2I引擎后,如何以最小的代价对链路改造多链路召回使其满足整体RT的要求,我们设计了如下的多路召回流程:
在实现的过程中,采用的是如上的并发多路召回的方式,目前索引引擎的种类分为两种,分别为Ha3引擎和KV引擎引擎。当前对于每一路引擎都是独立的召回,通过设置不同的召回量控制每一路引擎的召回结果。
问题讨论与优化方向
在上述基础的语义向量召回基础上,本季度我们也做了一些其他的尝试,下面主要简单描述尝试的方案,以及阶段性的结论分析:
-
增加个性化向量召回
向量召回的理想目标是检索出“相关且高成交效率”的商品,而基于语义的向量召回更多的是考虑“相关”目标,“高成交效率”的目标则是在粗排/精排阶段实现。如果能在召回阶段就考虑成交效率,一步到位,应该是更加理想的状态。
因此,我们也尝试了主流的个性化向量的建模方案:
- 模型侧:仍为DSSM架构,升级query tower变为user&query tower,item tower不变;
- 数据侧:使用点击/成交user&query-item对为正样本,曝光为点击/点击未成交的样本为负样本,同时增加随机采样负样本;
- 特征侧:增加个性化特征,如用户画像和行为特征、商品和卖家维度的统计特征等。
如上的改造期望使向量模型学习到个性化特征,但结果是模型失去了“相关性”的度量能力(原因也容易想到,在正负样本的构造过程中,“曝光未点击”的负样本极大的可能是“相关”样本),其实上述方案被叫做“双塔粗排模型”更加贴切。
基于这个结论有两条优化路径:
- 在个性化向量召回过程中或过程后增加相关性策略(如类目、核心词匹配),方案看上去可行,但回过头来想,其实和使用“类目/核心词召回,个性化向量粗排”差别不大,已经失去了向量召回的初衷,因此废弃。
- 融合个性化和相关性向量,在召回阶段兼顾二者目标,比较有代表性的工作如百度的《Mobius: Towards the next generation of query-ad matching in Baidu's sponsored search.》等,该部分方案是相对合理的解决方向,同样也是后续的探索方向。
-
引入多模态
上述语义向量召回方案,并未考虑多模态信息,集团也有很多工作,这里也尝试了类似方案,使用多模态bert强化item侧的特征,事实上离线指标也能得到一定程度提升,但相对地,其链路的也变得更重,对资源的消耗也急剧增加。综合考虑下,当前线上主要还是使用文本模态。该方向仍需继续探索,以兼顾链路的时效性以及资源的ROI。
-
关于相关性控制
闲鱼搜索满意度和badcase率是一个比较严格的监测指标,而扩大召回势必会增加badcase透出风险,依赖语义的向量召回更是如此,由于没有严格的Term命中限制,不可避免地对一些query存在语义漂移现象,对于少见的长尾query更甚。
事实即是如此,在实践过程中,仅使用语义向量召回,虽然成交效率可以取得可观的提升,但badcase率也相应的急剧提升,这里我们的解决方案也相对常规:
- 考虑到语义向量匹配分可以度量query-item相关性,但一刀切的方式卡匹配分阈值会收窄成交效率的收益,因此我们使用动态阈值方法对候选进行召回截断,动态阈值通过离线query历史行为统计得到;
- 增加query-item类目匹配限制;
- 增加核心term匹配限制,同时兼容同义词。
- 其他体感问题:不合理价格、虚标价格、站外引流等商品的过滤
如此经过一系列的调餐,达到了搜索满意度和badcase率指标的可接受程度的微跌,但却损失2个点左右的人均买卖家效率指标(相对不做满意度优化)。这个方向上,仍然有一部分优化空间,如更柔型的相关性控制(当前版本核心term匹配这里限制的比较严格),更准确的语义向量表达等。相应的后续会从向量表征和机制策略两方面进行优化。
基于行为的I2I召回
当前召回个性化能力不足,对于不同用户的相同query,召回相同的候选,召回截断后可能损失更具个性化的相关商品。
建模目标
分析发现,用户通过搜索到详情页猜你喜欢,通过猜你喜欢场景成交的商品占比可观,通过case分析发现,该部分diff商品,靠常规的文本召回方法难以成功检索到。一个参考统计数据,搜索导流到猜你喜欢的点击PV里 ,仅有约25%在当前query的召回集合中,也就是说有75%没有被召回。如一个例子:搜索引擎中检索“迪卡侬优惠券”,返回结果量不足,而点击相关商品进入详情页猜你喜欢却可以关联处满足需求的商品。
因此考虑直接通过I2I方法召回检索池中的商品也会有一定的搜索效率提升空间。
方案设计
而在搜索场景下I2I实际上需要转化为Q2I2I,因此该部分实验可以分为两个阶段:
- Q2I:通过用户搜索query,得到trigger item,该部分有两种方式:1)直接通过当前query实时在线曝光的top item作为trigger商品;2)离线统计该query下历史成交、点击、加购、询单的item集合,在线请求query2trigger表。
- I2I:trigger item到扩召回item的映射,这部分同样有多种选择:1)使用闲鱼推荐或搜索场景的i2i召回表;2)直接使用详情页猜你喜欢的曝光点击日志表,构造I2I映射表,trigger为详情页主商品id。
通过以上两个阶段可以组合出Q2I2I的候选方案,最终通过AB test择优选择。使用上述方案,最优核心七日人均买卖家提升可以达到+4%的人均买卖家提升,人均成单相应涨幅有+5%。但相应的也伴随着严重的相关性指标下跌的情况。如比较典型的,对于优惠券/会员充之类的query,个性化召回往往会出现比较明显的发散性召回:
对此,我们将相关性的优化也分成了两个阶段,Query2trigger item的相关性,保证trigger item和query的相关性;I2I后,通过query与候选item的类目、核心词以及语义匹配分过滤相关性。在维持相关性波动不大的情况下,最终实现七日+1.8%人均买卖家,人均成单+2.65%。可以看到严格的相关性限制,也缩小了成交效率的空间,也是后续的优化方向,如使用更加soft的相关性限制。
各AB收益对比
经过本季度搜索召回优化,核心指标上有了明显的提升,推全时人均买卖家:近3日人均买卖家相对提升+4.66%;近7日人均买卖家相对提升+3.31%,人均成单:近3日人均成单+5.3%;近7日人均成单+4.02%;
其中,阶段性的参考切割实验对比:
- 向量召回:七日人均买卖家+2.25%,人均成单+2.69%;
- 行为I2I召回:七日人均买卖家+1.8%,人均成单+2.65%;
PS:参考主引擎等数量扩召回,七日人均买卖家+2.3%(未扣除相关性折损,即成交效率收益,损失相关性指标较严重。)相比之下,排除精排候选增多引入的不公平对比,扩召回优化的收益仍然可观。
总结与展望
闲鱼搜索场景相对常规的电商场景有不小的差异,如商品上下架频繁、整体库存不足,各类型商品库存分布差异大、用户query发散、商品类型发散等,卖家侧也是盘踞着各种类型的用户,自由闲置卖家、小专业卖家、黄牛卖家、“投机倒把”的中间商等,因此在算法策略优化中需要考虑的细节较多,召回优化过程中也是如此。
而对于后续优化方向上,在上面的讨论中其实也比较明确,总结一点即是召回效率与搜索体感指标的trade off,如何在保证相关的前提下尽可能召回高成交效率的商品,将是一个长期探索的方向。
参考资源
2、Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A.N., Kaiser, L., Polosukhin, I.: Attention is all you need. In: NeurIPS (2017)
3、Mobius: Towards the next generation of query-ad matching in Baidu's sponsored search
4、Shortcut Learning in Deep Neural Networks
召回返回万级别的候选,对于商品搜索可能有非常多满足用户需求的候选,用户个性化召回有发挥作用的余地。但是在其它场景下,例如短视频平台的搜索,可能满足query的视频量都没这么多,这个时候,召回阶段引入个性化的价值是什么呢?难道仅针对高频query做吗
召回个性化有一部分内涵作用在Query理解截断,比如不同用户识别为不同的搜索意图,进而个性化召回。
如果像你说的真正符合用户query的候选集如果不多,我其实也觉得个性化意义不大😂