關於Solr搜索標點與符號的中文分詞你必須知道的(mmseg源碼改造)
摘要:在中文搜索中的標點、符號往往也是有語義的,比如我們要搜索“C++”或是“C#”,我們不希望搜索出來的全是“C”吧?那樣對程序員來說是個噩夢。然而在中文分詞工具mmseg中,它的中文分詞是將標點與符號均去除的,它認為對於中文來講標點符號無意義,這明顯不能滿足我們的需求。那么怎樣改造它讓它符合我們的要求呢?本文就是針對這一問題的詳細解決辦法,我們改mmseg的源代碼。
關鍵字:Solr, mmseg, 中文, 分詞, 標點, 符號, 語義
前提:Solr(5.0.0版本),mmseg4j(1.10.0版本)
作者:王安琪(博客地址:http://www.cnblogs.com/wgp13x/)
0、Solr的mmseg默認中文分詞效果
做個實驗,入Solr的語句為:t#\"\&\*CTY C# "#"&*^#とう華뭄내ㅛ #\"\&\*C8:8。3 C# \"#\"&*^#√とう ,使用的是mmseg中的“max-word”型分詞。分詞后會變成什么樣呢?在對Solr進行簡單的mmseg配置操作后,我們在Solr的Analysis中對以上語句進行分析,如下圖所示。
圖0-1 mmseg默認中文分詞效果
從上圖中可以看出,默認的mmseg“max-word”型分詞將所有的標點、符號都拋棄掉了,余下的只是中文、數字、英文、韓文、日文等。經過mmseg的其他類型如:“complex”和“simple”分析操作后,其結果也是把所有的標點、符號均刪除。然而使用Ansj進行中文分詞的話,其默認是不刪除標點符號的。使用IKAanalyzer來進行中文分詞,它也刪除掉所有的標點符號。具體情況見博客:中文分詞器性能比較 http://www.cnblogs.com/wgp13x/p/3748764.html。
mmseg在中文分詞過程中刪除標點符號,這直接導致搜索不出標點和符號,因為被刪除的將不被建立索引,如:搜索“#”,返回的是所有。為了解釋這個問題,我們分析一下Solr創建索引的過程。
1、Solr創建索引的過程
在創建索引的過程中,進入的每一句字符串,均依據fieldType中配置的:tokenizer及以下的filter,從上至下依次處理。正如下圖所示的,當進入的字符串為 #Yummm :) Drinking a latte at ... 第一步經過StandardTokenizer后,變成了一個個單詞:Yummm | Drinking | a | latte | at | ,可以看出這一步就已經將標點符號去除掉了,並使用標點符號和空格將句子划分成一個個單詞。第二步經過的是StopFilter,它將stop words:a at in 等刪掉,它認為他們是無語義的詞匯,這要看具體情況了,這步結束后原文變成了:Yummm | Drinking | latte | 。第三步是經過LowercaseFilter,很明顯從字面上解釋就是把所有的單詞小寫化,最終的結果是:yummm | drinking | latte |。
圖1-1 Solr創建索引的過程
在搜索的過程中,新入的搜索字符串,也需要經歷這幾個過程,再將經歷這些過程后的單詞以“與”或“或”的關系,進行搜索。這就解釋了,上一個問題,為什么輸入的搜索條件是“#”,返回的是所有,因為條件經歷這些過程后,條件是空,即搜索所有了。
2、Solr的mmseg經過改進后的中文分詞效果
經過我們的改進,在入Solr的語句為:!,工;1 - 低 ... 時, 中文分詞效果如下圖所示。
圖2-1 mmseg經過改進后的中文分詞效果
從上圖可以看到,經過MMST后,所有的單詞都已經大寫小化了,所以可以去除LowerCaseFilter,對結果不影響,即在配置中將<filter class="solr.LowerCaseFilterFactory"/>去掉。再次分析的效果如下圖所示:
圖2-2 mmseg經過改進后並去除LowerCaseFilter后的中文分詞效果
可以看出,C++這樣輸入的輸出變成了:c | + | +,這樣的話,當搜索條件為入C++時,便可以匹配出來了!這正是我們想要的。最終效果可以從下圖中看出,在圖2-3中將一串帶有標點符號的字符串添加入Solr的mmseg fild中。在圖2-4中對mmseg fild搜索帶有標點符號的字符串,可以看到,剛添加的字符串被正確搜索到了!
圖2-3 添加帶有標點符號的Document
圖2-4 搜索條件帶有標點符號的搜索結果
3、Solr的mmseg的中文分詞效果改進辦法
首先,根據mmseg作者chenlb https://github.com/chenlb/mmseg4j-solr 的提示與啟發,可以在next()函數中進行修改源碼,以達到不去除標點符號的目的。我們在mmseg源碼中找到MMSeg類中存在next()函數,通過閱讀源碼,我們知道,這即是對已識別的各種類型的字符進行分門別類地處理,如數字、字母、韓語等。函數內對其他的字符均視為無效字符,其中標點與符號便落入了此類別,其對此類別的字符處理辦法是:“不理睬”。下面就是我依照中文字符的處理過程,編寫了標點與符號的處理過程,同時對空格及Tab、\n這些字符采取“不理睬”策略,因為他們真的是無語義的,具體的代碼如下。
public Word next() throws IOException { // 先從緩存中取 Word word = bufWord.poll(); ; if (word == null) { bufSentence.setLength(0); int data = -1; boolean read = true; while (read && (data = readNext()) != -1) { read = false; // 默認一次可以讀出同一類字符,就可以分詞內容 int type = Character.getType(data); String wordType = Word.TYPE_WORD; switch (type) { 。。。。。。。。 case Character.SPACE_SEPARATOR: case Character.CONTROL: read = true; break; default: // 其它認為無效字符 // read = true; bufSentence.appendCodePoint(data); readChars(bufSentence, new ReadCharByType(type)); // bufWord.add(createWord(bufSentence, Word.TYPE_LETTER)); currentSentence = createSentence(bufSentence); bufSentence.setLength(0); }// switch // 中文分詞 if (currentSentence != null) { do { Chunk chunk = seg.seg(currentSentence); for (int i = 0; i < chunk.getCount(); i++) { bufWord.add(chunk.getWords()[i]); } } while (!currentSentence.isFinish()); currentSentence = null; } word = bufWord.poll(); } return word; }
經過編譯后,將MMSeg類相關的class替換到mmseg4j-core-1.10.0.jar目錄下,如圖3-1所示。然后重新部署Solr,一切運行正常!
圖3-1 編譯並替換MMSeg
4、Solr的配置補充
經過剛才的操作,已經解決了標點與符號刪除的問題。下面講一下autoGeneratePhraseQueries的配置。
圖4-1 mmSeg配置
如上圖的配置所示,autoGeneratePhraseQueries="false",autoGeneratePhraseQueries配置為false有下面的作用:將搜索關鍵詞分詞后,以或的條件進行搜索,比如入的是 ,搜索關鍵詞是
,關鍵詞經過分詞后有些分詞結果不在Doc范圍內,但是仍舊可以搜索出來;然而如果autoGeneratePhraseQueries="true" ,則搜索不出來,此時是且的關系。
這簡直是太棒了!