分类:
2008-05-21 15:13:14
字典用于删除那些不应该在搜索中出现的词(屏蔽词)、以及规范化一些有多重形式的词,这样同一个词的不同的衍申结果也可以被搜索到。成功规范化之后的词被称作语意(lexeme)。除了改进搜索质量,规范化和删除屏蔽词可以减少 tsvector 形式的文档的尺寸,因此也可以提高性能。规范化并不总是有语言含义,通常是取决于应用的语意的。
一些规范化的例子:
一个字典是这样的东西,它接受记号的输入,然后返回:
PostgreSQL 为许多语言都提供了预定义的词典。还有好多预定义的模版,可以用于试用客户化参数创建新的词典。每个预定义的词典都在下面描述。如果没有现成的合适模版,我们可以创建一个新的;参阅 PostgreSQL 源代码的 contrib/ 目录获取样例。
一个文本搜索配置会把一个分词程序和一堆试用分词程序输出的记号的词典绑定在一起。对于分词程序能返回的每个记号类型,配置都会声明一个独立的词典列表。如果分词程序发现对应的记号类型,那么在列表中的词典会轮流进行查询,直到某个词典识别出它是一个已知的单词。如果它被识别为是一个屏蔽词,或者如果没有什么词典认识这个极好,那么它就会被抛弃,因此也不会被索引,也就不会被找到了。配置一系列词典的通用规则是把最狭窄,最专用的词典放在最先,然后是更普通的词典,最后是最通用的词典,比如一个 Snowball 词干或者是 simple,它们可以识别一切的东西。比如,对于一个天文学相关的搜索(astro_en 配置),我们可以把记号类型 asciiword (ASCII 单词)绑定到一个天文学术语的同义词典、一个通用英语词典和一个 Snowball 英语词干上:
ALTER TEXT SEARCH CONFIGURATION astro_en ADD MAPPING FOR asciiword WITH astrosyn, english_ispell, english_stem;
屏蔽词是那些非常常见的词,几乎在所有文档中都出现,并且没有歧视值。因此,它们可以在全文搜索的环境中忽略掉。比如,每个英文文本都几乎包含 a 和 the,因此在索引里面存储它们是没有任何意义的。不过,屏蔽词会影响 tsvector 里面的位置,因此也会间接地影响相关性:
SELECT to_tsvector('english','in the list of stop words'); to_tsvector ---------------------------- 'list':3 'stop':5 'word':6
缺失的位置 1,2,4 都是因为屏蔽词造成的。有和没有屏蔽词计算出来的相关性的差别是相当大的:
SELECT ts_rank_cd (to_tsvector('english','in the list of stop words'), to_tsquery('list & stop')); ts_rank_cd ------------ 0.05 SELECT ts_rank_cd (to_tsvector('english','list stop words'), to_tsquery('list & stop')); ts_rank_cd ------------ 0.1
如何对待屏蔽词是由具体的词典来决定的。比如,ispell 词典首先规范化单词,然后查看屏蔽词列表,而 Snowball 词干首先检查屏蔽词列表。这样的不同的行为的原因是为了减少噪音。
simple 词典模版通过把输入的记号转换成小写然后参照一个屏蔽词列表文件进行检查来运行。如果单词出现在该文件中,则返回一个空的数组,导致该记号被丢弃。如果没有,那么该单词的小写形式会以规范化的语意(lexeme)的形式返回。另外,这个词典也可以配置成把非屏蔽词当做无法识别的东西返回,这样就允许将它们传递到列表中的下一个词典进行处理。
下面是一个使用 simple 模版定义的词典的例子:
CREATE TEXT SEARCH DICTIONARY public.simple_dict ( TEMPLATE = pg_catalog.simple, STOPWORDS = english );
这里,english 是屏蔽词文件的基础名。该文件的全称是 $SHAREDIR/tsearch_data/english.stop,这里的 $SHAREDIR 意思是 PostgreSQL 安装的共享数据目录,通常是 /usr/local/share/postgresql (如果你不知道,那么用 pg_config --sharedir 来判断)。这个文件的格式只是一列单词,一个词一行。空白行和结尾的空白会被忽略,大写会转换成小写,然后就没有其它对此文件内容的处理了。
现在我们可以测试我们的词典:
SELECT ts_lexize('public.simple_dict','YeS'); ts_lexize ----------- {yes} SELECT ts_lexize('public.simple_dict','The'); ts_lexize ----------- {}
如果没有在屏蔽词文件中找到,我们也可以选择返回 NULL,而不是小写单词。这个行为是通过把该词典的 Accept 参数设置为 false 选中的。继续我们的例子:
ALTER TEXT SEARCH DICTIONARY public.simple_dict ( Accept = false ); SELECT ts_lexize('public.simple_dict','YeS'); ts_lexize ----------- SELECT ts_lexize('public.simple_dict','The'); ts_lexize ----------- {}
在缺省的设置 Accept = true 的时候,simple 词典只是放在词典列表的末尾的时候才有用,因为它不会给后面的词典传递任何记号。相对的是,如果 Accept = false,那么它就只是在后面至少还有一个词典的场合下才有用。
这个词典模版用于创建一个用同义词替换某个单词的词典。不支持短语(这方面用知识词典)。一个同义词词典可以用于克服语言学的问题,比如,为了避免英语词干词典把单词 'Paris' 修改成 'pari'。我们只要在同义词词典里面放一行 Paris paris,然后把这个词典放在 english_stem 词典之前就行了:
SELECT * FROM ts_debug('english', 'Paris'); alias | description | token | dictionaries | dictionary | lexemes -----------+-----------------+-------+----------------+--------------+--------- asciiword | Word, all ASCII | Paris | {english_stem} | english_stem | {pari} CREATE TEXT SEARCH DICTIONARY my_synonym ( TEMPLATE = synonym, SYNONYMS = my_synonyms ); ALTER TEXT SEARCH CONFIGURATION english ALTER MAPPING FOR asciiword WITH my_synonym, english_stem; SELECT * FROM ts_debug('english', 'Paris'); alias | description | token | dictionaries | dictionary | lexemes -----------+-----------------+-------+---------------------------+------------+--------- asciiword | Word, all ASCII | Paris | {my_synonym,english_stem} | my_synonym | {paris}
同义词模版要求的唯一的参数是 SYNONYMS,它是其配置文件的基础名 — 在上面的例子里是 my_synonym。这个文件的全称会是 $SHAREDIR/tsearch_data/my_synonyms.syn (这里的 $SHAREDIR 是 PostgreSQL 安装的共享数据目录)。这个文件的格式是每个要被替换的单词一行,后面跟着它的同义词,用空格分隔。空白行和结尾的空白会被忽略,大写会转换成小写。
知识词典(有时候缩写成 TZ)是一个单词的集合,他包括单词和短语的关系,也就是:宽词(BT),窄词(NT),优选词,相关词等等。
基本上,一个知识词典用一个词替换了所有非优选的词,然后,还可以选择保存原来的词进行索引。PostgreSQL 的当前的知识词典的实现是一个同义词词典的扩展,添加了短语的支持。一个知识词典要求一个有下面格式的配置文件:
# this is a comment sample word(s) : indexed word(s) more sample word(s) : more indexed word(s) ...
这里的冒号(:)是短语和其替代品之间的分隔符。
知识词典使用一个子词典(通过该词典的配置声明)来在检查短语匹配之前规范化输入文本。我们只能选择一个子词典。如果子词典无法识别一个单词,那么就会报告一个错误。在这种情况下,你应该删除这个词或者告诉子词典这个词的信息。你可以在一个已索引的词前面放一个星号(*)以避免在它上面使用子字典,但是所有的 sample 单词都应该是子字典知道的。
如果有多个短语匹配输入,那么知识词典选取最长的匹配,然后绑定就断开,使用最后一个定义。
被子词典识别的特定的屏蔽词是不能声明的;我们需要用 ? (问好)标记屏蔽词可能出现的位置。比如,假设根据子字典, a 和 the 都是屏蔽词:
? one ? two : swsw
匹配 a one the two 和 the one a two;两个都被 swsw。
因为知识词典有能力识别短语,所以它必须记住自己的状态并且和分析器交互。一个知识词典使用这些赋值以检查它是应该处理下一个词,还是应该停止累积。知识词典的配置必须小心。比如,如果知识词典配置成只处理 asciiword 记号,那么像 one 7 这样的知识词典定义将不会工作,因为记号类型 uint 并没有赋予知识词典。
要定义一个知识词典,使用 thesaurus 模版。比如:
CREATE TEXT SEARCH DICTIONARY thesaurus_simple ( TEMPLATE = thesaurus, DictFile = mythesaurus, Dictionary = pg_catalog.english_stem );
这里:
现在我们可以在一个配置里把知识词典 thesaurus_simple 和期望的记号类型绑定起来了,比如:
ALTER TEXT SEARCH CONFIGURATION russian ALTER MAPPING FOR asciiword, asciihword, hword_asciipart WITH thesaurus_simple;
假设我们有个简单的天文学知识词典 thesaurus_astro,它包含一些天文学单词的组合:
supernovae stars : sn crab nebulae : crab
下面我们创建了一个词典,并且把一些记号类型给天文知识词典和英文词干词典绑定:
CREATE TEXT SEARCH DICTIONARY thesaurus_astro ( TEMPLATE = thesaurus, DictFile = thesaurus_astro, Dictionary = english_stem ); ALTER TEXT SEARCH CONFIGURATION russian ALTER MAPPING FOR asciiword, asciihword, hword_asciipart WITH thesaurus_astro, english_stem;
现在我们看看它是如何工作的。ts_lexize 现在在测试知识词典不是很有用,因为它把输入看做单个记号。我们可以用 plainto_tsquery 和 to_tsvector,它们会把输入分析成多个记号:
SELECT plainto_tsquery('supernova star'); plainto_tsquery ----------------- 'sn' SELECT to_tsvector('supernova star'); to_tsvector ------------- 'sn':1
原则上,如果我们给参数加引号,那么哦我们可以使用 to_tsquery:
SELECT to_tsquery('''supernova star'''); to_tsquery ------------ 'sn'
请注意 supernova star 在 thesaurus_astro 里匹配 supernovae stars 是因为我们在知识词典的定义里声明了 english_stem 词干。词干会删除 e 和 s。
要和替换同时索引原来的次,我们只要包含定义的右边就行了:
supernovae stars : sn supernovae stars
SELECT plainto_tsquery('supernova star'); plainto_tsquery ----------------------------- 'sn' & 'supernova' & 'star'
Ispell 词典模版支持形态学的词典,它可以把一个单词的许多不同的语言学形式转换成同一个语意(lexeme)。比如,英语的 Ispell 词典可以匹配搜索词 bank 的所有词尾变化和动词词性的变化,比如:banking,banked,banks,banks'和bank's。
标准的 PostgreSQL 发布并未包含任何 Ispell 配置文件。大量语言的词典可以从 Ispell 里找到。还有,还支持某些更现代的词典文件格式-- MySpell(OO<2.0.1)和 Hunspell(OO >= 2.0.2)。大量的词典可以在 OpenOffice Wiki 上找到。
要创建一个 Ispell 词典,使用内置的 ispell 模版并声明几个参数:
CREATE TEXT SEARCH DICTIONARY english_ispell ( TEMPLATE = ispell, DictFile = english, AffFile = english, StopWords = english );
这里,DictFile,AffFile 和 StopWords 声明词典,后缀和屏蔽词的基本命。屏蔽词文件格式和前面我们介绍过的简单字典类型的一样。其它文件的格式我们没有在这里描述,但是可以从上面提到的网站上获得。
Ispell 词典通常识别优先的单词集,这样它们后面就可以跟着更宽范围的词典;比如,一个 Snowball 词典,它可以识别一切的东西。
Ispell 词典支持分裂的组合词。这个特性很好,并且 PostgreSQL 也支持它。请注意后缀文件应该使用 compoundwords controlled 语句声明一个特殊标志,标记词典的词是可以有组合格式的:
compoundwords controlled z
下面是一些挪威语的例子:
SELECT ts_lexize('norwegian_ispell', 'overbuljongterningpakkmesterassistent'); {over,buljong,terning,pakk,mester,assistent} SELECT ts_lexize('norwegian_ispell', 'sjokoladefabrikk'); {sjokoladefabrikk,sjokolade,fabrikk}
Snowball 词典模版是基于 Martin Porter 的项目的,他是流行的 Porter 的英语词干算法的发明人。现在 Snowball 提供了许多语言的词干算法(参阅 Snowball 的站获取更多信息)。每个算法都知道如何把常见形式的单词归约成一个基本的,该语言内拼写的词,或者说词干。一个 Snowball 词典要求一个语言参数来表示使用哪个词干,以及一个可选的屏蔽词文件给出要删除的一个单词列表。(PostgreSQL 的标准屏蔽词列表在 Snowball 项目里也有提供。)比如,有一个等效于下面的内置的定义
CREATE TEXT SEARCH DICTIONARY english_stem ( TEMPLATE = snowball, Language = english, StopWords = english );
屏蔽词文件格式和前面解释过的一样。
Snowball 词典识别一切东西,不管它是不是简化该单词,所以它应该放在词典列表的末尾。把它放在任何其它词典前头都是没啥用的,因为一个记号从不会穿过它到达别的词典。