給全文搜尋引擎Manticore (Sphinx) search 增加中文分詞 原 薦
文章首發於我的技術部落格:你可以在上面看到更多的Python教程 和python爬蟲教程
Sphinx search 是一款非常棒的開源全文搜尋引擎,它使用C++開發,索引和搜尋的速度非常快,我使用sphinx的時間也有好多年了。最初使用的是coreseek,一個國人在sphinxsearch基礎上添加了mmseg分詞的搜尋引擎,可惜後來不再更新,sphinxsearch的版本太低,bug也會出現;後來也使用最新的sphinxsearch,它可以支援幾乎所有語言,通過其內建的ngram tokenizer對中文進行索引和搜尋。
但是,像中文、日文、韓文這種文字使用ngram還是有很大弊端的:
當Ngram=1時,中文(日文、韓文)被分解成一個個的單字,就像把英文分解成一個個字母那樣。這會導致每個單字的索引很長,搜尋效率下降,同時搜尋結果習慣性比較差。
當Ngram=2或更大時,會產生很多無意義的“組合”,比如“的你”、“為什”等,導致索引的字典、索引檔案等非常大,同時也影響搜尋速度。
基於以上弊端,為中日韓文字加入分詞的tokenizer是很有必要的。
於是決定來做這件事。先去Sphinxsearch網站去看看,發現它已經發布了新的3.x版本,而且加入了很多很棒的特性,然而它從Sphinxsearch 3.x 開始,暫時不再開源 . 不過,部分前Sphinxsearch的開發人員跳出來成立新團隊,在Sphinx 2.x版本基礎上開發自己的Manticoresearch。這兩者很像,從它們的名字就可以看出來,這倆都是獅身怪獸。
Sphinx 是(古埃及)獅身人面像,Manticore 是(傳說中的)人頭獅身龍(蠍)尾怪獸
Manticoresearch 從Sphinxsearch 繼承而來, 並做了效能優化 . 因此,我選擇了Manticoresearch 來新增中日韓分詞。
首先從Manticoresearch的github倉庫pull最新的程式碼來談價,後面我也會盡力與Manticoresearch的主分支保持同步。
演算法實現
演算法基於字典,具體是cedar的實現的雙陣列trie。cedar是C++實現的高效雙陣列trie,也是分詞字典的最佳之選。cedar的協議是GNU GPLv2, LGPLv2.1, and BSD;或者email聯絡作者所要其它協議。
通過最小匹配(而非單字)來匹配字典和字串,把字串分割成最短(而非單字)的詞。如果遇到處理不了的歧義時,以單字做詞。這樣的目的是,保證搜尋時能找到這些內容而不丟失。
稍微解釋一下,對於搜尋引擎的分詞為什麼這麼做:
- 搜尋引擎要能找到儘可能全內容:最徹底的方法是ngram=1,每個字單獨索引,這樣你搜索一個單字“榴”時,含有“榴蓮”的文字會被找到,但缺點就如前面所說。
- 搜尋引擎要能找到儘可能相關的內容: 分詞就是比較好的方法,對詞進行索引,這樣你搜索一個單字“榴”時,含有“榴蓮”的文字就不會被找到。但分詞的粒度要小,比如“程式語言”這是一個片語,如果把這個分成一個詞,你搜索“程式設計”時,就找不到只含“程式語言”的文字,同樣的,“上海市”要分成“上海”和“市”,等等。所以,“最小匹配”適用於搜尋引擎。
編譯安裝
從github倉庫manticoresearch-seg 獲取原始碼,編譯方法跟Manticoresearch一樣,具體看官方文件 。
使用方法
1. 準備詞表把所有詞寫到一個txt檔案,一行一個詞,如下所示:
# words.txt 中文 中國語 중국어
2. 建立字典成功編譯程式碼後,就會得到建立字典的可執行程式make_segdictionary
. 然後執行命令:
./make_segdictionary words.txt words.dict
這樣就得到了字典檔案: words.dict
3. 配置索引只需在配置檔案的 index {...}
新增一行即可:
index { ... seg_dictionary = path-to-your-segmentation-words-dictionary ... }
提醒:分詞對批量索引和實時索引都起作用。
吐槽
新增分詞最初的想法是,我的程式碼作為新增檔案加入專案,只在原有檔案個別處新增就好。這樣做分得比較清楚,後面對manticore官方倉庫提交程式碼也比較清晰。於是就嘗試這樣做。
然而,Sphinx的程式碼組織的真是有點亂,Manticore沿用Sphinx的程式碼所以架構是一樣的。最大的一個cpp檔案sphinx.cpp 竟然有3萬多行程式碼,很多類的宣告直接放在這個.cpp 檔案裡面,而沒有放到標頭檔案sphinx.h裡面。 因為我實現的分詞tokenizer必須要繼承它的類保持介面一致。嘗試著把cpp檔案的一些宣告移到.h檔案,結果是越移越多,要對原始檔案做很大改動,甚至可能要重新架構原始碼。不是不可以重新架構,一來會很費時間,二來向官方提交程式碼很難被接受,三是跟官方程式碼保持同步就很費勁,最終還是在原來sphinx.cpp檔案中新增分詞tokenizer: CSphTokenizer_UTF8Seg 。
當然,Sphinx的程式碼的類的繼承關係比較清晰,繼承原來的tokenizer實現新的也不算費事,修改了4個原始碼檔案就新增好了分詞tokenizer。