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

在这里插入图片描述
搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术细节以及项目实战(含码源)

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

前人栽树后人乘凉,本专栏提供资料:

  1. 推荐系统算法库,包含推荐系统经典及最新算法讲解,以及涉及后续业务落地方案和码源
  2. 本专栏会持续更新业务落地方案以及码源。同时我也会整理总结出有价值的资料省去你大把时间,快速获取有价值信息进行科研or业务落地帮助你快速完成任务落地,以及科研baseline

1.倒排索引

索引,初衷都是为了快速检索到你要的数据。(加快查询和搜索速度),在信息检索领域使用比较广泛。核心的需求是:如何从超大规模的内容库中召回匹配关键字的结果。

在mysql的索引,如果对某一个字段加了索引,一般来说查询该字段速度是可以有显著的提升。每种数据库都有自己要解决的问题(或者说擅长的领域),对应的就有自己的数据结构,而不同的使用场景和数据结构,需要用不同的索引,才能起到最大化加快查询的目的。 对 Mysql 来说,是 B+ 树,对 Elasticsearch/Lucene 来说,是倒排索引。

比如,在谷歌中搜索包含 ”推荐系统“ 关键字的内容。最直观的做法是针对数据库中所有内容一条一条匹配。但这样查找复杂度至少是O(n),面对成千上亿的海量数据,效率上远远达不到要求。因此,搜索引擎常规做法都是预先针对内容建立一个关键字索引。记录关键字对应的文档Id,位置,甚至是权重(分数),查询的时候,直接到表中获取关键字的文档列表,倒排索引一般使用hash索引结构,查询复杂度O(1)。可以极大减少检索时间。

在这里插入图片描述

1.1 正排索引和倒排索引区别

正排索引(Forward Index)和倒排索引(Inverted Index)是信息检索系统中常用的两种索引结构。

思考一下:一本书如果想要快速看到想要章节怎么做,如果想通过某个单词找页码怎么操作

在这里插入图片描述
目录和索引页,其实就很形象的可以比喻为正排索引和倒排索引。为了进一步加深理解,再看看熟悉的搜索引擎。没有搜索引擎时,我们只能直接输入一个网址,然后获取网站内容,这时我们的行为是document -> words。此谓「正向索引」。后来,我们希望能够输入一个单词,找到含有这个单词,或者和这个单词有关系的文章,即word -> documents。于是我们把这种索引,叫「反向索引」,或者「倒排索引」

正排索引是将文档中的每个词语作为关键词,以文档为单位建立的索引,其中每个文档都包含了关键词及其位置信息。正排索引适合于需要对文档进行全文搜索、排序、过滤等场景。例如,在一个新闻网站中,如果用户想要搜索包含某个关键字的文章,就需要使用正排索引。

倒排索引以关键词为索引,以文档为单位建立的索引。在倒排索引中,每个关键词都对应了包含该关键词的文档列表,其中包含了该关键词在文档中出现的位置信息。倒排索引适合于需要对关键词进行搜索的场景。例如,在一个搜索引擎中,如果用户想要搜索包含某个关键词的网页,就需要使用倒排索引。

正排索引和倒排索引的区别可以用以下图示来表示:

正排索引:
文档1:
this is a test document
文档2:
this is another document

倒排索引:
this:文档1,文档2
is:文档1,文档2
a:文档1
test:文档1
document:文档1,文档2

可以看到,正排索引以文档为单位建立索引,每个文档都包含了关键词及其位置信息;而倒排索引以关键词为单位建立索引,每个关键词都对应了包含该关键词的文档列表。

1.2 倒排索引技术细节

比较有意思文章可以参考:
怎样建立一个简单的倒排索引?漫画讲解

【漫画】ES原理 必知必会的倒排索引和分词

1.2.1 传统关系型数据库和 ES 的差别,搜索引擎原理

先设想一个关于搜索的场景,假设我们要搜索一首诗句内容中带“前”字的古诗,

