博客列表 >Elasticsearch:使用同义词 synonyms 来提高搜索效率

Elasticsearch:使用同义词 synonyms 来提高搜索效率

哈
原创
2021年11月03日 15:04:28842浏览

阿里云官方镜像站:
https://developer.aliyun.com/mirror/?utm_content=g_1000304570

在我们的很多情况下,我们希望在搜索时,有时能够使用一个词的同义词来进行搜索,这样我们能搜索出来更多相关的内容。我们可以通过 text analysis 来帮助我们形成同义词。文本分析通常应用于你建立索引时的所有文档以及发送给 Elasticsearch 的所有查询。在进行同义词搜索时,我们有如下的几种方案:

  • 在建立索引时 (indexing),通过 analyzer 建立 synonyms 的反向索引 (inverted index)
  • 在 query 时,通过 search analyzer 对查询的词建立 synonyms
  • 在 indexing 及 query 时,同时建立反向索引中的 synonym 及在 query 时为查询的词建立 synonyms
    那么在实际的使用中,我们到底是用上述的哪种方案呢?在下面的例子中,你将看到在 query 时使用 synonym 会更加灵活,并且更容易让我们更新同义词的名单已经更好地支持 multi-word synonyms。

在今天的文章中,我们将分别论述。

在 query 时对词进行同义词解析

首先,我们来创建一个具有如下 anaylzer 及 mapping 的一个索引:

  1. PUT myindex
  2. {
  3. "settings": {
  4. "analysis": {
  5. "filter": {
  6. "my_synonyms": {
  7. "type": "synonym_graph",
  8. "synonyms": [
  9. "China, chn, PRC, People's Republic of China"
  10. ]
  11. }
  12. },
  13. "analyzer": {
  14. "my_analyzer": {
  15. "type": "custom",
  16. "tokenizer": "standard",
  17. "filter":[
  18. "lowercase",
  19. "my_synonyms"
  20. ]
  21. }
  22. }
  23. }
  24. },
  25. "mappings": {
  26. "properties": {
  27. "content": {
  28. "type": "text",
  29. "analyzer": "standard",
  30. "search_analyzer": "my_analyzer"
  31. }
  32. }
  33. }
  34. }

在上面,我们使用 synonym_graph 过滤器对 quey 时的词进行过滤。在这个过滤器中,我们把如下的一个词都视为同义词:

  1. China, chn, PRC, People's Republic of China

在mapping 中,我们定义了 search_analyzer 为 my_analyzer,也就是说在 query 时,它会对所有的词进行分词。但凡有任何一个词是 China, chn, PRC, People’s Republic of China 其中的一个,它都将被视为同义词。

我们首先来创建一个文档:

  1. PUT myindex/_doc/1
  2. {
  3. "content": "I like People's Republic of China"
  4. }

运行上面的指令,我们将创建一个 content 为 I like People’s Republic of China 的文档。

接下来,我们做如下的查询:

  1. GET myindex/_search
  2. {
  3. "query": {
  4. "match": {
  5. "content": "China"
  6. }
  7. }
  8. }

那么显示的结果是:

  1. {
  2. "took" : 256,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 1,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 1.4384104,
  16. "hits" : [
  17. {
  18. "_index" : "myindex",
  19. "_type" : "_doc",
  20. "_id" : "1",
  21. "_score" : 1.4384104,
  22. "_source" : {
  23. "content" : "I like People's Republic of China"
  24. }
  25. }
  26. ]
  27. }
  28. }

可能有人说了,这是因为上面的 content 里本身就含有 China, 所以上面的结果证明不了什么。接下来,我们进行如下的搜索:

  1. GET myindex/_search
  2. {
  3. "query": {
  4. "match": {
  5. "content": "prc"
  6. }
  7. }
  8. }

结果,我们可以发现,我们同样显示上面的搜索的结果。这个说明了这个同义词的搜索是成功的。

接下来,我们想搜索 silk road 也能搜索出中国来,那么我怎么做呢?

我们来执行如下的命令:

  1. POST myindex/_close
  2. PUT myindex/_settings
  3. {
  4. "analysis": {
  5. "filter": {
  6. "my_synonyms": {
  7. "type": "synonym_graph",
  8. "synonyms": [
  9. "china, silk road, chn, PRC, People's Republic of China"
  10. ]
  11. }
  12. },
  13. "analyzer": {
  14. "my_analyzer": {
  15. "type": "custom",
  16. "tokenizer": "standard",
  17. "filter": [
  18. "lowercase",
  19. "my_synonyms"
  20. ]
  21. }
  22. }
  23. }
  24. }
  25. POST myindex/_open

我们可以通过更新 setting 来实现这个。在上面请注意:当我们更新一个索引的 index 时,我们必须先把它关掉,等设置好后,在重新打开。否则会有错误。那么经过上面的修改后,我们重新运行如下的搜索:

  1. GET myindex/_search
  2. {
  3. "query": {
  4. "match": {
  5. "content": "silk road"
  6. }
  7. }
  8. }

那么上面的搜索结果将会显示我们之前显示的结果。在这里 silk road 也就是和之前的其它词都是同义词。

有人可能觉得上面在 settings 里配置太多的同义词很麻烦(如果同义词很多的话)。按照 Elastic 的官方文档,我们可以把所有的同义词放到一个文档中。首先,我们在 Elasticsearch 的 config 目录中,创建一个叫做 analysis 的子目录,然后创建一个叫做 synonyms.txt 的文档,而它的内容如下:

  1. $ pwd
  2. /Users/liuxg/elastic/elasticsearch-7.8.0/config/analysis
  3. liuxg:analysis liuxg$ cat synonyms.txt
  4. "china, silk road, chn, PRC, People's Republic of China",
  5. "elk, elastic stack"

在这里,我们多添加了一个 elk, elastic stack 的同义词。我们来创建一个新的索引:

  1. PUT myindex1
  2. {
  3. "settings": {
  4. "analysis": {
  5. "filter": {
  6. "my_synonyms": {
  7. "type": "synonym_graph",
  8. "synonyms_path": "analysis/synonyms.txt"
  9. }
  10. },
  11. "analyzer": {
  12. "my_analyzer": {
  13. "type": "custom",
  14. "tokenizer": "standard",
  15. "filter":[
  16. "lowercase",
  17. "my_synonyms"
  18. ]
  19. }
  20. }
  21. }
  22. },
  23. "mappings": {
  24. "properties": {
  25. "content": {
  26. "type": "text",
  27. "analyzer": "standard",
  28. "search_analyzer": "my_analyzer"
  29. }
  30. }
  31. }
  32. }

运行完上的指令后,我们来创建一个文档:

  1. PUT myindex1/_doc/1
  2. {
  3. "content": "I love elastic stack"
  4. }

然后我们做如下的搜索:

  1. GET myindex1/_search
  2. {
  3. "query": {
  4. "match": {
  5. "content": "elk"
  6. }
  7. }
  8. }

上面的搜索结果显示:

  1. {
  2. "took" : 451,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 1,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 0.5753642,
  16. "hits" : [
  17. {
  18. "_index" : "myindex1",
  19. "_type" : "_doc",
  20. "_id" : "1",
  21. "_score" : 0.5753642,
  22. "_source" : {
  23. "content" : "I love elastic stack"
  24. }
  25. }
  26. ]
  27. }
  28. }

显然,我可以看到搜索 elk,我们就可以搜索到含有 elastic stack 的文档。

在实际的使用中,如果我们更新 synonyms.txt 文件,那么,我们可以使用如下的 API 来进行更新:

POST myindex1/_reload_search_analyzers

在建立索引时建立同义词

针对这种情况,我们可以在建立索引的时候,就把同义词建立好。这样,我们可以在 query 时,不使用同义词解析。在这种情况下,我们可以使用 synonym 过滤器,而不是 synonym_graph 过滤器。

我们接下来使用如下的命令来创建一个新的索引:

  1. PUT myindex2
  2. {
  3. "settings": {
  4. "analysis": {
  5. "filter": {
  6. "my_synonyms": {
  7. "type": "synonym",
  8. "synonyms": [
  9. "china, silk road, chn, PRC, People's Republic of China",
  10. "elk, elastic stack"
  11. ]
  12. }
  13. },
  14. "analyzer": {
  15. "my_analyzer": {
  16. "tokenizer": "standard",
  17. "filter": [
  18. "lowercase",
  19. "my_synonyms"
  20. ]
  21. }
  22. }
  23. },
  24. "number_of_shards": 1
  25. },
  26. "mappings": {
  27. "properties": {
  28. "content": {
  29. "type": "text",
  30. "analyzer": "my_analyzer"
  31. }
  32. }
  33. }
  34. }

