中文检索
对于中文用户来说,中文与英文天然使用空格分隔每个单词不同,中文需要通过分词技术将连续文本拆解为语义单元才能实现高效的倒排索引匹配。在本系列笔记的第一篇中我们搭建ES时就直接安装了IK分词器,介绍索引字段Mapping设计时也曾说明过对中文的text类型字段应使用IK分词器的Analyzer,但并没有做深入说明。这篇笔记我们将进一步介绍分词器相关的概念以及中文分词器相关配置。
Analyzer 分析器
ES中,Analyzer(分析器)负责将一段文本转换成可以被索引和搜索的Token(词项),一个Analyzer由三部分组成:
Character Filters 字符过滤器:字符过滤器负责在分词前对原始文本进行预处理,例如去除HTML标签、替换特殊字符等。一个Analyzer中可以有零个或多个Character Filter,它们会按顺序依次执行。
Tokenizer 分词器:分词器是核心组件,它负责将文本切割成Token序列并记录每个Token的起止位置。一个Analyzer有且只有一个Tokenizer。
Token Filters 词项过滤器:词项过滤器负责对Tokenizer输出的词项做进一步加工,例如转换为小写、去除停用词、添加同义词等。一个Analyzer中可以有零个或多个Token Filter,它们会按顺序依次执行。
实际开发中,我们可以通过调用ES的API直接观察分词结果,下面例子中我们使用IK分词器分析一段中文文本。
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "计算机汉字输入方法"
}
对于ik_max_word,分词结果的Token是按中文词来拆分的,如果我们将Analyzer换成默认的standard将得到截然不同的效果,后者处理英文可以基于空格等按单词拆分,处理连续中文只能按字拆分,这也是中文检索需要使用IK分词器的原因。IK分词器是ES中使用最广泛的中文分词插件,它基于词典匹配进行中文分词,IK其实提供了两种分词模式:
ik_max_word:最细粒度拆分,尽可能多地切分词语,适合用于索引时建立尽可能多的词项以提升召回率。
ik_smart:智能模式,进行语义最优切分,减少歧义。适合搜索时使用以提升搜索精准度。
以输入“计算机汉字输入方法”为例,ik_max_word会将其拆分为“计算机”、“计算”、“算机”、“汉字输入”、“汉字”、“输入”、“方法”,而ik_smart会将其拆分为“计算机”、“汉字输入”、“方法”。
实际开发中,ik_max_word和ik_smart是配合使用的,索引时对被索引的数据使用ik_max_word,检索时对输入使用ik_smart,这样配合使用建立的倒排索引覆盖了最多的词项组合,而查询时的词项又尽量精准,因此能有效命中目标文档,兼顾了召回率和精准度。
前面章节我们曾多次使用下面这个索引作为例子,它的name和description字段会被用于中文检索,分词器也是按照ik_max_word和ik_smart配合设置的。
PUT /products
{
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"description": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"category": { "type": "keyword" },
"brand": { "type": "keyword" },
"price": { "type": "double" },
"stock": { "type": "integer" },
"tags": { "type": "keyword" },
"is_deleted": { "type": "boolean" },
"created_at": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss"
},
"location": { "type": "geo_point" }
}
}
}
自定义词典
IK分词器内置了一套词典,但内置词典不可能覆盖所有场景,尤其是行业专有名词、品牌新词、网络流行词等内容。如果我们的业务场景高频使用的专有名词IK词典没有收录,分词器工作时就可能被错误地切分导致检索结果较差,这时需要我们手动配置IK词典。
IK分词器支持通过配置文件加载本地自定义词典,不过这个配置文件可能出现在两个地方。对于离线手动安装方式,配置文件在{ES根目录}/plugins/analysis-ik/config/IKAnalyzer.cfg.xml,对于使用elasticsearch-plugin install命令安装的情况,配置文件不会生成在插件目录,而是自动迁移到了ES的全局配置目录{ES根目录}/config/analysis-ik/IKAnalyzer.cfg.xml。配置文件中默认有如下内容。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict"></entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
当我们需要添加自定义词典时,按以下方式配置词典文件的文件名,例如custom_words.dic,词典文件需要和配置文件放在同一目录下。
<entry key="ext_dict">custom_words.dic</entry>
词典文件必须是UTF-8编码无BOM纯文本文件,换行符需要是Linux风格的LF,每行一个词,否则可能出现词典文件不生效的情况。
鸿蒙OS
澎湃S1
骁龙8至尊版
A18芯片
修改词典后,我们注意需要重启ES节点词典变更才会生效,重启完成后可以使用/_analyze接口测试自定义词典效果。此外,添加自定义词典后,索引里已经存在的旧数据不会自动重新分词,倒排索引不会自动更新;只有新写入的数据才会按新词库分词!如果你想让旧数据也应用新的词库,唯一的办法是创建一个新索引,将旧数据导入新索引中,然后在应用层切换到新索引或使用ES的别名机制切流。
配置停用词
停用词是指在检索中没有实际语义价值的词,例如“的”、“了”、“是”、“在”、“和”等,这类词在文档中大量出现,如果不过滤会影响检索效率和检索准确度。IK分词器已内置了一份中文停用词词典,我们也可以通过前面提到的ext_stopwords配置扩展停用词列表。此外,在创建索引时,我们也可以通过自定义Analyzer来显式配置停用词过滤,下面是一个例子。
PUT /my_test_index
{
"settings": {
"analysis": {
"filter": {
"my_stop_filter": {
"type": "stop",
"stopwords": [
"的", "了", "是", "在", "和", "呃", "嗯", "那个", "然后"
]
}
},
"analyzer": {
"my_ik_analyzer": {
"tokenizer": "ik_max_word",
"filter": ["my_stop_filter"]
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "my_ik_analyzer"
}
}
}
}
配置同义词
同义词在中文检索中非常重要。用户搜索“手机”,可能也想看到“移动电话”、“智能机”的结果;搜索“笔记本”,可能也想看到“笔电”、“便携电脑”的结果。ES支持同义词配置,通过配置同义词表,可以显著提升检索的召回率。
ES支持在创建索引时通过词项过滤器的synonym配置同义词表并用于自定义Analyzer中,下面是一个例子。
PUT /my_test_index
{
"settings": {
"analysis": {
"filter": {
"my_synonyms": {
"type": "synonym",
"synonyms": [
"手机,移动电话,智能机",
"笔记本,笔电,便携电脑",
"耳机,耳麦",
"iphone,苹果手机"
]
}
},
"analyzer": {
"ik_with_synonyms": {
"type": "custom",
"tokenizer": "ik_smart",
"filter": ["my_synonyms"]
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "ik_with_synonyms"
}
}
}
}
实际开发中,同义词表可能比较大,而且每次创建索引都指定一大串同义词表也不利于维护。ES中同义词表也支持通过文件加载。下面例子中,同义词表文件synonyms.dic实际在{ES根目录}/config/analysis/synonyms.dic。
"filter": {
"my_synonyms": {
"type": "synonym",
"synonyms_path": "analysis/synonyms.dic"
}
}
拼音分词插件
中文搜索场景中用户有时会用拼音来搜索,例如输入“pingguo”来搜索“苹果”。ES中可以通过elasticsearch-analysis-pinyin插件支持类似情况的拼音检索。如果你使用第一章完全相同的Docker方式搭建的ES,可以直接执行以下命令安装该插件,注意插件版本需要和ES完全匹配,安装完成后需要重启ES节点。
docker exec -it elasticsearch bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-pinyin/8.13.4
要在创建索引时同时用上IK分词器和拼音插件,我们需要自定义词项过滤器并用于自定义Analyzer中,下面是一个例子。
PUT /my_test_index
{
"settings": {
"analysis": {
"filter": {
"my_pinyin_filter": {
"type": "pinyin",
"keep_separate_first_letter": false,
"keep_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"lowercase": true,
"remove_duplicated_term": true
}
},
"analyzer": {
"ik_max_pinyin_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word",
"filter": ["my_pinyin_filter"]
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "ik_max_pinyin_analyzer",
"search_analyzer": "ik_smart"
}
}
}
}
拼音过滤器的常用配置说明如下。
| 配置项 | 说明 |
|---|---|
keep_full_pinyin |
保留每个汉字的全拼,如"ping"、"guo" |
keep_joined_full_pinyin |
保留全拼连写,如"pingguo" |
keep_original |
同时保留原始中文词 |
limit_first_letter_length |
首字母缩写最大长度 |
remove_duplicated_term |
去除重复词项 |
代码中,我们只将拼音分析器用于索引阶段,搜索时仍使用ik_smart,这是有意而为之的,这种配置下用户输入中文或拼音都能命中文档,但同时也避免了中文搜索词被误切成拼音字母导致的匹配问题。