往期文章:

  1. 推荐系统工程系列(1):浅谈推荐系统架构
  2. 推荐系统工程实践(2):详解去重设计与实践

本文同步发表于

  1. 知乎专栏: 倒排索引在召回中的应用
  2. 微信公众号: yanianthe的公众号: 倒排索引在召回中的应用

欢迎订阅,一起学习一起成长~

文章目录

  • 1. 什么是倒排索引
  • 2.倒排索引在推荐系统中的应用
    • 2.1 内容召回
    • 2.2 策略召回
  • 3. 倒排索引的难点
    • 3.1 索引的划分
    • 3.2 索引的构建
    • 3.3 索引的更新
  • 4.推荐场景中的倒排索引
    • 4.1 选型比较
  • 5 总结
  • 6 参考

1. 什么是倒排索引

倒排索引这个概念,在信息检索领域使用比较广泛。核心的需求是:如何从超大规模的内容库中召回匹配关键字的结果

比如,在谷歌中搜索包含 ”推荐系统“ 关键字的内容。最直观的做法是针对数据库中所有内容一条一条匹配。但这样查找复杂度至少是O(n),面对成千上亿的海量数据,效率上远远达不到要求。

因此,搜索引擎常规做法都是预先针对内容建立一个关键字索引。记录关键字对应的文档Id,位置,甚至是权重(分数),查询的时候,直接到表中获取关键字的文档列表,倒排索引一般使用hash索引结构,查询复杂度O(1)。可以极大减少检索时间。

2.倒排索引在推荐系统中的应用

在个性化推荐领域,倒排索引同样承担着召回的大任。搜索场景中,关键字是用户主动输入,推荐系统中的"关键字",更多的是依靠用户当下场景,上下文以及浏览行为等。

2.1 内容召回

其中一个比较大的应用场景是内容召回,也叫CB类召回。

在工业级推荐系统中,经常会离线分析用户的行为,得到一份对不同标签的偏好值。做的细一些,还会根据时间,偏好等不同维度进行划分。

根据用户的不同画像系统,通过倒排索引召回,可以增加用户的内容丰富度,比如最近 原神比较火,用户也频繁点击观看,那么下一次推荐中,可以召回更多原神相关的内容。

内容召回的特点是: 用户兴趣画像明文化,召回可解释性强。但同时也比较依赖内容的标签体系。

内容标签体系常见的挑战和难点有:

  1. 多媒体的内容理解: 如图片,文字,音乐,视频等。不同类型内容的主要建模手法略有不同。感兴趣可以看看,最近两年视频领域很火的多模态技术
  2. 统一的标签体系: 内容解析完后,需要对内容进行打上统一标签。比较常见的一种是按照行业进行多级分类,比如 体育-篮球-NBA等。

大厂里,一般都有专门的内容中台,负责内容解析,给到推荐业务使用的,基本是解析好可以直接使用的数据格式。

如 标签,分类,title,作者Id,内容Id等信息。

大致的数据流转如下

2.2 策略召回

除了主流的画像召回。倒排索引还可以灵活应用在策略召回上,比如热度召回,新物品召回。产品也可以基于标签规则进行内容选取,在不同时间点进行运营和投放。

3. 倒排索引的难点

推荐系统的内容数据量级,一般情况下远远小于搜索引擎。搜索引擎检索查询的是全网内容,而推荐系统,大部分以垂类内容为主,比如小说,影视,商品等。而且为了用户体验,内容一般会有时效性,过期就会从推荐池子中剔除(比如新闻领域,对内容的时效就会要求高一些)。像信息流领域,内容池的大小一般也是千万到亿级别左右。

不同的业务和数据量级下,对倒排索引的要求也都不太一样。比如量级小,但是要求索引实时更新,数据量级大,要求索引检索性能高等等。我尝试着从索引的划分,构建,更新,使用等几个角度,讨论下倒排索引的难点和常见的解决方案,以及在ES中的做法。

索引的内容知识体系非常庞大,笔者能力有限,有纰漏错误在所难免,权当与大家交流讨论。

