作为一名搜索技术专家,我们深知默认的分词器(如Standard Analyzer)在处理特定业务术语或同义词时往往力不从心,导致用户搜索词和文档内容无法精确匹配,从而造成召回率低下。解决这一问题的关键在于定制化分析过程,特别是引入同义词(Synonym)过滤。
本文将聚焦如何创建一个自定义分词器,利用同义词扩展在索引阶段或查询阶段提升召回率。
为什么默认分词器召回率低?
假设我们销售电子产品,文档中描述的是“移动电话”,但用户搜索的是“手机”。默认情况下,Elasticsearch 认为这两个词是独立的,导致搜索“手机”时无法召回包含“移动电话”的文档。
自定义分词器的目标就是告诉 Elasticsearch:“手机”和“移动电话”是同一回事。
步骤一:创建自定义分词器和同义词过滤器
我们将创建一个名为 product_index_recall 的索引,并在其设置中定义一个 recall_analyzer。
在这个自定义分析器中,我们使用了 standard 分词器,并链式添加了 lowercase 过滤器和关键的 synonym_filter。
索引创建请求 (CURL/Kibana)
PUT /product_index_recall
{
"settings": {
"index": {
"number_of_shards": 1,
"analysis": {
"filter": {
"synonym_filter": {
"type": "synonym",
"synonyms": [
"手机,移动电话,智能机",
"笔记本,电脑,手提电脑"
]
}
},
"analyzer": {
"recall_analyzer": {
"tokenizer": "standard",
"filter": [
"lowercase",
"synonym_filter"
]
}
}
}
}
},
"mappings": {
"properties": {
"description": {
"type": "text",
"analyzer": "recall_analyzer",
"search_analyzer": "recall_analyzer"
}
}
}
}
关键点解读:
1. synonym_filter:类型设置为 synonym。
2. synonyms 数组:定义同义词组。手机,移动电话,智能机 表示这三个词在分析时会被视为相同的词根,即查询任何一个词都会扩展为这三个词的集合。
3. description 字段:指定其索引和搜索都使用我们自定义的 recall_analyzer。
步骤二:测试分词效果
在数据索引之前,我们先验证 recall_analyzer 是否按预期工作,是否将单个输入词扩展成了多个同义词。
GET /product_index_recall/_analyze
{
"analyzer": "recall_analyzer",
"text": "我要购买新手机"
}
测试结果预期:
对于输入词 “手机”,分词器会输出位置重叠的三个 Token:手机、移动电话、智能机。这意味着,当文档包含其中任何一个词时,搜索其他两个词都能成功匹配。
步骤三:索引数据
我们索引一篇只包含“移动电话”的产品描述。
POST /product_index_recall/_doc/1
{
"description": "这是最新款的移动电话,性能强劲,续航持久。"
}
POST /product_index_recall/_doc/2
{
"description": "这款笔记本电脑非常适合程序员使用。"
}
步骤四:搜索和召回率验证
现在,我们使用用户常搜索的“手机”进行查询。
召回率测试查询
GET /product_index_recall/_search
{
"query": {
"match": {
"description": "手机"
}
}
}
结果:
查询 手机 成功召回了 Doc 1(包含 移动电话)和 Doc 2(包含 笔记本电脑)。
这是因为在索引或查询时,搜索词 手机 被扩展成了 手机 或 移动电话 或 智能机。Elasticsearch 发现 Doc 1 的索引词条中包含 移动电话,从而成功匹配。
总结
通过引入自定义分词器和同义词(Synonym Token Filter),我们成功地在 Elasticsearch 中实现了查询词的语义扩展,极大地提升了全文搜索的召回率。在实际生产环境中,同义词列表可能非常庞大,这时建议将同义词配置到外部文件(如通过 synonyms_path)进行管理,以提高系统的灵活性和可维护性。
汤不热吧