在elasticsearch 中更好的处理同义词

3 篇文章 0 订阅
2 篇文章 0 订阅

需求

使用 ES 进行作为搜索引擎时一般会出现这样的场景,有一个同义词表,当查询时,也能命中到同义词。举例来说,画图,绘图 是一对同义词,当用户搜索 画图 时, 我们往往希望包含绘图 的doc 也在召回结果中。

实现1,query time

思路是在query 时,扩大搜索范围,比如说搜索 绘图 ,首先查询同义词库,然后在搜索的时候,添加同义词搜索:

# 原搜索词 给一个较高的权重,其他同义词给一个较低的权重 
GET test_synonym_3/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "title": {
              "query": "绘图",
              "boost": 10
            }
          }
        },
        {
          "match": {
            "title": {
              "query": "画图",
              "boost": 1
            }
          }
        }
      ]
    }
  }
}

这种方式可以实现同义词扩召回,并且可以返回结果上,原词召回的doc 排在 同义词召回的结果之前。但是有两个问题:

  • 在搜索时,需要查同义词库,然后再拼接query body,比较麻烦
  • 搜索词 如果不是单个词的话,首先需要分词,然后将分词结果每一项,都进行类似的查找,然后拼接 query body。对查询结果的影响上,比如searchword 是 工作绘图, 这样的结果就是 title:工作^10 or (title:绘图^10 or title:画图^1) ,这样导致的结果是 一个文档出现 工作,另一个文档同时出现 画图,绘图 , 那么 显然 画图 带来的得分更多,对结果排序造成影响。

实现2,index time

这里的index time 是指在建立倒排索引时,也就是存储 doc 时,加上 synonym filter ,当一个 同义词词库中包含的词,那么该词所有的 同义词在 同 position 上都加上。

# 定义 mapping
PUT /test_synonym_3
{
  "settings": {
    "index": {
      "analysis": {
        "analyzer": {
          "custom_analyzer": {
            "tokenizer": "whitespace",
            "filter": [
              "synonym"
            ]
          }
        },
        "filter": {
          "synonym": {
            "type": "synonym",
            "synonyms": [
              "画图,绘图",
              "hello,nihao"
            ]
          }
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "custom_analyzer"
      }
    }
  }
}
# 写入两个 doc 
POST test_synonym_3/_doc/1
{
  "title":"this is my 画图"
}

POST test_synonym_3/_doc/2
{
  "title":"this is my 绘图"
}
# 查询
GET test_synonym_3/_search
{
  "query": {
    "match": {
      "title": {
        "query": "绘图"
      }
    }
  }
}
# 结果;
{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 0.2656341,
    "hits" : [
      {
        "_index" : "test_synonym_3",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.2656341,
        "_source" : {
          "title" : "this is my 画图"
        }
      },
      {
        "_index" : "test_synonym_3",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.2656341,
        "_source" : {
          "title" : "this is my 绘图"
        }
      }
    ]
  }
}

从上面的结果中看到 搜索 绘图, 结果全部展示,召回结果是完整的,但是和搜索意图有一些区别,绘图 的 doc 应该在 画图的前面。

实现3,index time

考虑到上面的查询无法区分原词召回还是同义词召回,所以使用多字段index ,然后在不同的字段上设置不同的权重。

# 增加字段
PUT /test_synonym_3
{
  "settings": {
    "index": {
      "analysis": {
        "analyzer": {
          "custom_analyzer": {
            "tokenizer": "whitespace"
          },
          "custom_analyzer_synonym": {
            "tokenizer": "whitespace",
            "filter": [
              "synonym"
            ]
          }
        },
        "filter": {
          "synonym": {
            "type": "synonym",
            "synonyms": [
              "画图,绘图",
              "hello,nihao"
            ]
          }
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "custom_analyzer",
        "fields": {
          "synonym": {
            "type": "text",
            "analyzer": "custom_analyzer_synonym"
          }
        }
      }
    }
  }
}
# 进行查询
GET test_synonym_3/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "title": {
              "query": "绘图",
              "boost": 10
            }
          }
        },
        {
          "match": {
            "title.synonym": {
              "query": "绘图",
              "boost": 1
            }
          }
        }
      ]
    }
  }
}

上面这种方式,通过不同字段分配不同权重,这个方法和方法一,有些类似,好处是在搜索时比较简单,坏处是 1, 增加字段,也就是增加了存储占用。2,在index 时,如果同义词库发生变化,不能及时生效。3,同样会有 有同义词的原词会比没有同义词的原词 造成较大的得分结果。但是好处是简单,所以往往被大家所接受。

思考

