ITKeyword,专注技术干货聚合推荐

注册 | 登录

《Lucene In Action》第四章.Analysis(分词)

duck_genuine 分享于 2010-12-08

2020腾讯云共同战“疫”,助力行业复工(优惠前所未有!),
地址https://cloud.tencent.com/act/cps/redirect?redirect=1053

2020阿里云最低价产品入口,含代金券(新老用户有优惠),
地址https://www.aliyun.com/minisite/goods

原文http://www.coder4.com/archives/761 简单来说,Analysis就是把field Text转化成基本的Term的形式。 通过分词,将Text转化为Token,Token+对应的Field即为Term。 分词的处理包括:萃取、丢弃标点、移除发音、小写、移除常用单词、去除变形(去掉过去时等)等。 本章将介绍如何使用内置的分词器,以及如何根据语言、环境等特点创建自己的分词器。 4.1

使用Analysis 分词用于所有需要将Text转化成Term的场合,在Lucene中主要有两个: 1、Index(索引) 2、使用QueryParser的时候。 首先从一个简单例子看各内置分词器的效果: 例子1 对 ”The quick brown fox jumped over the lazy dogs” 进行分词的结果: WhitespaceAnalyzer : [The] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs] SimpleAnalyzer : [the] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs] StopAnalyzer : [quick] [brown] [fox] [jumped] [over] [lazy] [dogs] StandardAnalyzer: [quick] [brown] [fox] [jumped] [over] [lazy] [dogs] 只有被分词处理后的Term才能被检索到。 例子2 对 ”XY&Z Corporation – xyz@example.com ” 进行分词 WhitespaceAnalyzer: [XY&Z] [Corporation] [-] [xyz@example.com] SimpleAnalyzer: [xy] [z] [corporation] [xyz] [example] [com] StopAnalyzer: [xy] [z] [corporation] [xyz] [example] [com] StandardAnalyzer: [xy&z] [corporation] [xyz@example.com] 各个内置分词器的简介 WhitespaceAnalyzer:仅仅按照空白分隔开 SimpleAnalyzer:在非字母处分隔开,并小写化,它将丢弃所有数字。 StopAnalyzer:与SimpleAnalyser相同,除此之外,去除英文中所有的stop words(如a the等),可以自己指定这个集合。 StandardAnalyzer:较为高级的一个,能识别公司名字、E-mail地址、主机名等同时小写化并移除stop words。 在Index时使用Anaylsis Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_CURRENT); IndexWriter writer = new IndexWriter(directory, analyzer, IndexWriter.MaxFieldLength.UNLIMITED); 注意如果需要分词,在创建Field的时候,必须指定Field.Index.ANALYZED或者Field.Index.ANALYZED_NO_NORMS。 如果Text想被整体索引而不被分词,使用Field.Index.NOT_ANALYZED或者Field.Index.NOT_ANALYZED_NO_NORMS 另外,对于是否存储分词前原文,由Field.Store.YES or Field.Store.NO控制。 在QueryParser时使用Anaylsis QueryParser parser = new QueryParser(Version.LUCENE_CURRENT , “contents”, analyzer); Query query = parser.parse(expression); Anaylsis将expression拆分为必要的Term以用于检索,但Lucene并不会把全部的expression交给Analysis处理,而是分块进行。 例如对于 “president obama” +harvard +professor 会单独地触发analyser三次,分别是 1.president obama 2.harvard 3.professor 一般来说,index和parser应该使用相同的Analysis. Analysis不适用的场景 Analysis只适用于文本内部使用,只限制在一个Field中 用。对于HTML这种包含<body>、<head>等多个属性即多个Field的不适用,在这种情况下,需要在Analysis前进行Parsing ,即预处理 。 4.2