在这里插入图片描述
如果用像 MySQL 这样的 RDBMS 来存储古诗的话,我们应该会去使用这样的 SQL 去查询

selectnamefrom poems wherecontentlike"%%

这种我们称为顺序扫描法,需要遍历所有的记录进行匹配。

不但效率低,而且不符合我们搜索时的期望,比如我们在搜索“ABCD"这样的关键词时,通常还希望看到"A",“AB”,“CD”,“ABC”的搜索结果

搜索引擎的搜索原理简单概括如下:

  • 内容爬取,停顿词过滤,比如一些无用的像"的",“了”之类的语气词/连接词
  • 内容分词,提取关键词
  • 根据关键词建立倒排索引
  • 用户输入关键词进行搜索

如果你了解 ES 应该知道,ES 可以说是对 Lucene 的一个封装,里面关于倒排索引的实现就是通过 lucene 这个 jar 包提供的 API 实现的,所以下面讲的关于倒排索引的内容实际上都是 lucene 里面的内容

在这里插入图片描述
这样我们一输入“前”,借助倒排索引就可以直接定位到符合查询条件的古诗。

当然这只是一个很大白话的形式来描述倒排索引的简要工作原理。在 ES 中,这个倒排索引是具体是个什么样的,怎么存储的等等,这些才是倒排索引的精华内容.

1.2.2 倒排索引概念

  • term:关键词这个东西是我自己的讲法,在 ES 中,关键词被称为 term。

  • postings list

还是用上面的例子,{静夜思, 望庐山瀑布}

是 “前” 这个 term 所对应列表。在 ES 中,这些被描述为所有包含特定 term 文档的 id 的集合。由于整型数字 integer可以被高效压缩的特质,integer 是最适合放在 postings list 作为文档的唯一标识的,ES会对这些存入的文档进行处理,转化成一个唯一的整型 id。

在这里插入图片描述

再说下这个 id 的范围,在存储数据的时候,在每一个 shard 里面,ES 会将数据存入不同的 segment,这是一个比 shard 更小的分片单位,这些 segment 会定期合并。在每一个 segment 里面都会保存最多 2^31 个文档,每个文档被分配一个唯一的 id,从0到(2^31)-1。

倒排索引的核心组成
在这里插入图片描述

  • 单词词典:记录所有文档的单词,一般都比较大。还会记录单词到倒排列表的关联信息。
  • 倒排列表:记录了单词对应的文档集合,由倒排索引项组成。倒排索引项包含如下信息:
    • 文档ID,用于获取原始信息
    • 单词频率TF,记录该单词在该文档中的出现次数,用于后续相关性算分
    • 位置Position,记录单词在文档中分词的位置,用于语句搜索(phrase query)
    • 偏移Offset,记录单词在文档的开始和结束位置,实现高亮显示

在这里插入图片描述
类型是用来定义数据结构的,你可以认为是 MySQL 中的一张表。文档就是最终的数据了,你可以认为一个文档就是一条记录。

比如一首诗,有诗题、作者、朝代、字数、诗内容等字段,,我们可以建立一个名叫 Poems 的索引,然后创建一个名叫 Poem 的类型,类型是通过 Mapping 来定义每个字段的类型。比如诗题、作者、朝代都是 Keyword 类型,诗内容是 Text 类型,而字数是 Integer 类型,最后就是把数据组织成 Json 格式存放进去了。

在这里插入图片描述
ES由 Analyzer 组件对文档执行一些操作并将具体子句拆分为 token/term,简单说就是分词,然后将这些术语作为倒排索引存储在磁盘中。ES的JSON文档中的每一个字段,都有自己的倒排索引,当然你可以指定某些字段不做索引,优点是这样可以节省磁盘空间。但是不做索引的话字段无法被搜索到。 注意两个关键词:分词和倒排索引。倒排索引我相信你已经懂了!分词我们马上就来聊聊!

在这里插入图片描述

1.3 ES分词

用_analyze来分析一下ES会如何对它进行分词及倒排索引:
在这里插入图片描述
ES将它分成了一个个字,这是ES中默认的中文分词。掌握分词要先懂两个名词:analysis与analyzer

  • analysis

