ElasticSearch分析器解析(十四)

ES在文本字段的索引建立和搜索阶段都会用到分析器。分析器一般用在下面两个场景中:

  1. 创建或更新文档时(合称索引时),对相应的文本字段进行分词处理;
  2. 查询文本字段时,对查询语句进行分词。

        ES中的分析器有很多种,但是所有分析器的结构都遵循三段式原则,即字符过滤器、分词器和词语过滤器。其中,字符过滤器可以有0个或多个,分词器必须只有一个,词语过滤器可以有0个或多个。从整体上来讲,三个部分的数据流方向为字符过滤器→分词器→分词过滤器。如下所示为一个分析器的构成示例:

         文本先以字符流的形式流经字符过滤器,在本例中,在本例中,由两个子字符过滤器组成一个字符过滤器组合。字符过滤器处理完字符后将结果传递给分词器,分词器对文本进行分词处理后将结果又传递给分词过滤器。在本例中,由两个子分词过滤器组成一个分词过滤器组合。最终,分析器输出分词后每个词的信息,至此,一个分析器的处理流程结束。

        对于不同的分析器,上述三部分的工作内容是不同的,为了正确匹配,如果在数据写入时指定了某个分析器,那么在匹配查询时也需要设定相同的分析器对查询语句进行分析。

字符过滤器

        字符过滤器是分析器处理文本数据的第一道工序,它接收原始的字符流,对原始字符流中的字符进行添加、删除或者转换操作,进而改变原始的字符流。

        例如,原始数据中可能包含来自爬虫的结果,字符过滤器可以去除文本中的HTML标签,也可以将原始文本中的一些特殊字符进行转义,如把“&”转换为and。

        总而言之,字符过滤器就是对原始文本做一些粗加工的工作,为后续的分词做准备。

ES内置了一些字符过滤器,其中常用的字符过滤器及其功能如下表所示:

名称功能
映射关系字符过滤器根据配置的映射关系替换字符
HTML擦除过滤器去掉HTML元素
正则表达式替换过滤器用正则表达式处理字符串

分词器

        分词器在分析器中负责非常重要的一环工作——按照规则来切分词语。对于英文来说,简单的分词器通常是根据空格及标点符号进行切分。然而对于中文分词来说,字符之间往往没有空格,因此采用英文的切分规则是不可取的。中文分词有多种切分方案。不同的分词器采用的方案不同,处理后的结果也可能不同。分词器对文本进行切分后,需要保留词语与原始文本之间的对应关系,因此分词器还负责记录每个Token的位置,以及开始和结束的字符偏移量。

在ES中内置了一些分词器,其中常用的分词器及其功能如下表所示:

名称功能
标准分词器对英文分词时,基于语法分词;对中文分词时,切分成单字
字母分词器使用非字母的字符作为分词标记
小写分词器功能上等同于字母分词器,并且把所有分词结果转换为小写形式
空格分词器使用空格作为分词标记

分词过滤器

        分词过滤器接收分词器的处理结果,并可以将切分好的词语进行加工和修改,进而对分词结果进行规范化、统一化和优化处理。例如,它可以将文本中的字母全部转换为小写形式,还可以删除停用词(如的、这、那等),还可以为某个分词增加同义词。

在ES中内置了一些分词过滤器,其中常用的分词过滤器及其功能如下表所示:

名称功能
Lower Case过滤器将所有字母转换为小写形式
Stop Token过滤器将停用词从分词结果中移除
同义词分词过滤器为分词结果添加同义词

分析器的使用

        ES提供了分析器的调用API,使用户可以方便地对比不同分析器的分析结果。另外,ES提供了一些开箱即用的内置分析器,这些分析器其实就是字符过滤器、分词器和分词过滤器的组合体,可以在索引建立时和搜索时指定使用这些分析器。当然,如果这些分析器不符合需求,用户还可以自定义分析器。

测试分析API

