全文检索lucene中文分词的一些总结

全文检索lucene中文分词的一些总结

 

全文检索几乎是所有内容管理系统软件(CMS)必备的功能,在对公司的CMS产品的开发维护过程中,全文检索始终是客户重点关注的模块,为满足客户各式各样越来越高的要求,对全文检索曾做过一段时间相对深入的研究,尤其是对分词机制,趁如今换工作比较空闲之际做个简单总结。

 

1、 什么是中文分词

学过英文的都知道,英文是以单词为单位的,单词与单词之间以空格或者逗号句号隔开。而中文则以字为单位,字又组成词,字和词再组成句子。所以对于英文,我们可以简单以空格判断某个字符串是否为一个单词,比如I love China,love 和 China很容易被程序区分开来;但中文“我爱中国”就不 一样了,电脑不知道“中国”是一个词语还是“爱中”是一个词语。把中文的句子切分成有意义的词,就是中文分词,也称切词。我爱中国,分词的结果是:我 爱 中国。

目前中文分词还是一个难题———对于需要上下文区别的词以及新词(人名、地名等)很难完美的区分。国际上将同样存在分词问题的韩国、日本和中国并称为CJK(Chinese Japanese Korean),对于CJK这个代称可能包含其他问题,分词只是其中之一。

 

2、 中文分词的实现

Lucene自带了几个分词器WhitespaceAnalyzer,SimpleAnalyzer,StopAnalyzer,StandardAnalyzer,ChineseAnalyzer,CJKAnalyzer等。前面三个只适用于英文分词,StandardAnalyzer对可最简单地实现中文分词,即二分法,每个字都作为一个词,这样分出来虽然全面,但有很多缺点,比如,索引文件过大,检索时速度慢等。ChineseAnalyzer是按字分的,与StandardAnalyzer对中文的分词没有大的区别。 CJKAnalyzer是按两字切分的, 比较武断,并且会产生垃圾Token,影响索引大小。以上分词器过于简单,无法满足现实的需求,所以我们需要实现自己的分词算法。

现有的中文分词算法可分为三大类:基于字符串匹配的分词方法、基于理解的分词方法和基于统计的分词方法。后面两者只是听说过,没深入接触过,这里着重讲下基于字符串匹配的分词方法。

基于字符串匹配的分词方法又叫做机械分词方法,它是按照一定的策略将待分析的汉字串与一个“充分大的”机器词典中的词条进行配,若在词典中找到某个字符串,则匹配成功(识别出一个词)。按照扫描方向的不同,串匹配分词方法可以分为正向匹配和逆向匹配;按照不同长度优先匹配的情况,可以分为最大(最长)匹配和最小(最短)匹配;按照是否与词性标注过程相结合,又可以分为单纯分词方法和分词与标注相结合的一体化方法。常用的几种机械分词方法如下:

    1)正向最大匹配法(由左到右的方向);

    2)逆向最大匹配法(由右到左的方向);

    3)最少切分(使每一句中切出的词数最小)。

这种分词方法,首先要有一个词库。一个好的分词器需要一个庞大优良的词库以及设计优秀的数据结构来缓存该词库。下面使用一个名为MMAnalyzer的开源分词器做简单的分词演示,然后大致讲下怎么样基于lucene实现自己的分词器。MMAnalyzer 简介:

 

1、支持英文、数字、中文(简体)混合分词

2、常用的数量和人名的匹配

3、超过22万词的词库整理

4、实现正向最大匹配算法

5、词典的动态扩展

6、分词效率: 第一次分词需要1-2秒(读取词典),之后速度基本与Lucene自带分词器持平。内存消耗: 30M+

 

MMAnalyzer的分词算法如下:

 

1、读取一个字,然后联想,直到联想到不能为止。如果当前可以构成词,便返回一个Token。

2、如果当前不能构成词语,便回溯到最近的可以构成词语的节点,返回。

3、最差的情况就是返回第一个单字。

4、然后从返回结果的下一个字重新开始联想。

 

public static void main(String[] args) throws IOException {

String text = "2008年前三季度,美国次贷危机升级,全球金融持续动荡,世界经济增长全面放缓,全球经济增长动力减弱,世界主要经济体与新兴市场正面临巨大的外部冲击。";

Analyzer analyzer = new MMAnalyzer();

TokenStream stream = analyzer.tokenStream("xxx", new StringReader(text));

        while (true) {

            Token token = stream.next();

            if (token == null) break;

            System.out.print("[" + token.termText() + "] ");

        }

}

