调研背景和目的

目前公司的推荐系统已经初步形成召回-精排-重排的架构,但在一些基础设施上还有诸多的不足,这次调研旨在借鉴外部公司优秀成熟的推荐工程架构方案,寻找当前最适合公司的系统设计方案,进一步优化工程链路。

闲鱼推荐系统调研

淘系推荐系统多基于 BE、RTP 引擎实现业务的定制化,比较常见的如手淘首猜、手猫首猜等场景。而闲鱼推荐系统有一定的业务特殊性,由于是二手交易所以商品多为“孤品”,对实时性要求也相对较高。

整体架构

推荐系统流程和涉及模块如下图所示

image.png
image.png
  1. 用户信息: 通过用户 id 从用户中心服务获取用户的基本信息
  2. Trigger: 一般为用户历史浏览、点击、互动行为的商品,或者用户的属性等。通过数据回流将 trigger 信息存储在在线系统,通过用户 ID 查询出来,参与后续的流程
  3. 召回: 基于用户的特征和 trigger 信息,从全量的亿级别候选集中,挑选出万级别的商品,送入后续的算分排序阶段
  4. 粗/精排: 粗排一般使用简单网络结构如双塔,挑选千级别进精排。精排阶段会使用复杂模型
  5. 重排: 重排阶段,则会体现场景的业务诉求,做一下打散干预,比如类目打散、价格打散等
  6. 结果返回: 选取 topK 个商品,一般 10-20 个,填充商品信息后返回调用方
  7. 日志&埋点: 对展示给用户的商品做端上日志

在业务发展的过程中也存在电商推荐普遍存在的问题,如推荐场景的增加带来的迭代开发和维护成本增加,应对这种情况,通常需要工程团队抽象链路组件和模块,实现场景的配置化,下面展示了闲鱼推荐链路开发工作流。

image.png
image.png

整个闲鱼的推荐平台如下图所示,大致上包含了 1. 底池圈品 2. 算法离线训练 3.引擎层(BE、RTP 之类) 4. 应用服务(类似 TPP 应用层) ,当然还会有其他外围系统如 AB 实验系统、KM 监控等等。

image.png
image.png

下图展示了用户请求后的完整链路
注: 对比下我们的链路,最大的区别是特征读取环节和 summary 环节以及重排阶段。在闲鱼的召回中充分利用到用户的实时行为,而我们的召回多以离线方式产出,在线召回阶段不需要复杂的用户特征了;另一方面 summary 这一步我们算法服务不涉及,由调用方(导购为主) 补全商品信息;重排阶段我们是放在了精排中作为一个模块,算法管理后台配置决定是否重排和重排的方式。

image.png
image.png

圈品逻辑

推荐候选池: 即圈品构建推荐底池,ETL 后在商品上打上场景标,圈品的逻辑在体量大一些的公司都存在,一方面是业务逻辑需要,另一方面是缩小推荐量级。下图展示了推荐候选池的构建过程,整体应该是基于 SARO 平台完成的。

image.png
image.png

召回引擎

召回引擎: 提供了三种索引方式,分别是 i2i 索引,x2i 索引,深度召回,标准化输入与输出。

image.png
image.png

对于输入,第一种为 Trigger 格式,引擎将以传入的 Trigger 作为 key,从 i2i 与 x2i 索引中执行 KV 检索与倒排检索。第二种则是针对深度召回,上游传入模型预测出的 embedding 向量,再经由向量引擎完成检索。输出则是召回检索得到的商品 item_id 与对应的召回 recall_score。

  • i2i 召回,通过用户的行为日志,计算商品间的用户共现得分,以此为相似度。以用户有过行为的商品 ID 为 TriggerKey 查询召回商品。
image.png
image.png
  • x2i: x 可以是商品的 tag、class、brand、query、pool_ids 等,根据用户全域的行为构建用户偏好,对商品标题信息进行分词,以及用户的 tag,class,品牌,搜索场景下对应 query 等,最终构建倒排索引进行检索
image.png
image.png

可以看到 i2i 和 x2i 召回在存储上有所不同,i2i 使用了 KKV 结构而 x2i 使用了倒排,应该是基于性能的考量,i2i 不需要复杂查询

  • 深度召回:主要通过深度网络模型,来预测用户与商品的相似性。模型分别计算出用户侧向量与商品侧向量,在线检索时,根据用户侧向量,通过向量引擎完成 ANN 检索出 topK 个商品。
image.png
image.png

算分引擎

算分引擎将输入的待打分候选商品集关联上商品特征,并结合用户的特征,通过深度网络模型的计算执行,完成候选商品集中每一个商品对该用户的个性化预测得分。将算分排序模型的输入输出进行标准化,也提供了模型定制化的能力。有些场景不太适应通用的多目标模型,可遵循协议将模型接入,每一个模型具备一个唯一的标识 biz_name,场景配置上选择该 biz_name 即可