为了更好地理解分析器的运行结果,可以使用ES提供的分析API进行测试。在DSL中可以直接使用参数analyzer来指定分析器的名称进行测试,分析API的请求形式如下:

POST _analyze
{
  "analyzer": "${analyzer_name}", //指定分析器名称
  "text":"${analyzer_text}"//待分析文本
}

以下示例使用standard分析器分析一段英文:

POST _analyze
{
  "analyzer": "standard",
  "text": "the hotel is not clean"
}

上述文本的分析结果如下:

{
  "tokens" : [
    {
      "token" : "the",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "hotel",
      "start_offset" : 4,
      "end_offset" : 9,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "is",
      "start_offset" : 10,
      "end_offset" : 12,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "not",
      "start_offset" : 13,
      "end_offset" : 16,
      "type" : "<ALPHANUM>",
      "position" : 3
    },
    {
      "token" : "clean",
      "start_offset" : 17,
      "end_offset" : 22,
      "type" : "<ALPHANUM>",
      "position" : 4
    }
  ]
}

根据以上结果可以看到,standard分析器对文本进行分析时,按照空格把上面的句子进行了分词。分析API返回信息的参数说明如下:

  • token:文本被切分为词语后的某个词语;
  • start_offset:该词在文本中的起始偏移位置;
  • end_offset:该词在文本中的结束偏移位置;
  • type:词性,各个分词器的值不一样;
  • position:分词位置,指明该词语在原文本中是第几个出现的。

start_offset和end_offset组合起来就是该词在原文本中占据的起始位置和结束位置。

下面使用standard分析器分析一段中文文本:

POST _analyze
{
  "analyzer": "standard",
  "text":"北京嘉怡假日旅馆"
}

分析结果如下:

{
  "tokens" : [
    {
      "token" : "北",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<IDEOGRAPHIC>",
      "position" : 0
    },
    {
      "token" : "京",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "<IDEOGRAPHIC>",
      "position" : 1
    },
    {
      "token" : "嘉",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "<IDEOGRAPHIC>",
      "position" : 2
    },
    {
      "token" : "怡",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "<IDEOGRAPHIC>",
      "position" : 3
    },
    {
      "token" : "假",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "<IDEOGRAPHIC>",
      "position" : 4
    },
    {
      "token" : "日",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "<IDEOGRAPHIC>",
      "position" : 5
    },
    {
      "token" : "旅",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "<IDEOGRAPHIC>",
      "position" : 6
    },
    {
      "token" : "馆",
      "start_offset" : 7,
      "end_offset" : 8,
      "type" : "<IDEOGRAPHIC>",
      "position" : 7
    }
  ]
}

        使用standard分析器对中文进行分析时,由于中文没有空格,无法根据空格进行切分,因此只能按单字进行切分,并给出了每个单字的词性。

        在中文里,两个单字的词性和每个单字的词性是不同的,因此使用standard分析器分析中文时给出的词性不具备参考价值,不仅如此,ES内置的其他分析器也不适合分析中文。

        除了指定分析器进行请求分析外,用户还可以指定某个索引的字段,使用这个字段对应的分析器对目标文本进行分析。下面使用旅馆索引的title字段对应的分析器分析文本。

POST /hotel/_analyze
{
  "field": "title",
  "text": "北京嘉怡假日旅馆"
}

另外,用户还可以在API中自定义分析器对文本进行分析。比如以下自定义的分析器,该分析器的分词器使用standard,分词过滤器使用Lower Case,其将分词后的结果转换为小写形式:

GET _analyze
{
  "tokenizer": "standard", //标准分词器
  "filter": ["lowercase"], //分词过滤器
  "text":"This is a Test" //待分析文本
}

结果返回:

