本文介绍了ElasticSearch中的同义词搜索功能,旨在提升搜索智能性和用户体验。文章详细说明了两种同义词管理方案:静态同义词库和动态同义词API。静态同义词库通过配置外部同义词文件实现,易于维护但更新需重新加载索引或重启节点;动态同义词API则允许通过API实时更新同义词,适用于频繁更新的系统,但需Elasticsearch 8.10及以上版本。文章还提供了Python实现动态同义词管理的示例代码,并总结了两种方案的不同特性和适用场景。最后,作者提及了自己的NLP研究方向和相关项目。
在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字段已经生效了!
上述的静态同义词库在实际使用时,会碰到问题:如果同义词库需要频繁更新或者动态更新,都需要重启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,请谨慎使用。
注意:如果未删除使用了该同义词库的索引,那么该删除同义词库的命令将会运行失败。
对于上述的动态同义词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"
# 查看同义词集列表
def list_synonym_sets():
try:
response = es.transport.perform_request(method="GET", url="/_synonyms")
print("\n同义词集列表:")
print(response)
except Exception as e:
print(f"\n获取同义词集列表时出错: {e}")
# 查看特定同义词集的详细信息
def get_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}")
# 查看特定同义词规则
def get_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}")
# 更新同义词集
def update_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}")
# 更新特定同义词规则
def update_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