在搜索引擎技术中,分词对于影响搜索引擎结果排序有着至关重要的作用。与英文不同的是,中文之间没有空格,并且由于中国文字的博大精深,常常一句话可以分出很多不同效果的词汇,这里就不做举例了,想必大家都有所体会。所以对于一个中文搜索引擎来说,中文分词技术是十分重要的,也是十分讲究的。
在搜索引擎运行的机制中,有很多种中文分词的办法,例如正向最大匹配分词,逆向最大分析,基于统计的分词,基于词库的分词等。但是在实际的搜索引擎运行过程中,分词的办法却没有这么简单了。因为搜索引擎不仅仅要去考虑到分词结果的正确性,还需要考虑到对于分不出来的词的处理。
所以能写出一个好的分词算法是很不容易的,由于水平有限,这里主要介绍下Lucene.net和网上几个分词方法。大家有更好的分词方法还望分享!
我们来看下Lucene.net2.3.1中有关分词的源代码:
这里不对每一个类做详细的讲解了,感兴趣的朋友可以到网上下载源码研究下,这里说说这里比较主要的东西。
Analyzer
它叫分析器,也可以叫分词器。 分析(Analysis),在Lucene当中指的是将域(Field)文本转换为最基本的索引表示单元—— 项(Term)的过程。在搜索过程中,这些项用于决定什么样的文档能匹配查询条件。 分析器(Analyzer)对分析操作进行了封装。分析器通过执行若干操作,将文本语汇单元化,这些操作可能包括提取单词、去除标点符号、去掉语汇单元上的音调符号、将字母转换为小写(也称为 规格化)、移除常用词、将单词转换为词干形式(词干还原),或者将单词转换为基本形(lemmatization)等。这个过程也称为 语汇单元化过程(tokenization),而从文本流中得到的文本块称为 语汇单元(tokens)。各语汇单元与关联的域(Field)名相结合就形成了各个项(Term)。
分析操作发生在两个阶段:建立 索引以及 使用QueryParser对象时。
TokenStream
Lucene核心API提供的分析器构建模块:
类 名 |
描 述 |
TokenStream |
基类,包含next()方法和close()方法 |
Tokenizer |
Tokenstream类的输入为Reader对象 |
CharTokenizer |
其他基于字符的Tokenizer类的父类,包含抽象方法isTokenChar()。当isTokenChar()= =true时,输出连续的语汇单元块。该类还具有对字符规格化处理(如转换为小写)的能力。Token对象限制的最大字符数为255个 |
WhitespaceTokenizer |
isTokenChar()值为true时的CharTokenizer子类,用于处理所有非空格的字符 |
LetterTokenizer |
isTokenChar()值为true并且Character.isLetter()值为true时的CharTokenizer类 |
LowerCaseTokenizer |
将所有字符小写化的LetterTokenizer类 |
StandardTokenizer |
复杂的基于语法的语汇单元切分器,用于输出高级类型的语汇单元,如E-Mail地址等(详见4.3.2节)。每个输出的语汇单元标记为一个特殊类型,这些类型中的一部分需要使用StandardFilter类特殊处理 |
TokenFilter |
TokenStream类的输入为为另一个TokenStream对象 |
LowerCaseFilter |
将语汇单元文本转换为小写形式 |
StopFilter |
移除指定集合中的停用词 |
PorterStemFilter |
利用Poter词干提取算法(Poter stemming algorithm)将语汇单元还原为其词干。例如,单词country和countries的词干都是countri |
StandardFilter |
接收一个StandardTokenizer对象作为参数。用于在缩写词中去掉点号或在带有缩写形式的短语中去掉’s(以s结尾单词的缩写形式) |
TokenStream有两个不同的子类:Tokenizer和TokenFilter。
Tokenizer是TokenStream的子类,它将Reader对象中的数据切分成语汇单元。当你索引Field.Text(String,String)域或者Field.UnStored(String,String)域(即被索引域的构造函数接受一个String对象作为其参数时)中的String对象时,Lucene会将构造函数中的String对象包装在StringReader中,以便进行语汇单元的切分。
TokenStream类的第二个子类——TokenFilter,允许你将多个TokenStream对象连接在一起。这个强大机制使它成为了一个名副其实的流过滤器。一个TokenStream对象被传递到TokenFilter中,并在传递过程中,由过滤器对其进行添加、删除或者更改等操作。
内置分词的几个类
内置在Lucene.Net里的分词都被放在项目的Analysis目录下,也就是Lucene.Net.Analysis命名空间下。分词类的命名一般都是以“Analyzer”结束,比如StandardAnalyzer,StopAnalyzer,SimpleAnalyzer等。全部继承自Analyzer类。而它们一般各有一个辅助类,一般以”“Tokenizer”结尾,分词的逻辑大都在辅助类完成。
一下是Lucene.net中主要的几个分词类:
分析器 |
内部操作步骤 |
WhitespaceAnalyzer |
在空格处进行语汇单元的切分 |
SimpleAnalyzer |
在非字母字符处切分文本,并将其转换为小写形式 |
StopAnalyzer |
在非字母字符处切分文本,然后小写化,再移除停用词 |
StandardAnalyzer |
基于复杂的语法来实现语汇单元化;这种语法规则可以识别e-mail地址、首字母缩写词、汉语-日语-汉语字符、字母数字等;小写化;并移除停用词 |
以上分词器的分词效果各不相同,StandardAnalyzer是公认的最有用的内置分析器。它能够将下列类型的词汇准确地转化为语汇单元:字母数字混合编列的词汇、由各单词首字母组成的缩写词、公司名称、e-mail地址、电脑主机名、数字、带撇号的单词、序列号、IP地址以及CJK(汉语、日语、韩语)字符。StandardAnalyzer还可以通过与StopAnalyzer相同的机制(它们使用同一个默认的英语列表,或使用重载的构造函数指定一个用户编写的字符串数组作为停用词表)来移除停用词。
我们通过SimpleAnalyzer的代码,简单的了解下分析器是怎样工作的:
public sealed class SimpleAnalyzer : Analyzer
{
public override TokenStream TokenStream(System.String fieldName, System.IO.TextReader reader)
{
return new LowerCaseTokenizer(reader);
}
public override TokenStream ReusableTokenStream(System.String fieldName, System.IO.TextReader reader)
{
Tokenizer tokenizer = (Tokenizer) GetPreviousTokenStream();
if (tokenizer == null)
{
tokenizer = new LowerCaseTokenizer(reader);
SetPreviousTokenStream(tokenizer);
}
else
tokenizer.Reset(reader);
return tokenizer;
}
}
LowerCaseTokenizer对象依据文本中的非字母字符(通过Character.isLetter()方法进行判断)来切割文本,去掉非字母字符,并且就如它名字所暗示我们的那样,将所有字母转换为小写。TokenStream的作用类似于枚举器,它会连续地返回Token对象,并在到达文本的末尾时返回null。
其他分词算法:
很遗憾的是,Lucene.net内置的几个分词类在实际的开发中很难满足需求,这就需要我们自己设计并实现比较完善的分词算法。这里分享两个牛人的分词算法:
birdshover 老师的二元分词法:http://www.cnblogs.com/birdshover/archive/2008/08/31/1279894.html
eaglet老师的盘古分词:http://www.cnblogs.com/eaglet/archive/2009/08/13/1545420.html
小结:
分词技术远不止我说的这些,Lucene.net中的分词技术很值得大家学习和研究。强烈建议大家下载源码学习一下。《Lucene in Action》一书中有对分析器更详细的讲解,大家可以参看。