给全文搜索引擎Manticore (Sphinx) search 增加中文分词

3次阅读

共计 2127 个字符,预计需要花费 6 分钟才能阅读完成。

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 一样,具体看官方文档。
使用方法

准备词表 把所有词写到一个 txt 文件,一行一个词,如下所示:
words.txt
中文中国語중국어

创建字典 成功编译代码后,就会得到创建字典的可执行程序 make_segdictionary. 然后执行命令:./make_segdictionary words.txt words.dict

这样就得到了字典文件: words.dict

配置索引 只需在配置文件的 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。
文章首发于我的个人博客猿人学你也可以关注我的个人公众号:猿人学 Python

正文完
 0