ElasticSearch pinyin分詞支持多音字
背景
我們有一個搜索業務底層採用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));
}
}
面對龐大的多音字列表,通過手工維護、修改詞彙列表顯然無法完全達到目的。
爲此,我們決定調整這部分代碼滿足我們線上業務的需求。
調整
這部分僅僅介紹調整思路,不設計具體代碼實現。
1. nlp-lang
拼音分詞會調用 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']
多音字的多個讀音用空格分割。
2. elasticsearch-analysis-pinyin
- 首先在原來的分詞器基礎上新增
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個
。可想而之,耗時會非常長。
我們的使用場景中,僅僅針對物品名稱進行分詞,名稱不會很長,暫時沒有遇到性能瓶頸。