文本分析,是将全文本转换为一系列单词的过程,也叫分词。analysis是通过analyzer(分词器)来实现的,可以使用Elasticearch内置的分词器,也可以自己去定制一些分词器。

  • analyzer(分词器)

由三部分组成:

  • Character Filter:将文本中html标签剔除掉。
  • Tokenizer:按照规则进行分词,在英文中按照空格分词
  • Token Filter:将切分的单词进行加工,小写,删除 stopwords(停顿词,a、an、the、is等),增加同义词

注意:除了在数据写入时将词条进行转换,查询的时候也需要使用相同的分析器对语句进行分析。即我们写入苹果的时候分词成了苹和果,查询苹果的时候同样也是分词成苹和果去查。

1.3.1ES内置分词器

  1. Standard Analyzer - 默认分词器,按词切分,小写处理
  2. Simple Analyzer - 按照非字母切分(符号被过滤), 小写处理
  3. Stop Analyzer - 小写处理,停用词过滤(the,a,is)
  4. Whitespace Analyzer - 按照空格切分,不转小写
  5. Keyword Analyzer - 不分词,直接将输入当作输出
  6. Patter Analyzer - 正则表达式,默认\W+(非字符分割)
  7. Language - 提供了30多种常见语言的分词器
  8. Customer Analyzer 自定义分词器

demo展示:

#默认分词器 按词切分 小写处理
GET _analyze
{
  "analyzer": "standard",
  "text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}#可以发现停用词被去掉了
GET _analyze
{
  "analyzer": "stop",
  "text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}

1.3.2 中文扩展分词器

中文分词在所有搜索引擎中都是一个很大的难点,中文的句子应该是切分成一个个的词,但是一句中文,在不同的上下文,其实是不同的理解,例如: 这个苹果,不大好吃/这个苹果,不大,好吃。

有一些比较不错的中文分词插件:IK、THULAC等。我们可以试试用IK进行中文分词。

#安装插件
https://github.com/medcl/elasticsearch-analysis-ik/releases
在plugins目录下创建analysis-ik目录 解压zip包到当前目录 重启ES
#查看插件
bin/elasticsearch-plugin list
#查看安装的插件
GET http://localhost:9200/_cat/plugins?v

** IK分词器:支持自定义词库、支持热更新分词字典 **

  • ik_max_word: 会将文本做最细粒度的拆分,比如会将“这个苹果不大好吃”拆分为"这个,苹果,不大好,不大,好吃"等,会穷尽各种可能的组合;
  • ik_smart: 会做最粗粒度的拆分,比如会将“这个苹果不大好吃”拆分为"这个,苹果,不大,好吃"
curl -X GET "localhost:9200/_analyze?pretty" -H 'Content-Type: application/json' -d'
{
  "analyzer" : "ik_max_word",
  "text" : "这个苹果不大好吃"
}

1.3.3 如何使用分词器

# 创建索引时候指定某个字段的分词器
PUT iktest
{
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "ik_smart"
      }
    }
  }
}
# 插入一条文档
PUT iktest/_doc/1
{
  "content":"这个苹果不大好吃"
}
# 测试分词效果
GET /iktest/_analyze
{
  "field": "content",
  "text": "这个苹果不大好吃"  
}

1.4 ES应用

Elasticsearch( ES )是一款功能强大的开源分布式实时搜索引擎,在日志分析(主要应用场景)、企业级搜索、时序分析等领域有广泛应用
在这里插入图片描述

1.5 小结

  • 反向索引又叫倒排索引,是根据文章内容中的关键字建立索引。
  • 搜索引擎原理就是建立反向索引。
  • Elasticsearch 在 Lucene 的基础上进行封装,实现了分布式搜索引擎。
  • Elasticsearch 中的索引、类型和文档的概念比较重要,类似于 MySQL 中的数据库、表和行
  • Elasticsearch 也是 Master-slave 架构,也实现了数据的分片和备份。
  • Elasticsearch 一个典型应用就是 ELK 日志分析系统。

