ES(Elasticsearch)同义词方案

最佳实践

Posted by Xiaofei on September 15, 2023

以下内容针对8.10版本的es

概念

首先,区分几个概念:

  • Synonym Set:也就是同义词的数据部分,这部分可以通过API构建,也可以通过文件构建,官方推荐API
  • Synonym Filter:在analyzer的filter(也叫token filter,与char_filter区分开就好)中使用,包括Synonym graph和Synonym(是的,filter就叫Synonym),建议使用前者,后者在一个词对一个词组的时候,可能有问题,参见:https://www.elastic.co/guide/en/elasticsearch/reference/current/token-graphs.html#token-graphs-invalid-token-graphs 和 https://www.elastic.co/guide/en/elasticsearch/reference/current/token-graphs.html#token-graphs-invalid-token-graphs ,因此【一定】要用synonym graph
  • Analyzer:Analyzer包含Character filters(一般不用)、Tokenizer、以及Filter,也就是说,Analyzer包含filter,filter中可以有多个,其中一个可以是SynonymFilter,然后在其中指明SynonymSet(多种形式)

含同义词时的搜索逻辑

核心资料:https://www.elastic.co/guide/en/elasticsearch/reference/current/token-graphs.html

最核心的理解就是,一句话在搜索的时候,不仅仅是一串词,而是包含位置信息的,而某些词是可以跨越位置或在同一个位置的,因此同义词可以开展:

image-20230915181434779

加了一个同义词quick=fast之后:

image-20230915181521844

一个更核心的例子是:

假设domain name system的一个同义词是dns,那么一个query如果是domain name system is fragile,那么他会变成:

image-20230915103603081

因此可以同时匹配这两个文档:

1
2
dns is fragile
domain name system is fragile

应用同义词

demo场景

我们先构造一个demo场景,便于检验效果

synonym dns, domain name system
document d1: dns is fragile
d2: domain name system is fragile
d3: name system is fragile
query q1: dns
q2: domain name system
q3: name system
q4: domain apple name system

我们的预期是:

  • q1命中d1、d2,证明同义词生效
  • q2命中d1、d2,证明同义词生效,注意这里不要命中query_3,原因简单来说是我们已经知道domain name system这三个词是一个词组了,那么就不要拆开了
  • q3命中d2、d3:因为是分散的几个词,和dns这个缩写没关系
  • q4命中d2、d3:因为是分散的几个词,和dns这个缩写没关系

启动es

这一点直接参见https://www.elastic.co/guide/en/elasticsearch/reference/current/setup.html

要是只是简单看效果,可以参见:https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html

构造synonym set

1
2
3
4
5
6
7
8
PUT _synonyms/my-synonyms-set
{
  "synonyms_set": [
    {
      "synonyms": "dns, domain name system"
    }
  ]
}

新建索引

实例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
PUT /test-index
{
  "settings": {
    "analysis": {
      "filter": {
        "synonyms_filter": {
          "type": "synonym_graph",
          "synonyms_set": "my-synonyms-set",
          "updateable": true
        }
      },
      "analyzer": {
        "my_index_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase"]
        },
        "my_search_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "synonyms_filter"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "text": {
        "type": "text",
        "analyzer": "my_index_analyzer",
        "search_analyzer": "my_search_analyzer"
      }
    }
  }
}

需要注意:

  • 第6行到第10行定义了同义词,updateable必须是true,用的synonym set也是上面构造的
  • 同义词只在search_analyzer生效

插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /test-index/_doc
{
  "text": "dns is fragile"
}

POST /test-index/_doc
{
  "text": "domain name system is fragile"
}

POST /test-index/_doc
{
  "text": "name system is fragile"
}


请求检验效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
GET test-index/_search
{
  "query": {
    "match": {
      "text": {
        "query": "dns"
      }
    }
  }
}

GET test-index/_search
{
  "query": {
    "match": {
      "text": {
        "query": "domain name system"
      }
    }
  }
}

GET test-index/_search
{
  "query": {
    "match": {
      "text": {
        "query": "name system"
      }
    }
  }
}

GET test-index/_search
{
  "query": {
    "match": {
      "text": {
        "query": "domain apple name system"
      }
    }
  }
}

执行后,会发现结果是符合我们预期的

我们主要看看q2 domain name system为什么没有命中 d3 name system is fragile

1
2
3
4
5
6
7
8
9
10
11
GET test-index/_search
{
  "query": {
    "match": {
      "text": {
        "query": "domain name system"
      }
    }
  },
  "explain": true
}

然后我们会看到:

image-20230915192508780

也就是说,domain name system这三个词被当成了一个词组,因此单纯的 d3 name system is fragile是不满足的。

增加同义词

如果我们希望新增一组同义词,即

1
2
3
4
5
6
7
8
PUT _synonyms/my-synonyms-set
{
  "synonyms_set": [
    {
      "synonyms": "dns, domain name system, name system"
    }
  ]
}

注意此时执行后,结果json里显示的是 "result": "updated",也就是说,是更新了现有同义词

此时我们再次执行q1,会发现三篇都被召回了。

如果我们想删除同义词(例如想删掉刚刚的name system这个词),那么还是执行PUT,即

1
2
3
4
5
6
7
8
PUT _synonyms/my-synonyms-set
{
  "synonyms_set": [
    {
      "synonyms": "dns, domain name system"
    }
  ]
}

执行完再执行q1,就发现只有两篇召回了

参考资料

同义词核心文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/search-with-synonyms.html

analyze过程:https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis.html

指明搜索时使用的analyzer:https://www.elastic.co/guide/en/elasticsearch/reference/current/specify-analyzer.html#specify-search-analyzer

Token Graphs:尤其是关于同义词过程的可视化(也就是文中示意图的来源):https://www.elastic.co/guide/en/elasticsearch/reference/current/token-graphs.html

构造synonym set:https://www.elastic.co/guide/en/elasticsearch/reference/current/put-synonyms-set.html 更多:https://www.elastic.co/guide/en/elasticsearch/reference/current/synonyms-apis.html

构造SynonymGraphFilter:https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-synonym-graph-tokenfilter.html