es 修改拼音分词器源码实现汉字/拼音/简拼混合搜索时同音字不匹配
[版权声明]:本文章由danvid发布于http://danvid.cnblogs.com/,如需转载或部分使用请注明出处
在业务中经常会用到拼音匹配查询,大家都会用到拼音分词器,但是拼音分词器匹配的时候有个问题,就是会出现同音字匹配,有时候这种情况是业务不希望出现的。
业务场景:我输入"纯生pi酒"进行搜索,文档中有以下数据:
doc[1]:{"name":"纯生啤酒"}
doc[2]:{"name":"春生啤酒"}
doc[3]:{"name":"纯生劈酒"}
以上业务点是我输入"纯生pi酒"理论上业务希望只返回doc[1]:{"name":"纯生啤酒"}和doc[3]:{"name":"纯生劈酒"}其他的不是我要的数据,因为从业务角度来看,我已经输入"纯生"了,理论上只需要返回有"纯生"的数据(当然也有很多情况,会希望把"春生"也返回来),正常使用拼音分词器,会把doc[2]也会返回,原因是拼音分词器会把doc[2]变成:
{"tokens": [{"token": "c","start_offset": 0,"end_offset": 1,"type": "word","position": 0},{"token": "chun","start_offset": 0,"end_offset": 1,"type": "word","position": 0},{"token": "s","start_offset": 1,"end_offset": 2,"type": "word","position": 1},{"token": "sheng","start_offset": 1,"end_offset": 2,"type": "word","position": 1},{"token": "p","start_offset": 2,"end_offset": 3,"type": "word","position": 2},{"token": "pi","start_offset": 2,"end_offset": 3,"type": "word","position": 2},{"token": "j","start_offset": 3,"end_offset": 4,"type": "word","position": 3},{"token": "jiu","start_offset": 3,"end_offset": 4,"type": "word","position": 3}] }
由于"纯生"和"春生"是同音字,分词结果doc[1]和doc[2]是一样的,所以把doc[2]匹配上就是理所当然了,那么如何解决?
其实我们的需求是就当输入搜索文本时(搜索文本中可能同时存在中文/拼音),搜索文本中有[中文] 则按[中文]匹配,有[拼音]则按[拼音]匹配即可,这样就屏蔽掉了输入中文时匹配到同音字的问题。那么我们可以这样思考,我们索引的时候同时存在 全拼/简拼/中文 三种分词,搜索的时候 输入中有中文则按中文一个个分开,有英文则按拼音进行分词即可 例如:
索引时"纯生啤酒"分词为:
索引分词:{"tokens": [{"token": "c","start_offset": 0,"end_offset": 1,"type": "word","position": 0},{"token": "chun","start_offset": 0,"end_offset": 1,"type": "word","position": 0},{"token": "纯","start_offset": 0,"end_offset": 1,"type": "word","position": 0},{"token": "s","start_offset": 1,"end_offset": 2,"type": "word","position": 1},{"token": "sheng","start_offset": 1,"end_offset": 2,"type": "word","position": 1},{"token": "生","start_offset": 1,"end_offset": 2,"type": "word","position": 1},{"token": "p","start_offset": 2,"end_offset": 3,"type": "word","position": 2},{"token": "pi","start_offset": 2,"end_offset": 3,"type": "word","position": 2},{"token": "啤","start_offset": 2,"end_offset": 3,"type": "word","position": 2},{"token": "j","start_offset": 3,"end_offset": 4,"type": "word","position": 3},{"token": "jiu","start_offset": 3,"end_offset": 4,"type": "word","position": 3},{"token": "酒","start_offset": 3,"end_offset": 4,"type": "word","position": 3}] }
搜索"纯生pi酒",分词为:
搜索分词:{"tokens": [{"token": "纯","start_offset": 0,"end_offset": 1,"type": "word","position": 0},{"token": "生","start_offset": 1,"end_offset": 2,"type": "word","position": 1},{"token": "pi","start_offset": 2,"end_offset": 4,"type": "word","position": 2},{"token": "酒","start_offset": 4,"end_offset": 5,"type": "word","position": 3}] }
这样就可以只匹配出有"纯"|"生"|"酒"这几个字的数据了,而不是把"春"字的doc也匹配出来,既然解决思路有了,就找方案了。
由于目前的es的拼音分词器是没有分离中文并保留中文的功能,所以就需要修改其源码增加这个功能(使用的拼音分词器: https://github.com/medcl/elasticsearch-analysis-pinyin)
源码的话在上面地址上可以下在,源码的原理大概讲一下,就是他调用一个nlp工具包( https://github.com/NLPchina)先对输入文本解析成拼音 即"纯生pi酒"会解析成["chun","sheng",null,null,"酒"]数组(这里再提一句这个nlp工具包会对词组进行解析,而不是单个字进行解析例如"厦/门"会解析成"xia/men"而不是"sha/men"这个确实有用很多,当然他还有很多工具,例如简繁体转化等等,大家可以学习使用一哈),然后再单独对英文数字放到buff里面进行二次匹配,采用"正向最大匹配"和"逆向最大匹配"取出最优解(这些都是常用的分词手法)匹配出拼音字符,源代码如下:
// 分别正向、逆向最大匹配,选出最短的作为最优结果
List<String> forward = positiveMaxMatch(pinyinText, PINYIN_MAX_LENGTH);if (forward.size() == 1) { // 前向只切出1个的话,没有必要再做逆向分词 pinyinList.addAll(forward);} else { // 分别正向、逆向最大匹配,选出最短的作为最优结果 List<String> backward = reverseMaxMatch(pinyinText, PINYIN_MAX_LENGTH); if (forward.size() <= backward.size()) { pinyinList.addAll(forward); } else { pinyinList.addAll(backward); }}
至于拼音字典匹配结构由于拼音的数量不多,拼音源码采用了HashSet的结构而不是我们ik里面的字典树。("正向最大匹配"和"逆向最大匹配"百度一大把就不在这说了)
原理大概讲完了根据需求我们是不需要管英文数字这一块的匹配逻辑的,只需要修改中文转拼音这附近的逻辑即可。
首先我们先写一个中文分割的工具类或者方法如下:
public class ChineseUtil {/*** 汉字始*/public static char CJK_UNIFIED_IDEOGRAPHS_START = '\u4E00';/*** 汉字止*/public static char CJK_UNIFIED_IDEOGRAPHS_END = '\u9FA5';public static List<String> segmentChinese(String str){if (StringUtil.isBlank(str)) {return Collections.emptyList();}List<String> lists = str.length()<=32767?new ArrayList<>(str.length()):new LinkedList<>();for (int i=0;i<str.length();i++){char c = str.charAt(i);if(c>=CJK_UNIFIED_IDEOGRAPHS_START&&c<=CJK_UNIFIED_IDEOGRAPHS_END){lists.add(String.valueOf(c));}else{lists.add(null);}}return lists;} }
汉字始或者汉字止这个查一下nlp工具的源码(PinyinUtil)就可以找到,或者百度。然后在拼音源码中的PinyinConfig类中添加一项中文分割的配置:
默认false就可以了,然后我们需要修改两个类(PinyinTokenFilter/PinyinTokenizer),这两个类是最要的分词类,对应es的analysis的filter和tokenizer
由于这两个类修改地方是一样的我就随便讲一个,首先需要修改构造器的校验,添加刚刚增加的配置:
然后修改该类的readTerm()方法,如下:
两个类都修改完就完成源码修改了,现在需要对源码重新进行打包,mvn install以下就可以了,你就会拿到elasticsearch-analysis-pinyin-5.6.4.jar(你下载源码的时候要下载release的版本进行修改,版本也要对应你的es哦),同时在源码的lib拿到nlp-lang-1.7.jar包 ,再加上resource中的plugin-descriptor.properties(这个需要定义插件版本,启动类等东西,这个去拼音release版本中找个可用的插件解压一下跟着配置就可以了),最后变成下面这个样子:
放在一个文件夹里面,这个就是打包好的插件了,名字自己命名即可,然后放到es的plugin目录里面就完成修改了。
剩下就是修改index的setting和mapping,修改思想就是按照开头说的那样search_analyzer和analyzer分开即可,如下:
PUT /test_index {"settings": {"analysis": {"analyzer": {"pinyin_chinese_analyzer": {"tokenizer": "pinyin_tokenizer"},"pinyin_analyzer": {"tokenizer": "pinyin_chinese_tokenizer"}}, "tokenizer": {"pinyin_chinese_tokenizer": {"type": "pinyin","keep_first_letter": false,"keep_separate_first_letter": false,"keep_full_pinyin":false,"keep_original":false,"limit_first_letter_length":50,"keep_separate_chinese": true,"lowercase":true},"pinyin_tokenizer": {"type": "pinyin","keep_first_letter": false,"keep_separate_first_letter": true,"keep_full_pinyin":true,"keep_original":false,"limit_first_letter_length":50,"keep_separate_chinese": true,"lowercase":true}}}}, "mappings": {"indexType":{"properties": {"name":{"type": "text","search_analyzer": "pinyin_chinese_analyzer","analyzer": "pinyin_analyzer"}}}} }
查询使用match_pharse即可(使用原理可以参考我的文章https://www.cnblogs.com/danvid/p/10570334.html),当然也可以用其他,根据业务来把。
下面是简单的验证结果:索引中有以下文档doc[1]:{"name": "雪花纯生啤酒200ml"}|doc[2]:{"name": "雪花纯爽啤酒200ml"}|doc[3]:{"name": "雪花春生啤酒200ml"}
查询输入: GET /test_index/_search {"query": {"match_phrase": {"name": "xuehcs"}} } 结果: "hits": [{"_index": "test_index","_type": "indexType","_id": "2","_source": {"name": "雪花纯爽啤酒200ml"}},{"_index": "test_index","_type": "indexType","_id": "1","_source": {"name": "雪花纯生啤酒200ml"}},{"_index": "test_index","_type": "indexType","_id": "3","_source": {"name": "雪花春生啤酒200ml"}}] 查询输入: GET /test_index/_search {"query": {"match_phrase": {"name": "xueh纯生"}} } 结果: "hits": [{"_index": "test_index","_type": "indexType","_id": "1","_source": {"name": "雪花纯生啤酒200ml"}}]
总结:其实解决思路并不复杂,不过其实在修改源码之前也考虑过其他方案,例如通过修改tokenizer为standard或者ik+fliter为pinyin进行分词等,但是总是存在各种问题不尽人意,用standard的时候由于已经拆分成了字,所以会出现"厦门"这种多音字被转化为"shamen"而不是"xiamen",而ik分词则在使用match_phrase时可控性较差~加上受词库的影响,最后才决定使用修改源码增加功能的方式~如果大家有更好的方式可以推荐一下
[说明]:elasticsearch版本5.6.4
转载于:https://www.cnblogs.com/danvid/p/10691547.html
es 修改拼音分词器源码实现汉字/拼音/简拼混合搜索时同音字不匹配相关推荐
- es修改IK分词器源码 mysql热词动态更新(报错解决x3)
最近在公司遇到的一个问题,给elasticsearch配置ik热部署mysql词库. 我是参照下面这个博客来做的 https://www.cnblogs.com/xiaoxiaoliu/p/11218 ...
- 31_ElasticSearch 修改IK分词器源码来基于mysql热更新词库
31_ElasticSearch 修改IK分词器源码来基于mysql热更新词库 更多干货 分布式实战(干货) spring cloud 实战(干货) mybatis 实战(干货) spring boo ...
- Elasticsearch7.15.2 修改IK分词器源码实现基于MySql8的词库热更新
文章目录 一.源码分析 1. 默认热更新 2. 热更新分析 3. 方法分析 二.词库热更新 2.1. 导入依赖 2.2. 数据库 2.3. JDBC 配置 2.4. 打包配置 2.5. 权限策略 2. ...
- 【转载保存】修改IK分词器源码实现动态加载词典
链接:http://www.gongstring.com/portal/article/index/id/59.html 当前IKAnalyzer从发布最后一个版本后就一直没有再更新,使用过程中,经常 ...
- es ik分词热更新MySQL,ElasticSearch(25)- 改IK分词器源码来基于mysql热更新词库
代码地址 已经修改过的支持定期从数据库中提取新词库,来实现热更新.代码: https://github.com/csy512889371/learndemo/tree/master/elasticse ...
- ik mysql热加载分词_Elasticsearch 之(25)重写IK分词器源码来基于mysql热更新词库...
热更新在上一节< IK分词器配置文件讲解以及自定义词库>自定义词库,每次都是在es的扩展词典中,手动添加新词语,很坑 (1)每次添加完,都要重启es才能生效,非常麻烦 (2)es是分布式的 ...
- 庖丁解牛分词器---源码下载---错误问题解决
庖丁解牛分词器---源码下载 地址:http://download.csdn.net/detail/u014737138/9349677 由于国内的环境限制,访问不了Google ,同时网上那些下载 ...
- CentOS安装Elasticsearch_IK分词器拼音分词器_部署kibana_部署es集群
CentOS安装Elasticsearch_IK分词器_部署kibana_部署es集群 一.部署单点es ①:创建网络 因为我们还需要部署kibana容器,因此需要让es和kibana容器互联.这里先 ...
- es自定义拼音分词器处理中文拼音排序问题
1.先上结论,如下mapping可以解决es拼音排序问题 {"settings": {"number_of_shards": "3",&qu ...
- Elasticsearch——分布式搜索引擎01(索引库、文档、RestAPI、RestClient、拼音分词器、IK分词器)
Elasticsearch--分布式搜索引擎01(索引库.文档.RestAPI.RestClient.拼音分词器.IK分词器) 一.初识 elesticsearch 1.1 简介 1.2 倒排索引(重 ...
最新文章
- 信号与系统-2021年春季学期-考试信息
- extend implements多个对象_「每天三分钟跟我学Java」之Java面向对象的封装、继承、多态...
- adprw指令通讯案例_实例 | 三菱FX3U485无协议通讯程序详解(含程序)
- 学习总结:JavaScript学习分享
- 推荐两份学习 Kotlin 和机器学习的资料
- Java开发 明华usbkey_明华驱动官方版下载-明华usbkey数字证书驱动下载v3.0.2420.9 最新版-当易网...
- 基于遗传算法优化极限学习机预测及其MATLAB实现-附代码
- mysql数据库实验4
- 邮箱不能发送大附件,什么邮箱可以发送超大附件?
- python 城市地图_Python查询一个城市的谷歌地图的经度和纬度
- 超级邮件群发代理服务器设置,超级邮件群发教程
- windows商店直接安装ubuntu子系统
- 什么是迭代器(Iterator)
- c语言子函数作用是什么意思,C语言编译器中常见的函数用法以及作用详解
- Java入门(四):进阶
- 【JavaSe,Day03,note】
- 太爽啦,GitHub网站1S变VS Code
- Appium基础学习之 | Bootstrap源码分析
- 统计分析及建模小结(1)
- 案例聚焦:Ping32助力树兰医疗建设全面终端安全管理体系