AI魔法学院客服
深度解析ElasticSearch同义词管理:打造更智能的搜索体验
本文介绍了ElasticSearch中的同义词搜索功能,旨在提升搜索智能性和用户体验。文章详细说明了两种同义词管理方案:静态同义词库和动态同义词API。静态同义词库通过配置外部同义词文件实现,易于维护但更新需重新加载索引或重启节点;动态同义词API则允许通过API实时更新同义词,适用于频繁更新的系统,但需Elasticsearch 8.10及以上版本。文章还提供了Python实现动态同义词管理的示例代码,并总结了两种方案的不同特性和适用场景。最后,作者提及了自己的NLP研究方向和相关项目。
 2024-12-20
收藏 复制地址分享海报


ElasticSearch同义词搜索

ElasticSearch中引入同义词(synonyms)搜索功能的主要目的是提升搜索的智能性和用户体验,具体体现在以下几个方面:

1.1. 覆盖多种表达方式
用户输入的搜索词可能存在多种表达方式(如方言、行业术语或个人习惯用语),但这些词实际指向同一个概念。

o• 例如,“土豆”与“马铃薯”本质上是同义词,但如果搜索引擎只识别一种表达方式,就可能错过相关结果。

2.2. 提升搜索精准度与全面性
如果未配置同义词,用户可能无法获取到完整或准确的搜索结果,影响体验。通过将不同的表达方式映射为统一标准形式,搜索引擎可以更加全面、精准地返回用户需要的数据。

假设用户在搜索“土豆”,而数据库中的记录使用了“马铃薯”

o未配置同义词ElasticSearch只能严格匹配“土豆”,可能会返回空结果。

o配置同义词ElasticSearch将“土豆”映射到“马铃薯”,从而返回所有包含“马铃薯”的相关记录,解决用户问题。

本文将会介绍ElasticSearch中的两种同义词管理方案:静态同义词库动态同义词API

静态同义词库

ElasticSearch中提供了同义词库文件配置,只需要在设置索引(index)时配置好对应的同义词库文件即可。通过外部同义词文件(如synonyms.txt)管理同义词列表,ES在启动时加载该文件。

o• 优点:易于维护和更新同义词列表。

o• 缺点:更新同义词文件后需要重新加载索引或重启节点。

以下是示例同义词文件(synonyms.txt):

西红柿,番茄
土豆,马铃薯
自行车,脚踏车,单车

在上述文件中,每一行中以英文逗号隔开,代表这些短语都互为同义词。

my_index索引为例,我们引入同义词库的配置如下:

PUT my_index
{
  
"settings":{
    
"number_of_shards":1,
    
"number_of_replicas":1,
    
"analysis":{
      
"filter":{
        
"word_sync":{
          
"type":"synonym",
          
"synonyms_path":"/usr/share/elasticsearch/plugins/ik/config/synonyms.txt"
        
}
      
},
      
"analyzer":{
        
"ik_sync_smart":{
          
"filter":[
            
"word_sync"
          
],
          
"type":"custom",
          
"tokenizer":"ik_smart"
        
}
      
}
    
}
  
},
  
"mappings":{
    
"properties":{
      
"title":{
        
"analyzer":"ik_sync_smart",
        
"type":"text"
      
},
      
"content":{
        
"analyzer":"ik_sync_smart",
        
"type":"text"
      
}
    
}
  
}
}

在上述配置中,请留意同义词库文件(synonyms.txt)的完整路径为正确地址。另外,对title, content字段在使用ik分词器的同时引入同义词管理。

我们插入两条样例数据:

PUT my_index/_doc/1
{
"title":"测试1",
"content":"我喜欢吃土豆。"
}

PUT my_index/_doc/2
{
"title":"测试2",
"content":"我喜欢骑自行车。"
}

查看所有数据(前10条足够用):

GET my_index/_search
{
  "query": {
    "match_all": { }
  },
  "size": 10
}

结果如下:

{
  
"took":120,
  
"timed_out":false,
  
"_shards":{
    
"total":1,
    
"successful":1,
    
"skipped":0,
    
"failed":0
  
},
  
"hits":{
    
"total":{
      
"value":2,
      
"relation":"eq"
    
},
    
"max_score":1,
    
"hits":[
      
{
        
"_index":"my_index",
        
"_id":"1",
        
"_score":1,
        
"_source":{
          
"title":"测试1",
          
"content":"我喜欢吃土豆。"
        
}
      
},
      
{
        
"_index":"my_index",
        
"_id":"2",
        
"_score":1,
        
"_source":{
          
"title":"测试2",
          
"content":"我喜欢骑自行车。"
        
}
      
}
    
]
  
}
}