{
  "tokens" : [
    {
      "token" : "this",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "is",
      "start_offset" : 5,
      "end_offset" : 7,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "a",
      "start_offset" : 8,
      "end_offset" : 9,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "test",
      "start_offset" : 10,
      "end_offset" : 14,
      "type" : "<ALPHANUM>",
      "position" : 3
    }
  ]
}

内置分析器

        ES已经内置了一些分析器供用户使用,在默认情况下,一个索引的字段类型为text时,该字段在索引建立时和查询时的分析器是standard。standard分析器是由standard分词器、Lower Case分词过滤器和Stop Token分词过滤器构成的。   

        standard分析器没有字符过滤器。除了standard分析器之外,ES还提供了simple分析器、language分析器、whitespace分析器及pattern分析器等,这些分析器的功能如下表:

名称功能
simple分析器按非字母字符进行词语拆分,并将所有词语转换为小写
language分析器语言分析器
whitespace分析器按照空白字符拆分词语
pattern分析器使用正则表达式将文本拆分为词语

    另外,用户也可以自定义分析器,并且可以在索引建立或搜索时指定自定义分析器。

索引时使用分析器

        文本字段在索引时需要使用分析器进行分析,ES默认使用的是standard分析器。如果需要指定分析器,一种方式是在索引的settings参数中设置当前索引的所有文本字段的分析器,另一种方式是在索引的mappings参数中设置当前字段的分析器。

        以下示例在settings参数中指定在旅店索引的所有文本字段中使用simple分析器进行索引构建:

PUT /hotel
{
  "settings": {
    "analysis": {
      "analyzer": {
        "default":{
          "type":"simple"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      ....
    }
  }
}

以下示例在mappings参数中指定在旅店索引的title字段中使用whitespace分析器进行索引构建。

PUT /hotel
{
  "mappings": {
    "properties": {
      "title":{
        "type": "text",
        "analyzer": "whitespace"
      }
    }
  }
}

搜索时使用分析器

为了搜索时更加协调,在默认情况下,ES对文本进行搜索时使用的分析器和索引时使用的分析器保持一致。当然,用户也可以在mappings参数中指定字段在搜索时使用的分析器。如下示例展示了这种用法:

PUT /hotel
{
  "mappings": {
    "properties": {
      "title":{
        "type": "text",
        "analyzer": "whitespace",
        "search_analyzer": "whitespace"
      }
    }
  }
}

注意,这里指定的搜索分析器和索引时的分析器是一致的,但是在大多数情况下是没有必要指定的,因为在默认情况下二者就是一致的。如果指定的搜索分析器和索引时的分析器不一致,则ES在搜索时可能出现有不符合预期的匹配情况,因此该设置在使用时需要慎重选择。

自定义分析器

        当系统内置的分析器不满足需求时,用户可以使用自定义分析器。在有些场景中,某个文本字段不是自然语言而是在某种规则下的编码。

        例如,在旅馆索引中有个sup_env字段,其值为“APP,H5,WX”,表示当前旅馆可以在App、Web端和微信小程序端上显示。假设当前搜索用户使用的是H5或App客户端,则需要过滤掉不支持在这两个客户端上显示的旅馆。

        首先,需要在索引创建的DSL中定义分析器comma_analyzer,该分析器中只有一个分词组件,该分词组件使用逗号进行词语切分;然后在mappings中使用analyzer参数指定字段sup_env的分析器为定义好的comma_analyzer分析器。具体的DSL如下:

PUT /hotel
{
  "settings": {
    "analysis": {
      "analyzer": {
        "common_analyzer":{ //自定义解析器
          "tokenizer":"common_tokenizer"
        }
      },
      "tokenizer":{ //定义分词器
        "common_tokenizer":{
          "type":"pattern",
          "pattern":"," //制定切分分隔符
        }
      }
    }
  }, 
  "mappings": {
    "properties": {
      "title":{
        "type": "text",
        "analyzer": "whitespace",
        "search_analyzer": "whitespace"
      },
      "sup_env":{
        "type": "text",
        "analyzer": "common_analyzer"
      }
    }
  }
}

下面向旅馆索引中插入几条数据:

POST /_bulk
{"index":{"_index":"hotel","_id":"001"}}
{"title":"北京嘉怡假日旅馆","city":"北京","sup_env":"APP,H5","price":50.000}
{"index":{"_index":"hotel","_id":"002"}}
{"title":"北京欣欣旅馆","city":"北京","sup_env":"H5,WX","price":50.000}
{"index":{"_index":"hotel","_id":"003"}}
{"title":"北京旅馆","city":"北京","sup_env":"WX","price":50.000}

当前用户的客户端为H5或App,当搜索“北京”关键词时应该构建的DSL如下:

GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "city": "北京"
          }
        },
        {
          "match": {
            "sup_env": "APP,H5"
          }
        }
      ]
    }
  }
}

