分词

来源:互联网 发布:数据统计岗位职责 编辑:程序博客网 时间:2024/03/29 22:44
转载自:http://blog.itpub.net/28624388/viewspace-765691/

   Analyzer通过对文本的分析来建立TokenStreams(分词数据流)。TokenStream是由一个个Token(分词组成的数据流)。所以说Analyzer就代表着一个从文本数据中抽取索引词(Term)的一种策略。TokenStream即是从Document的域(field)中或者查询条件中抽取一个个分词而组成的一个数据流。TokenSteam中是一个个的分词,而每个分词又是由一个个的属性(Attribute)组成。对于所有的分词来说,每个属性只有一个实例。这些属性都保存在AttributeSource中,而AttributeSource正是TokenStream的父类。


TokenStream的工作流程:
   1.实例化TokenStream, 添加属性到AttributeSource,或从AttributeSource中获取属性.
   2.调用reset()方法,设置stream的初始状态
   3.调用increamStoken()方法,来获取下一个分词。这个方法会被docuemnt中的每一个分词调用。所以一个有效的实现对于好的性能来说是至关重要的。
   4.调用end()方法来完成一些收尾工作
   5.调用close()方法来释放stream关联的一些资源。


AttributeSource:
   一个AttributeSource中包含着一个由不同AttributeImpl组成的列表,以及添加和获取它们的一些方法。在同一个AttributeSource实例中 每个属性只有一个单实例。AttributeSource通过AttributeFactory来创建AttributeImpl的实例。通过State来标示每个AttributeImpl的状态。
AttributeImpl类介绍
   1. AttributeImpl:一个可以往attributeSource中添加属性的基类,属性通常用来以动态的,线程安全的方式往一个流的数据源中添加数据,如tokenStream。
   2.CharTermAttributeImpl:保存Token对应的term文本
   3.FlagsAttributeImpl:在Tokenizer链中,用以在不同的节点之间传递标识信息。该类同TypeAttribute有着相似的目录但他们之间还是有所不同的,Flags可以用于不同TokenFilter之间分词(Token)信息的加密。
   4.TypeAttributeImpl:分词的词汇类型,默认值为“word”
   5.KeywordAttributeImpl:该属性用于标识一个分词(token)为关键字。对于TokenStream来说可以用此属性判断分词(Token)是否为关键字来决定是否进行修改,对于TokenFilter来说可以根据分词是否为关键字来进行跳过(skip)处理。
   6.OffsetAttributeImpl:Token分词的起始字符,结束字符偏移量
   7.PositionIncrementAttribute:它表示tokenStream中的当前token与前一个token在实际的原文本中相隔的词语数量

   8.PositionLengthAttributeImpl:Token所占用的位置个数


