生活随笔
收集整理的這篇文章主要介紹了
Lucene 中的Tokenizer, TokenFilter学习
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
https://brandnewuser.iteye.com/blog/2305140
lucene中的TokenStream,TokenFilter之間關系 TokenStream是一個能夠在被調用后產生語匯單元序列的類,其中有兩個類型:Tokenizer和TokenFilter,兩者的不同在于TokenFilter中包含了一個TokenStream作為input,該input仍然可以為一種TokenFilter進行遞歸封裝,是一種組合模式;而Tokenzier接受一個Reader對象讀取字符并創建語匯單元,TokenFilter負責處理輸入的語匯單元,通過新增、刪除或者修改屬性的方式來產生新的語匯單元。
? 對照我們之前分析的同義詞TokenizerFactory相關配置,其數據流的過程如下: Java代碼??
java.io.Reader?->?com.chenlb.mmseg4j.solr.MMSegTokenizer?->?SynonymFilter?->?StopFilter?->?WordDelimiterFilter?->?LowerCaseFilter?->?RemoveDuplicatesTokenFilter?? ? 對于某些TokenFilter來說,在分析過程中對事件的處理順序非常重要。當指定過濾操作順序時,還應該考慮這樣的安排對于應用程序性能可能造成的影響。 在solr中,schema.xml(最新版本已經修改為managed-schema)的作用是告訴solr該如何對輸入的文檔進行索引。 http://www.liaozhida.net/solr/solr%E7%B3%BB%E5%88%97%E4%B8%83%E8%AF%A6%E8%A7%A3schema-xml%E7%89%B9%E6%80%A7.html 對于每個不同的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中的詞,最后將剩下的詞元轉換為同義詞;對于查詢,先經過一個基本的分詞器,然后轉換為小寫字母就可以了。 Java代碼??
<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),因此替換最終的目的沒有達到。 Java代碼??
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;?? ?? ???? ????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.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串聯起來: Java代碼??
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()));?? ????}?? }?? ? 我們定義一個簡易的同義詞匹配引擎: Java代碼??
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);?? ????}?? }?? ? 對最終結果進行測試: Java代碼??
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,所以同義詞記在了一起。 Java代碼??
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時,對某個字段設置同義詞時,可以使用: Java代碼??
<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中。 Java代碼??
String?line?=?null;?? ????while?((line?=?in.readLine())?!=?null)?{?? ??????if?(line.length()?==?0?||?line.charAt(0)?==?'#')?{?? ????????continue;? ??????}?? ?? ?????? ??????String?sides[]?=?split(line,?"=>");?? ??????if?(sides.length?>?1)?{? ????????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());?? ????????}?? ???????? ????????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,組成該鏈條結果。 Java代碼??
@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用于同義詞過濾。 Java代碼??
@Override?? ??public?TokenStream?create(TokenStream?input)?{?? ???? ???? ????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” 指定這個單詞列表的單詞不被修改 通過分隔符分割單元
轉載于:https://www.cnblogs.com/davidwang456/articles/10470938.html
總結
以上是生活随笔 為你收集整理的Lucene 中的Tokenizer, TokenFilter学习 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。