运行上面的DSL后,ES返回的结果如下:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.6081687,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "001",
        "_score" : 1.6081687,
        "_source" : {
          "title" : "北京嘉怡假日旅馆",
          "city" : "北京",
          "sup_env" : "APP,H5",
          "price" : 50.0
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "002",
        "_score" : 0.7015199,
        "_source" : {
          "title" : "北京欣欣旅馆",
          "city" : "北京",
          "sup_env" : "H5,WX",
          "price" : 50.0
        }
      }
    ]
  }
}

由上面的结果可以看到,索引中有3个文档,只有文档001和文档002对应的旅馆城市中包含“北京”且可以在H5或App客户端显示。使用自定义的分词器可以将以逗号分隔的字段进行分词后建立索引,从而在搜索时也使用逗号分隔符进行匹配搜索。

中文分析器

        搜索引擎分析器的最终结果都是以分词结果为单位进行输出的。分词工作是搜索引擎的基础性工作,分词结果的质量高低对后面的搜索结果匹配起着非常关键的作用。中文相对于英文等西方语言有独有的一些特点,ES内置的分析器一般很难适用于中文搜索,因此针对中文的ES分词器插件应运而生。

        对于英文来说,一个文档很容易被切分成关键词的集合,因为除了标点符号外都是由空格把各个英文单词进行分隔的。例如I have a red car,用空格进行切分的结果为I/have/a/red/car。对于中文来说,一般由一个或多个字组合在一起形成一个词语,并且句子中没有词的界限。根据不同的使用场景,对于词语切分颗粒度的需求也是不一样的,请看如下示例。

例句:我来到北京清华大学。

分词结果1:我/来到/北京/清华/华大/大学/清华大学

分词结果2:我/来到/北京/清华大学

上面的两种分词方式都是正确的,它们可以应用在不同的场景中。

中文分词根据实现原理和特点,分词的切分算法主要有两种,即基于词典的分词算法和基于统计的机器学习算法。

1、基于词典的分词算法

基于词典的分词算法是按照某种策略将提前准备好的词典和待匹配的字符串进行匹配,当匹配到词典中的某个词时,说明该词分词成功。该算法是匹配算法中最简单、速度最快的算法,其分词算法分为3种,即正向最大化匹配法、逆向最大化匹配法和双向最大化匹配法。

2、基于统计的机器学习算法

基于统计的机器学习算法的主要思想是事先构建一个语料库,该语料库中是标记好的分词形式的语料,然后统计每个词出现的频率或者词与词之间共现的频率等,基于统计结果给出某种语境下应该切分出某个词的先验概率。后续进行分词时,使用先验概率给出文本应该切分的结果。这类算法中代表的算法有HMM、CRF、深度学习等,比如结巴分词基于HMM算法、HanLP分词工具基于CRF算法等。

