Swing 一文着重介绍了算法本身的细节,本文介绍 Q2I2I 召回的在搜索业务中的一些经验。阅读本文之前请先阅读 Swing for Search(Swing 算法在搜索召回中的应用)。
Trigger 选取
我们在上文中介绍过,Q2I2I 的 trigger 也可以在 pre-rank 之后选取 top item. 但这种方法主要问题是一般由于延时、计算量限制,这样召回的物品可能没有办法再走一遍 pre-rank 流程了。这种方法比较适用于常常出现召回数量不足的系统。当 pre-rank 之后发现召回数量不足时,可以使用这种方法召回一些 item 作为补充。
而更合理的做法是在召回阶段设法选取一些 item 作为 trigger. 保留几十个较为合适,可用的方案包括,
- 最佳来源是历史点击数据,统计每个 query 下高点击 item 是最高质量的搜索结果
- 历史点击数据本身就可以作为一种召回方法(高点击召回),并作为各种 rank 模型的一个强 feature
- 只适用于热门 query
- 热门 query 还可以离线做全库排序,同样可以一种召回方法,并用作 Q2I2I trigger
- Text Match 等方式
I2I 工程实现方式
给定 Trigger 之后,如何实现 I2I 召回就容易多了。但还是可以有两种方式:最直观的一种方法是把 I2I 列表写进 Redis Cache, 直接读 Cache. 这种方法简单而且可控性好,可以很方便的调整每个 Trigger 的召回数量。
还有一种不那么直观的方法,搜索系统中常常使用 ElasticSearch 或者类似的倒排系统,于是可以把 I2I 表作为 tag 写进文档倒排表中。I2I 表格原则上是对称的,对倒排索引不需要特殊处理。这种方法的好处是可以与文本方法结合,比如要求 item 满足一定的文本匹配条件。
这种方法多了一层抽象,而且由于是倒排表,不容易直接控制每个 Trigger 的召回数量,反而是可以控制倒排索引的 I2I list 长度,间接控制召回数量。ElasticSearch 在召回时需要给出一个排序公式,如果 I2I tag 命中 文档数量过多,同时 ElasticSearch 召回数量有限,那么,最后主要起作用的其实是这个排序公式。于是,这种方法会有 trigger 数量,倒排索引 I2I tag 数量,ElasticSearch 召回数量,排序公式等众多需要小心决策的参数,而且这些参数对 I2I 实际命中数量的控制都是间接的。
收益讨论
Q2I2I 召回在搜索场景中效果意外的很不错。笔者曾经认为,这种召回方式因为是「两跳」的,效果可能不会很好。既然有方法实现 Q2I 第一跳,为什么还要额外的第二跳呢?但实际经验表明,这种方法即使在 text match, vector recall 等方法之后上线,也能有一定的收益。如果较早上线,甚至能够占据相当大的流量,成为主力召回方法。对此,笔者认为原因可能包括,
- Query 和 item 在不同空间,因此 Q2I 难度较大,但 item 彼此之间在同一个空间之中,I2I 难度较小
- 第一跳 Q2I 因为要求的 trigger 数量不是很多,较容易选取质量较好的 item 作为 trigger
- 第二跳 I2I 并没有增加太多难度
- 对于召回系统而言,只要后级 pre-rank & rank 能够支撑,召回数量提升往往带来明显的整体指标提升
- Q2I2I 能够提供相当大的召回数量(trigger 数量 * 每个 trigger 召回数量)
- 基于用户行为的 Swing 具有非常好的多样性,并且自然的集中在热门物品上,是一种很好的基线方法
- 尽管是搜索场景,Swing 算法在构建用户行为序列时并不区分用户的 query