12.6. Словари #
Словари полнотекстового поиска предназначены для исключения стоп-слов (слов, которые не должны учитываться при поиске) и нормализации слов, чтобы разные словоформы считались совпадающими. Успешно нормализованное слово называется лексемой. Нормализация и исключение стоп-слов не только улучшает качество поиска, но и уменьшает размер представления документа в формате tsvector
, и, как следствие, увеличивает быстродействие. Нормализация не всегда имеет лингвистический смысл, обычно она зависит от требований приложения.
Несколько примеров нормализации:
Лингвистическая нормализация — словари Ispell пытаются свести слова на входе к нормализованной форме, а стеммеры убирают окончания слов
Адреса URL могут быть канонизированы, чтобы например следующие адреса считались одинаковыми:
http://www.pgsql.ru/db/mw/index.html
http://www.pgsql.ru/db/mw/
http://www.pgsql.ru/db/../db/mw/index.html
Названия цветов могут быть заменены их шестнадцатеричными значениями, например
red, green, blue, magenta -> FF0000, 00FF00, 0000FF, FF00FF
При индексировании чисел можно отбросить цифры в дробной части для сокращения множества всевозможных чисел, чтобы например 3.14159265359, 3.1415926 и 3.14 стали одинаковыми после нормализации, при которой после точки останутся только две цифры.
Словарь — это программа, которая принимает на вход фрагмент и возвращает:
массив лексем, если входной фрагмент известен в словаре (заметьте, один фрагмент может породить несколько лексем)
одну лексему с установленным флагом
TSL_FILTER
для замены исходного фрагмента новым, чтобы следующие словари работали с новым вариантом (словарь, который делает это, называется фильтрующим словарём)пустой массив, если словарь воспринимает этот фрагмент, но считает его стоп-словом
NULL
, если словарь не воспринимает полученный фрагмент
В PostgreSQL встроены стандартные словари для многих языков. Есть также несколько предопределённых шаблонов, на основании которых можно создавать новые словари с изменёнными параметрами. Все эти шаблоны описаны ниже. Если же ни один из них не подходит, можно создать и свои собственные шаблоны. Соответствующие примеры можно найти в каталоге contrib/
инсталляции PostgreSQL.
Конфигурация текстового поиска связывает анализатор с набором словарей, которые будут обрабатывать выделенные им фрагменты. Для каждого типа фрагментов, выданных анализатором, в конфигурации задаётся отдельный список словарей. Найденный анализатором фрагмент проходит через все словари по порядку, пока какой-либо словарь не увидит в нём знакомое для него слово. Если он окажется стоп-словом или его не распознает ни один словарь, этот фрагмент не будет учитываться при индексации и поиске. Обычно результат определяет первый же словарь, который возвращает не NULL
, и остальные словари уже не проверяются; однако фильтрующий словарь может заменить полученное слово другим, которое и будет передано следующим словарям.
Общее правило настройки списка словарей заключается в том, чтобы поставить наиболее частные и специфические словари в начале, затем перечислить более общие и закончить самым общим словарём, например стеммером Snowball или словарём simple
, который распознаёт всё. Например, для поиска по теме астрономии (конфигурация astro_en
) тип фрагментов asciiword
(слово из букв ASCII) можно связать со словарём синонимов астрономических терминов, затем с обычным английским словарём и наконец со стеммером английских окончаний Snowball:
ALTER TEXT SEARCH CONFIGURATION astro_en ADD MAPPING FOR asciiword WITH astrosyn, english_ispell, english_stem;
Фильтрующий словарь можно включить в любом месте списка, кроме конца, где он будет бесполезен. Фильтрующие словари бывают полезны для частичной нормализации слов и упрощения задачи следующих словарей. Например, фильтрующий словарь может удалить из текста диакритические знаки, как это делает модуль unaccent.
12.6.1. Стоп-слова #
Стоп-словами называются слова, которые встречаются очень часто, практически в каждом документе, и поэтому не имеют различительной ценности. Таким образом, при полнотекстовом поиске их можно игнорировать. Например, в каждом английском тексте содержатся артикли 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
просматривают свой список стоп-слов в первую очередь. Это различие в поведении объясняется стремлением уменьшить шум.
12.6.2. Простой словарь #
Работа шаблона словарей simple
сводится к преобразованию входного фрагмента в нижний регистр и проверки результата по файлу со списком стоп-слов. Если это слово находится в файле, словарь возвращает пустой массив и фрагмент исключается из дальнейшего рассмотрения. В противном случае словарь возвращает в качестве нормализованной лексемы слово в нижнем регистре. Этот словарь можно настроить и так, чтобы все слова, кроме стоп-слов, считались неопознанными и передавались следующему словарю в списке.
Определить словарь на основе шаблона 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
имеет смысл, только если за ним следует ещё минимум один словарь.
Внимание
Большинство словарей работают с дополнительными файлами, например, файлами стоп-слов. Содержимое этих файлов должно иметь кодировку UTF-8. Если база данных работает в другой кодировке, они будут переведены в неё, когда сервер будет загружать их.
Внимание
Обычно в рамках одного сеанса дополнительный файл словаря загружается только один раз, при первом использовании. Если же вы измените его и захотите, чтобы существующие сеансы работали с новым содержимым, выполните для этого словаря команду ALTER TEXT SEARCH DICTIONARY
. Это обновление словаря может быть «фиктивным», фактически не меняющим значения никаких параметров.
12.6.3. Словарь синонимов #
Этот шаблон словарей используется для создания словарей, заменяющих слова синонимами. Словосочетания такие словари не поддерживают (используйте для этого тезаурус (Подраздел 12.6.4)). Словарь синонимов может помочь в преодолении лингвистических проблем, например, не дать стеммеру английского уменьшить слово «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}
Шаблон synonym
принимает единственный параметр, SYNONYMS
, в котором задаётся базовое имя его файла конфигурации — в данном примере это my_synonyms
. Полным именем файла будет $SHAREDIR/tsearch_data/my_synonyms.syn
(где $SHAREDIR
указывает на каталог общих данных PostgreSQL). Содержимое этого файла должны составлять строки с двумя словами в каждой (первое — заменяемое слово, а второе — его синоним), разделёнными пробелами. Пустые строки и окружающие пробелы при разборе этого файла игнорируются.
Шаблон synonym
также принимает необязательный параметр CaseSensitive
, который по умолчанию имеет значение false
. Когда CaseSensitive
равен false
, слова в файле синонимов переводятся в нижний регистр, вместе с проверяемыми фрагментами. Если же он не равен true
, регистр слов в файле и проверяемых фрагментов не меняются, они сравниваются «как есть».
В конце синонима в этом файле можно добавить звёздочку (*
), тогда этот синоним будет рассматриваться как префикс. Эта звёздочка будет игнорироваться в to_tsvector()
, но to_tsquery()
изменит результат, добавив в него маркер сопоставления префикса (см. Подраздел 12.3.2). Например, предположим, что файл $SHAREDIR/tsearch_data/synonym_sample.syn
имеет следующее содержание:
postgres pgsql postgresql pgsql postgre pgsql gogle googl indices index*
С ним мы получим такие результаты:
mydb=# CREATE TEXT SEARCH DICTIONARY syn (template=synonym, synonyms='synonym_sample'); mydb=# SELECT ts_lexize('syn', 'indices'); ts_lexize ----------- {index} (1 row) mydb=# CREATE TEXT SEARCH CONFIGURATION tst (copy=simple); mydb=# ALTER TEXT SEARCH CONFIGURATION tst ALTER MAPPING FOR asciiword WITH syn; mydb=# SELECT to_tsvector('tst', 'indices'); to_tsvector ------------- 'index':1 (1 row) mydb=# SELECT to_tsquery('tst', 'indices'); to_tsquery ------------ 'index':* (1 row) mydb=# SELECT 'indexes are very useful'::tsvector; tsvector --------------------------------- 'are' 'indexes' 'useful' 'very' (1 row) mydb=# SELECT 'indexes are very useful'::tsvector @@ to_tsquery('tst', 'indices'); ?column? ---------- t (1 row)
12.6.4. Тезаурус #
Тезаурус (или сокращённо TZ) содержит набор слов и информацию о связях слов и словосочетаний, то есть более широкие понятия (Broader Terms, BT), более узкие понятия (Narrow Terms, NT), предпочитаемые названия, исключаемые названия, связанные понятия и т. д.
В основном тезаурус заменяет исключаемые слова и словосочетания предпочитаемыми и может также сохранить исходные слова для индексации. Текущая реализация тезауруса в PostgreSQL представляет собой расширение словаря синонимов с поддержкой фраз. Конфигурация тезауруса определяется файлом следующего формата:
# это комментарий образец слов(а) : индексируемые слова другой образец слов(а) : другие индексируемые слова ...
Здесь двоеточие (:
) служит разделителем между исходной фразой и её заменой.
Прежде чем проверять соответствие фраз, тезаурус нормализует файл конфигурации, используя внутренний словарь (который указывается в конфигурации словаря-тезауруса). Этот внутренний словарь для тезауруса может быть только одним. Если он не сможет распознать какое-либо слово, произойдёт ошибка. В этом случае необходимо либо исключить это слово, либо добавить его во внутренний словарь. Также можно добавить звёздочку (*
) перед индексируемыми словами, чтобы они не проверялись по внутреннему словарю, но все слова-образцы должны быть известны внутреннему словарю.
Если входному фрагменту соответствуют несколько фраз в этом списке, тезаурус выберет самое длинное определение, а если таких окажется несколько, самое последнее из них.
Выделить во фразе какие-то стоп-слова нельзя; вместо этого можно вставить ?
в том месте, где может оказаться стоп-слово. Например, в предположении, что a
и the
— стоп-слова по внутреннему словарю:
? one ? two : swsw
соответствует входным строкам a one the two
и the one a two
, так что обе эти строки будут заменены на swsw
.
Как и обычный словарь, тезаурус должен привязываться к лексемам определённых типов. Так как тезаурус может распознавать фразы, он должен запоминать своё состояние и взаимодействовать с анализатором. Учитывая свои привязки, он может либо обрабатывать следующий фрагмент, либо прекратить накопление фразы. Поэтому настройка тезаурусов в системе требует особого внимания. Например, если привязать тезаурус только к типу фрагментов asciiword
, тогда определение в тезаурусе one 7
не будет работать, так как этот тезаурус не связан с типом uint
.
Внимание
Тезаурусы используются при индексации, поэтому при любом изменении параметров или содержимого тезауруса необходима переиндексация. Для большинства других типов словарей при небольших изменениях, таких как удаление и добавление стоп-слов, переиндексация не требуется.
12.6.4.1. Конфигурация тезауруса #
Для создания нового словаря-тезауруса используется шаблон thesaurus
. Например:
CREATE TEXT SEARCH DICTIONARY thesaurus_simple ( TEMPLATE = thesaurus, DictFile = mythesaurus, Dictionary = pg_catalog.english_stem );
Здесь:
thesaurus_simple
— имя нового словаряmythesaurus
— базовое имя файла конфигурации тезауруса. (Полным путём к файлу будет$SHAREDIR/tsearch_data/mythesaurus.ths
, где$SHAREDIR
указывает на каталог общих данных PostgreSQL.)pg_catalog.english_stem
— внутренний словарь (в данном случае это стеммер Snowball для английского) для нормализации тезауруса. Заметьте, что внутренний словарь имеет собственную конфигурацию (например, список стоп-слов), но здесь она не рассматривается.
Теперь тезаурус thesaurus_simple
можно связать с желаемыми типами фрагментов в конфигурации, например так:
ALTER TEXT SEARCH CONFIGURATION english ALTER MAPPING FOR asciiword, asciihword, hword_asciipart WITH thesaurus_simple;
12.6.4.2. Пример тезауруса #
Давайте рассмотрим простой астрономический тезаурус 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
совпадает с supernovae stars
в thesaurus_astro
, так как мы подключили стеммер english_stem
в определении тезауруса. Этот стеммер удалил конечные буквы e
и s
.
Чтобы проиндексировать исходную фразу вместе с заменой, её нужно просто добавить в правую часть соответствующего определения:
supernovae stars : sn supernovae stars SELECT plainto_tsquery('supernova star'); plainto_tsquery ----------------------------- 'sn' & 'supernova' & 'star'
12.6.5. Словарь Ispell #
Шаблон словарей Ispell поддерживает морфологические словари, которые могут сводить множество разных лингвистических форм слова к одной лексеме. Например, английский словарь Ispell может связать вместе все склонения и спряжения ключевого слова bank
: banking
, banked
, banks
, banks'
,bank's
и т. п.
Стандартный дистрибутив PostgreSQL не включает файлы конфигурации Ispell. Загрузить словари для множества языков можно со страницы Ispell. Кроме того, поддерживаются и другие современные форматы словарей: MySpell (OO < 2.0.1) и Hunspell (OO >= 2.0.2). Большой набор соответствующих словарей можно найти на странице OpenOffice Wiki.
Чтобы создать словарь Ispell, выполните следующие действия:
загрузите файлы конфигурации словаря. Пакет с дополнительным словарём OpenOffice имеет расширение
.oxt
. Из него необходимо извлечь файлы.aff
и.dic
, и сменить их расширения на.affix
и.dict
, соответственно. Для некоторых файлов словарей также необходимо преобразовать символы в кодировку UTF-8 с помощью, например, таких команд (для норвежского языка):iconv -f ISO_8859-1 -t UTF-8 -o nn_no.affix nn_NO.aff iconv -f ISO_8859-1 -t UTF-8 -o nn_no.dict nn_NO.dic
скопируйте файлы в каталог
$SHAREDIR/tsearch_data
загрузите эти файлы в PostgreSQL следующей командой:
CREATE TEXT SEARCH DICTIONARY english_hunspell ( TEMPLATE = ispell, DictFile = en_us, AffFile = en_us, Stopwords = english);
Здесь параметры DictFile
, AffFile
и StopWords
определяют базовые имена файлов словаря, аффиксов и стоп-слов. Файл стоп-слов должен иметь тот же формат, что рассматривался выше в описании словаря simple
. Формат других файлов здесь не рассматривается, но его можно узнать по вышеуказанным веб-адресам.
Словари Ispell обычно воспринимают ограниченный набор слов, так что за ними следует подключить другой, более общий словарь, например, Snowball, который принимает всё.
Файл .affix
для Ispell имеет такую структуру:
prefixes flag *A: . > RE # As in enter > reenter suffixes flag T: E > ST # As in late > latest [^AEIOU]Y > -Y,IEST # As in dirty > dirtiest [AEIOU]Y > EST # As in gray > grayest [^EY] > EST # As in small > smallest
А файл .dict
— такую:
lapse/ADGRS lard/DGRS large/PRTY lark/MRS
Формат файла .dict
следующий:
basic_form/affix_class_name
В файле .affix
каждый флаг аффиксов описывается в следующем формате:
условие > [-отсекаемые_буквы,] добавляемый_аффикс
Здесь условие записывается в формате, подобном формату регулярных выражений. В нём возможно описать группы [...]
и [^...]
. Например, запись [AEIOU]Y
означает, что последняя буква слова — "y"
, а предпоследней может быть "a"
, "e"
, "i"
, "o"
или "u"
. Запись [^EY]
означает, что последняя буква не "e"
и не "y"
.
Словари Ispell поддерживают разделение составных слов, что бывает полезно. Заметьте, что для этого в файле аффиксов нужно пометить специальным оператором 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}
Формат MySpell представляет собой подмножество формата Hunspell. Файл .affix
словаря Hunspell имеет следующую структуру:
PFX A Y 1 PFX A 0 re . SFX T N 4 SFX T 0 st e SFX T y iest [^aeiou]y SFX T 0 est [aeiou]y SFX T 0 est [^ey]
Первая строка класса аффиксов — заголовок. Поля правил аффиксов указываются после заголовка:
имя параметра (PFX или SFX)
флаг (имя класса аффиксов)
отсекаемые символы в начале (в префиксе) или в конце (в суффиксе) слова
добавляемый аффикс
условие в формате, подобном регулярным выражениям.
Файл .dict
подобен файлу .dict
словаря Ispell:
larder/M lardy/RT large/RSPMYT largehearted
Примечание
Словарь MySpell не поддерживает составные слова. С другой стороны, Hunspell поддерживает множество операции с ними, но в настоящее время PostgreSQL использует только самые простые из этого множества.
12.6.6. Словарь Snowball #
Шаблон словарей Snowball основан на проекте Мартина Потера, изобретателя популярного алгоритма стемминга для английского языка. Сейчас Snowball предлагает алгоритмы и для многих других языков (за подробностями обратитесь на сайт Snowball). Каждый алгоритм знает, как для данного языка свести распространённые словоформы к начальной форме. Для словаря Snowball задаётся обязательный параметр language
, определяющий, какой именно стеммер использовать, и может задаваться параметр stopword
, указывающий файл со списком исключаемых слов. (Стандартные списки стоп-слов PostgreSQL используется также в и проекте Snowball.) Например, встроенное определение выглядит так
CREATE TEXT SEARCH DICTIONARY english_stem ( TEMPLATE = snowball, Language = english, StopWords = english );
Формат файла стоп-слов не отличается от рассмотренного ранее.
Словарь Snowball распознаёт любые фрагменты, даже если он не может упростить слова, так что он должен быть самым последним в списке словарей. Помещать его перед другими словарями нет смысла, так как после него никакой фрагмент не будет передан следующему словарю.