我们有一个搜索业务底层采用ElasticSearch作为搜索引擎,在索引的过程中,使用了ik中文分词、拼音分词、同义词等多种分词器。ES和各种插件的组合已经能满足我们线上90%的需求,但是仍有部分需求无法覆盖,我们在拼音分词的时候过程中就遇到了无法解决的问题。
比如在 三一重工
中,一重
这个词在拼音词库 polyphone.txt
中有对应的词汇,读作 yichong
,因此这整个词的读音为 sanyichonggong
, 但是其真实读音应该为 sanyizhonggong
。这是因为在拼音分词过程中会先去词库中检索是否有对应的词汇,没有的话再用单字拼音代替最后拼接在一起。
再比如在 蔚来汽车
中,蔚来
算是一个新词,在拼音词库 polyphone.txt
中没有对应的词汇,因此这个词对应的拼音是每个字的拼音拼接而成,结果为 yulai
。但是其真实读音应该为 weilai
, 那么我们的用户就无法通过拼音 weilai
搜索到相关的内容。
经过查看拼音分词源代码发现,拼音分词其实是调用nlp-lang这个项目里的方法实现的分词器。而这个nlp-lang项目中,拼音解析如果遇到多音字仅仅返回第一个拼音,这样很多读音都无法获取到。
if(temp.length()==1){
//单个字取第一个拼音
lists.add(PinyinFormatter.formatPinyin(word.getParam()[0], format));
} else {
for (String t : word.getParam()) {
lists.add(PinyinFormatter.formatPinyin(t, format));
}
}
面对庞大的多音字列表,通过手工维护、修改词汇列表显然无法完全达到目的。
为此,我们决定调整这部分代码满足我们线上业务的需求。
这部分仅仅介绍调整思路,不设计具体代码实现。
拼音分词会调用 nlp-lang 中的一个方法,把中文字符串转换为拼音,获得一个字符串列表
List<String> pinyinList = Pinyin.pinyin(source);
我们在这个基础上新增了一个 multiplePinyin
方法,可以获取多音字所有读音,并且不再检索 polyphone.txt
中的词库对照表。
System.out.println(Pinyin.pinyin("蔚来"))
>>> ['yu', 'lai']
System.out.println(Pinyin.multiplePinyin("蔚来"))
>>> ['yu wei', 'lai']
System.out.println(Pinyin.pinyin("三一重工"))
>>> ['san', 'yi', 'chong', 'gong']
System.out.println(Pinyin.multiplePinyin("三一重工"))
>>> ['san', 'yi', 'zhong chong', 'gong']
多音字的多个读音用空格分割。
multiple_pinyin
类型的分词器和过滤器,确保不会影响到之前的拼音分词的功能。public Map<String, AnalysisModule.AnalysisProvider<org.elasticsearch.index.analysis.TokenFilterFactory>> getTokenFilters() {
Map<String, AnalysisModule.AnalysisProvider<org.elasticsearch.index.analysis.TokenFilterFactory>> extra = new HashMap<>();
extra.put("pinyin", PinyinTokenFilterFactory::new);
// 新增加的分词类型
extra.put("multiple_pinyin", MultiplePinyinTokenFilterFactory::new);
return extra;
}
multiple_pinyin
的分词器中使用上面新增的 Pinyin.multiplePinyin
方法获取到每个字的多音字。然后根据空格拆分后将所有可能的结果组合在一起。// pinyin "蔚来"
["yulai"]
// multiple_pinyin "蔚来"
["yulai", "weilai"]
// pinyin "三一重工"
["sanyichonggong"]
// multiple_pinyin "三一重工"
["sanyizhonggong", "sanyichonggong"]
// pinyin "厦门重工" (两个多音字:夏、重)
["xiamenzhonggong"]
// multiple_pinyin "厦门重工"
["shamenzhonggong", "shamenchonggong", "xiamenzhonggong", "xiamenchonggong"]
因为支持多音字的拼音分词是所有读音可能结果的笛卡尔积,因此当输入的字符串长度过大时,分词的结果可能会特别大。假如输入的字符串中有10个字是多音字,每个字都有2种读音,那么分词结果就有2^10个
。可想而之,耗时会非常长。
我们的使用场景中,仅仅针对物品名称进行分词,名称不会很长,暂时没有遇到性能瓶颈。
nlp-lang
elasticsearch-analysis-pinyin