content字段进行检索,我们输入马铃薯

GET my_index/_search
{
  
"query":{
    
"match":{
      
"content":"马铃薯"
    
}
  
}
}

返回结果如下:

{
    
"hits":{
    
"total":{
      
"value":1,
      
"relation":"eq"
    
},
    
"max_score":1.07389,
    
"hits":[
      
{
        
"_index":"my_index",
        
"_id":"1",
        
"_score":1.07389,
        
"_source":{
          
"title":"测试1",
          
"content":"我喜欢吃土豆。"
        
}
      
}
    
]
  
}
}

这说明我们的同义词配置在content字段已经生效了!

动态同义词API

上述的静态同义词库在实际使用时,会碰到问题:如果同义词库需要频繁更新或者动态更新,都需要重启ElasticSearch服务才会生效,这在很多场景下是无法容忍的。因此,从 8.10 版本之后,ElasticSearch引入了同义词API,允许通过API动态管理同义词集。

o• 优点:无需重启或重新索引即可更新同义词,管理更灵活。

o• 缺点:需要使用ES 8.10及以上版本。

我们通过API来创建一个名为synonyms_test的同义词库。

PUT _synonyms/synonyms_test
{
  
"synonyms_set":[
    
{
      
"id":"土豆",
      
"synonyms":"土豆,马铃薯"
    
},
    
{
      
"id":"番茄",
      
"synonyms":"番茄,西红柿"
    
}
  
]
}

查看该同义词库的内容:

GET _synonyms/synonyms_test

返回结果如下:

{
  
"count":2,
  
"synonyms_set":[
    
{
      
"id":"土豆",
      
"synonyms":"土豆,马铃薯"
    
},
    
{
      
"id":"番茄",
      
"synonyms":"番茄,西红柿"
    
}
  
]
}

也可查看该同义词库中具体一条数据的内容:

GET _synonyms/synonyms_test/土豆

返回结果如下:

{
  
"id":"土豆",
  
"synonyms":"土豆,马铃薯"
}

我们创建一个使用该同义词库的索引(index),配置如下:

PUT syno_test
{
  
"settings":{
    
"index":{
      
"analysis":{
        
"analyzer":{
          
"synonym_analyzer":{
            
"tokenizer":"ik_smart",
            
"filter":[
              
"my_synonyms"
            
]
          
}
        
},
        
"filter":{
          
"my_synonyms":{
            
"type":"synonym",
            
"synonyms_set":"synonyms_test",
            
"updateable":true
          
}
        
}
      
}
    
}
  
},
  
"mappings":{
    
"properties":{
      
"title":{
        
"type":"text",
        
"analyzer":"ik_smart",
        
"search_analyzer":"synonym_analyzer"
      
}
    
}
  
}
}

插入两条样例数据:

PUT syno_test/_doc/1
{
"title":"我喜欢吃番茄,骑自行车。"
}

PUT syno_test/_doc/2
{
"title":"我喜欢骑自行车。"
}

如果我们对title字段进行搜索,输入西红柿

GET syno_test/_search
{
  "query": {
    "match": {
      "title": "
西红柿"
    }
  }
}

返回结果如下:

{
    
"hits":{
    
"total":{
      
"value":1,
      
"relation":"eq"
    
},
    
"max_score":0.9227538,
    
"hits":[
      
{
        
"_index":"syno_test",
        
"_id":"1",
        
"_score":0.9227538,
        
"_source":{
          
"title":"我喜欢吃番茄,骑自行车。"
        
}
      
}
    
]
  
}
}

但如果搜索关键词为单车,则返回结果如下:

{
    
"hits":{
    
"total":{
      
"value":0,
      
"relation":"eq"
    
},
    
"max_score":null,
    
"hits":[]
  
}
}

此时,我们将单车的同义词进行动态更新,命令如下:

PUT _synonyms/synonyms_test/单车
{
    
"synonyms":"自行车,脚踏车,单车"
}

查看该同义词库(命令为:GET _synonyms/synonyms_test),返回结果如下:

{
  
"count":3,
  
"synonyms_set":[
    
{
      
"id":"单车",
      
"synonyms":"自行车,脚踏车,单车"
    
},
    
{
      
"id":"土豆",
      
"synonyms":"土豆,马铃薯"
    
},
    
{
      
"id":"番茄",
      
"synonyms":"番茄,西红柿"
    
}
  
]
}