返回结果如下:

[2008] [年前] [三季度] [美国] [次] [贷] [危机] [升级] [全球] [金融] [持续] [动荡] [世界经济] [增长] [全面] [放] [缓] [全球] [经济] [增长] [动力] [减弱] [世界] [主要] [经济] [体] [新兴] [市场] [正] [面临] [巨大] [外部] [冲击]

 

MMAnalyzer分词器有两个构造函数MMAnalyzer()和MMAnalyzer(int n)。

MMAnalyzer():采用正向最大匹配的中文分词算法,相当于分词粒度等于0。

MMAnalyzer(int n):参数为分词粒度:当字数 >= n,且能成词,该词就被切分出来。

另外MMAnalyzer还有以下常用方法:

addDictionary(FileReader reader):增加一个新词典,采用每行一个词的读取方式。

addWord(String newWord):往词库里新增加一个新词。

 

其中addWord方法测试了好像只会把新词加入到缓存了的词库中,并不会并永久性写入词典文件中。如果需要写入词典文件,可再按以下方法处理。

 

URL dictionaryPath = URLUtil.getResourceFileUrl("resources/dictionary.txt");

if(dictionaryPath != null){

    // new FileWriter(String, boolean) 第二个参数true表示追加文件到尾部

    BufferedWriter bw = new BufferedWriter(new FileWriter(dictionaryPath.getPath(), true));  

    bw.write(searchStr);//追加文件内容  

    bw.newLine();

    bw.close();

}

 

       当然也可自己实现分词器,实现过程很简单,首先实现一个Tokenizer(需要继承lucene包里的Tokenizer抽象类),覆写里面的next()方法,这也是lucene分词器实现的最关键的地方。然后再实现一个Analyzer(需要继承lucene包里的Analyzer抽象类),将上面实现的Tokenizer指定给该Analyzer。

 

3、  中文分词一些常见问题及解决办法

3.1 分词的缺失

比如同义词。用户搜 "北京 饭店" 能不能把" 首都 饭店"也列出来呢? 这个分词器无能为力。所以这个问题,解决办法就只能是在分词之前,我们再加一层:同义词返回模块。这个思路很不错,也比较简单,很容易实现。关键是词库的建立。

 

3.2 优先级

例如:我还清晰地记得我们坐在江边聊天的情境。

分出来是: 我 还清 晰 地 记得 我们 坐在 江边 聊天 的 情境。

结果: 清晰 被拆开了。

这个是基于词库的分词算法固有的问题。没有很好的解决方法。有统计结果表明,单纯使用正向最大匹配的错误率为1/169,单纯使用逆向最大匹配的错误率为1/245。有一种解决方案是正向匹配结果后再逆向匹配一次,然后比较结果,消除歧义。最好加入词汇概率统计功能.有歧义的用概率决定。

 

3.3 最大匹配的问题

比如搜索“三季度”这个词,词库里同时有 “三季度” 和 “季度”这两个词,分词时按最大正向匹配 则 “三季度” 被分成一个完整的词,按 “季度” 去检索反而搜不出来了。

解决办法:缩短分词粒度,当字数等于或超过该粒度参数,且能成词,该词就被切分出来。

 

3.4 新词识别

      新词,也就是那些在字典中都没有收录过,但又确实能称为词的那些词。最典型的是人名,人可以很容易理解句子“王军虎去广州了”中,“王军虎”是个词,因为是一个人的名字,但要是让计算机去识别就困难了。如果把“王军虎”做为一个词收录到字典中去,全世界有那么多名字,而且每时每刻都有新增的人名,收录这些人名本身就是一项巨大的工程。即使这项工作可以完成,还是会存在问题,例如:在句子“王军虎头虎脑的”中,“王军虎”还能不能算词?

     新词中除了人名以外,还有机构名、地名、产品名、商标名、简称、省略语等都是很难处理的问题,而且这些又正好是人们经常使用的词,因此对于搜索引擎来说,分词系统中的新词识别十分重要。目前新词识别准确率已经成为评价一个分词系统好坏的重要标志之一。

 

其他的还有如热度、高亮显示等问题。总言之,中文分词机制的好坏,直接影响到用户对搜索结果的满意度,所以如何分词是搜索引擎的重中之重。

相关推荐