文本中包含许多文本处理步骤,比如:分词,大写转小写,词干化,同义词转化和许多的文本处理。
文本分析既用于索引时对一文本域的处理,也用于查询时查询字符串的文本处理。文本处理对搜索引擎的搜索结果有着重要的影响,特别是对如召回率的影响。
文本分析是将一个文本域的值转化为一个词序列。词是Lucene实际索引和搜索时的最小单元。分析作用于索引时原始的输入值,将转化后的词顺序保存到Lucene的索引结构中。文本分析也同样作用于查询时所输入的查询串中的查询词和查询短语,转化后的词将用于查询Lucene的索引。不用进行文本分析的查询是前缀查询,通配符查询和模糊查询。
看下面的配置:
1 <fieldType name="text_en_splitting" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="true"> 2 3 <analyzer type="index"> 4 5 <!--<charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ISOLatin1Accent.txt"/>--> 6 7 <tokenizer class="solr.WhitespaceTokenizerFactory"/> 8 9 <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords_en.txt" enablePositionIncrements="true"/> 10 11 <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/> 12 13 <filter class="solr.LowerCaseFilterFactory"/> 14 15 <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/> 16 17 <filter class="solr.PorterStemFilterFactory"/> 18 19 </analyzer> 20 21 <analyzer type="query"> 22 23 <!--<charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ISOLatin1Accent.txt"/>--> 24 25 <tokenizer class="solr.WhitespaceTokenizerFactory"/> 26 27 <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/> 28 29 <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords_en.txt" enablePositionIncrements="true" 30 31 /> 32 33 <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" 34 35 splitOnCaseChange="1"/> 36 37 <filter class="solr.LowerCaseFilterFactory"/> 38 39 <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/> 40 41 <filter class="solr.PorterStemFilterFactory"/> 42 43 </analyzer> 44 45 </fieldType>
这个配置例子定义了两个分析器,每个都指定了将文本转换为词序列的处理步骤。Type属性可以指定为index或是query值,分别表示是索引时用的分析器,和查询时所用的分析器。如果在索引和查询时使用相同的分析器,你可以不指定type属性值。上面示例中的两个分析器之间的区别很小。
分析器的配置中可以选用一个或多个字符过滤器(character filter),字符过滤器是对原始文本进行字符流级别的操作。它通常可以用于大小写转化,去除字母上标等等。在字符过滤器之后是分词器(Tokenizer),它是必须要配置的。分析器会使用分词器将字符流切分成词元(Token)系列,通常用在空格处切分这种简单的算法。后面的步骤是可选的,比如词元过滤器(Token Filter)(一般简称过滤器),会对词元(Token)进行许多种操作。最后产生的词元会被称为词(Term),即用于Lucene实际索引和查询的单位。注意有些词元过滤器如WordDelimeterFilterFactory也进行分词操作,但是它们是在词元上操作,而真正的分词器是操作一个字符流。
最后,我有必须对autoGeneratePhraseQueries布尔属性补充两句,这个属性只能用于文本域。如果在查询文本分析时产生了多个词元,比如Wi-Fi分词为Wi和Fi,那么默认情况下它们只是两个不同的搜索词,它们没有位置上的关系。但如果autoGeneratePhraseQueries被设置,那么这两个词元就构造了一个词组查询,即“WiFi”,所以索引中“WiFi”必须相邻才能被查询到。在新Solr版本中,默认它被设置为false。我不建议使用它。
Character Filter
字符过滤器在<charFilter>元素中定义,它是对字符流进行处理。字符过滤器种类不多。这个特性只有下面第一个介绍的比较常见。
l MappingCharFilterFactory:它将一个字符(或字符串)映射到另一个,也可以映射为空。换言之,它是一个查找-替换的功能。在mapping属性中你可以指定一个配置文件。Solr的示例配置中包括了两个有用的映射配置文件:
>> mapping-FoldToASCII.txt:一个丰富的将non-ASCII转化成ASCII的映射。如果想了解字符映射更多的细节,可以阅读这个文件顶部的注释。这个字符过滤器有一个类似的词元过滤器ASCIIFoldFilterFactory,这个词元过滤器运行速度更快,建议使用它。
>> maping-ISOLatinAccent.txt:一个更小的映射文件子集,它只能将ISO Latin1上标映射。FoldToASCII内容更丰富,所以不建议使用这个配置。
l HTMLStripCharFilterFactory:它用于HTML和XML,它不要求它们格式完全正确。本质上它是移除所有的标记,只留下文本内容。移除脚本内容和格式元素。转义过的特殊字符被还原(比如&)。
l PatternReplaceCharFilterFactory:根据pattern属性中的正则表达式进行查找,并根据replacement属性中的值进行替换。它的实现需要一个缓冲区容器,默认设置为10000个字符,可以通过maxBlockChars进行配置。分词器和词元过滤器中也有正则表达式组件。所以你应该只在会影响分词的影响下使用它,比如对空格进行处理。
Tokenization
分词器在<tokenizer>元素中定义,它将一个字符流切分成词元序列,大部分它会去除不重要的符号,比如空字符和连接符号。一个分析器有且只应有一个分词器。你可选的分词器如下:
l KeywordTokenizerFactory:这个分词器不进行任何分词!整个字符流变为单个词元。String域类型也有类似的效果,但是它不能配置文本分析的其它处理组件,比如大小写转换。任何用于排序和大部分Faceting功能的索引域,这个索引域只有能一个原始域值中的一个词元。
l WhitespaceTokenizerFactory:文本由空字符切分(即,空格,Tab,换行)。
l StandardTokenizerFactory:它是一个对大部分西欧语言通常的分词器。它从空白符和其它Unicode标准中的词分隔符处进行切分。空白符和分隔符会被移除。连字符也被认为是词的分隔符,这使得它不适合与WordDelimiterFilter一起用。
l UAX29URLEmailTokenizer:它表现的与StandardTokenizer相似,但它多了一个识别e-mail,URL并将它们视为单个词元的特性。
l ClassicTokenizerFactory:(曾经的StandardTokenizer)它是一个英语的通用分词器。对英语来说,它优于StandardTokenizer。它可以识别有点号的缩写词,比如I.B.M.。如果词元中包含数字它不会在连字符处分词,并可以将Email地址和主机名视为单个词元。并且ClassicFilter词元过滤器经常与这个分词器配合使用。ClassicFilter会移除缩写词中的点号,并将单引号(英语中的所有格)去除。它只能与ClassicTokenizer一起使用。
l LetterTokenizerFactory:这个分词器将相邻的字母(由Unicode定义)都视为一个词元,并忽略其它字符。
l LowerCaseTokenizerFactory:这个分词器功能上等同于LetterTokenizer加上LowerCaseFilter,但它运行更快。
l PatternTokenizerFactory:这个基于正则表达式的分词器可以以下面两种方式工作:
通过一个指定模式切分文本,例如你要切分一个用分号分隔的列表,你可以写:<tokenizer class="solr.PatternTokenizerFactory" pattern=";*" />.
只选择匹配的一个子集作为词元。比如:<tokenizer class="solr.PatternTokenizerFactory" pattern="\‘([^\‘]+)\‘" group="1" />。组属性指定匹配的哪个组将被视为词元。如果你输入的文本是aaa ‘bbb’ ‘ccc’,那么词元就是bbb和ccc。
l PathHierachyTokenizerFactory:这是一个可配置的分词器,它只处理以单个字符分隔的字符串,比如文件路径和域名。它在实现层次Faceting中很有用,或是可以过滤以某些路径下的文件。比如输入字符串是/usr/local/apache会被分词为三个词元:/usr,/usr/local,/usr/local/apache。这个分词器有下面四个选项:
Delimiter:分隔字符:默认为/
Replace:将分隔字符替换为另一字符(可选)
Reverse:布尔值表明是否层次是从右边开始,比如主机名,默认:false。
Skip:忽略开头的多少个词元,默认为0.
l 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先通过配置选项中的定义切分词元。(示例中右边以逗号分隔是处理后的词,选项默认全为true):
l 词间的分隔符切分:Wi-Fi切为Wi,Fi
l 字母和数据间的切分:SD500切为SD,500(如果设置splitOnNumerics)
l 忽略任何分隔符:/hello—there, dude切为hello, there, dude
l 移除所有格’s:David’s切为Divid(如果设置stemEnglishPocessive)
l 在小写到大小时切分:WiFi切为Wi,Fi(如果设置splitOnCaseChange)
此时,如果下面的选项没有设置,上面这些切分后的词都要被过滤掉。因为默认下面的选项设置为false,你一般至少要设置下面其中一项。
l 如果设置generateWordParts或是generateNumberParts,那么全是字母或是全是数字的词元就会不被过滤。他们还会受到连接选项的进一步影响。
l 连接多个全字母的词元,设置catenateWords(比如wi-fi连接为wifi)。如果generateWordParts设置了,这个例子还是会产生wi和fi,反过来不成立。catenateNumbers工作方式也是相似的。catenateAll会考虑连接所有的词到一起。
l 要保留原始的词,设置preserveOriginal。
下面是一个对上面选项的解释的例子:
WiFi-802.11b 切为 Wi,Fi,WiFi,802,11,80211,b,WiFi80211b, WiFi-802.11b
内部实现中,过滤器在寻找单词边界前对每个字符赋予一个类型(比如:字母,数字)。这个类型是由Unicode字符类型决定的。如果你想自定义过滤器对字符的类型归类,你可以在type选项中提供一个或多个映射文件。一个实现的例子是在Twitter中,你想将”#”和”@”视为类型ALPHA。要了解这个难解的特性,可以看SOLR-2059。
如果有一些词,你不想用过滤器处理,你可以在procected属性中指定包括这些词的配置文件。其它一些过滤器也有相似的特性。
在Solr的text_en_splitting域类型中使用WordDelimiterFilter是一个合理的方式:在索引和查询的时候都产生词和数字,但只在索引时进行连接操作,因为在查询时进行连接是多余的。
Stemming
词干化是去除词尾变化或是有时将派生词变回它们的词干——基本形的过程。比如,一种词干化算法可能会将Riding和Rides转化为Ride。词干化有助于提高结果召回率,但是会对准确率造成负影响。如果你是处理普通文本,你用词干化会提高你的搜索质量。但是如果你要处理的文本都是名词,比如在MusicBrainz中的艺术家名字,那么深度的词干化可能会影响结果。如果你想提高搜索的准确率,并且不降低召回率,那么你可以考虑将数据索引到两个域,其中一个进行词干化,另一个不进行词干化,在搜索时查找这两个域。
大多词干器产生的词干化的词元都不再是一个拼写合法的单词,比如Bunnies会转化为Bunni,而不是Bunny,Quote转化为Quot,你可以在Solr的文本分析页面看到这些结果。如果在索引和查找时都进行词干化,那么是不会影响搜索的。但是一个域词干化之后,就无法进行拼写检查,通配符匹配,或是输入提示。因为这些特性要直接用索引中的词。
下面是一些适用于英文的词干器:
l SnowballPorterFilterFactory:这个词干器允许选择多种词干器算法,这些词干器算法是由一个名为Snowball的程序产生的。你可以在language属性中指定你要选择的词干器。指定为English会使用Porter2算法,它比原生的Porter的算法有一点点改进。指定为Lovins会使用Lovins算法,它比起Porter有一些改进,但是运行速度太慢。
l PorterStemFIlterFactory:它是原生的英语Porter算法,它比SnowBall的速度快一倍。
l KStemFilterFactory:这个英语词干器没有Porter算法激进。也就是在很多Porter算法认为应该词干化的时候,KSterm会选择不进行词干化。我建议使用它为默认的英语词干器。
l EnglishMinimalStemFilterFactory:它是一个简单的词干器,只处理典型的复数形式。不同于多数的词干器,它词干化的词元是拼写合法的单词,它们是单数形式的。它的好处是使用这个词干器的域可以进行普通的搜索,还可以进行搜索提示。