3.倒排索引在推荐系统召回中的应用

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

在这里插入图片描述

3.1 倒排索引的难点

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

不同的业务和数据量级下,对倒排索引的要求也都不太一样。比如量级小,但是要求索引实时更新,数据量级大,要求索引检索性能高等等。

3.1.1 索引的划分

当索引数据大到一台机器无法容纳时,如何解决:一般是通过索引拆分来解决大数据量的问题,有一点点类似mysql的分库分表。

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

  • 按照关键字划分: 针对关键字进行字典序或者hash取余方式
  • 按照文档Id划分: 针对文档id 进行hash取余方式。
    在这里插入图片描述

不同的索引划分方式对于查询的时候,流程也会略有不同。比如,同样查询包含 “推荐系统” + ”倒排索引“ 两个关键字的数据。

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

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

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

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

3.1.2 索引的构建

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

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

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

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

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

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

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

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

在这里插入图片描述
在ES中,还引入了FST树,不仅共享前缀,还共享后缀。

在这里插入图片描述

3.1.3 索引的更新

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

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

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

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

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

在这里插入图片描述

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

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

在这里插入图片描述

3.2 推荐场景中的倒排索引

4.1 选型比较
针对现阶段的倒排索引技术栈,大厂核心的搜索系统都是自研,但与搜索引擎不同,推荐场景的数据量级小,一般都是千万到亿左右,开源界也都有不错的解决方案。

DB-Engines 搜索引擎排名:DB-Engines Ranking 根据受欢迎程度对数据库管理系统进行排名。该排名每月更新一次。

DB-Engines 搜索引擎排名

在这里插入图片描述
开源主流的主要有:

  • sphnix: C++ 编写,性能上要优于ES,增量更新支持能力较弱。
  • Elasticsearch: jvm,基于 luence开发。易用性高,横向扩展方便。支持向量检索 + 近实时检索
  • solr: 基于luence, 倾向于静态索引,实时检索能力较差
1.Lucene
系别:JAVA
基于Java的一个开放源代码的全文检索引擎工具包,注意是工具包,所以严格来说它并不是一个搜索引擎服务程序,开发者需要了解搜索引擎的基本原理和Lucene的用法,然后根据需求用Java来开发。

2.Elasticsearch
系别:JAVA
基于Lucene,仅支持json(可通过插件支持各种主流富文本),索引插入效率高,对比solr更适合近实时查询,但是对历史数据查询速度较solr慢。可用于索引更新频繁需求近实时查询较强的系统。支持分布式,无需额外中间件管理。支持中文分词。社区庞大,Java支持好,有spring-data

3.Solr(solar)
系别:JAVA
基于Lucene,支持pdf,word,txt等富文本索引(内容索引),索引插入时查询效率会降低,不适合近实时查询(索引更新频繁),对历史数据查询速度快,多用于传统应用。支持分布式,但需要zookeeper管理。支持中文分词。社区庞大,Java支持好。

Lucene,Elasticsearch,Solr,Xapian,Sphinx主流搜索引擎的对比

参考链接:
几款多模态向量检索引擎:Faiss 、milvus、Proxima、vearch、Jina等

在一些迭代快,业务规模较小的推荐团队,可以考虑使用ES。且最新版本的ES也支持向量检索,同时ES调优也有比较成熟的方案,像腾讯云 & 阿里云上售卖的Elasticsearch,会做一些内核优化。(比如腾讯云的 FST 堆外内存优化,多级cache缓存等),基于ES做画像召回时,还可以基于业务的特性,针对部分热门的tag,做缓存,这样也可以提高整体的召回性能,减少成本等。

4.参考文章

Elasticsearch 如何做到快速检索 - 倒排索引的秘密

搜索引擎倒排索引的设计与实践

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

知乎搜索框背后的Query理解和语义召回技术

腾讯万亿级 Elasticsearch 内存效率提升技术解密

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

汀、人工智能

十分感谢您的支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值