基于正向最大匹配法的Lucene分词器

来源:互联网 发布:阿里云ssh连接不上 编辑:程序博客网 时间:2024/05/22 09:06

fromhttp://www.lucene.org.cn/read.php?tid=49(Lucene 中文论坛 tidy)

 

Apache Lucene作为一个开放源码的搜索软件包应用越来越广泛,但是对于中文用户来说其提供的两个中文分词器(CJKAnalyzer、ChineseAnalyzer)的功能又太弱了。所以迫切需要开发自己的中文分词器,而开发适用的分词器是一项很有挑战的工作。我想在文章中实现一个中文分词器,让它实现机械分词中最简单的算法--正向最大匹配法。目前普遍认为这一算法的错分率为1/169,虽然这不是一个精确的分词算法,但是它的实现却很简单。我想它已经可以满足一些项目的应用了。这一算法是依赖于词库的,词库的好坏对于错分率有重要影响,因此还想介绍一个词库。
solo L

关键词:apache jakarta,java,lucene,Analyzer,ChineseAnalyzer,CJKAnalyzer,全文检索,索引,分词,分词器,中文分词,机械分词法,正向最大匹配,搜索,搜索引擎,专业搜索,词库
发布日期:2006年07月10日   更新日期:2006年07月30日
什么是中文分词
众所周知,英文是以词为单位的,词和词之间是靠空格隔开,而中文是以字为单位,句子中所有的字连起来才能描述一个意思。例如,英文句子I am a student,用中文则为:“我是一个学生”。计算机可以很简单通过空格知道student是一个单词,但是不能很容易明白“学”、“生”两个字合起来才表示一个词。把中文的汉字序列切分成有意义的词,就是中文分词,有些人也称为切词。我是一个学生,分词的结果是:我 是 一个 学生。

中文分词技术
现有的分词技术可分为三类:

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

正向最大匹配法(由左到右的方向)
逆向最大匹配法(由右到左的方向)
分词器实现
这个实现了机械分词中正向最大匹配法的Lucene分词器包括两个类,CJKAnalyzer和CJKTokenizer,他们的源代码如下:

package org.solol.analysis;

import java.io.Reader;
import java.util.Set;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.StopFilter;
import org.apache.lucene.analysis.TokenStream;

/**
* @author solo L
*
*/
public class CJKAnalyzer extends Analyzer {//实现了Analyzer接口,这是lucene的要求
   public final static String[] STOP_WORDS = {};
   
   private Set stopTable;    

   public CJKAnalyzer() {
       stopTable = StopFilter.makeStopSet(STOP_WORDS);
   }

   @Override
   public TokenStream tokenStream(String fieldName, Reader reader) {
       return new StopFilter(new CJKTokenizer(reader), stopTable);
   }    
}    

package org.solol.analysis;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.TreeMap;

import org.apache.lucene.analysis.Token;
import org.apache.lucene.analysis.Tokenizer;

/**
* @author solo L
*
*/
public class CJKTokenizer extends Tokenizer {
   //这个TreeMap用来缓存词库
   private static TreeMap simWords = null;

   private static final int IO_BUFFER_SIZE = 256;

   private int bufferIndex = 0;

   private int dataLen = 0;

   private final char[] ioBuffer = new char[IO_BUFFER_SIZE];

   private String tokenType = "word";

   public CJKTokenizer(Reader input) {
       this.input = input;
   }

   //这里是lucene分词器实现的最关键的地方
   public Token next() throws IOException {
       loadWords();

       StringBuffer currentWord = new StringBuffer();

       while (true) {
           char c;
           Character.UnicodeBlock ub;

           if (bufferIndex >= dataLen) {
               dataLen = input.read(ioBuffer);
               bufferIndex = 0;
           }

           if (dataLen == -1) {
               if (currentWord.length() == 0) {
                   return null;
               } else {
                   break;
               }
           } else {
               c = ioBuffer[bufferIndex++];                
               ub = Character.UnicodeBlock.of(c);
           }
           //通过这个条件不难看出这里只处理了CJK_UNIFIED_IDEOGRAPHS,
           //因此会丢掉其它的字符,如它会丢掉LATIN字符和数字
           //这也是该lucene分词器的一个限制,您可以在此基础之上完善它,
           //也很欢迎把您完善的结果反馈给我
           if (Character.isLetter(c)
                   && ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS) {
               tokenType = "double";
               if (currentWord.length() == 0) {
                   currentWord.append(c);                    
               } else {
                   //这里实现了正向最大匹配法
                   String temp = (currentWord.toString() + c).intern();
                   if (simWords.containsKey(temp)) {
                       currentWord.append(c);                        
                   } else {
                       bufferIndex--;
                       break;
                   }
               }
           }
       }
       Token token = new Token(currentWord.toString(), bufferIndex
               - currentWord.length(), bufferIndex, tokenType);
       currentWord.setLength(0);
       return token;
   }
   //装载词库,您必须明白它的逻辑和之所以这样做的目的,这样您才能理解正向最大匹配法是如何实现的
   public void loadWords() {
       if (simWords != null)return;
       simWords = new TreeMap();

       try {
           InputStream words = new FileInputStream("simchinese.txt");
           BufferedReader in = new BufferedReader(new InputStreamReader(words,"UTF-8"));
           String word = null;

           while ((word = in.readLine()) != null) {
               //#使得我们可以在词库中进行必要的注释
               if ((word.indexOf("#") == -1) && (word.length() < 5)) {
                   simWords.put(word.intern(), "1");
                   if (word.length() == 3) {
                       if (!simWords.containsKey(word.substring(0, 2).intern())) {
                           simWords.put(word.substring(0, 2).intern(), "2");
                       }
                   }
                   if (word.length() == 4) {
                       if (!simWords.containsKey(word.substring(0, 2).intern())) {
                           simWords.put(word.substring(0, 2).intern(), "2");
                       }
                       if (!simWords.containsKey(word.substring(0, 3).intern())) {
                           simWords.put(word.substring(0, 3).intern(), "2");
                       }

                   }
               }
           }
           in.close();
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
}

词库
这里使用的词库来源于网络,库中约有12万词,存储在simchinese.txt这个文本文件中,格式为每词一行,可以用#进行注释。您可以从参考资料中下载到它。

词库的好坏直接影响分词的效果,因此对词库的维护也是很重要的任务,有好多工作可以做。

分词效果
这是我在当日的某新闻搞中随意选的一段话:
此外,巴黎市政府所在地和巴黎两座体育场会挂出写有相同话语的巨幅标语,这两座体育场还安装了巨大屏幕,以方便巴黎市民和游客观看决赛。

分词结果为:
此外 巴黎 市政府 所在地 和 巴黎 两座 体育场 会 挂出 写有 相同 话语 的 巨幅 标语 这 两座 体育场 还 安装 了 巨大 屏幕 以 方便 巴黎 市民 和 游客 观看 决赛

提示
这个lucene分词器还比较脆弱,要想将其用于某类项目中您还需要做一些工作,不过我想这里的lucene分词器会成为您很好的起点。

关于作者

solo L,solo L是一个充满了热情的软件开发者。从2001年接触Java开始就一直没有停止过使用她。在日常的项目开发中经常使用开源软件包。

参考资料

Apache Lucene
http://lucene.apache.org/
下载文章中用到的词库
http://www.solol.org/technologic/java/j-lucene/simchinese.zip

原创粉丝点击