可以看到,此时这个同义词库中已经动态更新了单车的同义词。

我们再次对title字段输入单车进行检索,返回结果如下:

{
    
"hits":{
    
"total":{
      
"value":2,
      
"relation":"eq"
    
},
    
"max_score":0.13786995,
    
"hits":[
      
{
        
"_index":"syno_test",
        
"_id":"2",
        
"_score":0.13786995,
        
"_source":{
          
"title":"我喜欢骑自行车。"
        
}
      
},
      
{
        
"_index":"syno_test",
        
"_id":"1",
        
"_score":0.12562492,
        
"_source":{
          
"title":"我喜欢吃番茄,骑自行车。"
        
}
      
}
    
]
  
}
}

可以看到,此时可以检索出单车的相关内容,说明我们刚才动态更新的同义词条对title字段的检索生效了!

同义词库的删除命令为DELETE _synonyms/synonyms_test,请谨慎使用

注意:如果未删除使用了该同义词库的索引,那么该删除同义词库的命令将会运行失败。

使用Python实现动态同义词管理

对于上述的动态同义词API方案,如果使用Python代码实现(注意第三方模块的elasticsearch要与使用的ES版本一致,笔者这版的版本为8.13.0),示例代码如下:

# -*- coding: utf-8 -*-
import json

from elasticsearch import Elasticsearch

# 创建 Elasticsearch 客户端实例,连接到本地的 Elasticsearch 服务
es = Elasticsearch(
"http://localhost:9200")

# 定义同义词集的 ID
set_id = 
"python_synonyms_test"
# 定义同义词规则的 ID
rule_id = 
"rule1"


# 查看同义词集列表
deflist_synonym_sets():
    
try:
        response = es.transport.perform_request(method=
"GET", url="/_synonyms")
        
print("\n同义词集列表:")
        
print(response)
    
except Exception as e:
        
print(f"\n获取同义词集列表时出错: {e}")


# 查看特定同义词集的详细信息
defget_synonym_set(set_id):
    
try:
        response = es.transport.perform_request(
"GET", f"/_synonyms/{set_id}")
        
print(f"\n同义词集 '{set_id}' 的详细信息:")
        
print(response)
    
except Exception as e:
        
print(f"\n获取同义词集 '{set_id}' 时出错: {e}")


# 查看特定同义词规则
defget_synonym_rule(set_id, rule_id):
    
try:
        response = es.transport.perform_request(
"GET", f"/_synonyms/{set_id}/{rule_id}")
        
print(f"\n同义词集 '{set_id}' 中规则 '{rule_id}' 的详细信息:")
        
print(response)
    
except Exception as e:
        
print(f"\n获取同义词规则 '{rule_id}' 时出错: {e}")


# 更新同义词集
defupdate_synonym_set(set_id, synonyms_payload):
    
try:
        response = es.transport.perform_request(method=
"PUT",
                                                url=
f"/_synonyms/{set_id}",
                                                headers={
"Content-Type": "application/json"},
                                                body=synonyms_payload)
        
print(f"\n同义词集 '{set_id}' 更新成功:")
        
print(response)
    
except Exception as e:
        
print(f"\n更新同义词集 '{set_id}' 时出错: {e}")


# 更新特定同义词规则
defupdate_synonym_rule(set_id, rule_id, synonym_rule):
    
try:
        response = es.transport.perform_request(method=
"PUT",
                                                url=
f"/_synonyms/{set_id}/{rule_id}",
                                                headers={
"Content-Type": "application/json"},body=synonym_rule)
        
print(f"\n同义词集 '{set_id}' 中规则 '{rule_id}' 更新成功:")
        
print(response)
    
except Exception as e:
        
print(f"\n更新同义词规则 '{rule_id}' 时出错: {e}")


# 示例同义词集的定义
synonyms_payload = {
    
"synonyms_set": [
        {
            
"id": "rule1",
            
"synonyms": "自行车,脚踏车"
        },
        {
            
"id": "rule2",
            
"synonyms": "手机,移动电话"
        }
        
# 可以添加更多的同义词规则
    ]
}

# 示例同义词规则的定义
synonym_rule = {
    
"synonyms": "自行车,脚踏车,单车"
}

# 调用函数示例
if __name__ == "__main__":
    
# 查看同义词集列表
    list_synonym_sets()
    
# 更新同义词集
    update_synonym_set(set_id, synonyms_payload)
    
# 查看更新同义词集后的列表
    
print("\n更新后的同义词集列表:")
    list_synonym_sets()

    
