标签:
文本分析时搜索引擎的核心工作之一,对文本包含许多处理步骤,比如:分词、大写转小写、词干化、同义词转化等。简单的说,文本分析就说将一个文本字段的值转为一个一个的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。我不建议使用它。
在我们深入特定分析组件的细节之前,有必要去熟悉Solr的分析页面,它是一个很好的实验和查错工具,绝对不容错过。你将会用它来验证不同的分析配置,来找到你最想要的效果,你还可以用它来找到你认为应该会匹配的查询为什么没有匹配。在Solr的管理页面,你可以看到一个名为[Analysis]的链接,你进入后,会看到下面的界面。
界面上的第一个选项是必选的,你可选择直接通过字段类型名称来选择类型,你也可以间接地通过一个字段的名字来选择自端类型。在上面的示例中,我选择了title字段
通过Schema Browser可以看到这个字段类型是 text_general
点击灰色的 text_general,可以看到这个字段的分析器中定义的分词器和过滤器
接下来,你可以分析索引或是查询文本,也可以两者同时分析。你需要输入些文本到文本框中以进行分析。将字段值放到Index文本框中,将查询文本放入Query文本框中,点击Analyze按钮看到一下文本处理结果,因为还没有中文处理,所以中文都被一个字一个字的分开处理了。
你可以选中verbose output来查看处理的详细信息,我希望你能自己试一下。
上图中每一行表示分析器处理链上的每一步的处理结果。比如第三个分析组件是LowerCaseFilter,它的处理结果就在第三行。前面的ST/SF/LCF应该是分词器和过滤器的简称。
下面我们接着来详细看看有哪些分词器和过滤器吧。
字符过滤器在<charFilter>元素中定义,它是对字符流进行处理。字符过滤器种类不多。这个特性只有下面第一个介绍的比较常见。
分词器在<tokenizer>元素中定义,它将一个字符流切分成词元序列,大部分它会去除不重要的符号,比如空字符和连接符号。
一个分析器有且只应有一个分词器,你可选的分词器如下:
还有用于其它语言的分词器,比如中文和俄语,还有ICUTokenizer会检测语言。另外NGramtokenizer会在后面讨论。可以在http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters中找到更多内容。
它也许不是一个正式的分词器,但是这个名为WordDeilimiterFilter的词元过滤器本质上是一个分词器。
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/>
上面并没有给出所有的选项,这个过滤器可以通过多种配置指定如切分和连接合成词,并有多种定义合成词的方法。这个过滤器通常与WhitespaceTokenizer配合,而不是StandardTokenizer。这个过滤器的配置中1是设置,0是重置。
WordDelimiterFilter先通过配置选项中的定义切分词元:
此时,如果下面的选项没有设置,上面这些切分后的词都要被过滤掉。因为默认下面的选项设置为false,你一般至少要设置下面其中一项。
下面是一个对上面选项的解释的例子:
WiFi-802.11b 切为 Wi,Fi,WiFi,802,11,80211,b,WiFi80211b, WiFi-802.11b
词干化是去除词尾变化或是有时将派生词变回它们的词干——基本形的过程。比如,一种词干化算法可能会将Riding和Rides转化为Ride。词干化有助于提高结果召回率,但是会对准确率造成负影响。如果你是处理普通文本,你用词干化会提高你的搜索质量。但是如果你要处理的文本都是名词,比如在MusicBrainz中的艺术家名字,那么深度的词干化可能会影响结果。如果你想提高搜索的准确率,并且不降低完整率,那么你可以考虑将数据索引到两个域,其中一个进行词干化,另一个不进行词干化,在搜索时查找这两个域。
大多词干器产生的词干化的词元都不再是一个拼写合法的单词,比如Bunnies会转化为Bunni,而不是Bunny,Quote转化为Quot,你可以在Solr的文本分析页面看到这些结果。如果在索引和查找时都进行词干化,那么是不会影响搜索的。但是一个域词干化之后,就无法进行拼写检查,通配符匹配,或是输入提示,因为这些特性要直接用索引中的词。
下面是一些适用于英文的词干器:
上面提到的词干器都是使用算法进行词干化,而不是通过词库进行词干化。语言中有许多的拼写规则,所以算法型的词干器是很难做到完美的,有时在不应该进行词干化的时候,也进行了词干化。
如果你发现了一些不应该进行词干化的词,你可以先使用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" />
进行同义词处理的目的是很好理解的,在搜索时搜索所用的关键词可能本身并不匹配文档中的任何一个词,但文档中有这个搜索关键词的同义词,但一般来讲你还是想匹配这个文档的。当然,同义词并一定不是按字典意义上同义词,它们可以是你应该中特定领域中的同义词。
这下一个同义词的分析器配置:
<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
在多行中指定多个词替换为共一同义词是允许的。如果一个源同义词已经被规则替换了,另一个规则替换这个替换后词,则这两个规则可以合并。
如果你要进行同义词扩展,你可以在索引时或是查询时进行同义处理。但不要在索引和查询时都处理,这样处理会得到正确的结果,但是会减慢处理速度。我建议在索引时进行扩展,因为在查询时进行会有下面的问题:
但是任何在索引时进行的分文本处理都是不灵活的。因为如果改变了同义词则需要完全重建索引才能看到效果。并且,如果在索引时进行扩展,索引会变大,如果你使用WordNet类似的同义词规则,可能索引大到你不能接受,所以你在同义词扩展规则上应该选择一个合理的度,但是我通常还是建议在索引时扩展。
你也许可以采用一种混合策略。比如,你有一个很大的索引,所以你不想对它经常重建,但是你需要使新的同义词迅速生效,所以你可以将新的同义词在查询时和索引时都使用。当全量索引重建完成后,你可以清空查询同义词文件。也许你喜欢查询时进行同义词处理,但你无法处理个别同义词有空格的情况,你可以在索引时处理这些个别的同义词。
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 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分析过滤器,它有两个选项:
如果要使用其它四个语音编码算法,你必须用这个过滤器:
<filter class="solr.PhoneticFilterFactory" encoder="RefinedSoundex" inject="false"/>
其中encoder属性值是第一段中的几个算法之一。
通常,文本索引技术用来查找整个单词,但是有时会查找一个索引单词的子串,或是某些部分。Solr支持通配符查询(比如mus*ainz),但是支持它需要在索引时过行一定的处理。
要理解Lucene在索引时内部是如何支持通配符查询是很有用的。Lucene内部会在已经排序的词中先查询非通配符前缀(上例中的mus)。注意前缀的长度与整个查询的时间为指数关系,前缀越短,查询时间越长。事实上Solr配置Lucene中不支持以通配符开头的查询,就是因为效率的原因。另外,词干器,语音过滤器,和其它一些文本分析组件会影响这种查找。比如,如果running被词干化为run,而runni*无法匹配。
Solr不支持通配符开头的查询,除非你对文本进行反向索引加上正向加载,这样做可以提高前缀很短的通配符查询的效率。
下面的示例应该放到索引文本分析链的最后:
<filter class="solr.ReversedWildcardFilterFactory" />
你可以在JavaDocs中了解一些提高效率的选项,但默认的就很不错:http://lucene.apache.org/solr/api/org/apache/solr/analysis/ReversedWildcardFilterFactory.html
Solr不支持查询中同时有配置符在开头和结尾,当然这是出于性能的考虑。
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的代价很高,前面的例子中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的过滤器代码要高,因为分词器将产生带空格的词,然而,这种方式可以支持跨词的通配符。
通常,搜索结果是由神奇的score伪字段进行排序的,但是有时候也会根据某个字段的值进行排序。除了对结果进行排序,它还有许多的作用,进行区间查询和对Facet结果进行排序。
MusicBrainz提供了对Artist和Lable名称进行排序的功能。排序的版本会将原来的名字中的某些词,比如“The”移到最后,用逗号分隔。我们将排序的名字域设置为indexed,但不是stored,因为我们要对它进行排序,但不进行展示,这与MusicBrainz所实现的有所不同。记住indexed和stored默认设置为true。因为有些文本分析组件会限制text域的排序功能,所以在你的Schema中要用于排序的文本域应该拷贝到另一个域中。copyField功能会很轻松地完成这个任务。String类型不进行文本分析,所以它对我们的MusicBrainz情况是非常适合的。这样我们就支持了对Artist排序,而没有派生任何内容。
Solr还包括许多其它的过滤器:
标签:
原文地址:http://www.cnblogs.com/zhoujg/p/5054122.html