美文网首页
lucene分词

lucene分词

作者: 尚亦汐 | 来源:发表于2016-07-21 13:19 被阅读0次

比较不同分词器的分词结果:

  • CJKAnalyzer二元覆盖的方式分词
Analyzer analyzer=new CJKAnalyzer();
       TokenStream tokenStream=analyzer.tokenStream("myfiled", new StringReader("待切分文本"));
       tokenStream.reset();
       while(tokenStream.incrementToken()){
           //取得下一个分词
           System.out.println("token:"+tokenStream);
       }
       analyzer.close();

结果:

  • SmartChineseAnalyzer
Analyzer analyzer=new SmartChineseAnalyzer();
        TokenStream tokenStream=analyzer.tokenStream("myfiled", new StringReader("待切分文本"));
        tokenStream.reset();
        while(tokenStream.incrementToken()){
            //取得下一个分词
            System.out.println("token:"+tokenStream);
        }
        analyzer.close();

结果:

  • StandardAnalyzer单字切分
Analyzer analyzer=new StandardAnalyzer();
        TokenStream tokenStream=analyzer.tokenStream("myfiled", new StringReader("待切分文本"));
        tokenStream.reset();
        while(tokenStream.incrementToken()){
            //取得下一个分词
            System.out.println("token:"+tokenStream);
        }
        analyzer.close();

结果:

自己动手写Analyzer

由于6.1.0版本相比于以前有很多改动,参照[1]中p148的例子,以及结合lucene6.1.0的文档,写一个简单的分词器例子。
  文档里面说,构建一个自己的分词器是非常简单的(I doubt that!),自己构建的分词器要继承Analyzer类,并且可以用现存的analysis components——CharFilter(可选),一个Tokenizer,以及TokenFilter(可选)——或者使用自己构建的组建,或者是混合来用。

  • 一个Whitespace tokenization的例子
      参照文档里面的一个例子(例子里面还用到了Version,但是在6.1.0里面似乎都已经摒弃这个了,所以就直接将其删掉):
import java.io.IOException;
import java.io.StringReader;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.core.WhitespaceTokenizer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;

public class MyAnalyzer extends Analyzer {
 
   public MyAnalyzer(){}
 
   @Override
   protected TokenStreamComponents createComponents(String fieldName) {
     return new TokenStreamComponents(new WhitespaceTokenizer());
   }
   
   public static void main(String[] args) throws IOException {
     // text to tokenize
     final String text = "This is a demo of the TokenStream API";
     
     MyAnalyzer analyzer = new MyAnalyzer();
     TokenStream stream 
             = analyzer.tokenStream("field", new StringReader(text));
     
     // get the CharTermAttribute from the TokenStream
     CharTermAttribute termAtt 
             = stream.addAttribute(CharTermAttribute.class);
 
     try {
       stream.reset();
     
       // print all tokens until stream is exhausted
       while (stream.incrementToken()) {
         System.out.println(termAtt.toString());
       }
     
       stream.end();
     } finally {
       stream.close();
     }
   }
}

MyAnalyzer类是Analyzer的子类,实现了createComponents方法,然后在主方法中,对stream进行循环,通过WhitesSpaceTokenizer中提供的CharTermAttirbute,打印出token中的term text
(这段话原文:
   In main() a loop consumes the stream and prints the term text of the tokens by accessing the CharTermAttribute that the WhitespaceTokenizer provides.)。
  Tokenizer的子类需要重写incrementToken方法,通过incrementToken方法遍历Tokenizer分析出的词,当还有词可以获取时,返回true;已经遍历到结尾时,返回false。
  上面应该是基于属性的方法(CharTermAttribute),将无用的词特征和想要的词特征分隔开。每个TokenStream在构造时,增加它想要的属性。在TokenStream的整个生命周期中都保留一个属性的引用。这样在获取所有和TokenStream实例相关的属性时,可以保证属性的类型安全。
  上面代码得到的结果是:


  1. 添加LengthFilter:
      如果我们需要去除长度小于等于2的tokens,我们可以通过添加LengthFilter来实现,只需要对createComponets()方法做一些改动:
 protected TokenStreamComponents createComponents(String fieldName) {
     final Tokenizer source=new WhitespaceTokenizer();
     TokenStream result=new LengthFilter(source, 3,Integer.MAX_VALUE);
     return new TokenStreamComponents(source,result);
}