# 查看特定同义词集
    get_synonym_set(set_id)

    
# 查看特定同义词规则
    get_synonym_rule(set_id, rule_id)
    
# 更新特定同义词规则
    update_synonym_rule(set_id, rule_id, synonym_rule)
    
# 查看更新后的特定同义词规则
    
print(f"{set_id}中的{rule_id}更新后的同义词规则:")
    get_synonym_rule(set_id, rule_id)

运行后输出结果如下:

同义词集列表:
{'count': 1, 'results': [{'synonyms_set': 'synonyms_test', 'count': 3}]}

同义词集 'python_synonyms_test' 更新成功:
{'result': 'created', 'reload_analyzers_details': {'_shards': {'total': 32, 'successful': 16, 'failed': 0}, 'reload_details': []}}

更新后的同义词集列表:

同义词集列表:
{'count': 2, 'results': [{'synonyms_set': 'python_synonyms_test', 'count': 2}, {'synonyms_set': 'synonyms_test', 'count': 3}]}

同义词集 'python_synonyms_test' 的详细信息:
{'count': 2, 'synonyms_set': [{'id': 'rule1', 'synonyms': '
自行车,脚踏车'}, {'id': 'rule2', 'synonyms': '手机,移动电话'}]}

同义词集 'python_synonyms_test' 中规则 'rule1' 的详细信息:
{'id': 'rule1', 'synonyms': '
自行车,脚踏车'}

同义词集 'python_synonyms_test' 中规则 'rule1' 更新成功:
{'result': 'updated', 'reload_analyzers_details': {'_shards': {'total': 32, 'successful': 16, 'failed': 0}, 'reload_details': []}}
python_synonyms_test
中的rule1更新后的同义词规则:

同义词集 'python_synonyms_test' 中规则 'rule1' 的详细信息:
{'id': 'rule1', 'synonyms': '
自行车,脚踏车,单车'}

总结

总结下上文中介绍的两种ElasticSearch中的同义词管理方案:同义词文件管理和同义词API管理,不同处如下:

特性

同义词文件管理

同义词API管理

适用场景

- 同义词更新频率较低的系统。
-
规模较小且易于手动管理的同义词列表。

- 需要频繁更新同义词的系统。
-
同义词列表较大或复杂,需动态管理。

更新方式

- 修改同义词文件后,需重新加载索引或重启节点以使更改生效。

- 通过API实时更新同义词,无需重启或重新索引,变更可立即生效。

维护成本

- 需要手动编辑和同步同义词文件,可能增加运维工作量。

- 通过API集中管理,降低手动操作和维护成本。

灵活性

- 更新过程较为繁琐,灵活性较低。

- 提供更高的灵活性,支持细粒度的同义词管理。

Elasticsearch版本

- 适用于所有支持同义词文件的Elasticsearch版本。

- 需要Elasticsearch 8.10及以上版本。

笔者从事NLP工作以来,对同义词抽取有相关方面工作,可参考文章NLP(四十九)别名发现模型的初次尝试,对应的Github项目为https://github.com/percent4/alias_find_system.

该项目也是笔者的研究方向之一,但囿于精力和时间未能持续更新迭代,希望后面有时间可以好好维护,并成为中文同义词库方面的基础性工作之一。也欢迎有兴趣的读者与我联系~

原文出自:https://mp.weixin.qq.com/s/BwJyv4CXkb82ZkVmKLXMhg

本文档由网友提供,仅限参考学习,如有不妥或产生版权问题,请联系我们及时删除。 客服请加微信:skillupvip
评论
1 评论
自封大师2024/12/20 10:12:03
感谢分享!这篇文章真是ElasticSearch同义词搜索的知音啊,讲解得既贴心又详细。静态和动态同义词管理的对比让我一目了然,特别是动态同义词API部分,对于需要频繁更新的系统来说太实用了。Python示例代码也很给力,直接就能上手操作。最后还提到了NLP研究方向,真是满满的干货!关心你NLP项目的进展,期待更多好文!
20秒读懂全文
伴读
### 1. 一句话总结文章摘要
本文介绍了ElasticSearch中的同义词搜索功能,包括静态同义词库和动态同义词API两种管理方案,以及使用Python实现动态同义词管理的方法。

### 2. 生成关键词和可能相关的关键词

**关键词**:
- ElasticSearch
- 同义词搜索
- 静态同义词库
- 动态同义词API
- Python实现
One More Thing
One More Thing again ...

找组织,加入AI魔法学院群