CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
分词与分析器 — CodeRoadMap
分词与分析器 Analyzer、BM25 与同义词。
分词与分析器:Analyzer、IK、BM25 与同义词
这一课你将能做到
说清楚 analyzer 三段结构(character filter → tokenizer → token filter),并能用 Analyze API 观察分词结果
安装 IK 分词器并为题库索引配置中文分词,区分 ik_smart 与 ik_max_word
配置自定义 analyzer(停用词、小写、同义词),并说出 BM25 打分与分词质量的关系
为什么要学
分词直接决定搜索质量:切错一个词,要么搜不到(召回不足),要么搜出一堆不相关的(准确率低)。ES 内置的标准分词器对中文是「逐字切」——「事务失效」切成「事」「务」「失」「效」四个字,搜索时任何单字都能命中,噪声极大。中文搜索几乎必装 IK 分词器;英语资料则要理解小写化、词干提取、停用词这些处理。
人话:analyzer 就是「文本进入倒排索引前的清洗流水线」。写入时怎么切,搜索时就怎么查——两边必须用同一套规则才能对上。这一课动手做实验,你会亲眼看到同一个句子在不同分词器下变成完全不同的词项。
先建立图像
Analyzer 三段流水线:
原文:"Spring Boot 的 @Transactional 失效了!!"
│
├─ character filter(字符过滤):去掉 HTML、替换字符
│
├─ tokenizer(分词器):切成词项流
│ ["Spring","Boot","的","@Transactional","失效","了"]
│
└─ token filter(词项过滤):小写化、去停用词、加同义词
["spring","boot","transactional","失效"]
→ 写入倒排索引
内置常用 analyzer:
名称 行为 适用 standard(默认) Unicode 切词、小写化;中文按字切 英文、混合 simple 按非字母切、小写 简单英文 whitespace 仅按空格切,不处理大小写 特殊日志 ik_smart 中文粗粒度切分(少而准) 搜索时 ik_max_word 中文细粒度切分(多而全) 索引时
跟着做
步骤 1:用 Analyze API 观察分词
# 标准分词器处理中文:逐字切分
curl -u elastic:abcd1234 -X POST "http://localhost:9200/_analyze" -H "Content-Type: application/json" -d'
{
"analyzer": "standard",
"text": "Spring事务失效的场景"
}'
# 看英文的小写化
curl -u elastic:abcd1234 -X POST "http://localhost:9200/_analyze" -H "Content-Type: application/json" -d'
{
"analyzer": "standard",
"text": "Redis Distributed Locks"
}'
返回 tokens 数组,每个 token 含 token(词项)、position(位置)、offset(字符起止)。position 决定短语匹配,offset 决定高亮位置——这解释了为什么分词会影响短语搜索。
也可以直接指定字段,复用该字段的分词配置:
curl -u elastic:abcd1234 -X POST "http://localhost:9200/questions/_analyze" -H "Content-Type: application/json" -d'
{
"field": "stem",
"text": "事务失效"
}'
步骤 2:安装 IK 分词器
版本必须与 ES 完全一致(8.11.0)。进入容器安装插件:
docker exec -it es01 bin/elasticsearch-plugin install \
https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-8.11.0.zip
docker restart es01
# 粗粒度
curl -u elastic:abcd1234 -X POST "http://localhost:9200/_analyze" -H "Content-Type: application/json" -d'
{
"analyzer": "ik_smart",
"text": "Spring事务在什么情况下会失效"
}'
# 细粒度(会切出更多词,如「什么情况」「情况下」)
curl -u elastic:abcd1234 -X POST "http://localhost:9200/_analyze" -H "Content-Type: application/json" -d'
{
"analyzer": "ik_max_word",
"text": "Spring事务在什么情况下会失效"
}'
经典搭配:索引时用 ik_max_word(切得细、召回广),搜索时用 ik_smart(切得粗、噪声小)。
步骤 3:为题库字段配置 IK curl -u elastic:abcd1234 -X PUT "http://localhost:9200/questions_ik" -H "Content-Type: application/json" -d'
{
"mappings": {
"properties": {
"stem": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"analysis": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"tags": { "type": "keyword" }
}
}
}'
analyzer 管写入,search_analyzer 管查询输入。改分词器只对之后写入的数据生效,历史数据需要 reindex。
步骤 4:IK 扩展词典与远程热更新 IK 自带词典不认识「云原生」「大模型」等新词时会切散。在 IK 配置目录 config/analysis-ik/IKAnalyzer.cfg.xml 中可指定本地扩展词典文件(每行一个词),或配置远程词典 URL——IK 会轮询该 URL,词表变化后自动热加载,无需重启。Docker 环境把配置目录挂载出来:
-v "$PWD/ik/config:/usr/share/elasticsearch/config/analysis-ik"
步骤 5:自定义 analyzer 与同义词 curl -u elastic:abcd1234 -X PUT "http://localhost:9200/questions_syn" -H "Content-Type: application/json" -d'
{
"settings": {
"analysis": {
"filter": {
"my_syn": {
"type": "synonym_graph",
"synonyms": [
"k8s,kubernetes",
"分布式锁,redis锁"
]
}
},
"analyzer": {
"ik_syn": {
"tokenizer": "ik_smart",
"filter": ["lowercase", "my_syn"]
}
}
}
},
"mappings": {
"properties": {
"stem": { "type": "text", "analyzer": "ik_syn" }
}
}
}'
验证:搜「k8s」能命中只写了「kubernetes」的题。注意同义词规则在索引期就展开(本配置是索引分析器),加新词后老数据不生效;8.x 也可只放 search_analyzer 让查询时展开,便于更新词表。
英文常见处理组合:standard tokenizer + lowercase + stop(去掉 the/of)+ stemmer(running→run)。词干提取能提召回,但过度提取会误伤,按需使用。
步骤 6:理解 BM25 与分词的关系 得分 ∝ 词在本文档出现的频率(TF,越高越好,但有饱和上限)
× 词的罕见程度(IDF,越罕见越值钱,「的」几乎不值钱)
÷ 文档长度归一化(长文档天然词多,要打折)
分词质量影响三件事:① 停用词没去掉,「的」「了」的 IDF 干扰排序;② 切得太碎,单字 IDF 失真、长文档惩罚算偏;③ 同义词缺失,语义相同的题得分为零。调搜索质量,先看分词,再谈算法参数。
讲清楚要点
写入和查询用同一套逻辑才能对上 :索引时切出的词项是「钥匙孔」,查询词是「钥匙」。配置错分词器或索引/查询分词器不匹配,是「搜不到」最隐蔽的原因。
ik_max_word 建索引、ik_smart 做查询 是经过验证的组合:建索引多切词保证各种说法都能查到;查询少切词减少误命中。反过来会让查询词过度展开、噪声上升。
分词器是索引级设置、数据级生效 :换分词器不动旧数据;评估改动必须 reindex,这也是别名策略的又一个理由。
同义词用 synonym_graph :它保留多词同义词的位置关系(短语正确),老的 synonym 对多词短语支持差。词表文件可放对象存储,由节点拉取,避免打包进镜像。
BM25 的两个可调参数在索引设置里 :k1 控制词频饱和速度(默认 1.2)、b 控制长度归一强度(默认 0.75)。99% 的场景不需要动——先保证分词正确,收益远大于调参。
你可能会懵的地方
standard 已经能搜到中文,为什么还要 IK?
逐字切意味着搜「事务」实际查的是「事」和「务」两个单字:任何含「业务」「服务」的题都会命中,结果满屏噪声。IK 把「事务」作为词项,查得准。
ik_smart 和 ik_max_word 结果差不多,怎么选?
句子越专业、越长差异越大。记住固定搭配即可:max_word 建索引(召回优先)、smart 查(准确优先)。
装完插件节点起不来?
99% 是插件版本和 ES 版本不一致。确认 _cat/plugins 或启动日志,卸载后装同版本。
同义词为什么有时候短语高亮错乱?
用了普通 synonym 且同义词是多词时位置插入不对。换 synonym_graph;复杂场景把同义词只放在 search_analyzer。
英文搜索要不要去掉停用词?
BM25 本身会给高频停用词很低的权重,但去掉能减小索引、让短语更干净。若用户会搜「to be or not to be」这种原句,则不能去。
错法对照 错法 问题 正解 中文索引用默认 standard 单字切分,噪声命中 IK + max_word/smart 搭配 索引和查询都用 ik_smart 召回不足,换说法搜不到 索引 max_word 改了 analyzer 等老数据生效 只对新写入生效 reindex 历史数据 同义词写进索引后频繁改词表 老数据无法更新 放 search_analyzer 或 reindex 插件版本「差不多就行」 节点启动失败 与 ES 版本严格一致 一上来调 BM25 参数 分词问题被掩盖 先分词后调参
卡点急救
中文搜索结果全是单字噪声 :_analyze 确认字段实际分词器;若仍是 standard,检查 mapping 中 analyzer 拼写和索引是否为目标索引,修正后 reindex。
IK 安装失败/下载超时 :用浏览器或 wget 先确认插件 URL 可达,手动下载 zip 后挂载进容器再 bin/elasticsearch-plugin install file:///path/xxx.zip(路径需在插件可访问目录)。
新词(专有名词)搜不到 :先确认 IK 切词结果,散切的词加入扩展词典并触发热更新;仍有问题检查词典文件编码(UTF-8 无 BOM)和每行一词的格式。
面试常问
说一下 analyzer 的组成。
三段:character filter 先对原文字符做处理(去 HTML、字符映射);tokenizer 把文本切成带位置和偏移的词项流,是唯一必须有的部分;token filter 再对词项做小写化、去停用词、同义词、词干提取等加工。写入和搜索都会经过 analyzer,二者使用的规则需要匹配。
中文搜索怎么解决分词问题?
内置 standard 对中文逐字切分,噪声大;通常安装 IK 分词器,索引时用 ik_max_word 细粒度切分保证召回,搜索时用 ik_smart 粗粒度切分保证准确;再通过扩展词典/远程热更新补充专业词汇,分词器变更后对历史数据 reindex。
BM25 的打分原理?
综合词频(TF,带饱和上限避免堆词刷分)、逆文档频率(IDF,罕见词权重高)和文档长度归一化(长文档打折)。相比老的 TF-IDF,BM25 的词频有上界、长度归一可调,排序更稳定,是 5.x 之后的默认算法。
自测三问
「写入切词」和「查询切词」为什么必须规则一致?本站题库分别用哪个 IK 模式?
搜「k8s」要命中写「kubernetes」的题,从配置到数据生效有哪些注意事项?
BM25 里 TF 为什么要设饱和上限?这防止了什么取巧行为?
小结一句话 分词器是倒排索引的「入口质检」:三段流水线决定词项质量,中文靠 IK(max_word 建、smart 查),相关性靠 BM25,而搜索质量问题永远先查分词再调算法。
延伸