3.1 索引的划分

当索引数据大到一台机器无法容纳时,如何解决?

一般是通过索引拆分来解决大数据量的问题,有一点点类似mysql的分库分表。

倒排索引的划分,通常有两种方式。

  1. 按照关键字划分: 针对关键字进行字典序或者hash取余方式
  2. 按照文档Id划分: 针对文档id 进行hash取余方式。

不同的索引划分方式对于查询的时候,流程也会略有不同。

比如,同样查询包含 “推荐系统” + ”倒排索引“ 两个关键字的数据。

  1. 按照关键字划分: 会根据关键字查询对应的索引,假如两个两个关键字落在不同索引上,则需要分别请求2次,然后再合并。假如落到1个索引上,则只需要请求1次。
  2. 按照docId: 由于每个index只拥有1部分数据,因此每次请求都会分别请求2个分片索引,然后再合并起来。

按关键字拆分,一个比较明显的问题是,倒排拉链表可能会非常长,可能会因此带来很多长尾问题。一般情况下,业界多采用按照docid的划分方式,也叫横向划分。由于文档分布在多台服务器上,每台机器的posting list 就会比较短,可以提升单机器的索引效率。而且更新文档的时候,也只需要更新一个分片。而按照关键字拆分时,就需要更新多个分片上的索引,会相对复杂一些。ElasticSearch 中, 文档的routing 路由机制,默认就是 docId。

当然横向切分也不是完美的,毕竟分片切得越多,请求就会相应的放大,因此需要根据系统的实际需求,做好相应的评估。

另一个就是分片之间的数据均衡问题,这块可以通过使用更均匀的hash算法或者虚拟/逻辑分片等手法(类似一致性hash的虚拟节点,redis 的slot机制也可以算是一种数据平衡手段),使得分片数据尽量平衡。

3.2 索引的构建

在搜索场景,数据源比较大,数据处理和索引的构建也很难在单机上完成。上一小节提到可以索引按照docId进行划分,因此大部分情况,也可以借助spark/hadoop等系统进行分布式构建。通过对文档Id计算hash取余,可以落到 kafka 的某个topic上,或者特定的hdfs上,进行划分。(也可以不区分,每个分片的构建任务过滤掉符合自己规则的docId也可。)

分布式构建过程中,每个文档解析为 <key,docId,score> 的pair列表,每个节点先本地构建倒排链表,map阶段结束后,各个节点再按照关键字 reduce起来。组成最终的倒排链。

构建完索引,在线服务只要加载下这个倒排表,使用hash的方式即可以 O(1)查到 docId。

但是在面对关键词典较大,可能有成千上万词典,对应的doc list 也可能很长。全内存加载成本比较高,倒排表大一些,也无法全部加载进内存,因此基本上都会使用 内存(存储关键词典) + 磁盘 (存储doclist) 的方式。

大概结构如下:哈希表的key 是关键字的hash值(int32)

哈希表O(1)的查询复杂度,带来了空间上的一些冗余(比如多存了hashvalue,为了防止冲突,使用拉链表,多存了next指针等)

实际工业级检索系统中,为了节约内存空间,会对词典表进行一些压缩。词典关键字虽然多,但有很多关键字其实是有重复的。

比如 “推荐系统” 和 “推荐引擎”,都含有 "推荐"两个字。常见的使用前缀树的方式进行压缩。

在ES中,还引入了FST树,不仅共享前缀,还共享后缀。

3.3 索引的更新

根据公开的方案和资料,工业级的搜索引擎一般都分为在线和离线模块。离线Indexer 负责索引的管理和构建,在线searcher 负责索引的查询与结果的合并。这样做的好处是能够减少索引构建阶段对于在线请求的干扰。

常规的索引更新都是使用双buffer切换,更新的时候开辟一块新内存,等加载完毕后,在替换一下引用。但这样的缺点是,所有的服务器都得预留1倍的内存,成本压力巨大。因此常见的方案都是分批小流量更新。每次更新的时候,服务下线,等待更新完毕后再重新上线服务。大的索引一般都会选择在陵城流量低的时候更新。

