zoukankan      html  css  js  c++  java
  • Lucene下引入ICTCLAS进行中文分词的实现方法

    Analysis包分析

    算法和数据结构分析:

    由于Analysis包比较简单,不详述了!

    算法:基于机械分词 1-gram,2-gram,HMM(如果使用ICTCLAS接口的话)

    数据结构:部分源码用到了Set ,HashTable,HashMap

    认真理解Token

    Lucene中的Analysis包专门用于完成对于索引文件的分词.Lucene中的Token是一个非常重要的概念.

    看一下其源码实现:

    public final class Token {

     String termText;                       // the text of the term

     int startOffset;                          // start in source text

     int endOffset;                           // end in source text

     String type = "word";                       // lexical type

     private int positionIncrement = 1;

     public Token(String text, int start, int end)

     public Token(String text, int start, int end, String typ)

     public void setPositionIncrement(int positionIncrement)

     public int getPositionIncrement() { return positionIncrement; }

     public final String termText() { return termText; }

     public final int startOffset() { return startOffset; }

    public void setStartOffset(int givenStartOffset)

     public final int endOffset() { return endOffset; }

    public void setEndOffset(int givenEndOffset)

     public final String type() { return type; }

     public String toString()

     }

    下面编一段代码来看一下

    TestToken.java

    package org.apache.lucene.analysis.test;

    import org.apache.lucene.analysis.*;

    import org.apache.lucene.analysis.standard.StandardAnalyzer;

    import java.io.*;

    public class TestToken

    {

     public static void main(String[] args)

     {

       String string = new String("我爱天大,但我更爱中国");

    //Analyzer analyzer = new StandardAnalyzer();

    Analyzer analyzer = new TjuChineseAnalyzer();

    //Analyzer analyzer= new StopAnalyzer();

        TokenStream ts = analyzer.tokenStream("dummy",new StringReader(string));

        Token token;

        try

        {

          int n=0;

          while ( (token = ts.next()) != null)

          {

            System.out.println((n++)+"->"+token.toString());

          }

        }

        catch(IOException ioe)

        {

          ioe.printStackTrace();

        }

     

     }

    }注意看其结果如下所示

    0->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,0,1,<CJK>,1)

    1->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,1,2,<CJK>,1)

    2->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,2,3,<CJK>,1)

    3->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,3,4,<CJK>,1)

    4->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,5,6,<CJK>,1)

    5->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,6,7,<CJK>,1)

    6->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,7,8,<CJK>,1)

    7->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,8,9,<CJK>,1)

    8->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,9,10,<CJK>,1)

    9->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,10,11,<CJK>,1)

    注意:其中”,”StandardAnalyzer给过滤掉了,所以大家注意第4Token直接startOffset5开始.

    如果改用StopAnalyzer()

    0->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(我爱天大,0,4,word,1)

    1->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(但我更爱中国,5,11,word,1)

    改用TjuChineseAnalyzer(我写的,下文会讲到如何去写)

    0->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,3,4,word,1)

    1->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(天大,6,8,word,1)

    2->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,19,20,word,1)

    3->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,22,23,word,1)

    4->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(中国,25,27,word,1)

    讲明白了Token,咱们来看以下其他的东西

    一个TokenStream是用来走访Tokeniterator(迭代器)

    看一下其源代码:

    public abstract class TokenStream {

     public abstract Token next() throws IOException;

     public void close() throws IOException {}

    }

    一个Tokenizeris-a TokenStream(派生自TokenStream),其输入为Reader

    看一下其源码如下:

    public abstract class Tokenizer extends TokenStream {

     protected Reader input;

     protected Tokenizer() {}

     protected Tokenizer(Reader input) {

        this.input = input;

     }

     public void close() throws IOException {

        input.close();

     }

    }

    一个TokenFilter is–a TokenStream(派生自TokenStream),其义如名就是用来完成对TokenStream的过滤操作,譬如

    StopWords,将Token变为小写等。

    源码如下:

    public abstract class TokenFilter extends TokenStream {

     protected TokenStream input;

     protected TokenFilter() {}

     protected TokenFilter(TokenStream input) {

        this.input = input;

     }

     public void close() throws IOException {

        input.close();

     }

    }

    一个Analyzer就是一个TokenStream工厂

    看一下其源码就:

    public abstract class Analyzer { 

     public TokenStream tokenStream(String fieldName, Reader reader)

     {

            return tokenStream(reader);

     }

     public TokenStream tokenStream(Reader reader)

     {

            return tokenStream(null, reader);

     }

    }

    好,现在咱们来看一下LuceneAnalysis包下面的各个类文件都是用来干什么的。按照字典排序。

    Analysis包中的源码详解

    Analyzer.java 上文已经讲过。

    CharTokenizer.java 此类为简单一个抽象类,用来对基于字符的进行简单分词(tokenizer

    LetterTokenizer.java两个非字符之间的字符串定义为token(举例来说英文单词由空白隔开,那个两个空白之间的字符串即被定义为一个token。备注:对于绝大多数欧洲语言来说,这个类工作效能很好。当时对于不用空白符分割的亚洲语言,效能极差(譬如中日韩)。)

    LowerCaseFilter.java is-a TokenFilter用于将字母小写化

    LowerCaseTokenizer is-a Tokenizer功能上等价于LetterTokenizerLowerCaseFilter

    PerFieldAnalyzerWrapper是一个Analyzer,因为继承自Analyzer当不同的域(Field)需要不同的语言分析器(Analyzer)时,这个Analyzer就派上了用场。使用成员函数addAnalyzer可以增加一个非缺省的基于某个Fieldanalyzer。很少使用。

    PorterStemFilter.java使用词干抽取算法对每一个token流进行词干抽取。

    PorterStemmer.java 有名的P-stemming算法

    SimpleAnalyzer.java

    StopAnalyzer.java   具有过滤停用词的功能

    StopFilter.java     StopFilter为一个Filter,主要用于从token流中去除StopWords

    Token.java       上面已讲.

    TokenFilter.java   上面已经讲了

    Tokenizer.java     上面已经讲了

    TokenStream.java   上面已经讲了

    WhitespaceAnalyzer.java

    WhitespaceTokenizer.java 只是按照space区分Token.

     

    由于Luceneanalyisis包下的Standard包下的StandardAnalyzer()功能很强大,而且支持CJK分词,我们简要说一下.

    此包下的文件是有StandardTokenizer.jj经过javac命令生成的.由于是机器自动生成的代码,可能可读性很差,想了解的话好好看看那个StandardTokenizer.jj文件就会比较明了了.

    Lucene常用的Analyzer功能概述.

    WhitespaceAnalyzer:仅仅是去除空格,对字符没有lowcase,不支持中文

    SimpleAnalyzer:功能强于WhitespaceAnalyzer,将除去letter之外的符号全部过滤掉,并且将所有的字符lowcase,不支持中文

    StopAnalyzer:StopAnalyzer的功能超越了SimpleAnalyzer,在SimpleAnalyzer的基础上
       
    增加了去除StopWords的功能,不支持中文

    StandardAnalyzer:英文的处理能力同于StopAnalyzer.支持中文采用的方法为单字切分.

    ChineseAnalyzer:来自于Lucenesand box.性能类似于StandardAnalyzer,缺点是不支持中英文混和分词.

    CJKAnalyzer:chedong写的CJKAnalyzer的功能在英文处理上的功能和StandardAnalyzer相同
       
    但是在汉语的分词上,不能过滤掉标点符号,即使用二元切分

    TjuChineseAnalyzer:我写的,功能最为强大.TjuChineseAnlyzer的功能相当强大,在中文分词方面由于其调用的为ICTCLASjava接口.所以其在中文方面性能上同与ICTCLAS.其在英文分词上采用了LuceneStopAnalyzer,可以去除 stopWords,而且可以不区分大小写,过滤掉各类标点符号.

    各个Analyzer的功能已经比较介绍完毕了,现在咱们应该学写Analyzer,如何diy自己的analyzer??

    如何DIY一个Analyzer

    咱们写一个Analyzer,要求有一下功能

    (1)    可以处理中文和英文,对于中文实现的是单字切分,对于英文实现的是以空格切分.

    (2)    对于英文部分要进行小写化.

    (3)    具有过滤功能,可以人工设定StopWords列表.如果不是人工设定,系统会给出默认的StopWords列表.

    (4)    使用P-stemming算法对于英文部分进行词缀处理.

    代码如下:

    public final class DiyAnalyzer

        extends Analyzer

    {

     private Set stopWords;

     public static final String[] CHINESE_ENGLISH_STOP_WORDS =

          {

          "a", "an", "and", "are", "as", "at", "be", "but", "by",

          "for", "if", "in", "into", "is", "it",

          "no", "not", "of", "on", "or", "s", "such",

          "t", "that", "the", "their", "then", "there", "these",

          "they", "this", "to", "was", "will", "with",

          "", "我们"

     };

     public DiyAnalyzer()

     {

        this.stopWords=StopFilter.makeStopSet(CHINESE_ENGLISH_STOP_WORDS);

     }

     

     public DiyAnalyzer(String[] stopWordList)

     {

        this.stopWords=StopFilter.makeStopSet(stopWordList);

     }

     

     public TokenStream tokenStream(String fieldName, Reader reader)

     {

        TokenStream result = new StandardTokenizer(reader);

        result = new LowerCaseFilter(result);

     

        result = new StopFilter(result, stopWords);

        result = new PorterStemFilter(result);

        return result;

     }

     

     public static void main(String[] args)

     {

        //好像英文的结束符号标点.,StandardAnalyzer不能识别

        String string = new String("我爱中国,我爱天津大学!I love China!Tianjin is a City");

        Analyzer analyzer = new DiyAnalyzer();

        TokenStream ts = analyzer.tokenStream("dummy", new StringReader(string));

        Token token;

        try

        {

          while ( (token = ts.next()) != null)

          {

            System.out.println(token.toString());

          }

        }

        catch (IOException ioe)

        {

          ioe.printStackTrace();

        }

     }

    }

    可以看见其后的结果如下:

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,1,2,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,2,3,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,3,4,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,6,7,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,7,8,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,8,9,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,9,10,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,10,11,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(i,12,13,<ALPHANUM>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(love,14,18,<ALPHANUM>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(china,19,24,<ALPHANUM>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(tianjin,25,32,<ALPHANUM>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(citi,39,43,<ALPHANUM>,1)

     

    到此为止这个简单的但是功能强大的分词器就写完了,下面咱们可以尝试写一个功能更强大的分词器.

    如何DIY一个功能更加强大Analyzer

    譬如你有词典,然后你根据正向最大匹配法或者逆向最大匹配法写了一个分词方法,却想在Lucene中应用,很简单

    你只要把他们包装成LuceneTokenStream就好了.下边我以调用中科院写的ICTCLAS接口为例,进行演示.你去中科院

    网站可以拿到此接口的free版本,谁叫你没钱呢,有钱,你就可以购买了.哈哈

    ,由于ICTCLAS进行分词之后,Java,中间会以两个空格隔开!too easy,我们直接使用继承Lucene

    WhiteSpaceTokenizer就好了.

    所以TjuChineseTokenizer 看起来像是这样.

    public class TjuChineseTokenizer extends WhitespaceTokenizer

    {

     public TjuChineseTokenizer(Reader readerInput)

     {

        super(readerInput);

     }

    }

    TjuChineseAnalyzer看起来象是这样

    public final class TjuChineseAnalyzer

        extends Analyzer

    {

     private Set stopWords;

     

     /** An array containing some common English words that are not usually useful

        for searching. */

     /*

         public static final String[] CHINESE_ENGLISH_STOP_WORDS =

          {

          "a", "an", "and", "are", "as", "at", "be", "but", "by",

          "for", "if", "in", "into", "is", "it",

          "no", "not", "of", "on", "or", "s", "such",

          "t", "that", "the", "their", "then", "there", "these",

          "they", "this", "to", "was", "will", "with",

          "", "我们"

         };

       */

     /** Builds an analyzer which removes words in ENGLISH_STOP_WORDS. */

     public TjuChineseAnalyzer()

     {

        stopWords = StopFilter.makeStopSet(StopWords.SMART_CHINESE_ENGLISH_STOP_WORDS);

     }

     

     /** Builds an analyzer which removes words in the provided array. */

     //提供独自的stopwords

     public TjuChineseAnalyzer(String[] stopWords)

     {

        this.stopWords = StopFilter.makeStopSet(stopWords);

     }

     

     /** Filters LowerCaseTokenizer with StopFilter. */

     public TokenStream tokenStream(String fieldName, Reader reader)

     {

        try

        {

          ICTCLAS splitWord = new ICTCLAS();

          String inputString = FileIO.readerToString(reader);

          //分词中间加入了空格

          String resultString = splitWord.paragraphProcess(inputString);

          System.out.println(resultString);

          TokenStream result = new TjuChineseTokenizer(new StringReader(resultString));

     

          result = new LowerCaseFilter(result);

          //使用stopWords进行过滤

         result = new StopFilter(result, stopWords);

          //使用p-stemming算法进行过滤

         result = new PorterStemFilter(result);

          return result;

     

        }

        catch (IOException e)

        {

          System.out.println("转换出错");

          return null;

        }

     }

     

     public static void main(String[] args)

     {

        String string = "我爱中国人民";

        Analyzer analyzer = new TjuChineseAnalyzer();

        TokenStream ts = analyzer.tokenStream("dummy", new StringReader(string));

        Token token;

        System.out.println("Tokens:");

        try

        {

          int n=0;

          while ( (token = ts.next()) != null)

          {

            System.out.println((n++)+"->"+token.toString());

          }

        }

        catch (IOException ioe)

        {

         ioe.printStackTrace();

        }

     }

    }

      1public class TjuChineseTokenizer extends WhitespaceTokenizer
      2
      3{
      4
      5 public TjuChineseTokenizer(Reader readerInput)
      6
      7 {
      8
      9    super(readerInput);
     10
     11 }

     12
     13}

     14
     15而TjuChineseAnalyzer看起来象是这样
     16
     17public final class TjuChineseAnalyzer
     18
     19    extends Analyzer
     20
     21{
     22
     23 private Set stopWords;
     24
     25
     26 
     27
     28 /** An array containing some common English words that are not usually useful
     29
     30    for searching. */

     31
     32 /*
     33
     34     public static final String[] CHINESE_ENGLISH_STOP_WORDS =
     35
     36      {
     37
     38      "a", "an", "and", "are", "as", "at", "be", "but", "by",
     39
     40      "for", "if", "in", "into", "is", "it",
     41
     42      "no", "not", "of", "on", "or", "s", "such",
     43
     44      "t", "that", "the", "their", "then", "there", "these",
     45
     46      "they", "this", "to", "was", "will", "with",
     47
     48      "我", "我们"
     49
     50     };
     51
     52   */

     53
     54 /** Builds an analyzer which removes words in ENGLISH_STOP_WORDS. */
     55
     56 public TjuChineseAnalyzer()
     57
     58 {
     59
     60    stopWords = StopFilter.makeStopSet(StopWords.SMART_CHINESE_ENGLISH_STOP_WORDS);
     61
     62 }

     63
     64
     65 
     66
     67 /** Builds an analyzer which removes words in the provided array. */
     68
     69 //提供独自的stopwords
     70
     71 public TjuChineseAnalyzer(String[] stopWords)
     72
     73 {
     74
     75    this.stopWords = StopFilter.makeStopSet(stopWords);
     76
     77 }

     78
     79
     80 
     81
     82 /** Filters LowerCaseTokenizer with StopFilter. */
     83
     84 public TokenStream tokenStream(String fieldName, Reader reader)
     85
     86 {
     87
     88    try
     89
     90    {
     91
     92      ICTCLAS splitWord = new ICTCLAS();
     93
     94      String inputString = FileIO.readerToString(reader);
     95
     96      //分词中间加入了空格
     97
     98      String resultString = splitWord.paragraphProcess(inputString);
     99
    100      System.out.println(resultString);
    101
    102      TokenStream result = new TjuChineseTokenizer(new StringReader(resultString));
    103
    104
    105 
    106
    107      result = new LowerCaseFilter(result);
    108
    109      //使用stopWords进行过滤
    110
    111     result = new StopFilter(result, stopWords);
    112
    113      //使用p-stemming算法进行过滤
    114
    115     result = new PorterStemFilter(result);
    116
    117      return result;
    118
    119
    120 
    121
    122    }

    123
    124    catch (IOException e)
    125
    126    {
    127
    128      System.out.println("转换出错");
    129
    130      return null;
    131
    132    }

    133
    134 }

    135
    136
    137 
    138
    139 public static void main(String[] args)
    140
    141 {
    142
    143    String string = "我爱中国人民";
    144
    145    Analyzer analyzer = new TjuChineseAnalyzer();
    146
    147    TokenStream ts = analyzer.tokenStream("dummy"new StringReader(string));
    148
    149    Token token;
    150
    151    System.out.println("Tokens:");
    152
    153    try
    154
    155    {
    156
    157      int n=0;
    158
    159      while ( (token = ts.next()) != null)
    160
    161      {
    162
    163        System.out.println((n++)+"->"+token.toString());
    164
    165      }

    166
    167    }

    168
    169    catch (IOException ioe)
    170
    171    {
    172
    173     ioe.printStackTrace();
    174
    175    }

    176
    177 }

    178
    179}

    180


    对于此程序的输出接口可以看一下

    0->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,3,4,word,1)

    1->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(中国,6,8,word,1)

    2->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(人民,10,12,word,1)

     

    OK,经过这样一番讲解,你已经对LuceneAnalysis包认识的比较好了,当然如果你想更加了解,还是认真读读源码才好,

    呵呵,源码说明一切!

  • 相关阅读:
    JQUERY1.9学习笔记 之基本过滤器(八) 最后元素选择器
    解决接收参数乱码,tomcat的URIEncoding=UTF-8
    mac下配置maven
    [forwarding] Struts2中action接收中文参数为乱码解决方法
    [forwarding]软考复习之软件架构风格
    [forwarding]Write operations are not allowed in read-only mode 只读模式下(FlushMode.NEVER/MANUAL)写操作不允许问题
    [forwarding]详解Linux命令行下常用svn命令
    [forwarding]mysql用户授权
    【转】Hibernate 查询语言Query Language(HQL)
    java.lang.NoSuchMethodError: antlr.collections.AST.getLine()I错误
  • 原文地址:https://www.cnblogs.com/cy163/p/1215499.html
Copyright © 2011-2022 走看看