有个经典笑话:护士看到病人在病房喝酒,就走过去小声叮嘱说:小心肝!病人微笑道:小宝贝。在这里,“小心肝!”这句话有歧义,从护士的角度理解是“小心/肝”,在病人的角度理解是“小心肝”。如果使用中文分词切分成“小心/肝”则可以消除这种歧义。 因为中文文本中词和词之间不像英文一样存在边界,所以中文分词是一个专业处理中文信息的搜索引擎首先面对的问题。英语、法语和德语等西方语言通常采用空格或标点符号将词隔开,具有天然的分隔符,所以词的获取比较简单。但是中文、日文和韩文等东方语言,虽然句子之间有分隔符,但词与词之间没有分隔符,所以需要靠程序切分出词。另外,除了可以用于全文查找,中文分词的方法也被应用到英语手写体识别中。因为在识别手写体时,单词之间的空格就不很清楚了。 要解决中文分词准确度的问题,是否提供一个免费版本的分词程序供人下载使用就够了?而像分词这样的自然语言处理领域的问题,很难解决。例如,通用版本的分词也许需要做很多修改后才能用到手机上。所以需要让人能看懂其中的代码与实现原理,并参与到改进的过程中才能更好地应用。 本章的中文分词和下章介绍的文档排重和关键词提取等技术都属于自然语言处理技术的范围。因为在中文信息处理领域,中文分词一直是一个值得专门研究的问题,所以单独作为一章。 4.1 Lucene中的中文分词 Lucene中处理中文的常用方法有三种。以“咬死猎人的狗”这句话的输出结果为例: 单字方式: ; 二元覆盖的方式: ; 分词的方式: 。 Lucene中的StandardTokenizer采用了单字分词的方式。CJKTokenizer采用了二元覆盖的实现方式。笔者开发的CnTokenizer采用了分词的方式,本章将介绍部分实现方法。 4.1.1 Lucene切分原理 Lucene中负责语言处理的部分在org.apache.lucene.analysis包。其中TokenStream类用来进行基本的分词工作,Analyzer类是TokenStream的外围包装类,负责整个解析工作。有人把文本解析比喻成人体的消化过程,输入食物,分解出有用的氨基酸和葡萄糖等。Analyzer类接收的是整段文本,解析出有意义的词语。 通常不需要直接调用分词的处理类analysis,而是由Lucene内部来调用,其中: 在做索引阶段,调用addDocument(doc)时,Lucene内部使用Analyzer来处理每个需要索引的列,如图4-1所示。 IndexWriter index = new IndexWriter(indexDirectory, new CnAnalyzer, //用支持分词的分析器 !incremental, IndexWriter.MaxFieldLength.UNLIMITED); 在搜索阶段,调用QueryParser.parse(queryText)来解析查询串时,QueryParser会调用Analyzer来拆分查询字符串,但是对于通配符等查询不会调用Analyzer。 Analyzer analyzer = new CnAnalyzer; //支持中文的分词 QueryParser parser = new QueryParser(Version.LUCENE_CURRENT,'title', analyzer); 因为在索引和搜索阶段都调用了分词过程,索引和搜索的切分处理要尽量一致,所以分词效果改变后需要重建索引。另外,可以用个速度快的版本,用来在搜索阶段切分用户的查询词,另外用一个准确切分的慢速版本用在索引阶段的分词。 为了测试Lucene的切分效果,是直接调用Analysis的例子: Analyzer analyzer = new CnAnalyzer; //创建一个中文分析器 //取得Token流 TokenStream ts = analyzer.tokenStream('myfield',new StringReader ('待切分文本')); while (ts.incrementToken) {//取得下一个词 System.out.println('token: '+ts)); }
|