`
brandNewUser
  • 浏览: 446113 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Lucene 中的Tokenizer, TokenFilter学习

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


 
 
 
对照我们之前分析的同义词TokenizerFactory相关配置,其数据流的过程如下:
 
java.io.Reader -> com.chenlb.mmseg4j.solr.MMSegTokenizer -> SynonymFilter -> StopFilter -> WordDelimiterFilter -> LowerCaseFilter -> RemoveDuplicatesTokenFilter
 
 
对于某些TokenFilter来说,在分析过程中对事件的处理顺序非常重要。当指定过滤操作顺序时,还应该考虑这样的安排对于应用程序性能可能造成的影响。
 
在solr中,schema.xml(最新版本已经修改为managed-schema)的作用是告诉solr该如何对输入的文档进行索引。
 
 
对于每个不同的field,需要设置其对应的数据类型,数据类型决定了solr如何去解释每个字段,以及怎样才能搜索到这个字段。在字段分析器中(field analyzers),指导solr怎样对输入的数据进行处理然后再构建出索引,类似于文本处理器或者文本消化器。
 
当一个document被索引或者检索操作的时候,分析器Analyzer会审阅字段field的文本内容,然后生成一个token流,analyzer可以由多个tokenizer和filter组成;tokenizer可以将field字段的内容切割成单个词或token,进行分词处理;filters可以接收tokenizer分词输出的token流,进行转化过滤处理,例如对词元进行转换(简繁体转换),舍弃无用词元(虚词谓词)。tokenizer和filter一起组成一个管道或者链条,对输入的文档和输入的查询文本进行处理,一系列的tokenizer和filter被称为分词器analyzer,得到的结果被存储成为索引字典用来匹配查询输入条件。
 
此外,我们还可以将索引分析器和查询分析器分开,例如下面的字段配置的意思:对于索引,先经过一个基本的分析器,然后转换为小写字母,接着过滤掉不在keepword.txt中的词,最后将剩下的词元转换为同义词;对于查询,先经过一个基本的分词器,然后转换为小写字母就可以了。
 
<fieldType name="nametext" class="solr.TextField">
  <analyzer type="index">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.KeepWordFilterFactory" words="keepwords.txt"/>
    <filter class="solr.SynonymFilterFactory" synonyms="syns.txt"/>
  </analyzer>
  <analyzer type="query">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
  </analyzer>
</fieldType>
 
 
在Lucene实战一书中,详解了如何从头编写一个同义词Analyzer,通过改写termAttribute以及positionIncrementAttribute的方式来达到实现同义词的方式,不过由于书上的示例比较陈旧,而charTermAttribute不能达到修改同义词元的目的(只能进行append),因此替换最终的目的没有达到。
 
 
public class SynonymFilter extends TokenFilter {

    private static final String TOKEN_TYPE_SYNONYM = "SYNONYM";

    private Stack<String> synonymStack;
    private SynonymEngine synonymEngine;
    private AttributeSource.State current;
    private final CharTermAttribute bytesTermAttribute;
    private final PositionIncrementAttribute positionIncrementAttribute;

    /**
     * Construct a token stream filtering the given input.
     *
     * @param input
     */
    protected SynonymFilter(TokenStream input, SynonymEngine synonymEngine) {
        super(input);
        this.synonymEngine = synonymEngine;
        synonymStack = new Stack<>();

        this.bytesTermAttribute = addAttribute(CharTermAttribute.class);
        this.positionIncrementAttribute = addAttribute(PositionIncrementAttribute.class);
    }

    @Override
    public boolean incrementToken() throws IOException {
        if (!synonymStack.isEmpty()) {
            String syn = synonymStack.pop();
            restoreState(current);

//            bytesTermAttribute.setBytesRef(new BytesRef(syn.getBytes()));
//            bytesTermAttribute.resizeBuffer(0);
            bytesTermAttribute.append(syn);

            positionIncrementAttribute.setPositionIncrement(0);
            return true;
        }

        if (!input.incrementToken()) {
            return false;
        }

        if (addAliasesToStack()) {
            current = captureState();
        }

        return true;
    }

    private boolean addAliasesToStack() throws IOException {
        String[] synonyms = synonymEngine.getSynonyms(bytesTermAttribute.toString());
        if (synonyms == null) {
            return false;
        }
        for (String synonym : synonyms) {
            synonymStack.push(synonym);
        }
        return true;
    }
}
 
 
Analyzer,用于将tokenizer和filter串联起来:
 
public class SynonymAnalyzer extends Analyzer {
    @Override
    protected TokenStreamComponents createComponents(String fieldName) {
        StandardTokenizer source = new StandardTokenizer();
        return new TokenStreamComponents(source, new SynonymFilter(new StopFilter(new LowerCaseFilter(source),
                new CharArraySet(StopAnalyzer.ENGLISH_STOP_WORDS_SET, true)), new TestSynonymEngine()));
    }
}
 
 
我们定义一个简易的同义词匹配引擎:
 
public interface SynonymEngine {
    String[] getSynonyms(String s) throws IOException;
}

public class TestSynonymEngine implements SynonymEngine {

    public static final Map<String, String[]> map = new HashMap<>();

    static {
        map.put("quick", new String[]{"fast", "speedy"});
    }

    @Override
    public String[] getSynonyms(String s) throws IOException {
        return map.get(s);
    }
}
 
对最终结果进行测试:
 
   
public static void main(String[] args) throws IOException {
        SynonymAnalyzer analyzer = new SynonymAnalyzer();
        TokenStream tokenStream = analyzer.tokenStream("contents", new StringReader("The quick brown fox"));
        tokenStream.reset();

        CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
        OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);
        PositionIncrementAttribute positionIncrementAttribute =
                tokenStream.addAttribute(PositionIncrementAttribute.class);
        TypeAttribute typeAttribute = tokenStream.addAttribute(TypeAttribute.class);

        int position = 0;
        while (tokenStream.incrementToken()) {
            int positionIncrement = positionIncrementAttribute.getPositionIncrement();
            if (positionIncrement > 0) {
                position += positionIncrement;
                System.out.println();
                System.out.print(position + " : ");
            }

            System.out.printf("[%s : %d ->  %d : %s]", charTermAttribute.toString(), offsetAttribute.startOffset(), offsetAttribute.endOffset(),
                    typeAttribute.type());
        }
 
 
测试出的结果,可以看出位置1的谓词the已经被剔除,位置2处加入了较多的同义词,由于使用的append,所以同义词记在了一起。
 
2 : [quick : 4 ->  9 : <ALPHANUM>][quickspeedy : 4 ->  9 : <ALPHANUM>][quickfast : 4 ->  9 : <ALPHANUM>]
3 : [brown : 10 ->  15 : <ALPHANUM>]
4 : [fox : 16 ->  19 : <ALPHANUM>]
 
 
 
 
Solr同义词设置
 
Solr中的同义词使用的是 SynonymFilterFactory 来进行加载的,我们需要在定义schema时,对某个字段设置同义词时,可以使用:
 
<fieldtype name="textComplex" class="solr.TextField" positionIncrementGap="100">
        <analyzer type="index">
            <tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="complex" dicPath="/Users/mazhiqiang/develop/tools/solr-5.5.0/server/solr/product/conf/dic" />
            <filter class="solr.StopFilterFactory" ignoreCase="false" words="stopwords.txt"/>
            <filter class="solr.WordDelimiterFilterFactory"/>
            <filter class="solr.LowerCaseFilterFactory"/>
            <filter class="solr.NGramFilterFactory" minGramSize="1" maxGramSize="20"/>
            <filter class="solr.StandardFilterFactory"/>
        </analyzer>
        <analyzer type="query">
            <tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="complex" dicPath="/Users/mazhiqiang/develop/tools/solr-5.5.0/server/solr/product/conf/dic" />
            <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
            <filter class="solr.StopFilterFactory" ignoreCase="false" words="stopwords.txt"/>
            <filter class="solr.WordDelimiterFilterFactory"/>
            <filter class="solr.LowerCaseFilterFactory"/>
            <!--  <filter class="solr.EdgeNGramFilterFactory" minGramSize="1" maxGramSize="20"/> -->
            <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
        </analyzer>
    </fieldtype>
 
 
需要配置对应的 synonyms 属性,指定 定义同义词的配置文件,设置是否忽略大小写等属性。
 
而在加载同义词时,对文件进行逐行读取(使用LineNumberReader),对于每一行的数据,先使用 => 作为分隔符,同义词在左右两边(左边作为input,右边作为output)都可以配置成多个,以逗号分隔,最后以笛卡尔积的形式将其放至map中。
 
String line = null;
    while ((line = in.readLine()) != null) {
      if (line.length() == 0 || line.charAt(0) == '#') {
        continue; // ignore empty lines and comments
      }

      // TODO: we could process this more efficiently.
      String sides[] = split(line, "=>");
      if (sides.length > 1) { // explicit mapping
        if (sides.length != 2) {
          throw new IllegalArgumentException("more than one explicit mapping specified on the same line");
        }
        String inputStrings[] = split(sides[0], ",");
        CharsRef[] inputs = new CharsRef[inputStrings.length];
        for (int i = 0; i < inputs.length; i++) {
          inputs[i] = analyze(unescape(inputStrings[i]).trim(), new CharsRefBuilder());
        }

        String outputStrings[] = split(sides[1], ",");
        CharsRef[] outputs = new CharsRef[outputStrings.length];
        for (int i = 0; i < outputs.length; i++) {
          outputs[i] = analyze(unescape(outputStrings[i]).trim(), new CharsRefBuilder());
        }
        // these mappings are explicit and never preserve original
        for (int i = 0; i < inputs.length; i++) {
          for (int j = 0; j < outputs.length; j++) {
            add(inputs[i], outputs[j], false);
          }
        }
 
 
所有的同义词加载完成后,会生成一个SynonymMap,该map就被用来在全文检索的过程中进行同义词替换。
 
在我们对某个单词进行查询时,可以查询到我们设置的字段query分析器结构,生成一个TokenizerChain对象,对应的Tokenizer为我们设置的分词器,filters为我们设置的过滤器链条,会根据过滤器链条Chain进行
 



 
 
 
通过input的方式设置同义词Filter,组成该链条结果。
 
@Override
  protected TokenStreamComponents createComponents(String fieldName) {
    Tokenizer tk = tokenizer.create();
    TokenStream ts = tk;
    for (TokenFilterFactory filter : filters) {
      ts = filter.create(ts);
    }
    return new TokenStreamComponents(tk, ts);
  }
 
 
而具体到每个FilterFactory,例如SynonymFilterFactory,都通过create方法来创建对应的Filter用于同义词过滤。
 
@Override
  public TokenStream create(TokenStream input) {
    // if the fst is null, it means there's actually no synonyms... just return the original stream
    // as there is nothing to do here.
    return map.fst == null ? input : new SynonymFilter(input, map, ignoreCase);
  }
 
 
创建一个SynonymFilter来进行最后真正的筛选,将同义词进行替换,整体的类结构图如下:
 


 
lucene内置的Token
 
lucene中除了内置的几个Tokenizer,在solr中的field analyzer以及index中也得到了应用,下面就对这几种filter进行测试,我们分析的文本为: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” 指定这个单词列表的单词不被修改
通过分隔符分割单元
 
 
 
 
 
 
 
 
 
 
  • 大小: 18.6 KB
  • 大小: 34.1 KB
  • 大小: 107.4 KB
分享到:
评论

相关推荐

    Lucene如何使用TokenFilter进行再分词

    Lucene如何使用TokenFilter进行再分词,里面又我的源代码和注释!!

    lucene学习lucene学习

    lucene学习lucene学习lucene学习lucene学习lucene学习lucene学习lucene学习lucene学习lucene学习lucene学习lucene学习lucene学习lucene学习lucene学习lucene学习lucene学习lucene学习lucene学习lucene学习lucene学习...

    auto-phrase-tokenfilter:Lucene自动短语TokenFilter实现

    Lucene自动短语TokenFilter实现 对令牌流执行“自动短语编制”。 自动短语是指标记序列,旨在描述单个事物,因此应进行搜索。 当在令牌流中检测到这些短语时,将发出代表该短语的单个令牌,而不是组成该短语的各个...

    lucene.NET 中文分词

    lucene.NET 中文分词 高亮 lucene.NET 中文分词 高亮 lucene.NET 中文分词 高亮 lucene.NET 中文分词 高亮

    Lucene的的学习资料及案例

    Lucene的的学习资料及案例,包括一个lucene的学习资料总结。供大家学习使用,也有本人写的一个小案例。

    Lucene中文分词器包

    来自“猎图网 www.richmap.cn”基于IKAnalyzer分词算法的准商业化Lucene中文分词器。 1. 正向全切分算法,42万汉字字符/每秒的处理能力(IBM ThinkPad 酷睿I 1.6G 1G内存 WinXP) 2. 对数量词、地名、路名的...

    Lucene中的FST算法描述

    描述了Lucene中如何使用FST算法构建term的内存索引,使用了很多图,直观的展现了FST图的构建流程,能够对想了解lucene内部实现机制原理的同学有帮助。

    lucene 3.0 API 中文帮助文档 chm

    lucene 3.0 API中文帮助,学习的人懂得的

    lucene-core-7.2.1-API文档-中文版.zip

    赠送jar包:lucene-core-7.2.1.jar; 赠送原API文档:lucene-core-7.2.1-javadoc.jar; 赠送源代码:lucene-core-7.2.1-sources.jar;...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。

    Lucene中文分词器组件

    Lucene中文分词器组件,不错的。

    Lucene与SQL对比学习

    Lucene与SQL对比学习,适用于新手

    Lucene 3.6 学习笔记

    (3) TokenFilter 23 4.3 分词属性 23 (1) 分词属性查看 24 (2) 分词属性对比 25 4.4 自定义分词器 26 (1) 自定义Stop分词器 26 (2) 实现简单同义词索引 27 第五章 高级搜索 32 5.1 搜索排序 34 (1) 建立搜索类 34 (2...

    Lucene.net学习帮助文档

    Lucene.net源码+中文学习文档,打造自己网站内的强大搜索引擎。

    lucene学习

    Lucene的基础知识 1、案例分析:什么是全文检索,如何实现全文检索 2、Lucene实现全文检索的流程 a) 创建索引 b) 查询索引 3、配置开发环境 4、创建索引库 5、查询索引库 ...solr在项目中的应用及电商搜索实现

    lucene-core-7.7.0-API文档-中文版.zip

    赠送jar包:lucene-core-7.7.0.jar; 赠送原API文档:lucene-core-7.7.0-javadoc.jar; 赠送源代码:lucene-core-7.7.0-sources.jar;...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。

    lucene 学习文档

    这里面是lucene的相关学习资料,特别适合新手学习。

    Lucene+compass学习资料

    Lucene+compass学习资料+ajax

    超系统学习Lucene全文检索技术视频教程

    在本套课程中,我们将全面的讲解Lucene技术,从简单应用到细节使用再到底层原理都有深入讲解。尤其对Lucene底层的存储结构,搜索算法,以及数据结构等晦涩难懂的知识做深入浅出式讲解。学习Lucene对日后学习它的下游...

    lucene中文分词器(paoding解牛)

    lucene搜索引擎中文分词器,版本2.0.4,强大的中文分词效果在其它中文分词器当中独领风骚

    lucene +中文分词

    Lucene 与中文分词的结合

Global site tag (gtag.js) - Google Analytics