当前,中文分词的难点主要有以下三方面:

  • 分词标准:不同的分词器使用的分词标准不同,分词的结果也不同。例如,在分词的颗粒度方面,对“中华人民共和国”进行切分时,粗粒度的分词就是“中华人民共和国”,细粒度的分词可能是“中华”“人民”“共和国”。
  • 分词歧义:使用分词器对文本进行切分,切分后的结果和原来的字面意义不同。例如,在“郑州天和服装厂”中,“天和”是厂名,是一个专有词,“和服”也是一个词,它们共用了“和”字。如果分词器不够精准,则很容易切分成“郑州、和服、服装、服装厂”,但是原文中并没有与“和服”有关的含义,因此这里就产生了歧义。
  • 新词识别:新词也称未登录词,即该词没有在词典或者训练语料中出现过。在这种情况下,分词器很难识别出该词。目前,新词识别问题的解决依赖于人们对分词技术和中文结构的进一步认识。

ES通过安装插件的方式来支持第三方分析器。比较常用的第三方中文分析器是HanLP和IK分析器

IK分析器

IK分析器是一个开源的、基于Java语言开发的轻量级的中文分词工具包,它提供了多种语言的调用库。在ES中,IK分析器通过第三方插件的方式来使用,其代码托管到了GitHub上,项目地址为https://github.com/medcl/elasticsearch-analysis-ik。IK分析器实现了词典的冷更新和热更新,用户可以选择适合自己的方式进行词典的更新。

ES中IK分析器的安装过程如下:

  1. 进入ES的plugins目录,然后新建目录,目录名称为ik-analysis。
  2. 进入ik-analysis目录,然后运行wget命令下载ES的IK分词插件,下载地址为https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.13.2/elasticsearch-analysis-ik7.13.2.zip。注意,插件版本要和当前的ES版本保持一致,否则可能会导致ES启动报错。
  3. 使用unzip命令对下载的插件文件进行解压缩
  4. 重新启动ES,观察其运行日志,当打印出类似loaded plugin[analysis-ik]的日志内容时,说明IK插件安装成功

IK分析器提供了两个子分析器,即ik_smart和ik_max_word,另外它还提供了两个和分析器同名的子分词器。安装完成后,可以使用这些分析器或分词器进行验证。

下例使用ik_max_word分析器对待测试文本进行分析:

POST _analyze
{
  "analyzer": "ik_max_word",
  "text": "北京嘉怡旅馆"
}

下例使用ik_smart分析器对待测试文本进行分析:

POST _analyze
{
  "analyzer": "ik_smart",
  "text": "北京嘉怡假日旅馆"
}

从上述两个运行结果中可以看到,ik_max_word和ik_smart分析器的主要区别在于切分词语的粒度上,ik_smart的切分粒度比较粗,而ik_max_word将文本进行了最细粒度的拆分,甚至穷尽了各种可能的组合。

        另外可以看到“嘉怡”这个词被切分成了“嘉”和“怡”,这个词没有在IK分析器的词典里,因此被切分成了两个单字,这需要为IK分析器添加词典来解决该问题。在IK分析器的安装目录下的config子目录中创建文件my.dict,在其中添加“嘉怡”即可。如果有更多的词语需要添加,则每个词语单独一行,添加示例如下所示:

添加完成后修改IK分析器的配置文件,路径为config/IKAnalyzer.cfg.xml,将新建的字典文件加入ext_dict选项中,如下图所示:

 

配置完成后重启ES,然后使用分析器分析上面的文本,此时“嘉怡”就可以被切分出来。此时可以使用ik_smart分析器进行验证。

安装完毕后,也可以将IK分析器应用到索引的字段中。下面将ik_max_word分析器设置为旅馆索引中title字段的默认分析器。 