结果如下:

看一下LengthFilter类的源码:

public final class LengthFilter extends FilteringTokenFilter {

private final int min;
private final int max;

private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);

public LengthFilter(TokenStream in, int min, int max) {
  super(in);
  if (min < 0) {
    throw new IllegalArgumentException("minimum length must be greater than or equal to zero");
  }
  if (min > max) {
    throw new IllegalArgumentException("maximum length must not be greater than minimum length");
  }
  this.min = min;
  this.max = max;
}

@Override
public boolean accept() {
  final int len = termAtt.length();
  return (len >= min && len <= max);
}
}

可以看到在LengthFilter类里面,CharTermAttribute被添加以及存储到termAtt实例中,因为只能存在一个CharTermAtribute的实例(in the chain,这里的chain应该是说TokenStream的生命周期中),所以例子中的addAttribute()方法引用的就是LengthFilter返回的已经存在的CharTermAttribute。
  通过查看在CharTermAttribute中的term text,去除掉过长或者过短的tokens。(CharTermAttribute就是对应Token中的词)
  
添加custom Attribute(自己定制一个Attribute)
  定义一个part-of-speech tagging(词性标注)的Attribute,名为PartOfSpeechAttribute,首先需要为这个Attribute定义接口:

import org.apache.lucene.util.Attribute;

public interface PartOfSpeechAttribute extends Attribute {
   public static enum PartOfSpeech {
     Noun, Verb, Adjective, Adverb, Pronoun, Preposition, Conjunction, Article, Unknown
   }
 
   public void setPartOfSpeech(PartOfSpeech pos);
 
   public PartOfSpeech getPartOfSpeech();
 }

然后写一个实现类,值得注意的是,在Lucene中,会默认检查一个Attribute的名字是否有后缀Impl,所以我们在这里实现类的名字为PartOfSpeechAttributeImpl。
  当然也可以实现AttributeFactory,这个工厂类接收Atrribute的接口作为参数,然后返回一个实例。

import org.apache.lucene.util.AttributeImpl;
import org.apache.lucene.util.AttributeReflector;

public final class PartOfSpeechAttributeImpl extends AttributeImpl implements PartOfSpeechAttribute{
    private PartOfSpeech pos=PartOfSpeech.Unknown;
    @Override
    public void setPartOfSpeech(PartOfSpeech pos) {
        this.pos=pos;
    }
    @Override
    public PartOfSpeech getPartOfSpeech() {
        return pos;
    }

    @Override
    public void clear() {
        pos=PartOfSpeech.Unknown;
    }

    @Override
    public void reflectWith(AttributeReflector reflector) {
    }

    @Override
    public void copyTo(AttributeImpl target) {
        ((PartOfSpeechAttribute)target).setPartOfSpeech(pos);
    }

}

上面这个类只存在一个变量,用来存储词性的token,它继承了AttributeImpl类并实现了里面的抽象方法。现在我们需要一个TokenFilter(Token过滤器),在这个例子中,我们设置一个很简单的filter:如果一个单词的首字母是大写,则标记为‘Noun’,其他标记为‘Unknown’.

import java.io.IOException;

import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;


public class PartOfSpeechTaggingFilter extends TokenFilter {
     PartOfSpeechAttribute posAtt 
          = addAttribute(PartOfSpeechAttribute.class);
     CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
 
     
     protected PartOfSpeechTaggingFilter(TokenStream input) {
       super(input);
     }
     
     public boolean incrementToken() throws IOException {
       if (!input.incrementToken()) {return false;}
       posAtt.setPartOfSpeech(
             determinePOS(termAtt.buffer(), 0, termAtt.length()));
       return true;
     }
     