举例:原文本:I'm a student. these are apples     
TokenSteam: [1:I'm ] [2:a] [3:student] [4:these] [5:are ] [6:apples]
(1) TermAttribute: 表示token的字符串信息。比如"I'm"
(2) TypeAttribute: 表示token的类别信息(在上面讲到)。比如 I'm 就属于<APOSTROPHE>,有撇号的类型
(3) OffsetAttribute:表示token的首字母和尾字母在原文本中的位置。比如 I'm 的位置信息就是(0,3)
(4) PositionIncrementAttribute:这个有点特殊,它表示tokenStream中的当前token与前一个token在实际的原文本中相隔的词语数量。比如: 在tokenStream中[2:a] 的前一个token是[1: I'm ] ,它们在原文本中相隔的词语数是1,则token="a"的PositionIncrementAttribute值为1。如果token是原文本中的第一个词,则默认值为1。因此上面例子的PositionIncrementAttribute结果就全是1了。
   如果我们使用停用词表来进行过滤之后的话:TokenSteam就会变成: [1:I'm ] [2:student] [3:apples]这时student的PositionIncrementAttribute值就不会再是1,而是与[1: I'm ]在原文本中相隔词语数量=2。而apples则变成了3。

   那么这个属性有什么用呢,用处很大的。加入我们想搜索一个短语student apples(假如有这个短语)。很显然,用户是要搜索出student apples紧挨着出现的文档。这个时候我们找到了某一篇文档(比如上面例子的字符串)都含有student apples。但是由于apples的PositionIncrementAttribute值是3,说明肯定没有紧挨着。


介绍几个类:

   Tokenizer:接受Reader字符流,将Reader进行分词操作, extends TokenStream。
   TokenFilter:将分词的语汇单元进行过滤, extends TokenStream。
   TokenStream:分词器处理完毕后得到的一个流,存储了分词的各种信息。
   分词流程:Read -----> Tokenizer -----> TokenFilter.0 -----> … -----> TokenFilter.n -----> TokenStream


几种分词器介绍
   StandardAnalyzer:标准分词器,如果用来处理中文,只是将其分成单个汉字,并不存在任何语义或词性。
   StopAnalyzer:被忽略的词分词器,被忽略的词就是在分词结果中,被丢弃的字符串,如标点、空格等。
   SimpleAnalyzer:简单分词器,一句话就是一个词,遇到标点、空格等,就将其之前的内容当作一个词。
   WhitespaceAnalyzer:空格分词,这个分词技术就相当于按照空格简单的切分字符串。
   mmseg4j: 用Chih-Hao Tsai 的MMSeg算法实现的中文分词器,并实现lucene的analyzer和solr的TokenizerFactory以方便在Lucene和Solr中使用。


由于mmseg4j暂不支持lucene5.0。以下示例均为lucene3.5版本。

public class AnalyzerUtils {public static void displayToken(String str, Analyzer analyzer) {try {TokenStream tokenStream = analyzer.tokenStream("---",new StringReader(str));// 创建一个属性,这个属性会添加流中,随着这个TokenStream增加CharTermAttribute charTerm = tokenStream.addAttribute(CharTermAttribute.class);tokenStream.reset();// 必须先调用reset方法,否则会报java.lang.IllegalStateExceptionwhile (tokenStream.incrementToken()) {System.out.print("" + charTerm + "|");}tokenStream.end();tokenStream.close();System.out.println();} catch (Exception e) {e.printStackTrace();}}public static void displayAllTokenInfo(String str, Analyzer analyzer) {try {TokenStream tokenStream = analyzer.tokenStream("content",new StringReader(str));// 位置增量的属性,存储语汇单元之间的距离,它表示tokenStream中的当前token与前一个token在实际的原文本中相隔的词语数量PositionIncrementAttribute pos = tokenStream.addAttribute(PositionIncrementAttribute.class);// 每个语汇单元的位置偏移量OffsetAttribute offset = tokenStream.addAttribute(OffsetAttribute.class);// 存储每一个语汇单元的信息(分词单元信息),保存Token对应的term文本CharTermAttribute charTerm = tokenStream.addAttribute(CharTermAttribute.class);// 使用的分词器的类型信息,分词的词汇类型,默认值为“word”TypeAttribute type = tokenStream.addAttribute(TypeAttribute.class);tokenStream.reset();for (; tokenStream.incrementToken();) {System.out.print(pos.getPositionIncrement() + ": ");System.out.print("|" + charTerm + "|" + "---offset["+ offset.startOffset() + "-" + offset.endOffset()+ "]---type:" + type.type() + "\n");}} catch (Exception e) {e.printStackTrace();}}}

/** * 自定义停用词过滤分词器 */public class MyStopAnalyzer extends Analyzer {private Set stops;public MyStopAnalyzer(String[] sws) {// 会自动将字符串数组转换为Setstops = StopFilter.makeStopSet(Version.LUCENE_35, sws, true);// 将原有的停用词加入到现在的停用词stops.addAll(StopAnalyzer.ENGLISH_STOP_WORDS_SET);}public TokenStream tokenStream(String str, Reader reader) {// 为这个分词器设定过滤链和Tokenizerreturn new StopFilter(Version.LUCENE_35, new LowerCaseFilter(Version.LUCENE_35, new LetterTokenizer(Version.LUCENE_35,reader)), stops);}}

/** * 自定义同义词过滤分词器 */public class MySameAnalyzer extends Analyzer {@Overridepublic TokenStream tokenStream(String arg0, Reader reader) {// TODO Auto-generated method stubDictionary dic = Dictionary.getInstance("data/");return new MySameTokenFilter(new MMSegTokenizer(new MaxWordSeg(dic),reader));}class MySameTokenFilter extends TokenFilter {private CharTermAttribute charTerm = null;private PositionIncrementAttribute pos = null;private AttributeSource.State current;private Stack<String> sames = null;private Map<String, String[]> map = null;protected MySameTokenFilter(TokenStream input) {super(input);charTerm = this.addAttribute(CharTermAttribute.class);pos = this.addAttribute(PositionIncrementAttribute.class);sames = new Stack<String>();map = new HashMap<String, String[]>();map.put("中国", new String[] { "天朝", "大陆" });}@Overridepublic boolean incrementToken() throws IOException {if (sames.size() > 0) {// 将元素出栈,并且获取这个同义词String str = sames.pop();// 还原状态restoreState(current);charTerm.setEmpty();charTerm.append(str);// 设置位置0pos.setPositionIncrement(0);return true;}if (!this.input.incrementToken())return false;if (addSames(charTerm.toString())) {// 如果有同义词将当前状态先保存current = captureState();}return true;}private boolean addSames(String string) {String[] sws = map.get(string);if (sws != null) {for (String str : sws) {sames.push(str);}return true;}return false;}}}

@Testpublic void test01() {String txt = "this is my blog,我来自湖北,武汉";Analyzer a1 = new StandardAnalyzer(Version.LUCENE_35);Analyzer a2 = new StopAnalyzer(Version.LUCENE_35);Analyzer a3 = new SimpleAnalyzer(Version.LUCENE_35);Analyzer a4 = new WhitespaceAnalyzer(Version.LUCENE_35);System.out.println(AnalyzerUtils.class.getResource("/"));Analyzer a5 = new MMSegAnalyzer(new File("E:/sina_workspace/Lucene/data"));AnalyzerUtils.displayToken(txt, a1);AnalyzerUtils.displayToken(txt, a2);AnalyzerUtils.displayToken(txt, a3);AnalyzerUtils.displayToken(txt, a4);AnalyzerUtils.displayToken(txt, a5);}
结果:

my|blog|我|来|自|湖|北|武|汉|my|blog|我来自湖北|武汉|this|is|my|blog|我来自湖北|武汉|this|is|my|blog,我来自湖北,武汉|this|is|my|blog|我|来自|湖北|武汉|

@Testpublic void test02() {String txt = "this is my blog,我来自湖北,武汉";Analyzer a1 = new StandardAnalyzer(Version.LUCENE_35);Analyzer a2 = new StopAnalyzer(Version.LUCENE_35);Analyzer a3 = new SimpleAnalyzer(Version.LUCENE_35);Analyzer a4 = new WhitespaceAnalyzer(Version.LUCENE_35);System.out.println(AnalyzerUtils.class.getResource("/"));Analyzer a5 = new MMSegAnalyzer(new File("data/"));AnalyzerUtils.displayAllTokenInfo(txt, a1);System.out.println("***************************");AnalyzerUtils.displayAllTokenInfo(txt, a2);System.out.println("***************************");AnalyzerUtils.displayAllTokenInfo(txt, a3);System.out.println("***************************");AnalyzerUtils.displayAllTokenInfo(txt, a4);System.out.println("***************************");AnalyzerUtils.displayAllTokenInfo(txt, a5);}
结果:

3: |my|---offset[8-10]---type:<ALPHANUM>1: |blog|---offset[11-15]---type:<ALPHANUM>1: |我|---offset[16-17]---type:<IDEOGRAPHIC>1: |来|---offset[17-18]---type:<IDEOGRAPHIC>1: |自|---offset[18-19]---type:<IDEOGRAPHIC>1: |湖|---offset[19-20]---type:<IDEOGRAPHIC>1: |北|---offset[20-21]---type:<IDEOGRAPHIC>1: |武|---offset[22-23]---type:<IDEOGRAPHIC>1: |汉|---offset[23-24]---type:<IDEOGRAPHIC>***************************3: |my|---offset[8-10]---type:word1: |blog|---offset[11-15]---type:word1: |我来自湖北|---offset[16-21]---type:word1: |武汉|---offset[22-24]---type:word***************************1: |this|---offset[0-4]---type:word1: |is|---offset[5-7]---type:word1: |my|---offset[8-10]---type:word1: |blog|---offset[11-15]---type:word1: |我来自湖北|---offset[16-21]---type:word1: |武汉|---offset[22-24]---type:word***************************1: |this|---offset[0-4]---type:word1: |is|---offset[5-7]---type:word1: |my|---offset[8-10]---type:word1: |blog,我来自湖北,武汉|---offset[11-24]---type:word***************************1: |this|---offset[0-4]---type:letter1: |is|---offset[5-7]---type:letter1: |my|---offset[8-10]---type:letter1: |blog|---offset[11-15]---type:letter1: |我|---offset[16-17]---type:word1: |来自|---offset[17-19]---type:word1: |湖北|---offset[19-21]---type:word1: |武汉|---offset[22-24]---type:word

@Testpublic void test03() {Analyzer a1 = new MyStopAnalyzer(new String[] { "I", "you" });System.out.println(StopAnalyzer.ENGLISH_STOP_WORDS_SET);// 原停用词// [but, be, with, such, then, for, no, will, not, are, and, their, if,// this, on, into, a, or, there, in, that, they,// was, is, it, an, the, as, at, these, by, to, of]String txt = "how are you thank you, I hate you";AnalyzerUtils.displayToken(txt, a1);}
结果:

[but, be, with, such, then, for, no, will, not, are, and, their, if, this, on, into, a, or, there, in, that, they, was, is, it, an, the, as, at, these, by, to, of]how|thank|hate|


@Testpublic void test04() {try {Analyzer a2 = new MySameAnalyzer();String txt = "this is my blog,我来自中国湖北,武汉";Directory dir = new RAMDirectory();IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(Version.LUCENE_35, a2));Document doc = new Document();doc.add(new Field("content", txt, Field.Store.YES,Field.Index.ANALYZED));writer.addDocument(doc);writer.close();IndexSearcher searcher = new IndexSearcher(IndexReader.open(dir));TopDocs tds = searcher.search(new TermQuery(new Term("content", "天朝")), 10);Document d = searcher.doc(tds.scoreDocs[0].doc);System.out.println(d.get("content"));AnalyzerUtils.displayAllTokenInfo(txt, a2);} catch (CorruptIndexException e) {e.printStackTrace();} catch (LockObtainFailedException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
结果:

this is my blog,我来自中国湖北,武汉----------11: |this|---offset[0-4]---type:letterthis----------11: |is|---offset[5-7]---type:letteris----------11: |my|---offset[8-10]---type:lettermy----------11: |blog|---offset[11-15]---type:letterblog----------11: |我|---offset[16-17]---type:word我----------11: |来自|---offset[17-19]---type:word来自----------11: |中国|---offset[19-21]---type:word0: |大陆|---offset[19-21]---type:word0: |天朝|---offset[19-21]---type:word天朝----------01: |湖北|---offset[21-23]---type:word湖北----------11: |武汉|---offset[24-26]---type:word武汉----------1











0 0