PUT /hotel
{
  "mappings": {
    "properties": {
      "title":{
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}

HanLP分析器

        HanLP是由一系列模型与算法组成的Java工具包,它从中文分词开始,覆盖词性标注、命名实体识别、句法分析、文本分类等常用的NLP任务,提供了丰富的API,被广泛用于Lucene、Solr和ES等搜索平台。就分词算法来说,它支持最短路分词、N-最短路分词和CRF分词等分词算法。用户可以在ES中安装HanLP分析器插件进行使用。

        HanLP分析器插件的安装比较简单,在ES的安装目录下执行bin/elasticsearch-plugin install${URL}命令即可,其中的URL是HanLP的安装文件链接。本书使用的ES版本为7.13.2,需要HanLP分析器也使用相同的版本,对应的链接地址为https://github.com/Kenn Falcon/elasticsearch-analysis-hanlp/releases/download/v7.13.2/elasticsearch-analysis-hanlp-7.10.2.zip,安装时可根据当前的版本进行选择。

        HanLP分析器提供了众多的子分析器,如hanlp、hanlp_standard、hanlp_crf和hanlp_n_short等。下例使用hanlp_standard分析器对待测试文本进行分析。

POST _analyze
{
  "analyzer": "hanlp_standard",
  "text":"北京嘉怡假日旅馆"
}

        HanLP分析器的分析结果和IK分析器的分析结果类似,而且“嘉怡”这个词也被切分成了“嘉”和“怡”。同样的,解决该问题也需要给HanLP分析器添加用户自定义词典。在${ES_HOME}/plugins/analysis-hanlp/data/dictionary/custom目录中新建字典文件my.dict,然后在该文件中添加需要的词语,当有多个词语需要添加时,每个词语单独一行。

       词语添加完成后修改${ES_HOME}config/analysis-hanlp/hanlp.properties配置文件,修改选项CustomDictionaryPath,在后面添加上面新建的字典文件即可,此处使用相对路径。

配置完成后等待1min左右,然后再使用HanLP分析器分析上面的文本,“嘉怡”就可以被切分出来了。另外,如果继续在上面的词典文件中添加词语,HanLP分析器会在后台每隔一段时间(大约1min)对分词词典进行自动热更新。每轮更新完成之后,新添加的词语即可被用户使用。

可以将HanLP分析器设置为索引字段的默认分析器,下例是将hanlp_standard分析器设置为旅馆索引中title字段的默认分析器。 

PUT /hotel
{
  "mappings": {
    "properties": {
      "title":{
        "type": "text",
        "analyzer": "hanlp_standard"
      }
    }
  }
}

 同义词搜索

        在搜索场景中,同义词用来处理不同的查询词,有可能是表达相同搜索目标的场景。

       例如,当用户的查询词为“带浴缸的旅馆”和“带浴池的旅馆”时,其实是想搜索有单独泡澡设施的旅馆。

        例如,在电商搜索中,同义词更是应用广泛,如品牌同义词Adidas和“阿迪达斯”,产品同义词“投影仪”和“投影机”,修饰同义词“大码”和“大号”等。

        用户在使用这些与同义词相关的关键词进行搜索时,搜索引擎返回的搜索结果应该是一致的。

        用户还可以通过ES中的分析器来使用同义词,使用方式分为两种,一种是在建立索引时指定同义词并构建同义词的倒排索引,另一种是在搜索时指定字段的search_analyzer查询分析器使用同义词。  

建立索引时使用同义词   

在ES内置的分词过滤器中,有一种分词过滤器叫作synonyms,它是一种支持用户自定义同义词的分词过滤器。以下是使用IK分析器和synonyms分词过滤器一起定义索引的DSL:

PUT /hotel
{
  "settings": {
    "analysis": {
      "filter": {
        "ik_synonyms_filter":{ //定义过滤器
          "type":"synonyms",
          "synonyms":[ //定义近义词
            "北京,首都",
            "天津,天津卫",
            "假日,度假"
            ]
        }
      },
      "analyzer": { //自定义分析器
        "ik_analyzer_synonyms":{
          "tokenizer":"ik_max_word",//指定分词器
          "filter":[ //指定分词过滤
            "lowercase",
            "ik_synonyms_filter"
            ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title":{
        "type": "text",
        "analyzer": "ik_analyzer_synonyms"
      }
    }
  }
}

查询时使用同义词

在ES内置的分词过滤器中还有个分词过滤器叫作synonym_graph,它是一种支持查询时用户自定义同义词的分词过滤器。以下是使用IK分析器和synonym_graph分词过滤器一起定义索引的DSL:

PUT /hotel
{
  "settings": {
    "analysis": {
      "filter": {
        "ik_synonyms_graph_filter":{
          "type":"synonyms_graph",
           "synonyms":[ //定义近义词
            "北京,首都",
            "天津,天津卫",
            "假日,度假"
            ]
        }
      },
      "analyzer": {
        "ik_analyzer_synonyms_graph":{
          "tokenizer":"ik_max_word",//指定分词器
          "filter":[ //指定分词过滤
            "lowercase",
            "ik_synonyms_graph_filter"
            ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title":{
        "type":"text",
        "search_analyzer": "ik_analyzer_synonyms_graph"
      }
    }
  }
}

对比两种方式的结果可以看到,命中的结果集和索引时使用的同义词一致,但是结果的排序却不同。这是因为在索引时使用同义词会计算全部的同义词的TF/IDF值,在搜索时进行的相关性计算,是将同义词和其他词同等对待,也就是将其TF/IDF值计算在内。而在搜索时使用同义词,需要ES将同义词转换后再进行相关性计算。

如果有更新同义词的需求,则只能使用查询时使用同义词的这种方式。首先需要先关闭当前索引:

POST /hotel/_close

下面更改索引的settings信息,新添加一组近义词“番茄,西红柿”:

PUT /hotel
{
  "settings": {
    "analysis": {
      "filter": {
        "ik_synonyms_graph_filter":{
          "type":"synonyms_graph",
           "synonyms":[ //定义近义词
            "北京,首都",
            "天津,天津卫",
            "假日,度假",
            "番茄,西红柿"
            ]
        }
      },
      "analyzer": {
        "ik_analyzer_synonyms_graph":{
          "tokenizer":"ik_max_word",//指定分词器
          "filter":[ //指定分词过滤
            "lowercase",
            "ik_synonyms_graph_filter"
            ]
        }
      }
    }
  }
}

打开索引:

POST /hotel/_open

如果同义词比较多,在settings中进行配置时将非常烦琐。ES支持用户将同义词放在文件中,文件的位置必须是在${ES_HOME}/config目录及其子目录下,注意该文件必须存在于ES集群中的每一个节点上。在${ES_HOME}/config目录下建立一个子目录mydict,然后在该目录下创建一个名称为synonyms.dict的文件,文件内容如下图所示:

然后在创建旅馆索引时,在settings中指定同义词文件及其路径,DSL如下:

PUT /hotel
{
  "settings": {
    "analysis": {
      "ik_synonyms_graph_filter":{
        "type":"synonym_graph",
        "synonyms_path":"mydict/synonyms.dict"
      },
      "analyzer": {
        "ik_analyzer_synonyms_graph":{
          "tokenizer":"ik_max_word",//指定分词器
          "filter":[ //指定分词过滤
            "lowercase",
            "ik_synonyms_graph_filter"
            ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title":{
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_analyzer_synonyms_graph"
      }
    }
  }
}

 当同义词词典文件内容更新时,执行:

POST /hotel/_reload_search_analyzers

执行上述请求后,同义词得到更新,后续就可以在查询中使用新添加的同义词了。

停用词

        停用词也叫停止词,是指文本在被分词之后的词语中包含的无搜索意义的词。什么叫作“无搜索意义”呢?假设文本为“这里的世界丰富多彩”,那么分词结果中的“这里”和“的”对于匹配这个文档来说意义不大,因为这两个词的使用频率非常高,并且没有太多独特的意义。在构建搜索引擎索引时,常常忽略这样的词,这样可以大大提升搜索效率。经常使用的中文和英文停用词可以在网站www.ranks.nl上提取,中文停用词地址为https://www.ranks.nl/stopwords/chinese-stopwords,英文停用词地址为https://www.ranks.nl/stopwords。

使用停用词过滤器

可以通过创建自定义分析器的方式使用停用词,方法是在分析器中指定停用词过滤器,在过滤器中可以指定若干个停用词。下面使用standard分词器和停用词过滤器组成一个自定义分析器进行索引定义DSL如下:

PUT /hotel
{
  "settings": {
    "analysis": {
      "filter": {
        "my_stop":{
          "type":"stop",
          "stopwords":[
              "我",
              "的",
              "这"
            ]
        }
      },
      "analyzer": {
        "standard_stop":{
          "tokenizer":"standard",
          "filter":["my_stop"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title":{
        "type": "text",
        "analyzer": "standard_stop"
      }
    }
  }
}

使用上述分析器进行文本分析,DSL如下:

POST /hotel/_analyze
{
  "field": "title",
  "text": "我的旅馆"
}

通过运行结果可以看到,“我的旅馆”中的“我”和“的”已经被停用词过滤器过滤,只剩下“旅”和“馆”。但是“旅”的开始位置是2,“馆”的开始位置是3,说明分析结果中“我”和“的”的位置被保留了下来,这种特意保留停用词的方式有助于后续的模糊搜索。

内置分析器中使用停用词

standard这种常用的分析器都自带有停用词过滤器,只需要对其参数进行相应设置即可。以下示例中使用standard分析器并通过设置其stopwords属性进行停用词的设定:

PUT /hotel
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_standard":{
          "type":"standard",
          "stopwords":["我","的","这"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title":{
        "type":"text",
        "analyzer": "my_standard"
      }
    }
  }
}

IK分析器中使用停用词

在默认情况下,IK分析器的分词器只有英文停用词,没有中文停用词。如果想要添加中文停用词,需要通过自定义停用词文件的形式进行添加。在${ES_HOME}/plugins/ik-analysis/config目录下创建my_stopword.dict文件,并在其中添加中文停用词即可,如下图:

添加完停用词后保存文件并退出,然后修改${ES_HOME}/plugins/ik-analysis/config/IKAnalyzer.cfg.xml文件,设置配置项ext_stopwords的值为停用词词典的文件名称,如下图:

 配置完成后重启ES,再使用下面DSL进行分析:

POST _analyze
{
  "analyzer": "ik_max_word",
  "text": "最牛掰的一个旅馆"
}

 HanLP分析器中使用停用词

在默认情况下,HanLP分析器的停用词是不启用的,需要配置自定义分析器并设置其启用停用词,请看如下示例:

PUT /hotel
{
  "settings": {
    "analysis": {
      "tokenizer": {
        "my_tokenizer":{
          "type":"hanlp_standard",
          "enable_stop_dictionary":true
        }
      },
      "analyzer": {
        "my_hanlp":{
          "tokenizer":"my_tokenizer"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title":"type",
      "analyzer":"my_hanlp"
    }
  }
}

在上面的示例中,自定义分析器中的my_tokenizer分词器的enable_stop_dictionary属性被设置为true,表示当前分词器启用停用词。下面使用该分析器对待测文本进行分析,DSL如下:

POST _analyze
{
  "analyzer": "ik_max_word",
  "text": "最牛掰的一个旅馆"
}

当然用户可以在HanLP分析器的停用词词典文件中添加停用词,文件位置为${ES_HOME}/plugins/analysis-hanlp/data/dictionary/stopwords.txt,在该文件中已经有一些HanLP分析器内置的停用词,在该文件末尾追加停用词“一个”即可,如下图:

 将停用词添加到词典中后保存并退出,然后重启ES,再使用HanLP分析器分析上面的文本,“一个”就会被识别为停用词而过滤掉