但这种天级更新,实时性会比较差。因此业界一般都是采取全量 + 增量(实时)更新的方案。

增量更新意味着要随时更新索引,修改倒排列表还要读写磁盘, 磁盘随机读写性能非常糟糕,会极大拖垮整体性能。

因此需要避免磁盘随机IO,将随机写改为顺序写(磁盘顺序写与操作内存同个量级)。LSM树就提供了这样的功能。

  1. 剥离变化与不变的数据。
  2. 每次修改的时候,先将数据存入内存结构中。当内存索引达到一定阈值或满足一定规则后,数据flush为不可变的表。
  3. 分层思想,每一层都有不同的容量限制,达到后会往下沉且合并。由于每个table都是有序的,因此合并过程中也是顺序读写的,不会产生随机磁盘io。
  4. 使用标记位来代替删除,合并时才真正删除。

这也是ES采取的方案。

在ES中,索引对应一个个的segment,查询时,同时搜索内存buffer 与 segment段。同时,面对segment文件越来越多,还可以配置不同的段合并策略。

4.推荐场景中的倒排索引

4.1 选型比较

针对现阶段的倒排索引技术栈,大厂核心的搜索系统都是自研,像百度,360,微信等。

但与搜索引擎不同,推荐场景的数据量级小,一般都是千万到亿左右,开源界也都有不错的解决方案。

搜索引擎开源热度排行热度排行。(ES稳居第一)

开源主流的主要有

  1. sphnix: C++ 编写,性能上要优于ES,增量更新支持能力较弱。
  2. Elasticsearch: jvm,基于 luence开发。易用性高,横向扩展方便。支持向量检索 + 近实时检索
  3. solr: 基于luence, 倾向于静态索引,实时检索能力较差

详细的对比

在一些迭代快,业务规模较小的推荐团队,可以考虑使用ES。且最新版本的ES也支持向量检索,同时ES调优也有比较成熟的方案,像腾讯云 & 阿里云上售卖的Elasticsearch,会做一些内核优化。(比如腾讯云的 FST 堆外内存优化,多级cache缓存等)

基于ES做画像召回时,还可以基于业务的特性,针对部分热门的tag,做缓存,这样也可以提高整体的召回性能,减少成本等。

5 总结

倒排索引是一个比较大的话题。搜索和推荐对于倒排索引的要求也不太一样,鉴于笔者能力范畴有限,难免有些错误和疏漏,也欢迎大家在评论区发表交流,你们在画像召回和索引上的一些实践和经验。

6 参考

  1. https://www.6aiq.com/article/1536482487251
  2. https://mp.weixin.qq.com/s?__biz=MzIwNzc2NTk0NQ==&mid=2247486597&idx=2&sn=5b53f956d25706d098268d92400f7f9b&chksm=970c2453a07bad45636a718c10ab4a058794b9a6df40aa1231d5116081dae9c49931cf9b003a#rd
  3. https://aijishu.com/a/1060000000100753
  4. https://mp.weixin.qq.com/s/ipuIq_E5rOPPF7_XdS5PGQ

