之前討論過想要有自行組合新聞的BLOG軟件,這種軟件最重要的部份就是搜尋和相似度統計的功能。要自行制作一個功能完整又效能高的搜尋器絕不容易,幸好早有開放源碼的軟件替我們做此工作。
Apache Lucene
Apache Lucene是一個開放源碼的搜尋器引擎,利用它可以輕易地為Java軟件加入全文搜尋功能。Lucene的最主要工作是替文件的每一個作索引,索引讓搜尋的效率比傳統的逐字比較大大提高,試想想google怎樣可以瞬即自全世界的文件中找到有關鍵字的網頁,但傳統的Windows檔案搜索卻常常要花半小時? Lucen提供一組解讀、過濾、分析文件、編排和使用索引的API,它的強大之處除了高效和簡單外,最重要的是使用者可以隨時應自己需要自訂其功能。
分析器
要為應用提高搜尋效率,不同語言,甚至不同類型的文章都需要特別的分析器(Analyzer)。Lucene 的分析器由分解器(Tokenizer)和過濾器(TokenFilter)組成,前者分割文字、找出文件中的最少單位(Token),後者過濾和改變輸入去方便處理。英語的文字分割簡單直接,但對於中文來說要有意義地分割則困難得多了。 雖然Lucene Sandbox Project下己有兩個可以分析中文的分析器 ChineseAnalyzer和CJKAnalyzer,但因為他們策略太簡單,各有不盡理想的地方。
中文詞語分割
以簡單句子為例:「我是中國人」,ChineseTokenizer會將之分割為五個中文字:「我、是、中、國、人」,CJKTokenizer則會將之分割為「我是、是中、中國、國人」四個二節的詞。前者的問題是沒有考慮中文詞語的問題,如搜尋「國中」一樣搜尋到「我是中國人」。後者的問題則是制做了大量沒意義的詞如「是中」「國人」,讓索引沒必要地增大、降低搜尋效率。理想的方法是按詞語分割,如「我、是 、中國人」,但要完美地分割中文卻很困難,多年以來直到現在都有無數的研究都在想有效的新方法。
如何研究更好的方法是一回事,實作一個足夠應用的分析器又是另一回事,要是不滿足於ChineseTokenizer和CJKTokenizer的簡單,又不想花時間研究,可以參考和應用前人所作的解決方案。
實作
管理線上中文工具網站的 Erik Peterson 開發了一個基於詞語的中文分割器。它的算法很簡單:準備一本中文詞典,將輸入的文章的每一字逐字和詞典比較,順序地找文章中可以對應詞語。例如「中國人」,首先找到「中」、找著找到「中國」、再找到「中國人」,分割器就會返回「中國人」作為Token。這種算法簡單但有效,字典用Tree實作的話效能也相當好,Eric的實作包括Perl和Java版本的程式。
怎樣應用這個分割器去制作Lucene的Tokenizer呢?當然我們可以研究它的算法自行寫一個實作Tokenizer介面的Segmenter Class,這樣效率最高但必須重寫整個程式。為了重用已有的資源,我的方案是先準備好輸入字串、經過segmenter分割成有空格的中文字,再用跟英文字一樣的用空格分割方法。
實作時發現以下一些問題(和解決方法):
- Segmenter要將整本字典放進記憶體的樹結構中,視電腦速度而定需要3秒到30秒準備,為了避免重覆這漫長過種使用了Singleton去產生Segmenter的實體,同時使用serialize的方法讓第二次執行時不用重新制作樹
- 原裝的StandardTokenizer設計以英語系為目標,原以為只要輸入有空格的中文即可分割中文,不過發現原來它對中日韓字體另有處理,它跟ChineseTokenizer一樣會把每個中文字當成一個token。幸好StandardTokenizer是使用JavaCC (類似YACC,是JAVA版的語言編譯器) 制作,只要更改原本StandardTokenizer.jj中的語法,把中文當作一般英文字母去處理就能達到原先的目標了。 (更改了的CStandardTokenizer.jj)
完成的Segmenter所造的index比CJK Analyzer或者Chinese Analyzer更少,但要注意由於要載入一個佔記憶體3M以上的大字典所以記憶體方面更求更高,而在分析少量文件時預載字典的overhead隨時比分析少,只有大量使用才會見到它的優點。
相關檔案:
- 源碼 (文字檔)
- 源碼和Ant的build檔
相關連結: