Исключения и словоформы — два полезных инструмента, встроенных в Manticore Search, которые вы можете использовать для повышения полноты и точности поиска. У них много общего, но также есть важные различия, которые я хотел бы рассмотреть в этой статье.
О токенизации
В чём разница между полнотекстовым поиском (также называемым свободным текстовым поиском) и поиском с подстановочными знаками, таким как:
- широко известный оператор
LIKEв той или иной форме - или более сложные регулярные выражения
? Конечно, различий множество, но всё начинается с того, что мы делаем с исходным вводным текстом в каждом из подходов:
- при подходе с подстановочными знаками мы обычно рассматриваем текст как целое
- в области полнотекстового поиска необходимо сначала токенизировать текст, а затем рассматривать каждый токен как отдельную сущность
Когда вы хотите токенизировать текст, вам нужно решить, как это сделать, в частности:
- Что должно быть разделителями и символами слов. Обычно разделителем считается символ, который не встречается внутри слова, например знаки пунктуации:
.,,,?,!,-и т.д. - Нужно ли сохранять регистр букв токенов. Обычно нет, поскольку это плохо для поиска, если вы не находите
Orangeпо ключевому словуorange.
Manticore делает всё это автоматически. Например, текст "What do I have? The list is: a cat, a dog and a parrot." токенизируется в:
mysql> drop table if exists t;
mysql> create table t(f text);
mysql> call keywords('What do I have? The list is: a cat, a dog and a parrot.', 't');
+------+-----------+------------+
| qpos | tokenized | normalized |
+------+-----------+------------+
| 1 | what | what |
| 2 | do | do |
| 3 | i | i |
| 4 | have | have |
| 5 | the | the |
| 6 | list | list |
| 7 | is | is |
| 8 | a | a |
| 9 | cat | cat |
| 10 | a | a |
| 11 | dog | dog |
| 12 | and | and |
| 13 | a | a |
| 14 | parrot | parrot |
+------+-----------+------------+
14 rows in set (0.00 sec)
Как видите:
- знаки пунктуации были удалены
- и все слова приведены к нижнему регистру
Проблема
Вот первая проблема: в некоторых случаях разделители рассматриваются как обычные символы слов, например в "Is c++ the most powerful language?" очевидно, что c++ — отдельное слово. Это легко понять людям, но не алгоритмам полнотекстового поиска, поскольку они видят знак плюса, не находят его в списке символов слов и удаляют из токена, в результате вы получаете:
mysql> drop table if exists t;
mysql> create table t(f text);
mysql> call keywords('Is c++ the most powerful language?', 't');
+------+-----------+------------+
| qpos | tokenized | normalized |
+------+-----------+------------+
| 1 | is | is |
| 2 | c | c |
| 3 | the | the |
| 4 | most | most |
| 5 | powerful | powerful |
| 6 | language | language |
+------+-----------+------------+
6 rows in set (0.00 sec)
ОК, но в чём проблема?
Проблема в том, что после такой токенизации, если вы ищете c#, например, вы найдёте вышеуказанное предложение:
mysql> drop table if exists t;
mysql> create table t(f text);
mysql> insert into t values(0,'Is c++ the most powerful language?');
mysql> select highlight() from t where match('c#');
+-------------------------------------------+
| highlight() |
+-------------------------------------------+
| Is <b>c</b>++ the most powerful language? |
+-------------------------------------------+
1 row in set (0.01 sec)
Это происходит потому, что c# также токенизируется до просто c, и тогда c из поискового запроса совпадает с c из документа, и вы получаете результат.
В чём решение? Есть несколько вариантов. Первый, который, вероятно, приходит в голову:
ОК, почему бы мне не добавить + и # в список символов слов?
Это хороший и справедливый вопрос. Давайте попробуем.
mysql> drop table if exists t;
mysql> create table t(f text) charset_table='non_cjk,+,#';
mysql> call keywords('Is c++ the most powerful language?', 't');
+------+-----------+------------+
| qpos | tokenized | normalized |
+------+-----------+------------+
| 1 | is | is |
| 2 | c++ | c++ |
| 3 | the | the |
| 4 | most | most |
| 5 | powerful | powerful |
| 6 | language | language |
+------+-----------+------------+
6 rows in set (0.00 sec)
Это работает, но + в списке сразу начинает влиять на другие слова и запросы, например:
mysql> drop table if exists t;
mysql> create table t(f text) charset_table='non_cjk,+,#';
mysql> call keywords('What is 2+2?', 't');
+------+-----------+------------+
| qpos | tokenized | normalized |
+------+-----------+------------+
| 1 | what | what |
| 2 | is | is |
| 3 | 2+2 | 2+2 |
+------+-----------+------------+
3 rows in set (0.00 sec)
Вы хотели, чтобы c++ был отдельным словом, но не 2+2, верно?
Правильно, что же мы можем сделать?
Чтобы обработать c++ особым образом, вы можете сделать его исключением.
Исключения
Итак, exceptions (также известные как синонимы) позволяют сопоставлять один или несколько терминов (включая термины с символами, которые обычно исключаются) с одним ключевым словом.
Давайте сделаем c++ исключением, поместив его в файл исключений:
➜ ~ cat /tmp/exceptions
c++ => c++
и используя этот файл при создании таблицы:
mysql> drop table if exists t;
mysql> create table t(f text) exceptions='/tmp/exceptions';
mysql> call keywords('Is c++ the most powerful language? What is 2+2?', 't');
+------+-----------+------------+
| qpos | tokenized | normalized |
+------+-----------+------------+
| 1 | is | is |
| 2 | c++ | c++ |
| 3 | the | the |
| 4 | most | most |
| 5 | powerful | powerful |
| 6 | language | language |
| 7 | what | what |
| 8 | is | is |
| 9 | 2 | 2 |
| 10 | 2 | 2 |
+------+-----------+------------+
10 rows in set (0.01 sec)
Ура, c++ теперь отдельное слово, знаки плюса не теряются, и с 2+2 всё в порядке.
Что нужно помнить об исключениях, так это то, что они очень просты, вовсе не умны, они делают ровно то, что вы им говорите, и ничего больше. В частности:
- они не меняют регистр
- если вы ошибётесь и поставите двойной пробел, они не преобразуют его в один пробел
и так далее. Они буквально рассматривают ваш ввод как массив байтов.
Например, люди пишут c++ как в нижнем, так и в верхнем регистре. Давайте попробуем вышеуказанное исключение в верхнем регистре?
mysql> drop table if exists t;
mysql> create table t(f text) exceptions='/tmp/exceptions';
mysql> call keywords('Is C++ the most powerful language? How about c++?', 't');
+------+-----------+------------+
| qpos | tokenized | normalized |
+------+-----------+------------+
| 1 | is | is |
| 2 | c | c |
| 3 | the | the |
| 4 | most | most |
| 5 | powerful | powerful |
| 6 | language | language |
| 7 | what | what |
| 8 | is | is |
| 9 | 2 | 2 |
| 10 | 2 | 2 |
+------+-----------+------------+
10 rows in set (0.00 sec)
Упс, C++ был токенизирован как просто c, потому что исключение — c++ (нижний регистр), а не C++ (верхний регистр).
Но вы заметили, что исключение представляет собой пару элементов, а не один: c++ => c++. Левая часть инициирует алгоритм исключений в тексте, правая — полученный токен. Попробуем добавить сопоставление C++ к c++?
➜ ~ cat /tmp/exceptions
c++ => c++
C++ => c++
mysql> drop table if exists t;
mysql> create table t(f text) exceptions='/tmp/exceptions';
mysql> call keywords('Is C++ the most powerful language? How about c++?', 't');
+------+-----------+------------+
| qpos | tokenized | normalized |
+------+-----------+------------+
| 1 | is | is |
| 2 | c++ | c++ |
| 3 | the | the |
| 4 | most | most |
| 5 | powerful | powerful |
| 6 | language | language |
| 7 | how | how |
| 8 | about | about |
| 9 | c++ | c++ |
+------+-----------+------------+
9 rows in set (0.00 sec)
Хорошо, теперь всё снова в порядке, поскольку и C++, и c++ токенизируются в токен c++. Так удовлетворительно.
Какие ещё хорошие примеры исключений:
AT&T => AT&Tиat&t => AT&T.M&M's => M&M'sиm&m's => M&M'sиM&m's => M&M'sU.S.A. => USAиUS => USA
Какие плохие примеры?
us => USA, потому что мы не хотим, чтобы каждоеusпревращалось вUSA.
Итак, правило большого пальца для исключений таково:
Если термин содержит специальные символы и обычно пишется так в тексте и в поисковом запросе — сделайте его исключением.
Синонимы
Пользователи Manticore Search также часто называют exceptions синонимами, потому что другой случай их использования — не просто сохранять специальные символы и регистр, а сопоставлять термины, написанные совершенно по‑разному, одному токену, например:
MS Windows => ms windows
Microsoft Windows => ms windows
Почему это важно? Потому что это позволяет легко находить документы с Microsoft Windows по запросу MS Windows и наоборот.
Пример:
mysql> drop table if exists t;
mysql> create table t(f text) exceptions='/tmp/exceptions';
mysql> insert into t values(0, 'Microsoft Windows is one of the first operating systems');
mysql> select * from t where match('MS Windows');
+---------------------+---------------------------------------------------------+
| id | f |
+---------------------+---------------------------------------------------------+
| 1514841286668976139 | Microsoft Windows is one of the first operating systems |
+---------------------+---------------------------------------------------------+
1 row in set (0.00 sec)
Так что на первый взгляд всё работает нормально, но при дальнейшем размышлении и учитывая, что исключения чувствительны к регистру и байтам, вы можете задать себе вопрос: «Не могут ли люди писать MicroSoft windows, MS WINDOWS, microsoft Windows и так далее?»
Да, могут. Поэтому если вы хотите использовать исключения для этого, будьте готовы к тому, что в математике это называется комбинаторным взрывом.
Выглядит совсем неплохо. Что мы можем с этим сделать?
Словоформы
Другой инструмент, похожий на исключения, — это wordforms. В отличие от исключений, словоформы применяются после токенизации входящего текста. Поэтому они:
- регистронезависимы (если только ваш
charset_tableне включает чувствительность к регистру) - не учитывают специальные символы
По сути они позволяют заменить одно слово другим. Обычно это используется для приведения разных форм слова к одной нормальной форме. Например, нормализовать все варианты, такие как «walks», «walked», «walking», до нормальной формы «walk»:
➜ ~ cat /tmp/wordforms
walks => walk
walked => walk
walking => walk
mysql> drop table if exists t;
mysql> create table t(f text) wordforms='/tmp/wordforms';
mysql> call keywords('walks _WaLkeD! walking', 't');
+------+-----------+------------+
| qpos | tokenized | normalized |
+------+-----------+------------+
| 1 | walks | walk |
| 2 | walked | walk |
| 3 | walking | walk |
+------+-----------+------------+
3 rows in set (0.00 sec)
Как вы можете видеть, все 3 слова были преобразованы в просто walk, и обратите внимание, что второе слово _WaLkeD!, даже будучи сильно деформированным, также было нормально преобразовано. Вы понимаете, к чему я веду? Да, пример с MS Windows. Давайте проверим, могут ли формы слов быть полезны для решения этой проблемы.
Давайте добавим всего 2 строки в файл форм слов:
➜ ~ cat /tmp/wordforms
ms windows => ms windows
microsoft windows => ms windows
и заполним таблицу несколькими документами:
mysql> drop table if exists t;
mysql> create table t(f text) wordforms='/tmp/wordforms';
mysql> insert into t values(0, 'Microsoft Windows is one of the first operating systems'), (0, 'porch windows'),(0, 'Windows are rolled down');
mysql> select * from t;
+---------------------+---------------------------------------------------------+
| id | f |
+---------------------+---------------------------------------------------------+
| 1514841286668976166 | Microsoft Windows is one of the first operating systems |
| 1514841286668976167 | porch windows |
| 1514841286668976168 | Windows are rolled down |
+---------------------+---------------------------------------------------------+
3 rows in set (0.00 sec)
Теперь давайте попробуем различные запросы:
mysql> select * from t where match('MS Windows');
+---------------------+---------------------------------------------------------+
| id | f |
+---------------------+---------------------------------------------------------+
| 1514841286668976166 | Microsoft Windows is one of the first operating systems |
+---------------------+---------------------------------------------------------+
1 row in set (0.00 sec)
✅ MS Windows находит Microsoft Windows нормально.
mysql> select * from t where match('ms WINDOWS');
+---------------------+---------------------------------------------------------+
| id | f |
+---------------------+---------------------------------------------------------+
| 1514841286668976166 | Microsoft Windows is one of the first operating systems |
+---------------------+---------------------------------------------------------+
1 row in set (0.01 sec)
✅ ms WINDOWS тоже работает нормально.
mysql> select * from t where match('mIcRoSoFt WiNdOwS');
+---------------------+---------------------------------------------------------+
| id | f |
+---------------------+---------------------------------------------------------+
| 1514841286668976166 | Microsoft Windows is one of the first operating systems |
+---------------------+---------------------------------------------------------+
1 row in set (0.00 sec)
✅ И даже mIcRoSoFt WiNdOwS находит тот же документ.
mysql> select * from t where match('windows');
+---------------------+---------------------------------------------------------+
| id | f |
+---------------------+---------------------------------------------------------+
| 1514841286668976166 | Microsoft Windows is one of the first operating systems |
| 1514841286668976167 | porch windows |
| 1514841286668976168 | Windows are rolled down |
+---------------------+---------------------------------------------------------+
3 rows in set (0.00 sec)
✅ Просто базовое windows находит все документы.
Таким образом, wordforms действительно помогает решить проблему.
Общее правило для форм слов:
Используйте формы слов для слов и фраз, которые могут быть написаны в разных формах и не содержат специальных символов.
Floor & Decor
Давайте рассмотрим еще один пример: мы хотим улучшить поиск по названию бренда Floor & Decor. Мы можем предположить, что люди могут писать это название в следующих формах:
Floor & Decor
Floor & decor
floor & decor
Floor and Decor
floor and decor
и других комбинациях с разным регистром букв.
Также:
Floor & Decor Holdings
Floor & Decor Holdings, inc.
и, снова, различные комбинации с разными заглавными буквами.
Теперь, когда мы знаем, как работают exceptions и wordforms, что мы можем сделать, чтобы охватить это название бренда?
Прежде всего, мы можем легко заметить, что каноническое название бренда — это Floor & Decor, т.е. оно включает специальный символ, который обычно считается разделителем слов, так что нам следует использовать exceptions? Но название длинное и может быть написано многими способами. Если мы используем exceptions, мы можем получить огромный список всех комбинаций. Более того, есть расширенные формы Floor & Decor Holdings и Floor & Decor Holdings, inc., которые могут сделать список еще длиннее.
Наиболее оптимальное решение в этом случае — просто использовать wordforms вот так:
➜ ~ cat /tmp/wordforms
floor & decor => fnd
floor and decor => fnd
floor & decor holdings => fnd
floor and decor holdings => fnd
floor & decor holdings inc => fnd
floor and decor holdings inc => fnd
Почему оно включает &? На самом деле, вы можете его пропустить:
floor decor => fnd
floor and decor => fnd
floor decor holdings => fnd
floor and decor holdings => fnd
floor decor holdings inc => fnd
floor and decor holdings inc => fnd
потому что wordforms все равно игнорирует несловесные символы, но просто для удобства чтения он был оставлен.
В результате вы получите каждую комбинацию, токенизированную как fnd, что будет нашим коротким ключом для этого названия бренда.
mysql> drop table if exists t; create table t(f text) wordforms='/tmp/wordforms';
mysql> call keywords('Floor & Decor', 't')
+------+-------------+------------+
| qpos | tokenized | normalized |
+------+-------------+------------+
| 1 | floor decor | fnd |
+------+-------------+------------+
1 row in set (0.00 sec)
mysql> call keywords('floor and Decor', 't')
+------+-----------------+------------+
| qpos | tokenized | normalized |
+------+-----------------+------------+
| 1 | floor and decor | fnd |
+------+-----------------+------------+
1 row in set (0.00 sec)
mysql> call keywords('Floor & Decor holdings', 't')
+------+----------------------+------------+
| qpos | tokenized | normalized |
+------+----------------------+------------+
| 1 | floor decor holdings | fnd |
+------+----------------------+------------+
1 row in set (0.00 sec)
mysql> call keywords('Floor & Decor HOLDINGS INC.', 't')
+------+--------------------------+------------+
| qpos | tokenized | normalized |
+------+--------------------------+------------+
| 1 | floor decor holdings inc | fnd |
+------+--------------------------+------------+
1 row in set (0.00 sec)
Является ли это идеальным окончательным решением? К сожалению, нет, как и многие другие вещи в области полнотекстового поиска. Всегда есть редкие случаи, и в этом случае тоже. Например:
mysql> drop table if exists t; create table t(f text) wordforms='/tmp/wordforms';
mysql> insert into t values(0,'It\'s located on the 2nd floor. Decor is also nice');
mysql> select * from t where match('Floor & Decor Holdings');
+---------------------+---------------------------------------------------+
| id | f |
+---------------------+---------------------------------------------------+
| 1514841286668976231 | It's located on the 2nd floor. Decor is also nice |
+---------------------+---------------------------------------------------+
1 row in set (0.00 sec)
Мы видим здесь, что Floor & Decor Holdings находит документ, в котором floor находится в конце первого предложения, а следующее начинается с Decor. Это происходит потому, что floor. Decor также токенизируется в fnd, так как мы используем только wordforms, которые нечувствительны к регистру букв и специальным символам:
mysql> call keywords('floor. Decor', 't');
+------+-------------+------------+
| qpos | tokenized | normalized |
+------+-------------+------------+
| 1 | floor decor | fnd |
+------+-------------+------------+
1 row in set (0.00 sec)
Ложное совпадение — это плохо. Чтобы решить эту конкретную проблему, мы можем использовать функциональность Manticore для обнаружения предложений и абзацев .
Теперь, если мы включим это, мы увидим, что документ больше не соответствует ключевому слову:
mysql> drop table if exists t; create table t(f text) wordforms='/tmp/wordforms' index_sp='1';
mysql> insert into t values(0,'It\'s located on the 2nd floor. Decor is also nice');
mysql> select * from t where match('Floor & Decor Holdings');
Empty set (0.00 sec)
потому что:
Floor & Decor, как мы помним, преобразуется вfndс помощьюwordformsindex_sp='1'разбивает текст на предложения- после разбиения
floor.иDecorоказываются в разных предложениях - и больше не соответствуют
fnd, и, следовательно, всем оригинальным формам этого слова
Заключение
Исключения и формы слов Manticore — это мощные инструменты, которые могут помочь вам точно настроить ваш поиск, в частности, улучшить полноту и точность, когда дело касается коротких терминов со специальными символами, которые должны быть сохранены, и более длинных терминов, которые должны быть связаны друг с другом. Но вам нужно помочь Manticore в этом, так как он не может решить, какими должны быть названия для вас.
Спасибо за чтение этой статьи!