由上面的经验来看,总结出三条重要的需求:

  • 同义词的搜索权重应小于原词,保证原词的召回结果排在前面
  • 同义词的及时生效问题
  • 如果同义词在 index time时 就加入到了 倒排索引中,那么会导致一些词的词频变高,就是说 同义词库中 画图,绘图 ,假设 画图 在index 中出现特别频繁,但是 绘图 频次很少,但是将同义词伴随加入会导致 绘图 频次的增加。

结合上面的三种尝试,发现 elasticsearch 对 同义词搜索本身支持的并不好。开始思考自己开发支持同义词搜索。

解决

实现自己的query 插件,开发方向:

  • 同义词 不能在 index time 时加入倒排索引。否则会改变同义词的词频
  • 在使用 同义词召回时,同义词召回得分应该设置为 0 , 否则 会使带有同义词的搜索词比没有同义词的搜索词对结果_score 贡献更大,但是 给同义词一个极小权重值也是可以的,因为就算是同义词之间也有 词频重要性区分,如果权重为 0 则会忽视同义词之间的差异,比如 画图,绘图,画画 ,对searchword: 画图 , 同义词 绘图,画画 也是不一样的。所以推荐一个极小值,能区分,但又不至于影响全局得分。

代码:https://github.com/muhao1020/synony_match.git 针对 ES7.3.2 开发。

效果如下:

# 定义 mapping
# 可以看到, 定义的 analyzer 有同义词filter的, 并且没有用于 index analyzer
PUT /test_synonym_3
{
  "settings": {
    "index": {
      "analysis": {
        "analyzer": {
          "custom_analyzer": {
            "tokenizer": "whitespace",
            "filter": [
              "synonym"
            ]
          }
        },
        "filter": {
          "synonym": {
            "type": "synonym",
            "synonyms": [
              "画图,绘图",
              "hello,nihao"
            ]
          }
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "whitespace"
      }
    }
  }
}
# 写入两个 doc 
POST test_synonym_3/_doc/1
{
  "title":"this is my 画图"
}

POST test_synonym_3/_doc/2
{
  "title":"this is my 绘图"
}

# 查询
# 使用自定义的query 设置同义词产生的权重
# 必传参数 query 和 synonym_analyzer 
# query 搜索内容
# synonym_analyzer 分词器,可以为全局分词器,也可以为index分词器,但应该使用带 synonym filter的分词器
# zero_terms_query 表示如果query被synonym_analyzer分次之后为0个term,全都是停用词,那么召回策略是什么,参考 https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html#query-dsl-match-query-zero
# synonym_type_boost 表示同义词召回内容加入的权重,默认是 0.00001GET test_synonym_3/_search
{
  "query": {
    "synonym_match": {
      "title": {
        "query": "this 绘图",
        "synonym_analyzer": "custom_analyzer",
        "zero_terms_query": "none",
        "synonym_type_boost" : 0.00001
      }
    }
  }
}


# 结果, 画图 作为 同义词,也召回了 doc ,但是为最后结果贡献了很低的得分。
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 0.87546873,
    "hits" : [
      {
        "_index" : "test_synonym_3",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.87546873,
        "_source" : {
          "title" : "this is my 绘图"
        }
      },
      {
        "_index" : "test_synonym_3",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.18233427,
        "_source" : {
          "title" : "this is my 画图"
        }
      }
    ]
  }
}

对于 synonym graph 部分请参考 https://www.shenyanchao.cn/blog/2014/11/25/better-synonym-handling-in-solr/

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Elasticsearch同义词过滤器实际上是一个基于词典的映射表,将同义词映射为一个或多个主词。在分析文本时,Elasticsearch 将会根据同义词过滤器的词典,自动将同义词替换为对应的主词。 具体来说,当一个文本被索引到 Elasticsearch 时,Elasticsearch 会先将文本分词,然后对每个词进行分析。在分析过程,如果遇到一个同义词Elasticsearch 就会将其替换为对应的主词,然后继续进行分析。这样,同义词就能够被视为相同的词汇,从而实现同义词检索。 同义词过滤器的实现依赖于 Elasticsearch 的分析器。分析器由一系列分词器和过滤器组成,其分词器将文本分割成单词,而过滤器则对单词进行处理同义词过滤器就是一种特殊的过滤器,它会在分析过程,对单词进行同义词替换。 同义词过滤器的词典可以是一个文本文件,也可以是一个 Elasticsearch 索引。如果使用文本文件,可以在词典指定同义词,每行一个同义词,用空格或逗号分隔。如果使用 Elasticsearch 索引,可以通过查询 API 动态获取同义词,从而实现动态的同义词检索。 总之,Elasticsearch同义词过滤器实现了同义词检索的功能,通过自动将同义词转换为主词,实现了对同义词处理和索引。这使得 Elasticsearch 能够更加准确地匹配用户的查询,并返回更加精确的搜索结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值