推荐系统(3):倒排索引在召回中的应用相关推荐

  1. 推荐系统[九]项目技术细节讲解z1:Elasticsearch 如何进行快速检索(ES倒排索引和分词原理)以及倒排索引在召回中的应用。

    搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排).系统架构.常见问题.算法项目实战总结.技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排 ...

  2. 【机器学习】因子分解机(FM) 原理及在召回中的应用(python实现)

    FM 原理及在召回中的应用(python实现) 1. 综述 为了学习推荐系统的召回模型,首先梳理了一下FM模型,权当是学习笔记,记录一下. FM(factor Machine,因子分解机)算法是一种基 ...

  3. 推荐系统(一)召回阶段

    (目前理解不深,待补充完整) 前言 推荐系统的三个阶段分为 召回 粗排 精排 粗排精排阶段又可统称为排序阶段. 召回是推荐系统的第一个阶段,当然前面还有user embedding阶段也就是提取用户和 ...

  4. 阿里ESAM:用迁移学习解决召回中的样本偏差

    题外话 我为什么要将我的论文笔记发出来?现在绝大多数论文都能够免费下载,既然人家能够读到原文,又为什么要花时间看你的读书笔记这种"二手货"? 打个比方,除了相声,我也非常爱听郭德纲 ...

  5. 推荐系统总结之深度召回模型(上)

    作者:一块小蛋糕 地址:https://zhuanlan.zhihu.com/p/267263561 整理:深度传送门 最近读完了李航.何向南的<Deep learning for matchi ...

  6. 「从零入门推荐系统」08:召回算法之5类基础召回算法

    作者 | gongyouliu 编辑 | gongyouliu 我们在上一篇文章中介绍了规则策略召回算法,这类方法非常简单,只需要利用一些业务经验和基础的统计计算就可以实现了.本节我们来讲解一些基础的 ...

  7. 一文讲懂召回中的 NCE NEG sampled softmax loss

    深度学习中与分类相关的问题都会涉及到softmax的计算.当目标类别较少时,直接用标准的softmax公式进行计算没问题,当目标类别特别多时,则需采用估算近似的方法简化softmax中归一化的计算. ...

  8. 推荐系统(4):深入浅出向量召回检索技术

    本文同步发表于: 知乎专栏: 推荐系统工程实践专栏 微信公众号: yanianthe的公众号 也欢迎关注我的微信公众号,一起学习成长吧~ 文章目录 1. 深入浅出向量 1.1 什么是向量(embedd ...

  9. 推荐系统实践之:召回算法和工程协同优化的若干经验

    ▐  背景 阿里妈妈展示广告召回大多采用 Tree-based Deep Model(以下简称TDM)模型,它通过对候选广告的聚类,构造了深达十余层的二叉树索引,并使用 beam search 在此索 ...

最新文章

  1. java提高篇(八)----详解内部类
  2. 就业模拟试题1_答案
  3. Java NIO ———— Buffer 缓冲区详解
  4. 之前画得太丑了,再来张好看的.我试着改小点.但是就看不清了
  5. 《精通Android 实例开发》——第1章,第1.15节向Eclipse中导入Android程
  6. 均方根误差不超过_快速平方根倒数算法
  7. JAVA面试要点002_Git中fetch和pull的区别
  8. mysql 主从 锁库_mysql 5.7.21 主从集群恢复GTID方式(不锁库)
  9. java设计一个立方体类box_实例1: 设计一个立方体类Box,定义三个属性,分别是长,宽,高。定义二个方法,分别计算并输出立方体的体积和表面积。_学小易找答案...
  10. 济宁a货翡翠,芜湖a货翡翠
  11. Xcode使用小技巧-filter查找功能和查看最近修改的文件
  12. 最火的C语言编程软件,适合编写C语言代码的编程软件有哪些
  13. 使用记事本编写并运行java代码
  14. 使用rufus-3.8 制作启动U盘安装Windows severs 2019
  15. linux下快速查找文件
  16. 不用邀请照样申请Gmail免费邮箱
  17. python链家二手房_python采集链家二手房信息
  18. YC孵化创业营2020-Adora Cheung - How to Prioritize Your Time 如何分配你时间的优先级
  19. 求两个圆公切线的模板
  20. ChatGPT修bug横扫全场,准确率达78%!程序员喜忧参半

热门文章

  1. 深空通信相关资料——以火星为例
  2. vue z-index层级显示问题
  3. zuul 里面的 prefix 和 strip-prefix 怎么使用
  4. 企业做CMMI认证有什么用?
  5. c++实现的阻塞队列
  6. 在Linux中设置共享目录
  7. 在云服务器搭建vulhub靶场
  8. 解决idea里面Tomcat无论怎么重新启动总是启动原先的项目问题
  9. Java实现字符串反转的几种方法
  10. 关于Linux下C语言编程execvp函数的一个问题