文本分析時搜索引擎的核心工作之一,對文本包含許多處理步驟,比如:分詞、大寫轉小寫、詞干化、同義詞轉化等。簡單的說,文本分析就說將一個文本字段的值轉為一個一個的token,然后被保存到Lucene的索引結構中被將來搜索用。當然,文本分析不僅在建立索引時有用,在查詢時對對所輸入的查詢串也一樣可以進行文本分析。在 Solr Schema設計 中我們介紹了許多Solr中的字段類型,其中最重要的是solr.TextField,這個類型可以進行分析器配置來進行文本分析。
接下來我們先來說說什么是分析器。
分析器
<fieldType name="nametext" class="solr.TextField"> <analyzer class="org.apache.lucene.analysis.WhitespaceAnalyzer"/> </fieldType>
<fieldType name="nametext" class="solr.TextField"> <analyzer> <tokenizer class="solr.StandardTokenizerFactory"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.StopFilterFactory"/> </analyzer> </fieldType>
現在我們來看下Solr示例Schema配置中的text_en_splitting字段類型的定義,看看它用了哪些分析組件。
<!-- A text field with defaults appropriate for English, plus aggressive word-splitting and autophrase features enabled. This field is just like text_en, except it adds WordDelimiterFilter to enable splitting and matching of words on case-change, alpha numeric boundaries, and non-alphanumeric chars. This means certain compound word cases will work, for example query "wi fi" will match document "WiFi" or "wi-fi". --> <fieldType name="text_en_splitting" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="true"> <analyzer type="index"> <!--<charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ISOLatin1Accent.txt"/>--> <tokenizer class="solr.WhitespaceTokenizerFactory"/> <!-- in this example, we will only use synonyms at query time <filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/> --> <!-- Case insensitive stop word removal. --> <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_en.txt" /> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/> <filter class="solr.PorterStemFilterFactory"/> </analyzer> <analyzer type="query"> <tokenizer class="solr.WhitespaceTokenizerFactory"/> <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/> <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_en.txt" /> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/> <filter class="solr.PorterStemFilterFactory"/> </analyzer> </fieldType>
Type屬性可以指定為index或是query值,分別表示是索引時用的分析器,和查詢時所用的分析器。如果在索引和查詢時使用相同的分析器,你可以不指定type屬性值。
分析器的配置中可以選用一個或多個字符過濾器(character filter),字符過濾器是對原始文本進行字符流級別的操作。它通常可以用於大小寫轉化,去除字母上標等等。在字符過濾器之后是分詞器(Tokenizer),它是必須要配置的。分析器會使用分詞器將字符流切分成詞元(Token)系列,通常用在空格處切分這種簡單的算法。后面的步驟是可選的,比如token過濾器(Token Filter)會對token進行許多種操作,最后產生的詞元會被稱為詞(Term),即用於Lucene實際索引和查詢的單位。
最后,我有必須對autoGeneratePhraseQueries布爾屬性補充兩句,這個屬性只能用於文本域。如果在查詢文本分析時產生了多個詞元,比如Wi-Fi分詞為Wi和Fi,那么默認情況下它們只是兩個不同的搜索詞,它們沒有位置上的關系。但如果autoGeneratePhraseQueries被設置,那么這兩個詞元就構造了一個詞組查詢,即“WiFi”,所以索引中“WiFi”必須相鄰才能被查詢到。在新Solr版本中,默認它被設置為false。我不建議使用它。
在Admin上對字段進行分析
在我們深入特定分析組件的細節之前,有必要去熟悉Solr的分析頁面,它是一個很好的實驗和查錯工具,絕對不容錯過。你將會用它來驗證不同的分析配置,來找到你最想要的效果,你還可以用它來找到你認為應該會匹配的查詢為什么沒有匹配。在Solr的管理頁面,你可以看到一個名為[Analysis]的鏈接,你進入后,會看到下面的界面。
界面上的第一個選項是必選的,你可選擇直接通過字段類型名稱來選擇類型,你也可以間接地通過一個字段的名字來選擇自端類型。在上面的示例中,我選擇了title字段
通過Schema Browser可以看到這個字段類型是 text_general
點擊灰色的 text_general,可以看到這個字段的分析器中定義的分詞器和過濾器
接下來,你可以分析索引或是查詢文本,也可以兩者同時分析。你需要輸入些文本到文本框中以進行分析。將字段值放到Index文本框中,將查詢文本放入Query文本框中,點擊Analyze按鈕看到一下文本處理結果,因為還沒有中文處理,所以中文都被一個字一個字的分開處理了。
你可以選中verbose output來查看處理的詳細信息,我希望你能自己試一下。
上圖中每一行表示分析器處理鏈上的每一步的處理結果。比如第三個分析組件是LowerCaseFilter,它的處理結果就在第三行。前面的ST/SF/LCF應該是分詞器和過濾器的簡稱。
下面我們接着來詳細看看有哪些分詞器和過濾器吧。
Character Filter
字符過濾器在<charFilter>元素中定義,它是對字符流進行處理。字符過濾器種類不多。這個特性只有下面第一個介紹的比較常見。
- MappingCharFilterFactory:它將一個字符(或字符串)映射到另一個,也可以映射為空。換言之,它是一個查找-替換的功能。在mapping屬性中你可以指定一個配置文件。Solr的示例配置中包括了兩個有用的映射配置文件:
-
- mapping-FoldToASCII.txt:一個豐富的將non-ASCII轉化成ASCII的映射。如果想了解字符映射更多的細節,可以閱讀這個文件頂部的注釋。這個字符過濾器有一個類似的詞元過濾器ASCIIFoldFilterFactory,這個詞元過濾器運行速度更快,建議使用它。
- maping-ISOLatinAccent.txt:一個更小的映射文件子集,它只能將ISO Latin1上標映射。FoldToASCII內容更豐富,所以不建議使用這個配置。
- HTMLStripCharFilterFactory:它用於HTML和XML,它不要求它們格式完全正確。本質上它是移除所有的標記,只留下文本內容。移除腳本內容和格式元素。轉義過的特殊字符被還原(比如&)。
- PatternReplaceCharFilterFactory:根據pattern屬性中的正則表達式進行查找,並根據replacement屬性中的值進行替換。它的實現需要一個緩沖區容器,默認設置為10000個字符,可以通過maxBlockChars進行配置。分詞器和詞元過濾器中也有正則表達式組件。所以你應該只在會影響分詞的影響下使用它,比如對空格進行處理。
Tokenization
分詞器在<tokenizer>元素中定義,它將一個字符流切分成詞元序列,大部分它會去除不重要的符號,比如空字符和連接符號。
一個分析器有且只應有一個分詞器,你可選的分詞器如下:
- KeywordTokenizerFactory:這個分詞器不進行任何分詞!整個字符流變為單個詞元。String域類型也有類似的效果,但是它不能配置文本分析的其它處理組件,比如大小寫轉換。任何用於排序和大部分Faceting功能的索引域,這個索引域只有能一個原始域值中的一個詞元。
- WhitespaceTokenizerFactory:文本由空字符切分(即,空格,Tab,換行)。
- StandardTokenizerFactory:它是一個對大部分西歐語言通常的分詞器。它從空白符和其它Unicode標准中的詞分隔符處進行切分。空白符和分隔符會被移除。連字符也被認為是詞的分隔符,這使得它不適合與WordDelimiterFilter一起用。
- UAX29URLEmailTokenizer:它表現的與StandardTokenizer相似,但它多了一個識別e-mail,URL並將它們視為單個詞元的特性。
- ClassicTokenizerFactory:(曾經的StandardTokenizer)它是一個英語的通用分詞器。對英語來說,它優於StandardTokenizer。它可以識別有點號的縮寫詞,比如I.B.M.。如果詞元中包含數字它不會在連字符處分詞,並可以將Email地址和主機名視為單個詞元。並且ClassicFilter詞元過濾器經常與這個分詞器配合使用。ClassicFilter會移除縮寫詞中的點號,並將單引號(英語中的所有格)去除。它只能與ClassicTokenizer一起使用。
- LetterTokenizerFactory:這個分詞器將相鄰的字母(由Unicode定義)都視為一個詞元,並忽略其它字符。
- LowerCaseTokenizerFactory:這個分詞器功能上等同於LetterTokenizer加上LowerCaseFilter,但它運行更快。
- PatternTokenizerFactory:這個基於正則表達式的分詞器可以以下面兩種方式工作:
- 通過一個指定模式切分文本,例如你要切分一個用分號分隔的列表,你可以寫:<tokenizer class="solr.PatternTokenizerFactory" pattern=";*" />.
- 只選擇匹配的一個子集作為詞元。比如:<tokenizer class="solr.PatternTokenizerFactory" pattern="\'([^\']+)\'" group="1" />。組屬性指定匹配的哪個組將被視為詞元。如果你輸入的文本是aaa ‘bbb’ ‘ccc’,那么詞元就是bbb和ccc。
- PathHierachyTokenizerFactory:這是一個可配置的分詞器,它只處理以單個字符分隔的字符串,比如文件路徑和域名。它在實現層次Faceting中很有用,或是可以過濾以某些路徑下的文件。比如輸入字符串是/usr/local/apache會被分詞為三個詞元:/usr,/usr/local,/usr/local/apache。這個分詞器有下面四個選項:
- Delimiter:分隔字符:默認為/
- Replace:將分隔字符替換為另一字符(可選)
- Reverse:布爾值表明是否層次是從右邊開始,比如主機名,默認:false。
- Skip:忽略開頭的多少個詞元,默認為0.
- WikipediaTokenizerFactory:一個用於Mediawiki語法(它用於wikipedia)的實驗性質的分詞器。
還有用於其它語言的分詞器,比如中文和俄語,還有ICUTokenizer會檢測語言。另外NGramtokenizer會在后面討論。可以在http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters中找到更多內容。
WordDelimiterFilter
它也許不是一個正式的分詞器,但是這個名為WordDeilimiterFilter的詞元過濾器本質上是一個分詞器。
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/>
上面並沒有給出所有的選項,這個過濾器可以通過多種配置指定如切分和連接合成詞,並有多種定義合成詞的方法。這個過濾器通常與WhitespaceTokenizer配合,而不是StandardTokenizer。這個過濾器的配置中1是設置,0是重置。
WordDelimiterFilter先通過配置選項中的定義切分詞元:
- 詞間的分隔符切分:Agile-Me切為Agile,Me
- 字母和數據間的切分:SD500切為SD,500(如果設置splitOnNumerics)
- 忽略任何分隔符:hello,Agile-Me切為hello, Agile,Me
- 移除所有格’s:David’s切為Divid(如果設置stemEnglishPocessive)
- 在小寫到大小時切分:Agile-Me切為agile,me(如果設置splitOnCaseChange)
此時,如果下面的選項沒有設置,上面這些切分后的詞都要被過濾掉。因為默認下面的選項設置為false,你一般至少要設置下面其中一項。
- 如果設置generateWordParts或是generateNumberParts,那么全是字母或是全是數字的詞元就會不被過濾。他們還會受到連接選項的進一步影響。
- 連接多個全字母的詞元,設置catenateWords(比如wi-fi連接為wifi)。如果generateWordParts設置了,這個例子還是會產生wi和fi,反過來不成立。catenateNumbers工作方式也是相似的。catenateAll會考慮連接所有的詞到一起。
- 要保留原始的詞,設置preserveOriginal。
下面是一個對上面選項的解釋的例子:
WiFi-802.11b 切為 Wi,Fi,WiFi,802,11,80211,b,WiFi80211b, WiFi-802.11b
Stemming
詞干化是去除詞尾變化或是有時將派生詞變回它們的詞干——基本形的過程。比如,一種詞干化算法可能會將Riding和Rides轉化為Ride。詞干化有助於提高結果召回率,但是會對准確率造成負影響。如果你是處理普通文本,你用詞干化會提高你的搜索質量。但是如果你要處理的文本都是名詞,比如在MusicBrainz中的藝術家名字,那么深度的詞干化可能會影響結果。如果你想提高搜索的准確率,並且不降低完整率,那么你可以考慮將數據索引到兩個域,其中一個進行詞干化,另一個不進行詞干化,在搜索時查找這兩個域。
大多詞干器產生的詞干化的詞元都不再是一個拼寫合法的單詞,比如Bunnies會轉化為Bunni,而不是Bunny,Quote轉化為Quot,你可以在Solr的文本分析頁面看到這些結果。如果在索引和查找時都進行詞干化,那么是不會影響搜索的。但是一個域詞干化之后,就無法進行拼寫檢查,通配符匹配,或是輸入提示,因為這些特性要直接用索引中的詞。
下面是一些適用於英文的詞干器:
- SnowballPorterFilterFactory:這個詞干器允許選擇多種詞干器算法,這些詞干器算法是由一個名為Snowball的程序產生的。你可以在language屬性中指定你要選擇的詞干器。指定為English會使用Porter2算法,它比原生的Porter的算法有一點點改進。指定為Lovins會使用Lovins算法,它比起Porter有一些改進,但是運行速度太慢。
- PorterStemFIlterFactory:它是原生的英語Porter算法,它比SnowBall的速度快一倍。
- KStemFilterFactory:這個英語詞干器沒有Porter算法激進。也就是在很多Porter算法認為應該詞干化的時候,KSterm會選擇不進行詞干化。我建議使用它為默認的英語詞干器。
- EnglishMinimalStemFilterFactory:它是一個簡單的詞干器,只處理典型的復數形式。不同於多數的詞干器,它詞干化的詞元是拼寫合法的單詞,它們是單數形式的。它的好處是使用這個詞干器的域可以進行普通的搜索,還可以進行搜索提示。
Correcting and augmenting stemming
上面提到的詞干器都是使用算法進行詞干化,而不是通過詞庫進行詞干化。語言中有許多的拼寫規則,所以算法型的詞干器是很難做到完美的,有時在不應該進行詞干化的時候,也進行了詞干化。
如果你發現了一些不應該進行詞干化的詞,你可以先使用KeywordMarkerFilter詞干器,並在它的protected屬性中指定不需要詞干化的詞元文件,文件中一行一個詞元。還有ignoreCase布爾選項。一些詞干器有或以前有protected屬性有相似的功能,但這種老的方式不再建議使用。
如果你需要指定一些特定的單詞如何被詞干化,就先使用StemmerOverrideFilter。它的dictionary屬性可以指定一個在conf目錄下的UTF-8編碼的文件,文件中每行兩個詞元,用tab分隔,前面的是輸入詞元,后面的是詞干化后的詞元。它也有ignoreCase布爾選項。這個過濾器會跳過KeywordMarkerFilter標記過的詞元,並且它會標記它替換過的詞元,以使后面的詞干器不再處理它們。
下面是三個詞干器鏈在分析器中配置的示例:
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt" /> <filter class="solr.StemmerOverrideFilterFactory" dictionary="stemdict.txt" /> <filter class="solr.PorterStemFilterFactory" />
Synonyms
進行同義詞處理的目的是很好理解的,在搜索時搜索所用的關鍵詞可能本身並不匹配文檔中的任何一個詞,但文檔中有這個搜索關鍵詞的同義詞,但一般來講你還是想匹配這個文檔的。當然,同義詞並一定不是按字典意義上同義詞,它們可以是你應該中特定領域中的同義詞。
這下一個同義詞的分析器配置:
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
synonyms的屬性值是在conf目錄下的一個文件。設置ignoreCase為true在查找同義詞時忽略大小寫。
在我們討論expand選項前,我們考慮一個例子。同義詞文件是一行行的。下面是一個顯式映射的例子,映射用=>符號表示:
i-pod, i pod =>ipod
這表示如果在輸入詞元流中如果發現i-pod(一個詞元)或是i pod(兩個詞元),都會替換為ipod。替換的同義詞也可以是多個詞元。逗號是分隔多個同義詞之間的分隔符,同義詞的詞元間用空格分隔。如果你要實現自定義的不用空格分隔的格式,有一個tokenizerFactory屬性,但它極少被使用。
你也可能看到配置文件里是這樣的格式:
ipod, i-pod, i pod
配置文件里沒有=>符號,它的意義由expand參數來決定,如果expand為true,它會被解釋為下面的顯式映射:
ipod, i-pod, i pod =>ipod, i-pod, i pod
如果expand設置為false,它就變為下面的顯式映射,第一個同義詞為替換同義詞:
ipod, i-pod, i pod =>ipod
在多行中指定多個詞替換為共一同義詞是允許的。如果一個源同義詞已經被規則替換了,另一個規則替換這個替換后詞,則這兩個規則可以合並。
Index-time versus query-time, and to expand or not
如果你要進行同義詞擴展,你可以在索引時或是查詢時進行同義處理。但不要在索引和查詢時都處理,這樣處理會得到正確的結果,但是會減慢處理速度。我建議在索引時進行擴展,因為在查詢時進行會有下面的問題:
- 一個源同義詞包含多個詞元(比如:i pod)不會在查詢時被查詢時被識別,因為查詢解析器會在分析器處理之前就對空格進行切分。
- 如果被匹配的一個同義詞在所有文檔中很少出現,那么Lucene打分算法中的IDF值會很高,這會使得得分不准確。
- 前綴,通配符查詢不會進行文本分析,所以不會匹配同義詞。
但是任何在索引時進行的分文本處理都是不靈活的。因為如果改變了同義詞則需要完全重建索引才能看到效果。並且,如果在索引時進行擴展,索引會變大,如果你使用WordNet類似的同義詞規則,可能索引大到你不能接受,所以你在同義詞擴展規則上應該選擇一個合理的度,但是我通常還是建議在索引時擴展。
你也許可以采用一種混合策略。比如,你有一個很大的索引,所以你不想對它經常重建,但是你需要使新的同義詞迅速生效,所以你可以將新的同義詞在查詢時和索引時都使用。當全量索引重建完成后,你可以清空查詢同義詞文件。也許你喜歡查詢時進行同義詞處理,但你無法處理個別同義詞有空格的情況,你可以在索引時處理這些個別的同義詞。
Stop Words
StopFilterFactory是一個簡單的過濾器,它是過濾掉在配置中指定的文件中的停詞(stop words),這個文件在conf目錄下,可以指定忽略大小寫。
<filter class="solr.StopFilterFactory" words="stopwords.txt" ignoreCase="true"/>
如果文檔中有大量無意義的詞,比如“the”,“a”,它們會使索引變大,並在使用短語查詢時降低查詢速度。一個簡單的方法是將這些詞經常出現的域中過濾掉,在包含多於一句(sentence)的內容的域中可以考慮這種作法,但是如果把停詞過濾后,就無法對停詞進行查詢了。所以如果你要使用,應該在索引和查詢分析器鏈中都使用。這通常是可以接受的,但是在搜索“To be or not to be”這種句子時,就會有問題。對停詞理想的做法是不要去過濾它們,以后介紹CommonGramsFilterFactory來解決這個問題。
Solr自帶了一個不錯的英語停詞集合。如果你在索引非英語的文本,你要用自己指定停詞。要確定你索引中有哪些詞經常出現,可以從Solr管理界面點擊進入SCHEMA BROWSER。你的字段列表會在左邊顯示,如果這個列表沒有立即出現,請耐心點,因為Solr要分析你索引里的數據,所以對於較大的索引,會有一定時間的延時。請選擇一個你知道包含有大量文本的域,你可以看到這個域的大量統計,包括出現頻率最高的10個詞。
Phonetic sound-like analysis
語音轉換(phonetic translation)可以讓搜索進行語音相似匹配。語音轉化的過濾器在索引和查詢時都將單詞編碼為phoneme。有五種語音編碼算法:Caverphone,DoubleMetaphone,Metaphone,RefinedSoundex和Soundex。有趣的是,DoubleMetaphone似乎是最好的選擇,即使是用在非英語文本上。但也許你想通過實驗來選擇算法。RefinedSoundex聲稱是拼寫檢查應用中最適合的算法。然而,Solr當前無法在它的拼寫檢查組件中使用語音分析。
下面是在schema.xml里推薦使用的語音分析配置。
<!-- for phonetic (sounds-like) indexing --> <fieldType name="phonetic" class="solr.TextField" positionIncrementGap="100" stored="false" multiValued="true"> <analyzer> <tokenizer class="solr.WhitespaceTokenizerFactory"/> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="0" catenateWords="1" catenateNumbers="0" catenateAll="0"/> <filter class="solr.DoubleMetaphoneFilterFactory" inject="false" maxCodeLength="8"/> </analyzer> </fieldType>
注意,語音編碼內部忽略大小寫。
在MusicBrainz Schema中,有一個名為a_phonetic使用這個域類型,它的域值是通過copyField拷貝的Artist名字。第四章你會學習到dismax查詢解析器可以讓你對不同的域賦不同的boost,同時查找這幾個域。你可以不僅僅搜索a_name域,你還可以用一個較低的boost來搜索a_phonenic域,這樣就可以進行兼顧語音搜索了。
用Solr的分析管理頁面,你可以看到這它將Smashing Pumpkins編碼為SMXNK|XMXNK PMPKNS(|表示兩邊的詞元在同一位置)。編碼后的內容看起來沒什么意義,實際它是為比較相似語音的效率而設計。
上面配置示例中使用的DoubleMetaphoneFilterFactory分析過濾器,它有兩個選項:
- Inject:默認設置為true,為true會使原始的單詞直接通過過濾器。這會影響其它的過濾器選項,查詢,還可能影響打分。所以最好設置為false,並用另一個域來進行語音索引。
- maxCodeLength:最大的語音編碼長度。它通常設置為4。更長的編碼會被截斷。只有DoubleMetaphone支持這個選項。
如果要使用其它四個語音編碼算法,你必須用這個過濾器:
<filter class="solr.PhoneticFilterFactory" encoder="RefinedSoundex" inject="false"/>
其中encoder屬性值是第一段中的幾個算法之一。
Substring indexing and wildcards
通常,文本索引技術用來查找整個單詞,但是有時會查找一個索引單詞的子串,或是某些部分。Solr支持通配符查詢(比如mus*ainz),但是支持它需要在索引時過行一定的處理。
要理解Lucene在索引時內部是如何支持通配符查詢是很有用的。Lucene內部會在已經排序的詞中先查詢非通配符前綴(上例中的mus)。注意前綴的長度與整個查詢的時間為指數關系,前綴越短,查詢時間越長。事實上Solr配置Lucene中不支持以通配符開頭的查詢,就是因為效率的原因。另外,詞干器,語音過濾器,和其它一些文本分析組件會影響這種查找。比如,如果running被詞干化為run,而runni*無法匹配。
ReversedWildcardFilter
Solr不支持通配符開頭的查詢,除非你對文本進行反向索引加上正向加載,這樣做可以提高前綴很短的通配符查詢的效率。
下面的示例應該放到索引文本分析鏈的最后:
<filter class="solr.ReversedWildcardFilterFactory" />
你可以在JavaDocs中了解一些提高效率的選項,但默認的就很不錯:http://lucene.apache.org/solr/api/org/apache/solr/analysis/ReversedWildcardFilterFactory.html
Solr不支持查詢中同時有配置符在開頭和結尾,當然這是出於性能的考慮。
N-grams
N-gram分析會根據配置中指定的子中最小最大長度,將一個詞的最小到最大的子串全部得到,比如Tonight這個單詞,如果NGramFilterFactory配置中指定了minGramSize為2,maxGramSize為5,那么會產生下面的索引詞:(2-grams):To, on , ni, ig, gh, ht,(3-grams):ton, oni, nig, ight, ght, (4-grams):toni, onig, nigh, ight, (5-grams):tonig,onigh, night。注意Tonight完整的詞不會產生,因為詞的長度不能超過maxGramSize。N-Gram可以用作一個詞元過濾器,也可以用作為分詞器NGramTokenizerFactory,它會產生跨單詞的n-Gram。
下是是使用n-grams匹配子串的推薦配置:
<fieldType name="nGram" class="solr.TextField" positionIncrementGap="100" stored="false" multiValued="true"> <analyzer type="index"> <tokenizer class="solr.StandardTokenizerFactory"/> <!-- potentially word delimiter, synonym filter, stop words, NOT stemming --> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.NGramFilterFactory" minGramSize="2" maxGramSize="15"/> </analyzer> <analyzer type="query"> <tokenizer class="solr.StandardTokenizerFactory"/> <!-- potentially word delimiter, synonym filter, stop words, NOT stemming --> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> </fieldType>
注意n-Gram只在索引時進行,gram的大小配置是根據你想進行匹配子串的長度而決定 的(示例中是最小是2,最長是15)。
N_gram分析的結果可以放到另一個用於匹配子串的域中。用dismaxquery解析器支持搜索多個域,在搜索匹配這個子串的域可以設置較小的boost。
另一個變形的是EdgeNGramTokenizerFactory和EdgeNGramFilterFactory,它會忽略輸入文本開頭或結尾的n-Gram。對過濾器來說,輸入是一個詞,對分詞器來說,它是整個字符流。除了minGramSize和maxGramSize之后,它還有一個side參數,可選值為front和back。如果只需要前綴匹配或是后綴匹配,那邊EdgeNGram分析是你所需要的了。
N-gram costs
n-Gram的代價很高,前面的例子中Tonight有15個子串詞,而普通的文本分析的結果一般只有一個詞。這種轉換會產生很多詞,也就需要更長的時間去索引。以MusicBrainz Schema為例,a_name域以普通方式索引並stored,a_ngram域對a_name中的值進行n-Gram分析,子串的長度為2-15。它不是一個stored域,因為Artist的名字已經保存在a_name中了。
a_name a_name + a_ngram
Increase
Indexing Time 46 seconds 479 seconds > 10x
Disk Size 11.7 MB 59.7 MB > 5x
Distinct Terms 203,431 1,288,720 > 6x
上表給出了只索引a_name和索引a_name和a_ngram的統計信息。注意索引時間增加了10倍,而索引大小增加了5倍。注意,這才只是一個域。
注意如果變大minGramSize的大小,nGram的代價會小很多。Edge nGraming也代價也會小,因為它只關心開頭或結尾的nGram。基於nGram的分詞器無疑會比基於nGram的過濾器代碼要高,因為分詞器將產生帶空格的詞,然而,這種方式可以支持跨詞的通配符。
Sorting Text
通常,搜索結果是由神奇的score偽字段進行排序的,但是有時候也會根據某個字段的值進行排序。除了對結果進行排序,它還有許多的作用,進行區間查詢和對Facet結果進行排序。
MusicBrainz提供了對Artist和Lable名稱進行排序的功能。排序的版本會將原來的名字中的某些詞,比如“The”移到最后,用逗號分隔。我們將排序的名字域設置為indexed,但不是stored,因為我們要對它進行排序,但不進行展示,這與MusicBrainz所實現的有所不同。記住indexed和stored默認設置為true。因為有些文本分析組件會限制text域的排序功能,所以在你的Schema中要用於排序的文本域應該拷貝到另一個域中。copyField功能會很輕松地完成這個任務。String類型不進行文本分析,所以它對我們的MusicBrainz情況是非常適合的。這樣我們就支持了對Artist排序,而沒有派生任何內容。
Miscellaneous token filters
Solr還包括許多其它的過濾器:
- ClassicFilterFactory:它與ClassicTokenizer配置,它會移除縮寫詞中的點號和末尾的’s:"I.B.M. cat's" => "IBM", "cat"
- EnglishProcessiveFilterFactory:移除’s。
- TrimFilterFactory:移除開頭和結尾的空格,這對於臟數據域進行排序很有用。
- LowerCaseFilterFactory:小寫化所有的文本。如果你要用WordDelimeterFilterFactory中的大小寫轉換切分功能,你就不要將這個過濾器放前面。
- KeepWordFilterFactory:只保留指定配置文件中的詞:<filter class="solr.KeepWordFilterFactory" words="keepwords.txt" ignoreCase="true"/> 如果你想限制一個域的詞匯表,你可以使用這個過濾器。
- LengthFilterFactory:過濾器會過濾掉配置長度之間的詞:<filter class="solr.LengthFilterFactory" min="2" max="5" />
- LimitTokenCountFilterFactory:限制域中最多有多少個詞元,數量由maxTokenCount屬性指定。Solr的solrconfig.xml中還有<maxFieldLength>設置,它對所有域生效,可以將它注釋掉,不限制域中的詞元個數。即使沒有強制限制,你還要受Java內存分配的限制,如果超過內存分配限制,就會拋出錯誤。
- RemoveDuplicatestTokenFilterFactory:保存重復的詞不出現在同一位置。當使用同義詞時這是可能發生的。如果還要進行其它的分本分析 ,你應該把這個過濾器放到最后。
- ASCIIFoldingFilterFactory:參見前面的“Character filter”一節中的MappingCharFilterFactory。
- CapitalizationFilterFactory:根據你指定的規則大寫每個單詞。你可以在http://lucene.apache.org/solr/api/org/apache/solr/analysis/CapitalizationFilterFactory.html中了解更多內容。
- PatternReplaceFilterFactory:使用正則表達式查找替換。比如:<filter class="solr.PatternReplaceFilterFactory" pattern=".*@(.*)" replacement="$1" replace="first" /> 這個例子是處理e-mail地址域,只取得地址中的域名。Replacement是正則表達式中的組,但它也可以是一個字符串。如果replace屬性設置為first,表示只替換第一個匹配內容。如果replace設置為all,這也是默認選項,則替換全部。
- 實現你自己的過濾器:如果現有的過濾器無法滿足你的需求。你可以打開Solr的代碼看一下里面是如何實現的。在你深入之前,你看PatternReplaceFilterFactory的實現是如此簡單。作為一個初學者,可以看一下在本書提供的補充資料中schema.xml中的rType域類型。
- 還有其它各式各樣的Solr過濾器,你可以在http://lucene.apache.org/solr/api/org/apache/solr/analysis/TokenFilterFactory.html 中了解所有的過濾器。