# 通过同义词过滤器与自定义词库实现语义扩展搜索
通过将同义词组配置到自定义同义词词库，并结合dynamic_synonym同义词过滤器在写入与查询阶段对词语进行自动扩展，使任意同义词均能双向命中相关文档，最终实现搜索召回率大幅提升。
#### 应用场景
在内容搜索、客服问答等场景中，用户可能会使用不同的词语表达同一意思。例如，文档记录的是"开心"，而用户搜索的是"高兴"，如果缺乏同义词机制，这篇文档将无法被召回，造成漏检。通过配置自定义同义词词库，并利用dynamic_synonym同义词过滤器，可在查询和写入阶段自动扩展同义词，从而实现跨词语的语义级搜索，显著提升搜索召回率。
典型应用：
- 内容平台搜索：用户使用口语化词汇搜索时，能精准匹配书面化文档内容。例如，搜索"笔记本"可召回"便携式电脑"的文章。
- 电商搜索扩展：商品描述使用正式名称，用户搜索时常用俗称或缩写。例如，搜索"手机"可召回描述为"移动终端"的商品。
- 医疗问答系统：同一疾病或药物存在多种称谓，患者使用通俗名称也能检索到相关内容。例如，搜索"心梗"可匹配数据库中记录的"心肌梗死"。
- 法律咨询检索：法律术语有正式与通俗表达之分，用户即使不熟悉专业术语，也能使用通俗说法找到所需内容。例如，搜索"离婚"可匹配文书中的"解除婚姻关系"。
- 企业知识库问答：支持缩写、别称与正式名称并行使用，确保检索覆盖全面。例如，搜索"OA系统"可召回关于"办公自动化系统"的文档。
 