在上面,我们使用了 my_analyzer 作为 myindex2 在索引时使用的分词器。它将使用 synonym 过滤器,并把如下的词视为同义词:

  1. "china, silk road, chn, PRC, People's Republic of China",
  2. "elk, elastic stack"

我们可以使用如下的方法来测试这个 analyzer:

  1. POST myindex2/_analyze
  2. {
  3. "text": "I like elk a lot",
  4. "analyzer": "my_analyzer"
  5. }

上面的命令显示的结果是:

  1. {
  2. "tokens" : [
  3. {
  4. "token" : "i",
  5. "start_offset" : 0,
  6. "end_offset" : 1,
  7. "type" : "<ALPHANUM>",
  8. "position" : 0
  9. },
  10. {
  11. "token" : "like",
  12. "start_offset" : 2,
  13. "end_offset" : 6,
  14. "type" : "<ALPHANUM>",
  15. "position" : 1
  16. },
  17. {
  18. "token" : "elk",
  19. "start_offset" : 7,
  20. "end_offset" : 10,
  21. "type" : "<ALPHANUM>",
  22. "position" : 2
  23. },
  24. {
  25. "token" : "elastic",
  26. "start_offset" : 7,
  27. "end_offset" : 10,
  28. "type" : "SYNONYM",
  29. "position" : 2
  30. },
  31. {
  32. "token" : "a",
  33. "start_offset" : 11,
  34. "end_offset" : 12,
  35. "type" : "<ALPHANUM>",
  36. "position" : 3
  37. },
  38. {
  39. "token" : "stack",
  40. "start_offset" : 11,
  41. "end_offset" : 12,
  42. "type" : "SYNONYM",
  43. "position" : 3
  44. },
  45. {
  46. "token" : "lot",
  47. "start_offset" : 13,
  48. "end_offset" : 16,
  49. "type" : "<ALPHANUM>",
  50. "position" : 4
  51. }
  52. ]
  53. }

你可以看到,尽管在测试的 text 没有 elastic stack,只有 elk,但是显示的结果了含有 elastic 及 stack 这两个 token。

我们接下来使用如下的命令来创建一个文档:

  1. PUT myindex2/_doc/1
  2. {
  3. "content": "I like elk a lot"
  4. }

我们使用如下的查询:

  1. GET myindex2/_validate/query?rewrite=true
  2. {
  3. "query": {
  4. "match": {
  5. "content": "elastic stack"
  6. }
  7. }
  8. }

上面显示的结果是:

  1. {
  2. "_shards" : {
  3. "total" : 1,
  4. "successful" : 1,
  5. "failed" : 0
  6. },
  7. "valid" : true,
  8. "explanations" : [
  9. {
  10. "index" : "myindex2",
  11. "valid" : true,
  12. "explanation" : """content:"elastic stack" content:elk"""
  13. }
  14. ]
  15. }

从上面的显示的结果来看,当我们搜索 elastic stack 时,它同时匹配 content: “elastic stack” 以及 content: elk。也就是说,如果文档里含有 elk,那么这个文档也将被搜索到。我们做如下的搜索:

  1. GET myindex2/_search
  2. {
  3. "query": {
  4. "match": {
  5. "content": "elastic stack"
  6. }
  7. }
  8. }

那么上面的命令显示的结果是:

  1. {
  2. "took" : 0,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 1,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 0.977273,
  16. "hits" : [
  17. {
  18. "_index" : "myindex2",
  19. "_type" : "_doc",
  20. "_id" : "1",
  21. "_score" : 0.977273,
  22. "_source" : {
  23. "content" : "I like elk a lot"
  24. }
  25. }
  26. ]
  27. }
  28. }

显然它已经把我们的想要的结果搜索出来了。

总结

在上面,我们展示了两种方法进行同义词的查询。在实际的使用中,你可以根据自己的情况适当进行选择。当然,我们有可以把上面的两种方法进行同时并用。通过这两种方法,也有可能会造成搜索的精确度的问题。这个是你必须要想清楚的。这个就像我们撒网打鱼一样,把网撒大了,捞上来的也有可能不是我们想要的。

原文链接:https://blog.csdn.net/UbuntuTouch/article/details/108003222

声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议