Analyzer内部详解 Analyzer类是抽象类,将text转换为TokenStream,一般只需要实现这个方法即可: public TokenStream tokenStream(String fieldName, Reader reader) 例如SimpleAnalyzer public final class SimpleAnalyzer extends Analyzer { @Override public TokenStream tokenStream(String fieldName, Reader reader) { return new LowerCaseTokenizer(reader); } @Override public TokenStream reusableTokenStream(String fieldName, Reader reader throws IOException { Tokenizer tokenizer = (Tokenizer) getPreviousTokenStream(); if (tokenizer == null) { tokenizer = new LowerCaseTokenizer(reader); setPreviousTokenStream(tokenizer); } else tokenizer.reset(reader); return tokenizer; } } LowerCaseTokenizer将text在非字母处分隔开,移除非字母的字符,并对字母小写化。 reusableTokenStream允许对已经使用过的TokenStream进行重用。 Token内部 Token除了存储每个独立词之外,还包含了每个词的Meta信息,例如在Text中的偏移pos,以及position increment,默认为1,但也可以调整以用于它用。 例如position increment = 0 可用于注入同义词 position increment = 2 表示中间有被删除的单词 Token还可以包含由Application指定的额外数据payload(byte [ ])。 TokenStream TokenStream用来产生一系列的Token,主要有Tokenizer和TokenFilter两种 ,他们均继承自TokenStream。 Tokenizer:从Reader读取字符,并创建Tokens TokenFilter:读入Tokens,并由此产生新 的Token,或删除部分 Token。 处理流程: Reader

->

Tokenizer -> TokenFilter -> …… -> TokenFilter -> Tokens 核心的Tokenizer Tokenizer:以Reader为输入的Token处理抽象类。 CharTokenizer:基于字符的Tokenizer抽象 类,当isTokenChar()返回true的时候,产生新Token,也具备小(大)写化的功能。 WhitespaceTokenizer:继承自CharTokenizer,所有非空白的char,isTokenChar()均返回true,其他false。 KeywordTokenizer:将全部输入作为一个Token。 LetterTokenizer:继承自CharTokenizer,所有字母的char,isTokenChar()均返回true,其他false。 LowerCaseTokenizer:继承自,LetterTokenizer,并让所有字母变成小写。 SinkTokenizer:吸收Tokens,可结合TeeTokenizer用于分隔Token。 StandardTokenizer:基于语法的Tokenizer ,例如可分离出高级结构例如E-Mail地址,可能要结合StandardFilter使用。 核心的TokenFilter LowerCaseFilter:将Token小写化。 StopFilter:将存在于list中的Stopwords 移除 PorterStemFilter:同一化词根,例如country和countries被转化为countri。 TeeTokenFilter:分隔TokenStream。 ASCIIFoldingFilter:?? CachingTokenFilter: LengthFilter:只接受Token长度在指定范围内的,其余丢弃。 StandardFilter:与StandardTokenizer配合使用,移除缩略词中的.省略号等。 在Analyzer中,可以把这些Tokenizer和TokenFilter结合起来使用 。 public TokenStream tokenStream(String fieldName, Reader reader) { return new StopFilter (true,

new LowerCaseTokenizer (reader),

stopWords); } 如何查看Analyzer的结果 AnalyzerUtils.displayTokens(analyzer, text); AnalyzerUtils将调用Analyzer模拟进行分词,并将生成的Token显示出来。 具体过程如下: public static void displayTokens(Analyzer analyzer, String text) throws IOException { displayTokens(analyzer.tokenStream(“contents”, new StringReader(text)));

//A } public static void displayTokens(TokenStream stream) throws IOException { TermAttribute term = (TermAttribute) stream.addAttribute(TermAttribute.class) while(stream.incrementToken()) { System.out.print(“[" + term.term() + "] “);

//B } } 步骤: 1、调用analyzer的tokenStream,生成TokenStream(N多Token)。 2、在TokenStream上注册Attibute, 2、使用stream.incrementToken()遍历TokenStream,使用注册的Attribute获取每一个Term。 也可以注册PositionIncrementAttribute、OffsetAttribute、TypeAttribute等以获取更详细的Term信息。 关于Attribute和AttributeSource 在Lucene2.9之后,废弃了单独的Token类,采用Attrubute来遍历Token以提升系统性能。 TokenStream继承自AttributeSource,它可以提供可拓展的强类型而无需耗时的运行时强制转换。 用法: 1.注册:通过TokenStream的addAttribute获得对应的Attribute, 2.遍历:在stream.incrementToken()返回true的情况下,通过attribute来读取具体属性。 这个属性是双向的,通过对Attribute的更改可同步更新到实际的Token中。 Start、End Offset能做什么? 一般被存储于TermVectors中,用于高亮 。 TokenType 一般来说,在TokenStream总,Token是有类型的,并且是有用的。 TypeAttribute可获得Token的具体类型 StandardAnalyzer和StandardTokenizer会自动给Token加上不同的类型。 Type不计入Index中,而只在Analysis中使用。 TokenFilter的顺序很重要 Filter需要在Token基础上进一步处理,因此经常依赖某一些处理结果。 例如StopFilter是大小写敏感的,所以要求之前Filter已经把字母全部小写化,否则可能The这种StopWord就没法被过滤了。 记住Analyzer是一个链条 ,因此一定要注意顺序 。 4.3

使用内置的Analyzer 内置的Analyzer:WhitespaceAnalyzer、SimpleAnalyzer、StopAnalyzer、KeywordAnalyzer、StandardAnalyzer,几乎可用于处理所有西方文字(西欧语系)。 StopAnalyzer 除了基础的分词、小写化外,还从Token中移除StopWords,这个StopWords的List可以指定,默认为: “a”, “an”, “and”, “are”, “as”, “at”, “be”, “but”, “by”, “for”, “if”, “in”, “into”, “is”, “it”,

“no”, “not”, “of”, “on”, “or”, “such”, “that”, “the”, “their”, “then”, “there”, “these”, “they”, “this”, “to”, “was”, “will”, “with” StandardAnalyzer StandardAnalyzer有“最通用”Analyzer之城,内置了JFlex进行语法分析。 能处理数字、字母、首字母缩略、公司名、电子邮件、计算机主机名、内部省略号的单词、数字、IP、中文字符等。 同时还包含StopWords、 4.4

Sounds Like查询 例如Indexs中的cool cat

但是查询词是kool cat,这就是Sounds Like。 可以构造下述的分词器: public class MetaphoneReplacementFilter extends TokenFilter { public static final String METAPHONE = “METAPHONE”; private Metaphone metaphoner = new Metaphone();

//#A private TermAttribute termAttr; private TypeAttribute typeAttr; public MetaphoneReplacementFilter(TokenStream input) { super(input); termAttr = (TermAttribute) addAttribute(TermAttribute.class); typeAttr = (TypeAttribute) addAttribute(TypeAttribute.class); } public boolean incrementToken() throws IOException { if (!input.incrementToken())

//#B return false;

//#C String encoded; encoded = metaphoner.encode(termAttr.term());

//#D termAttr.setTermBuffer(encoded); //#E typeAttr.setType(METAPHONE);

//#F return true; } } 标红的部分,将本kool替换成了cool,实际上可能是更简单的形式。 例如,对于The quick brown fox jumped over the lazy dogs 将被metaphoner.encode整理为[0] [KK] [BRN] [FKS] [JMPT] [OFR] [0] [LS] [TKS] 对于Sounds Like的文本:Tha quik brown phox jumpd ovvar tha lazi dogz 也就是两个类似的词被整理为精简版的单词,显然这个方法挺土的。 4.5

同义词检索 SynonymAnalyzer的目的是,将同义词替换为一个统一的,替换掉原来的位置。 public class SynonymAnalyzer extends Analyzer { private SynonymEngine engine; public SynonymAnalyzer(SynonymEngine engine) { this.engine = engine; } public TokenStream tokenStream(String fieldName, Reader reader) { TokenStream result = new SynonymFilter( new StopFilter(true, new LowerCaseFilter( new StandardFilter( new StandardTokenizer( Version.LUCENE_CURRENT, reader))), StopAnalyzer.ENGLISH_STOP_WORDS_SET), engine ); return result; } } 这个Analyzer除了在最后的Chain加上了SynonymFilter外并没有什么其他区别。 public class SynonymFilter extends TokenFilter { public static final String TOKEN_TYPE_SYNONYM = “SYNONYM”; private Stack synonymStack; private SynonymEngine engine; private TermAttribute termAttr; private AttributeSource save; public SynonymFilter(TokenStream in, SynonymEngine engine) { super(in); synonymStack = new Stack();

//#1 termAttr = (TermAttribute) addAttribute(TermAttribute.class); save = in.cloneAttributes(); this.engine = engine; } public boolean incrementToken() throws IOException { if (synonymStack.size() > 0) {

//#2 State syn = (State) synonymStack.pop(); //#2 restoreState(syn);

//#2 return true; } if (!input.incrementToken())

//#3 return false; addAliasesToStack();

//#4 return true;

//#5 } private void addAliasesToStack() throws IOException { String[] synonyms = engine.getSynonyms(termAttr.term());

//#6 if (synonyms == null) return; State current = captureState(); for (int i = 0; i < synonyms.length; i++) {

//#7 save.restoreState(current); AnalyzerUtils.setTerm(save, synonyms[i]);

//#7 AnalyzerUtils.setType(save, TOKEN_TYPE_SYNONYM); //#7 AnalyzerUtils.setPositionIncrement(save, 0);

//#8 synonymStack.push(save.captureState());

//#7 } } } 4.8

处理其他语言 处理其他语言面临的问题,特别是亚洲语言:无法通过空格来分词! 关于编码:应该统一使用UTF8。 对于其他西欧语言,可以使用SnowballAnalyzer(在contrib中) Character规则化 对于某些场景,需要在Analyzer(Tokenizer)之前,对Reader读入的字符进行规则化。 例如:将繁体中文字符替换成简体中文。 Lucene提供了CharFilters,用于包装Reader,从而完成字符的替换、规则化工作。 Core提供的唯一、具体的实现类是:MappingCharFilter,提供一个Mapping,将原始字符替换为目标字符~ 对亚洲语言进行分词 简言之:最好不要使用SimpleAnalyzer、StandardAnalyzer,书中推荐的是SmartChineseAnalyzer,类似的还有很多,网上一搜一大把~

  原文http://www.coder4.com/archives/761 简单来说,Analysis就是把field Text转化成基本的Term的形式。 通过分词,将Text转化为Token,Token+对应的Field即为Term。 分词的处理包括:萃取、

相关阅读排行


相关内容推荐

最新文章

×

×

请激活账号

为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。

您的注册邮箱: 修改

重新发送激活邮件 进入我的邮箱

如果您没有收到激活邮件,请注意检查垃圾箱。