#### 方案架构
图1架构图   
![](https://support.huaweicloud.com/bestpractice-css/figure/zh-cn_image_0000002659998937.png "点击放大")
- 索引阶段：原始数据经过同义词扩展后，所有同义词均被写入倒排索引。例如原文含"开心"，索引中同时存储"高兴"、"快乐"、"愉快"，无论用户使用哪个词搜索均可命中。
- 查询阶段：用户输入的关键词同样经过同义词扩展，再与倒排索引匹配，实现双向语义覆盖。
- 词库动态加载：同义词词库存储于OBS，更新后无需重启集群，新写入数据即可使用新词库。
- 自定义分词器：由分析器standard/ik_smart/ik_max_word（基础分词）+ 同义词过滤器dynamic_synonym组合成自定义的分词器。
 
#### 方案优势
- 语义级搜索覆盖：突破字面匹配限制，实现"搜一个词、覆盖一组意思"的语义扩展搜索，大幅提升召回率。
- 零代码配置：同义词规则以文本文件维护，业务运营人员即可自行添加新词组，无需修改任何代码或配置。
- 动态更新：词库文件通过OBS动态加载，更新词库无需重启集群，线上业务不受影响。
- 灵活的词组管理：一行定义一组同义词，支持任意数量词语组合，按业务需求灵活扩展。
- 与主词词库协同：同义词词库与主词词库可同时配置使用，确保同义词组内的专有词语在分词阶段不被错误拆分。
 
#### 约束限制
- 集群版本要求：2018年3月10日之前创建的集群，不支持自定义词库功能。本文的示例代码基于OpenSearch集群和Elasticsearch 7.x版本的集群，Elasticsearch集群版本低于7.x的操作命令请参见[示例代码（Elasticsearch版本\<7.x）]。
- 分词粒度影响：同义词扩展基于基础分词结果进行，如果某词在基础分词阶段已被拆分，则同义词扩展可能失效，建议将同义词组中的关键词同时加入主词词库，确保其在分词阶段被识别为独立词条后再进行同义词扩展。
 
#### 前提条件
- 目标集群处于"可用"状态，无正在进行中的任务。
- 确认操作账号已具备以下权限（可以在IAM服务的"权限管理 \> 权限"中查看和配置）：
  - 配置自定义词库的权限：
    ```
    "css:IKThesaurus:*"
    ```
    
  
  - OBS桶及对象的读取权限：
    ```
    obs:bucket:getBucketLocation
    obs:bucket:getBucketStoragePolicy
    obs:bucket:listAllMyBuckets
    obs:object:getObject
    ```
    
  
  - 如果OBS桶使用SSE-KMS加密模式时，还需KMS权限：
    ```
    "kms:cmk:create",
    "kms:dek:create",
    "kms:cmk:get",
    "kms:dek:decrypt",
    "kms:cmk:list"
    ```
    
   
 
#### 步骤一：准备词库文件
准备同义词词库，并上传至OBS桶中。
1. 准备同义词词库文件"synonym_custom.txt"。要求编码均为UTF-8无BOM格式，每行一组同义词，词间以英文逗号","分隔，英文小写，且文件大小不超过100MB。
   ```
   开心,高兴,快乐,愉快
   手机,移动终端
   ```
   
2. 将准备好的词库文件上传至OBS桶。 OBS桶必须与CSS集群位于同一区域，且"存储类别"为"标准存储"。
   
 
#### 步骤二：配置自定义词库
为集群配置自定义的同义词词库，使dynamic_synonym过滤器能够加载同义词规则。
1. 登录[云搜索服务管理控制台](https://console.huaweicloud.com/elasticsearch/)。
2. 在左侧导航栏，根据集群类型选择"集群管理 \> Elasticsearch"或"集群管理 \> OpenSearch"。
3. 在集群列表，单击目标集群名称，进入集群详情页。
4. 选择"集群配置 \> 自定义词库"，进入自定义词库配置页面。
5. 根据业务需求配置自定义词库。
   表1配置自定义词库 
   | 参数名称    | 操作说明                                                          |
   |:---|:---|
   | OBS桶    | 选择词库文件所在的OBS桶。                                             |
   | 主词词库    | 保持默认值"不更新"，表示不配置该词库。                                       |
   | 停词词库 | 保持默认值"不更新"，表示不配置该词库。                                       |
   | 同义词词库 | 选择"更新 \> 选择"，选择OBS桶中已上传的同义词词库文件"synonym_custom.txt"，单击"确定"。 |
      
   
6. 配置完成后，单击"保存"，在弹窗中单击"确定"，启动词库更新。 下方呈现词库配置信息。等待约1分钟，词库状态从"更新中"变更为"成功"，表示词库更新完成。
   图2词库状态   
   ![](https://support.huaweicloud.com/bestpractice-css/figure/zh-cn_image_0000002660118981.png) 
 
#### 步骤三：创建索引
创建索引并自定义分词器（组合分词和同义词扩展），使索引在写入和查询时都进行同义词扩展。
1. 在自定义词库页面，单击页面右上角的"Kibana"或"Dashboards"。
2. 在Kibana或Dashboards左侧导航栏选择"Dev Tools"，进入操作页面。
3. 执行以下命令，创建索引book并自定义分词器ik_synonym（组合ik_smart分析器和dynamic_synonym过滤器）。
   ```
   PUT book
   {
     "settings": {
       "analysis": {
         "filter": {
           "my_synonym": {
             "type": "dynamic_synonym"
           }
         },
         "analyzer": {
           "ik_synonym": {
             "filter": [
               "my_synonym"
             ],
             "type": "custom",
             "tokenizer": "ik_smart"
           }
         }
       }
     },
     "mappings": {
       "properties": {
         "content": {
           "type": "text",
           "analyzer": "ik_synonym",
           "search_analyzer": "ik_synonym"
         }
       }
     }
   }
   ```
   参数说明：
   - my_synonym：自定义过滤器的名称。
   
   - ik_synonym: 自定义分词器的名称。
   
   - ik_smart：IK分词器提供的粗粒度分析器。
    
   预期结果：
   ```
   {
     "acknowledged" : true,
     "shards_acknowledged" : true,
     "index" : "book"
   }
   ```
   
 
#### 步骤四：验证同义词分词效果
索引创建成功后，先验证同义词分词效果再导入数据，确保词库和分词器配置正确。
执行以下命令，查看自定义分词器ik_synonym的分词效果。
```
GET /book/_analyze
{
  "analyzer": "ik_synonym",
  "text": "我今天特别高兴"
}
```
预期结果：
```
{
  "tokens" : [
    { "token" : "我", "start_offset" : 0, "end_offset" : 1, "type" : "CN_WORD", "position" : 0 },
    { "token" : "今天", "start_offset" : 1, "end_offset" : 3, "type" : "CN_WORD", "position" : 1 },
    { "token" : "特别", "start_offset" : 3, "end_offset" : 5, "type" : "CN_WORD", "position" : 2 },
    { "token" : "高兴", "start_offset" : 5, "end_offset" : 7, "type" : "CN_WORD", "position" : 3 },
    { "token" : "开心", "start_offset" : 5, "end_offset" : 7, "type" : "SYNONYM", "position" : 3 },
    { "token" : "快乐", "start_offset" : 5, "end_offset" : 7, "type" : "SYNONYM", "position" : 3 },
    { "token" : "愉快", "start_offset" : 5, "end_offset" : 7, "type" : "SYNONYM", "position" : 3 }
  ]
}
```
结果解读：position 3位置同时出现"高兴"、"开心"、"快乐"和"愉快"四个词，说明同义词扩展已生效。使用这四个词中任意一个搜索，均可命中该文档。
#### 步骤五：写入测试数据
写入测试数据，用于验证同义词搜索效果。
执行以下命令，将测试文本写入索引book。
```
POST /book/_bulk
{"index":{"_id":"1"}}
{"content":"我今天特别高兴"}
{"index":{"_id":"2"}}
{"content":"孩子们玩得十分开心"}
{"index":{"_id":"3"}}
{"content":"你看他兴高采烈的"}
```
预期结果：
```
{
  "took" : 9,
  "errors" : false,
  "items" : [
    { "index" : { "_index" : "book", "_type" : "_doc", "_id" : "1", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 2, "failed" : 0 }, "_seq_no" : 0, "_primary_term" : 1, "status" : 201 } },
    { "index" : { "_index" : "book", "_type" : "_doc", "_id" : "2", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 2, "failed" : 0 }, "_seq_no" : 1, "_primary_term" : 1, "status" : 201 } },
    { "index" : { "_index" : "book", "_type" : "_doc", "_id" : "3", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 2, "failed" : 0 }, "_seq_no" : 2, "_primary_term" : 1, "status" : 201 } }
  ]
}
```
#### 步骤六：执行同义词搜索
通过同义词搜索验证自定义词库的配置效果。
执行以下命令，搜索关键词"快乐"：
```
GET /book/_search
{
  "query": {
    "match": {
      "content": "快乐"
    }
  }
}
```
预期结果：
```
{
  "took" : 15,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 0.8380518,
    "hits" : [
      {
        "_index" : "book",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.8380518,
        "_source" : {
          "content" : "我今天特别高兴"
        }
      },
      {
        "_index" : "book",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.8380518,
        "_source" : {
          "content" : "孩子们玩得十分开心"
        }
      }
    ]
  }
}
```
结果解读：文档1原文含"高兴"，文档2原文含"开心"，与搜索关键词"快乐"是同义词组，因此都被成功召回；文档3无相关词语，未被召回；说明同义词扩展生效。
 #### 示例代码（Elasticsearch版本\<7.x）
当Elasticsearch集群版本低于7.x时，需要自定义type名称，参考以下命令。
1. 执行以下命令，创建索引book并指定自定义分词器。
   ```
   PUT book
   {
     "settings": {
       "analysis": {
         "filter": {
           "my_synonym": {
             "type": "dynamic_synonym"
           }
         },
         "analyzer": {
           "ik_synonym": {
             "filter": [
               "my_synonym"
             ],
             "type": "custom",
             "tokenizer": "ik_smart"
           }
         }
       }
     },
     "mappings": {
       "type1": {
         "properties": {
           "content": {
             "type": "text",
             "analyzer": "ik_synonym",
             "search_analyzer": "ik_synonym"
           }
         }
       }
     }
   }
   ```
   
2. 执行以下命令，将测试数据写入索引book。
   ```
   POST /_bulk
   { "index" : { "_index" : "book", "_type" : "type1", "_id" : "1" } }
   { "content" : "我今天特别高兴" }
   { "index" : { "_index" : "book", "_type" : "type1", "_id" : "2" } }
   { "content" : "孩子们玩得十分开心" }
   { "index" : { "_index" : "book", "_type" : "type1", "_id" : "3" } }
   { "content" : "你看他兴高采烈的" }
   ```
   
3. 执行以下命令，通过同义词搜索验证配置结果。
   ```
   GET /book/type1/_search
   {
     "query": {
       "match": {
         "content": "快乐"
       }
     }
   }
   ```
   
 
