企业级搜索引擎Solr Schema和文本分析(Schema and Text Analysis)

Correcting and augmenting stemming
上面提到的词干器都是使用算法进行词干化,而不是通过词库进行词干化。语言中有许多的拼写规则,所以算法型的词干器是很难做到完美的,有时在不应该进行词干化的时候,也进行了词干化。

如果你发现了一些不应该进行词干化的词,你可以先使用KeywordMarkerFilter词干器,并在它的protected属性中指定不需要词干化的词元文件,文件中一行一个词元。还有ignoreCase布尔选项。一些词干器有或以前有protected属性有相似的功能,但这种老的方式不再建议使用。

如果你需要指定一些特定的单词如何被词干化,就先使用StemmerOverrideFilter。它的dictionary属性可以指定一个在conf目录下的UTF-8编码的文件,文件中每行两个词元,用tab分隔,前面的是输入词元,后面的是词干化后的词元。它也有ignoreCase布尔选项。这个过滤器会跳过KeywordMarkerFilter标记过的词元,并且它会标记它替换过的词元,以使后面的词干器不再处理它们。

下面是三个词干器链在分析器中配置的示例:[xml]

<filter class="solr.KeywordMarkerFilterFactory"

protected="protwords.txt" />

<filter class="solr.StemmerOverrideFilterFactory"

dictionary="stemdict.txt" />

<filter class="solr.PorterStemFilterFactory" />[/xml]

Synonyms
进行同义词处理的目的是很好理解的,在搜索时搜索所用的关键词可能本身并不匹配文档中的任何一个词,但文档中有这个搜索关键词的同义词,但一般来讲你还是想匹配这个文档的。当然,同义词并一定不是按字典意义上同义词,它们可以是你应该中特定领域中的同义词。

这下一个同义词的分析器配置:[xml]

<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt"

ignoreCase="true" expand="true"/>[/xml]

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目录下,可以指定忽略大小写。[xml]

<filter class="solr.StopFilterFactory" words="stopwords.txt" ignoreCase="true"/>[/xml]

如果你要使用,应该在索引和查询分析器链中都使用。

如果文档中有大量无意义的词,比如“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里推荐使用的语音分析配置。[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>[/xml]

注意,语音编码内部忽略大小写。

在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支持这个选项。

如果要使用其它四个语音编码算法,你必须用这个过滤器:[xml]

<filter class="solr.PhoneticFilterFactory" encoder="RefinedSoundex" inject="false"/>[/xml]

其中encoder属性值是第一段中的几个算法之一。

Substring indexing and wildcards
通常,文本索引技术用来查找整个单词。但是有时会查找一个索引单词的子串,或是某些部分。Solr支持通配符查询(比如mus*ainz),但是支持它需要在索引时过行一定的处理。要理解Lucene在索引时内部是如何支持通配符查询是很有用的。Lucene内部会在已经排序的词中先查询非通配符前缀(上例中的mus)。注意前缀的长度与整个查询的时间为指数关系,前缀越短,查询时间越长。事实上Solr配置Lucene中不支持以通配符开头的查询,就是因为效率的原因。另外,词干器,语音过滤器,和其它一些文本分析组件会影响这种查找。比如,如果running被词干化为run,而runni*无法匹配。

ReversedWildcardFilter
Solr不支持通配符开头的查询,除非你对文本进行反向索引加上正向加载。这样做可以提高前缀很短的通配符查询的效率。下面的示例应该放到索引文本分析链的最后:[xml]

<filter class="solr.ReversedWildcardFilterFactory" />[/xml]

你可以在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匹配子串的推荐配置:[xml]

<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>[/xml]

注意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中了。[xml]

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[/xml]

上表给出了只索引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排序,而没有派生任何内容。注意MusicBrainz Schema不支持Release名称排序,但我们还是选择支持。其中一个选择还是用string域类型。这是可以的,但是也许你想小写内容,移除标点,并将多个空格合为一个(如果数据不干净)。还甚至还可以用PatternReplaceFilterFactory将“The”这些词移到后面。这些选择取决于你的爱好。为了我们示例更精彩,我们选择后一种方式。我们用title_sort类型来进行这些工作。

另外,Lucene是根据Unicode编码值进行排序。你不会注意到排序顺序的问题。但如果你想根据不同语言进行另一种规则的排序,你可以尝试CollationKeyFilterFactory。因为它不常用,并且文档很详细,所以你可以直接看文档: http://wiki.apache.org/solr/UnicodeCollation。

Miscellaneous token filters
Solr还包括许多其它的过滤器:

ClassicFilterFactory:它与ClassicTokenizer配置,它会移除缩写词中的点号和末尾的’s:

“I.B.M. cat’s” => “IBM”, “cat”

EnglishProcessiveFilterFactory:移除’s。

TrimFilterFactory:移除开头和结尾的空格,这对于脏数据域进行排序很有用。

LowerCaseFilterFactory:小写化所有的文本。如果你要用WordDelimeterFilterFactory中的大小写转换切分功能,你就不要将这个过滤器放前面。

KeepWordFilterFactory:只保留指定配置文件中的词:[xml]

<filter class="solr.KeepWordFilterFactory" words="keepwords.txt"

ignoreCase="true"/>[/xml]

如果你想限制一个域的词汇表,你可以使用这个过滤器。

LengthFilterFactory:过滤器会过滤掉配置长度之我的词:[xml]

<filter class="solr.LengthFilterFactory" min="2" max="5" />[/xml]

LimitTokenCountFilterFactory:限制域中最多有多少个词元,数量由maxTokenCount属性指定。Solr的solrconfig.xml中还有设置,它对所有域生效,可以将它注释掉,不限制域中的词元个数。即使没有强制限制,你还要受Java内存分配的限制,如果超过内存分配限制,就会抛出错误。

RemoveDuplicatestTokenFilterFactory:保存重复的词不出现在同一位置。当使用同义词时这是可能发生的。如果还要进行其它的分本分析 ,你应该把这个过滤器放到最后。

ASCIIFoldingFilterFactory:参见前面的“Character filter”一节中的MappingCharFilterFactory。

CapitalizationFilterFactory:根据你指定的规则大写每个单词。你可以在http://lucene.apache.org/solr/api/org/apache/solr/analysis/CapitalizationFilterFactory.html中了解更多内容。

PatternReplaceFilterFactory:使用正则表达式查找替换。比如:[xml]

<filter class="solr.PatternReplaceFilterFactory" pattern=".*@(.*)"

replacement="$1" replace="first" />[/xml]

这个例子是处理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 中了解所有的过滤器。
solr过滤器的知识可以看solr的wiki:
http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters#Specifying_an_Analyzer_in_the_schema

Avatar photo

About Blackford

这是个最好的时代,这是个最坏的时代,这是个充满希望的春天,这是个令人绝望的冬天,我们前面什么都有,我们前面什么都没有。梦想,让我们一次次的走远,又一次次的回头,一个关于人生的梦想还在不断奔跑,带着喜悦和疼痛,不过一切才刚刚开始,并且直到今天也远远没有结束
This entry was posted in 架构运维 and tagged , . Bookmark the permalink.

发表评论

电子邮件地址不会被公开。 必填项已用*标注