     // determine the part of speech for the given term
     protected PartOfSpeechAttribute.PartOfSpeech 
               determinePOS(char[] term, int offset, int length) {
       // naive implementation that tags every uppercased word as noun
       if (length > 0 && Character.isUpperCase(term[0])) {
         return PartOfSpeechAttribute.PartOfSpeech.Noun;
       }
       return PartOfSpeechAttribute.PartOfSpeech.Unknown;
     }
   }

下面将这个filter运用到the chain in MyAnalyzer,同样是修改createComponents()方法:

   protected TokenStreamComponents createComponents(String fieldName) {
       final Tokenizer source=new WhitespaceTokenizer();
       TokenStream result=new LengthFilter(source, 3,Integer.MAX_VALUE);
       result=new PartOfSpeechTaggingFilter(result);
       return new TokenStreamComponents(source,result);
   }

得到的结果如下:
  


似乎跟之前相比没有改变。这表明了在TokenStream/Filter chain添加一个定制的attribute不会影响已经存在的consumers(TokenStream是生产者,产生Token,生成词索引程序的是消费者,调用TokenStream的increamentToken()方法得到一个Token),这是因为他们并不知道新的Attribute。现在需要让consumer来运用PartOfSpeechAttribute来打印:

  public static void main(String[] args) throws IOException {
     // text to tokenize
     final String text = "This is a demo of the TokenStream API";
     
     MyAnalyzer analyzer = new MyAnalyzer();
     TokenStream stream 
          = analyzer.tokenStream("field", new StringReader(text));
     
     // get the CharTermAttribute from the TokenStream
     CharTermAttribute termAtt 
          = stream.addAttribute(CharTermAttribute.class);
     
     //get the PartOfSpeechAttribute from TokenStream
     PartOfSpeechAttribute posAtt 
          = stream.addAttribute(PartOfSpeechAttribute.class);
     try {
       stream.reset();
     
       // print all tokens until stream is exhausted
       while (stream.incrementToken()) {
         System.out.println(termAtt.toString()+":"
                              +posAtt.getPartOfSpeech());
       }
     
       stream.end();
     } finally {
       stream.close();
     }
   }

得到的结果如下:


每个词都被标注上了PartOfSpeech的标签。
  
参考文献:
[1]罗刚. 解密搜索引擎技术实战--LUCENE & JAVA精华版(第3版)[M]. 电子工业出版社, 2016.

相关文章

  • Lucene分词

    1. 概念 所有传递给Lucene进行索引的文本都需要经历一个过程----分词,即:将文本分割为一个个的足够小的...

  • lucene分词

    比较不同分词器的分词结果: CJKAnalyzer二元覆盖的方式分词 结果: SmartChineseAnalyz...

  • Lucene中文分词

    中文分词算法现在一般分为三类:基于字符串匹配,基于理解,基于统计的分词。 基于字符串匹配分词:机械分词算法,这里我...

  • lucene分词(一)

    词汇单元流 lucene的分词过程,是从Reader中获取原始字符流,产生语汇单元流TokenStream的过程。...

  • lucene中文分词

    IK中文分词 DoubleArrayTrie的AC自动机

  • Lucene总结

    1 Lucene基础2 Lucene建索引和搜索3 域选项4 各种Query5 Lucene分词器6 近实时索引7...

  • Lucene介绍、分词详解

    Lucene介绍 1、Lucene简介 最受欢迎的java开源全文搜索引擎开发工具包。提供了完整的查询引擎和索引引...

  • IKAnalyzer集成

    IKAnalyzer 开源、轻量级的中文分词器,应用广 最想是作为lucene上使用而开发,后来发展为独立的分词组...

  • Linux下ElasticSearch及IK分词插件安装

    ElasticSearch及IK分词插件相关安装 一. 简介 ElasticSearch是一个基于Lucene的搜...

  • 03_Lucene学习笔记

    1. Lucene入门程序 2. Field 域 2.1 Field属性 是否分词(tokenized)是,将fi...

网友评论

      本文标题:lucene分词

      本文链接:https://www.haomeiwen.com/subject/tcebjttx.html