image.png
image.png

模型存在多个目标得分,比如 ctr_score、cvr_score、car_score 等。而最终的得分如何计算,场景内也支持配置运算表达式与加权&降权(有些场景倾向转化,有些场景则重成交,或者满足交易抵扣的商品需要提权),来满足不同的场景要求。
注: 这种配置化权重的做法在我们精排服务中也支持(ESMM 模型),只是整体上没有闲鱼做的这么标准化

image.png
image.png

贝壳推荐系统调研

贝壳的文章中提到了推荐系统在贝壳的演进过程,同时讲到了搜索推荐架构融合的话题,比较有启发性。

推荐系统演进过程

架构演进经历了四个大的迭代,最早期就是简单的基于内容和规则的推荐引擎,后面进一步增加了用户画像和协同过滤进行个性化推荐,再通过实时计算和实时模型实现实时个性化推荐,最后为了提升业务接入和迭代效率,推荐平台做了一个大的升级重构,支持业务配置化接入,最终升级为智能推荐平台。

  • 基于内容的推荐

用 Content-based 推荐算法,直接离线算出相似房源、热门房源等,然后写入 Redis,在线推荐服务再从 Redis 中查出离线计算好的可能感兴趣的房源,然后直接返回给用户进行推荐

image.png
image.png
  • 实时个性化推荐

在内容推荐的基础上,引入房源特征、实时用户画像和实时用户行为记录,升级为实时个性化推荐。个性化推荐底层新增经纪人作业数据、用户行为日志等数据,然后通过离线计算进行数据清洗和特征工程,生成房源特征和用户画像。再通过协同过滤算法,进行协同过滤推荐,然后把这些数据批量更新到在线存储引擎,包括离线计算好的召回数据、特征池和过滤集等,业务系统和推荐服务都会将实时埋点日志回流到实时计算服务和离线数仓中。从而实时更新召回数据和特征实现实时个性化推荐

image.png
image.png
  • 智能平台推荐

为了提升业务接入效率和效果迭代效率,不再每个业务场景对应一个独立的推荐服务,而是用同一套推荐服务支撑上层的所有业务,新接业务直接复用上线,而非重新开发启动一个服务。
将整个推荐服务做逻辑分层,分为应用层、计算层、数据层和模型层。应用层主要对外提供 API 接口,以及处理简单的业务规则和配置管理。计算层包含推荐的几个核心流程,如召回、融合、排序和过滤,会分别调用数据层和模型层。数据层统一对下层的在线存储系统进行基础的数据查询。模型层进行在线特征工程后会调用模型服务进行在线预测。计算层拿到数据层返回的结果后进行策略融合,然后调用模型层进行模型精排,最终返回给业务系统。
注:本质上也是接入层-召回层-排序层的抽象,和我们的做法基本上是一致的

搜索推荐架构统一

搜索和推荐有很多相通之处,在流程上基本非常相似,在数据和模型上有多可以复用,在外围平台工具层面也基本可以直接复用,不同之处主要是 Query,搜索围绕 Query 展开,而推荐没有明显的用户意图引导,以用户历史行为作为参考。

image.png
image.png

为什么要搜推架构统一: 核心是复用底层能力,避免重复建设带来的人力浪费和开发运维复杂性
架构统一方案架构图如下所示

image.png
image.png

我们做的主要的大的重构是在查询层,对原搜索和推荐系统的各模块进行了统一的整合。
最新的查询层主要分为六个核心模块,请求一开始会通过中控模块做参数校验、策略调度、缓存和兜底,然后中控会去调用下层各模块,先是意图解析模块(搜索使用,推荐不需要),拿到意图解析的结果后再去调用召回模块,召回的时候会先获取一些用户画像和特征,然后进行多路召回和融合过滤,返回给中控,中控得到召回的数据后调用排序,排序包括粗排和精排,接下来是重排,再之后调用理由模块,补充推荐理由,比如“满五唯一”,“近地铁”等等。拿到理由之后就会最终反馈给业务方,完成整个搜索推荐调用的过程

  • 中控服务

