文档章节

Lucene中的Tokenizer, TokenFilter学习

小致dad
 小致dad
发布于 2017/07/21 10:07
字数 2286
阅读 48
收藏 1

Lucene中的TokenStream,TokenFilter之间关系

TokenStream是一个能够在被调用后产生语汇单元序列的类,其中有两个类型:Tokenizer和TokenFilter,两者的不同在于TokenFilter中包含了一个TokenStream作为input,该input仍然可以为一种TokenFilter进行递归封装,是一种组合模式;而Tokenzier接受一个Reader对象读取字符并创建语汇单元,TokenFilter负责处理输入的语汇单元,通过新增、删除或者修改属性的方式来产生新的语汇单元。

对于某些TokenFilter来说,在分析过程中对事件的处理顺序非常重要。当指定过滤操作顺序时,还应该考虑这样的安排对于应用程序性能可能造成的影响。

当一个document被索引或者检索操作的时候,分析器Analyzer会审阅字段field的文本内容,然后生成一个token流,analyzer可以由多个tokenizer和filter组成;tokenizer可以将field字段的内容切割成单个词或token,进行分词处理;filters可以接收tokenizer分词输出的token流,进行转化过滤处理,例如对词元进行转换(简繁体转换),舍弃无用词元(虚词谓词)。tokenizer和filter一起组成一个管道或者链条,对输入的文档和输入的查询文本进行处理,一系列的tokenizer和filter被称为分词器analyzer,得到的结果被存储成为索引字典用来匹配查询输入条件。

此外,我们还可以将索引分析器和查询分析器分开,例如下面的字段配置的意思:对于索引,先经过一个基本的分析器,然后转换为小写字母,接着过滤掉不在keepword.txt中的词,最后将剩下的词元转换为同义词;对于查询,先经过一个基本的分词器,然后转换为小写字母就可以了。

定义TokenFilter对tokenstream进行过滤

package org.liuyuantao.lucene.analyzer.util;

import java.io.IOException;

import java.util.Stack;

import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
import org.apache.lucene.util.AttributeSource;

public class MySameTokenFilter extends TokenFilter {
    private CharTermAttribute cta = null;
    private PositionIncrementAttribute pia = null;
    private AttributeSource.State current;
    private Stack<String> sames = null;
    private SamewordContext samewordContext;

    protected MySameTokenFilter(TokenStream input, SamewordContext samewordContext) {
        super(input);
        cta = this.addAttribute(CharTermAttribute.class);
        pia = this.addAttribute(PositionIncrementAttribute.class);
        sames = new Stack<String>();
        this.samewordContext = samewordContext;
    }

    @Override
    public final boolean incrementToken() throws IOException {
        if (sames.size() > 0) {
            //将元素出栈,并且获取这个同义词
            String str = sames.pop();
            //还原状态
            restoreState(current);
            cta.setEmpty();
            cta.append(str);
            //设置位置0
            pia.setPositionIncrement(0);
            return true;
        }

        if (!this.input.incrementToken()) return false;

        if (addSames(cta.toString())) {
            //如果有同义词将当前状态先保存
            current = captureState();
        }
        return true;
    }

    private boolean addSames(String name) {
        String[] sws = samewordContext.getSamewords(name);
        if (sws != null) {
            for (String str : sws) {
                sames.push(str);
            }
            return true;
        }
        return false;
    }


}

Analyzer,用于将tokenizer和filter串联起来:

package org.liuyuantao.lucene.analyzer.util;

import org.apache.lucene.analysis.Analyzer;

import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.core.LowerCaseFilter;
import org.apache.lucene.analysis.core.StopAnalyzer;
import org.apache.lucene.analysis.core.StopFilter;
import org.apache.lucene.analysis.util.CharArraySet;
import org.liuyuantao.lucene.utils.IKTokenizer5x;

public class MySameAnalyzer extends Analyzer {
    private SamewordContext samewordContext;

    public MySameAnalyzer(SamewordContext swc) {
        samewordContext = swc;
    }

    @Override
    protected TokenStreamComponents createComponents(String fieldName) {
        Tokenizer tokenizer = new IKTokenizer5x(true);
        return new TokenStreamComponents(tokenizer, new MySameTokenFilter(new StopFilter(new LowerCaseFilter(tokenizer),
                new CharArraySet(StopAnalyzer.ENGLISH_STOP_WORDS_SET, true)), this.samewordContext));
    }
}

定义一个简易的同义词匹配引擎:

package org.liuyuantao.lucene.analyzer.util;

/**
 * 同义词库引擎
 */
