毫无疑问,使用同义词是搜索工程师工具箱中最重要的技巧之一。尽管新手有时会低估同义词的重要性,但几乎所有搜索系统都离不开它。与此同时,人们有时仍会低估与使用同义词相关的一些复杂情况和微妙情形,甚至高级用户也不例外。
用好同义词库,在搜索的过程中,在提升召回率上,有着奇迹般的力量。
最近正在做同义词库的工作,在提升召回效果方面,确实有很大的贡献。本篇文章中,将会详细讲解如何使用,其中的坑,以及各种玩法的最佳实践。本篇博文还会回答一些有关同义词使用方法的常见问题,并指出一些经常需要注意的相关事项。
同义词库使用需求
- 提升召回率。提升召回效果。
- 词库热更新需求。
前言、为什么要使用同义词
这里引用官方博文的描述。
为帮助大家理解同义词的巨大作用和灵活性,我们来快速看一下当今大多数搜索引擎的内在工作原理。搜索引擎会对文档和查询进行分析并将其拆解为最小的单元(通常称为词元,实际上就是抽象的符号)。搜索时,匹配过程会使用简单字串相似度,所以如果查询中有一些十分微小的拼写错误(例如“hous”,只比“house”少一个字母 e)或者使用名词的复数形式(“houses”),即使文档中包含名词的单数形式(“house”),搜索引擎也不会匹配到这份文档。词干提取器或模糊查询等工具虽然可以解决一些最常见的此类问题,但是它们并不能消除相关联的概念或想法之间的差异,也不能将文档或查询中稍有不同的单词用法视为等同。 这时同义词就派上了大用场。同义词的英文 synonym 来自于希腊语,分别是前缀 σύν(syn,表示“一起”)和 ὄνομα(ónoma,表示“名称”)。从它的词源可以看出,同义词表示的是在同一语言或领域中具有完全或基本相同意思的不同词语。实际上,同义词的范围非常广泛,包括一般同义词(“疲劳”和“困倦”)、缩写(英镑的两种写法“lb.”和“pound”)、电商搜索中产品的不同拼写(“iPod”和“i-Pod”)、细微的语言差异(例如均表示电梯的英式英语“lift”和美式英语“elevator”)、专业用词和普通用词(例如“犬”和“狗”),甚至单纯表示同一概念的两种方式(“宇宙”和“太空”)。通过提供恰当的同义词规则,搜索工程师能够就哪些词在各自领域内具有相似意思并应该采取相似处理方法提供相关信息。 对搜索引擎而言,至为重要的是知道文档中的哪个字词与查询内容相匹配,即使它们可能看起来并不一样。由于这涉及到十分具体的领域知识,所以用户需要提供恰当的规则。同义词筛选器可在定制分析器中使用,其能够基于用户定义的规则替换或添加其他词元,既可在索引时进行以便在索引后的文档中同时存储这些内容(例如词语的两种变体),也可在索引时进行以扩展搜索词并匹配到更多相关文档。我们稍后会讨论这两种方法的优缺点。
官方的描述,总是很晦涩。其实很简单,就是要解决我在本文开头写的两个需求点。
一、关于同义词最佳实践问题
- 如何用好同义词?
关于这个问题,其实应该清楚它的工作原理。这里举个例子,一篇文章中,用来描述被包围了,可以用八方受敌,也可以用四面楚歌来描述。再结合ES的内部存储原理,我们可以选择在索引阶段,在文章中遇到八方来敌的时候,会在倒排表中添加八方来敌和四面楚歌这两个词,文档id指向同一个。这个过程是同义词在索引阶段生效。
但是在索引阶段生效,有很大的弊端。
- 由于必须对所有同义词进行索引,所以索引规模会变大。整体存储空间增加一倍甚至是几倍。这在超大规模的集群中使用,绝对是无法接受的。想想300个节点的集群,增加一倍或者两倍,老板绝对不会同意。
- 搜索得分(依赖于字词统计数据)可能会受影响,因为同义词也会计算在内,所以不常见单词的统计数据会存在偏差。这里和BM25相关性分数有关,以为词频会发生很大的变化。
- 同义词库无法动态更新,除非进行重新索引,否则无法针对既有文档更改同义词规则。在超大规模的集群中,更新一下词库,重跑一遍数据,绝对不是一天两天能完成的,动辄上月。
同义词还可以在搜索阶段生效。这可以完美的解决以上在索引阶段生效的三个致命 的问题。其工作原理,理解起来也很容易,就是在搜索阶段中的分词的时候,匹配一下同 义词。例如一次搜索中搜了八方受敌,但是,实际上逻辑是这样的 (八方受敌 OR 四面 楚歌)。同样,这样做也是有代价的。它无法很大的应对超多关键词的检索。假如你的一 次搜索 100个关键词。经过同义词以后,有可能变成 200个甚至更多关键词。
- 检索过程中的性能损失。如果本身检索的关键词不多,那影响微乎其微。假如本来检索关键词就很多,又开启同义词,性能损失绝对会在一倍以上!!!即使是这样,在搜索阶段使用同义词,也远好于在索引阶段使用同义词。
- 检索关键词变多。检索的过程中,花费的资源也会更多。这点主要体现在CPU上。
但是在搜索阶段使用同义词。它是能够解决同义词库更新的需求的。统一词库可以随 时变动。而这个更新成本,并不大。对于7.3版本以后的ES来说,仅仅是触发一次reloadApi的事情。
POST my_index/_reload_search_analyzers
返回信息如下:
{
"_shards" : {
"total" : 3,
"successful" : 3,
"failed" : 0
},
"reload_details" : [
{
"index" : "my_index",
"reloaded_analyzers" : [
"synonym_analyzer" # 这里是我定义的带有同义词的分词器。
],
"reloaded_node_ids" : [
"jiIC9zJyTES_dMIw0w6n8A",
"dMKuVhnvQySXkO7AfZCXrA",
"_6o86PMrRlegvJfB_G8bTw"
]
}
]
}
-
- 同义词库的格式问题
ES中,同义词库可以有不同的格式。这里也有很多妙用!我举三个例子
- eg1 :上班 => 工作 干活
- eg2 :高兴,快乐,娱乐,兴奋
- eg3 : 工作 干活 => 上班
其中eg1,他的意思是,把上班,映射成两个词。假如你搜索上班,经过分词器以后,就变成了搜索 工作和干活。这样适合在搜索阶段使用。可以提升召回的数量和质量。
其中eg2,它的意思是,遇到高兴的时候,会分词器会解析成,高兴,快乐,娱乐,兴奋这四个词,同样真正的搜索也是用这四个词去搜索的。这样适合在搜索阶段使用。可以提升召回的数量和质量。
其中eg3,它的意思是,把工作和干活映射成上班。这样的可以应用在索引阶段。其实可以巧妙的帮业务去解决归一化的问题。假如中国,cn,zh,chaina,中华人民共和国,都想映射成中国,做标准化的数据,在检索阶段是可以提速的。同样在索引阶段也是可以节省倒排链的长度的。
二、实战
实际操作一下。
- 是否使用插件?
同义词其实是ES本身就有的能力。同义词插件,通常是用来做热更新的,或者是用来把词库放在远端(例如Nginx)。其实本质上是解决更新问题的。假如说你的集群有几百个节点,用本地词库,每次更新词库要更新几百个节点,这操作起来多少有点麻烦。
- 如何使用同义词?
在es里边其实使用的是filter,添加一个filter即可,并把这个filte在分词器中绑定。
Synonym token filter | Elasticsearch Guide [7.11] | Elastic
我这里只提几个点。
PUT /test_index
{
"settings": {
"index": {
"analysis": {
"analyzer": {
"synonym": {
"tokenizer": "whitespace",
# 这里引用了同义词的filter
"filter": [ "synonym" ]
}
},
"filter": {
"synonym": {
"type": "synonym",
# 注意这里,analysis一个目录,它是在es解压后的config下的目录。synonym.txt 是同义词库。具体文件,下边会给。
"synonyms_path": "analysis/synonym.txt",
# 这个参数,可以忽略同义词库中分词有问题的错误行。
"lenient": true,
# 这个参数很重要,只有开其他,才能实现同义词库的热更新。
"updateable": true
}
}
}
}
}
}
- 同义词库
这里分享了一个同一个以词库。请注意词库的内容。根据上述如何修改词库,自行决定。我是把同义词用逗号分隔的。我把这个文件中的 => 替换成了逗号。
- 如何动态更新
POST article_info_test_1109_2/reload_search_analyzers
# 执行结果
{
"_shards" : {
"total" : 3,
"successful" : 3,
"failed" : 0
},
"reload_details" : [
{
"index" : "article_info_test_1109_2",
# 这里可以看到有刷新的分词器。
"reloaded_analyzers" : [
"synonym_analyzer"
],
"reloaded_node_ids" : [
"jiIC9zJyTES_dMIw0w6n8A",
"dMKuVhnvQySXkO7AfZCXrA",
"_6o86PMrRlegvJfB_G8bTw"
]
}
]
}