中控主要负责参数校验、调度、缓存、降级等功能,中控服务的设计原则是希望它尽量不要有业务逻辑,通过减少迭代最大化的保证中控服务的稳定性
注: 这一点有点类似阿里搜索用的 SP,但是贝壳的中控应该更“薄”,和我们召回引擎中的模块编排也是相同的思想,同时贝壳这个超时降级的方案也非常值得学习
最后中控最大的作用就是降级,任何下游服务超时或异常都不会造成业务方的查询异常,各个模块都有默认超时时间设置,但同时会实时计算剩余时间,各模块的实际超时时间是该模块默认超时时间和剩余时间的最小值。
比如一个常规的调用链,开始调用意图解析,再调用召回,再反馈给业务方。假设我们调完重排要调用理由的时候,发现理由服务挂掉或者响应超时,中控则会跳过理由模块直接返回,相当于是降级返回。如果召回模块超时,中控也会跳过召回模块,直接访问 ES 或 Redis,然后再拿这些结果去走后续的流程,相当于跳过整个召回逻辑直接拿基础引擎返回的召回数据传给排序走后面的流程。比如在异常情况下我们调重排的时候发现已经花了 950ms,由于只剩下 50ms,所以再去调理由的时候,理由模块的超时时间会被实时设置为 50ms,而忽略其默认的超时时间。

  • 召回服务
image.png
image.png
  • 重排服务

为了便利的组合复用这些规则逻辑,重排实现了 workflow 的工作流机制。例如默认配置中会有去重、融合、计算得分、按字段排序等默认规则,而“opt-in”可以增加规则,“opt-out”可以去除规则

image.png
image.png

注:相对比较重,我们还是目前的重排还是多以策略打散为主,也有一些业务规则上的重排但是不太好做到标准化。

总结

现有架构

以算法管理后台为中心,召回引擎、精排引擎作为底层基础设施,对外提供商品(内容)推荐、搜索排序、类目预测、个性化分发等服务。以推荐服务为例,目前我们也做到了召回-精排等主要服务的解耦,可以实现多场景动态化配置,使得新的推荐场景的接入成本进一步降低。

主要问题

当前的系统架构还存在许多不足和不合理的地方,最主要的有以下几点

  • 系统耦合

业务初期算法底层支撑系统建设有所欠缺,导致部分业务系统需要自行完成召回、调用精排等操作,系统之间耦合程度比较高。独立出统一召回系统后,一定程度上降低了系统间的耦合性。但目前业务系统和算法服务系统之间仍有不少可改进之处,如业务系统通过场景 ID 获取精排配置后调用精排服务。理想情况下,业务系统只关心场景 ID,具体的召回、精排模型等配置对业务系统完全透明。

  • 特征管理

当前针对每一个精排模型,算法同学需要手动维护多组特征,一方面增加了算法同学的工作量,另一方面也增加了存储的成本。而一些大公司普遍会自研特征平台,实现离在线特征的存储和用户、商品特征的管理,为召回引擎、搜索引擎、精排引擎等提供基础底层服务。

  • 实时性

实时性主要体现在两方面,对用户行为的利用和商品元数据的增量更新。
在用户行为利用层面,以召回方式为例子,目前公司以离线召回为主,如双塔召回模型通过离线训练直接产出每个门店的召回结果,而更常见的做法是先在线通过模型计算用户维度向量再通过向量检索引擎获取召回物料;又如协同过滤召回中,目前我们会在离线阶段关联用户-商品行为表和商品-商品相关性表产出召回结果,而一些公司会通过在线方式获取用户一段时间内有过行为的商品作为 Trigger,再通过 Trigger 查询 i2i 表获取召回结果。上述两个例子都是将用户实时行为利用到召回中,对召回结果实时性有一定的作用。
在商品元数据更新层面,目前我们的链路中还不存在索引服务,也不存在增量消息更新。如发布端对商品基础信息做修改或商品售罄等情况,推荐侧是无法感知的,这对推荐结果的实时性有一定的影响。

  • 链路闭环

一条完整的推荐链路涉及离线数据分析处理、样本采集、模型训练、索引构建、召回&精排引擎部署、在线服务开发、埋点及端日志采集、AB 效果分析等。
埋点和日志采集涉及客户端配合,往往是比较容易出问题的一环,且一旦有问题也不容易被发现。目前我们分析实验效果需要关联离线报表(商品曝光+AB 平台日志,通过门店 ID 关联),因此如果对实验效果实时性有要求是无法满足的。一些公司会在服务端将实验信息添加到埋点中,客户端透传上报,这种方式更加灵活且避免了实验轮转带来的数据不准确问题,同时借助流计算可以做到实验的实时效果分析和业务指标的及时告警。

参考文档

[1] 详解闲鱼推荐系统(长文收藏) - 掘金
[2] BasicEngine — 基于 DII 平台的推荐召回引擎-阿里云开发者社区
[3] 如何快速拉起多路召回服务_智能推荐 AIRec-阿里云帮助中心
[4] 调用 BeRead 接口获取智能召回引擎的召回结果_智能推荐 AIRec-阿里云帮助中心
[5] 当你打开天猫的那一刻,推荐系统做了哪些工作?AI陈启伟_InfoQ 精选文章
[6] 贝壳找房 | 降本提效,贝壳搜索推荐架构统一之路 - AIQ