public interface SamewordContext {
    public String[] getSamewords(String name);
}

//实现类
package org.liuyuantao.lucene.analyzer.util;

import java.util.HashMap;
import java.util.Map;

public class SimpleSamewordContext implements SamewordContext {

	Map<String,String[]> maps = new HashMap<String,String[]>();
	public SimpleSamewordContext() {
		maps.put("中国",new String[]{"天朝","大陆"});
		maps.put("我",new String[]{"咱","俺"});
	}

	@Override
	public String[] getSamewords(String name) {
		return maps.get(name);
	}

}

对最终结果进行测试:

/**
     * 测试同义词库
     */
    @Test
    public void test05() {
        try {
            Analyzer a2 = new MySameAnalyzer(new SimpleSamewordContext());
            String txt = "我来自中国人,我来自中国山东临沂";
            Directory dir = new RAMDirectory();
            IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(a2));
            Document doc = new Document();
            doc.add(new TextField("content", txt, Field.Store.YES));
            writer.addDocument(doc);
            writer.close();
            AnalyzerUtils.displayAllTokenInfo(txt, a2);
            System.out.println("----------查询-------------");
            System.out.println("使用咱进行查询");
            IndexSearcher searcher = new IndexSearcher(DirectoryReader.open(dir));
            TopDocs tds1 = searcher.search(new TermQuery(new Term("content", "咱")), 10);
            ScoreDoc[] scoreDocs1 = tds1.scoreDocs;
            if (scoreDocs1 != null && scoreDocs1.length > 0) {
                for (ScoreDoc sd : scoreDocs1) {
                    Document d = searcher.doc(sd.doc);
                    System.out.println(d.get("content"));
                }
            }
            System.out.println("使用大陆进行查询");
            TopDocs tds2 = searcher.search(new TermQuery(new Term("content", "大陆")), 10);
            ScoreDoc[] scoreDocs2 = tds2.scoreDocs;
            if (scoreDocs2 != null && scoreDocs2.length > 0) {
                for (ScoreDoc sd : scoreDocs2) {
                    Document d = searcher.doc(sd.doc);
                    System.out.println(d.get("content"));
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

结果:

1:我[0-1]-->CN_WORD
0:俺[0-1]-->CN_WORD
0:咱[0-1]-->CN_WORD
1:来自[1-3]-->CN_WORD
1:中国人[3-6]-->CN_WORD
1:我[7-8]-->CN_WORD
0:俺[7-8]-->CN_WORD
0:咱[7-8]-->CN_WORD
1:来自[8-10]-->CN_WORD
1:中国[10-12]-->CN_WORD
0:大陆[10-12]-->CN_WORD
0:天朝[10-12]-->CN_WORD
1:山东[12-14]-->CN_WORD
1:临沂[14-16]-->CN_WORD
----------查询-------------
使用咱进行查询
我来自中国人,我来自中国山东临沂
使用大陆进行查询
我来自中国人,我来自中国山东临沂

从结果中可以看到,“我”、“俺”、“咱”是同义词,使用咱搜索的时候可以检索到。

“中国”、“大陆”、“天朝”是同义词,使用大陆搜索的时候,也是可以搜索到的。

lucene内置的Token

lucene中内置的几个Tokenizer,我们分析使用的文本为:Please email clark.ma@gmail.com by 09, re:aa-bb

StandardAnalyzer

1 : [please : 0 ->  6 : <ALPHANUM>]

2 : [email : 7 ->  12 : <ALPHANUM>]

3 : [clark.ma : 13 ->  21 : <ALPHANUM>]

4 : [gmail.com : 22 ->  31 : <ALPHANUM>]

6 : [09 : 35 ->  37 : <NUM>]

7 : [re:aa : 39 ->  44 : <ALPHANUM>]

8 : [bb : 45 ->  47 : <ALPHANUM>]

去除空格,标点符号,@;

 

ClassicAnalyzer

1 : [please : 0 ->  6 : <ALPHANUM>]

2 : [email : 7 ->  12 : <ALPHANUM>]

3 : [clark.ma@gmail.com : 13 ->  31 : <EMAIL>]

5 : [09 : 35 ->  37 : <ALPHANUM>]

6 : [re : 39 ->  41 : <ALPHANUM>]

7 : [aa : 42 ->  44 : <ALPHANUM>]

8 : [bb : 45 ->  47 : <ALPHANUM>]

能够识别互联网域名和email地址,
LetterTokenizer

1 : [Please : 0 ->  6 : word]

2 : [email : 7 ->  12 : word]

3 : [clark : 13 ->  18 : word]

4 : [ma : 19 ->  21 : word]

5 : [gmail : 22 ->  27 : word]

6 : [com : 28 ->  31 : word]

7 : [by : 32 ->  34 : word]

8 : [re : 39 ->  41 : word]

9 : [aa : 42 ->  44 : word]

10 : [bb : 45 ->  47 : word]

丢弃掉所有的非文本字符
KeywordTokenizer

1 : [Please email clark.ma@gmail.com by 09, re:aa-bb : 0 ->  47 : word]

 

将整个文本当做一个词元
LowerCaseTokenizer

1 : [please : 0 ->  6 : word]

2 : [email : 7 ->  12 : word]

3 : [clark : 13 ->  18 : word]

4 : [ma : 19 ->  21 : word]

5 : [gmail : 22 ->  27 : word]

6 : [com : 28 ->  31 : word]

7 : [by : 32 ->  34 : word]

8 : [re : 39 ->  41 : word]

9 : [aa : 42 ->  44 : word]

10 : [bb : 45 ->  47 : word]

对其所有非文本字符,过滤空格,标点符号,将所有的大写转换为小写
NGramTokenizer

可以定义最小minGramSize(default=1), 最大切割值maxGramSize(default=2),生成的词元较多。

假设minGramSize=2, maxGramSize=3,输入abcde,输出:ab abc abc bc bcd cd cde

读取字段并在给定范围内生成多个token
PathHierachyTokenizer

c:\my document\filea\fileB,new PathHierarchyTokenizer('\\', '/')

1 : [c: : 0 ->  2 : word][c:/my document : 0 ->  14 : word][c:/my document/filea : 0 ->  20 : word][c:/my document/filea/fileB : 0 ->  26 : word]

使用新的文件目录符去代替文本中的目录符
PatternTokenizer

需要两个参数,pattern正则表达式,group分组。

pattern=”[A-Z][A-Za-z]*” group=”0″

输入: “Hello. My name is Inigo Montoya. You killed my father. Prepare to die.”

输出: “Hello”, “My”, “Inigo”, “Montoya”, “You”, “Prepare”

进行正则表达式分组匹配
UAX29URLEmailTokenizer

1 : [Please : 0 ->  6 : <ALPHANUM>]

2 : [email : 7 ->  12 : <ALPHANUM>]

3 : [clark.ma@gmail.com : 13 ->  31 : <EMAIL>]

4 : [by : 32 ->  34 : <ALPHANUM>]

5 : [09 : 35 ->  37 : <NUM>]

6 : [re:aa : 39 ->  44 : <ALPHANUM>]

7 : [bb : 45 ->  47 : <ALPHANUM>]

去除空格和标点符号,但保留url和email连接

Lucene内置的TokenFilter

过滤器能够组成一个链表,每一个过滤器处理上一个过滤器处理过后的词元,所以过滤器的排序很有意义,第一个过滤器最好能处理大部分常规情况,最后一个过滤器是带有针对特殊性的。

ClassicFilter “I.B.M. cat’s can’t” ==> “I.B.M”, “cat”, “can’t” 经典过滤器,可以过滤无意义的标点,需要搭配ClassicTokenizer使用
ApostropheFilter

1 : [abc : 0 ->  3 : <ALPHANUM>]

2 : [I.B.M : 4 ->  9 : <ALPHANUM>]

3 : [cat : 10 ->  15 : <ALPHANUM>]

4 : [can : 16 ->  21 : <ALPHANUM>]

省略所有的上撇号
LowerCaseFilter

1 : [i.b.m : 0 ->  5 : <ALPHANUM>]

2 : [cat's : 6 ->  11 : <ALPHANUM>]

3 : [can't : 12 ->  17 : <ALPHANUM>]

转换成小写
TypeTokenFilter

<filter class=”solr.TypeTokenFilterFactory” types=”email_type.txt” useWhitelist=”true”/>

如果email_type.txt设置为ALPHANUM,会保留该类型的所有分析结果,否则会被删除掉

给定一个文件并设置成白名单还是黑名单,只有符合条件的type才能被保留
TrimFilter   去掉空格
TruncateTokenFilter

1 : [I.B : 0 ->  5 : <ALPHANUM>]

2 : [cat : 6 ->  11 : <ALPHANUM>]

3 : [can : 12 ->  17 : <ALPHANUM>]

截取文本长度,左边为prefixLength=3
PatternCaptureGroupFilter 可配置属性pattern和preserve_original(是否保留原文) 从输入文本中保留能够匹配正则表达式的
PatternReplaceFilter    
StopFilter   创建一个自定义的停词词库列表,过滤器遇到停词就直接过滤掉
KeepWordFilter 与StopFilter的含义正好相反  
LengthFilter 设置一个最小值min和最大值max 为词元的长度设置在一个固定范围
WordDelimiterFilter

A:-符号 wi-fi 变成wi fi
B:驼峰写法 LoveSong 变成 love song 对应参数
C:字母-数字 xiaomi100 变成 xiaomi 100
D:–符号 like–me 变成 like me
E:尾部的’s符号 mother’s 变成 mother
F:-符号 wi-fi 变成 wifi 于规则A不同的是没有分成两个词元
G:-符号,数字之间 400-884586 变成 400884586
H:-符号 无论字母还是数字,都取消-符号 wi-fi-4 变成wifi4

 

其他参数
splitOnCaseChange=”1″ 默认1,关闭设为0 规则B
generateWordParts=”1″ 默认1 ,对应规则AB
generateNumberParts=”1″ 默认1 对应规则F
catenateWords=”1″ 默认0 对应规则A
splitOnNumerics=”1″ 默认1,关闭设0 规则C
stemEnglishPossessive 默认1,关闭设0 规则E
catenateNumbers=”1″ 默认0 对应规则G
catenateAll=”1″ 默认0 对应规则 H
preserveOriginal=”1″ 默认0 对词元不做任何修改 除非有其他参数改变了词元

protected=”protwords.txt” 指定这个单词列表的单词不被修改

通过分隔符分割单元

 

© 著作权归作者所有

共有 人打赏支持
小致dad
粉丝 144
博文 536
码字总数 580295
作品 0
济南
技术主管
私信 提问
Lucene的索引问题?

1.我想指导lucene的索引我可不可以理解为自己设计的数据表,但是为什么不用数据库那? 2.真实的应用中还是存储为文件吗? ------------------------------------------ 我应该怎么有序、深入...

Cobbage
2013/07/31
228
1
ElasticSearch中分词器组件配置详解

首先要明确一点,ElasticSearch是基于Lucene的,它的很多基础性组件,都是由Apache Lucene提供的,而es则提供了更高层次的封装以及分布式方面的增强与扩展。 所以要想熟练的掌握的关于es中分...

九劫散仙
2015/11/23
1K
0
Apache Lucene 4.0 Beta 发布

Apache Lucene 4.0 发布 Beta 版,该版本包含大量的 bug 修复、优化和提升,值得关注的有: * IndexWriter.tryDeleteDocument 可根据文档 id 来删除,用于某些应用提升性能 * 新的 BloomFil...

oschina
2012/08/14
2.6K
5
关于英文分词器的具体实现

Java新手一枚 想编一个程序实现对文件夹内的所有txt文件(英文内容)单词的词频统计 需要用到英文分词器 于是在网上寻找代码 修修改改 还是有问题 下面把代码贴上来 import java.io.Buffere...

我是悟空
2014/03/23
1K
0
Lucene知识小总结9:分词

一、概念认识 1、常用的Analyer SimpleAnalyzer、StopAnalyzer、WhitespaceAnalyzer、StandardAnalyzer 2、TokenStream 分词器做好处理之后得到的一个流,这个流中存储了分词的各种信息,可以...

heroShane
2014/02/23
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Integer使用双等号比较会发生什么

话不多说,根据以下程序运行,打印的结果为什么不同? Integer a = 100;Integer b = 100;System.out.println(a == b);//print : trueInteger a = 200;Integer b = 200;System.out.pr...

兜兜毛毛
23分钟前
0
0
CockroachDB

百度云上的CockroachDB 云数据库 帮助文档 > 产品文档 > CockroachDB 云数据库 > 产品描述 开源NewSQL – CockroachDB在百度内部的应用与实践 嘉宾演讲视频及PPT回顾:http://suo.im/5bnORh ...

miaojiangmin
35分钟前
1
0
I2C EEPROM驱动实例分析

上篇分析了Linux Kernel中的I2C驱动框架,本篇举一个具体的I2C设备驱动(eeprom)来对I2C设备驱动有个实际的认识。 s3c24xx系列集成了一个基于I2C的eeprom设备at24cxx系列。at24cxx系列芯片包...

yepanl
36分钟前
2
0
设计模式之工厂模式

本篇博文主要翻译这篇文章: https://www.journaldev.com/1392/factory-design-pattern-in-java 由于翻译水平有限,自认为许多地方翻译不恰当,欢迎各位给出宝贵的建议,建议大家去阅读原文。...

firepation
今天
6
0

中国龙-扬科
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部