Sphinx

индекс
210,30

Как устроено ранжирование

Со временем Sphinx оброс большой кучей режимов поиска и ранжирования. Регулярно возникают вопросы про разное (от «как вытащить документ на 1е место» до «как рисовать от 1 до 5 звездочек в зависимости от степени совпадения»), которые на самом деле суть вопросы про внутреннее устройство тех режимов. В этом посте расскажу все, что вспомню: как устроены режимы поиска и режимы ранжирования, какие есть факторы ранжирования, как в точности рассчитываются факторы, как финальный вес, все такое. И, конечно, про звездочки!


Про режимы поиска и ранжирования


Прежде всего, разберемся, что вообще делают те режимы. Через API про них сейчас доступны 2 разных метода, SetMatchMode() и SetRankingMode(). Казалось бы, разные; но на самом деле, внутри там теперь одно и то же. Ранее, вплоть до версии 0.9.8 включительно, были доступны только режимы поиска, те. SetMatchMode(). Все они были реализованы разными ветками кода. Каждая из веток кода сама делала и поиск документов, и их ранжирование. Причем, понятно, по-разному. В версии 0.9.8 была начата разработка нового унифицированного движка поиска документов. Однако, чтобы не ломать совместимость, этот движок был доступен только в режиме SPH_MATCH_EXTENDED2. Начиная с 0.9.9 стало видно, что новый движок уже в целом достаточно стабильный и быстрый (а на всякий особый случай, если недоглядели, есть версия 0.9.8). Это позволило убрать кучу старого кода, и начиная с 0.9.9 все режимы поиска обрабатываются новым движком. Для совместимости, при использовании устаревших режимов поиска используется упрощенный код разбора запросов (который игнорирует операторы языка полнотекстовых запросов), и автоматически выставляется правильный (соответствующий режиму поиска) ранкер, но на этом все отличия и заканчиваются. Поэтому фактически вес документа (@weight) зависит только от режима ранжирования (ranker). Поэтому два нижеследующих запроса дадут одинаковые веса:

// 1й вариант
$cl->SetMatchMode ( SPH_MATCH_ALL );
$cl->Query ( "hello world" );

// 2й вариант
$cl->SetMatchMode ( SPH_MATCH_EXTENDED2 );
$cl->SetRankingMode ( SPH_RANK_PROXIMITY );
$cl->Query ( "hello world" );

Во втором варианте при этом можно написать @title hello (см. язык запросов). В первом нельзя (см. совместимость).

Ранкеры рассчитывают вес документа по нескольким доступным им (и только им) внутренним факторам. Можно сказать, что разные ранкеры просто по-разному собирают факторы в конечный вес. Два наиболее важных фактора это 1) классический статистический фактор BM25, используемый большинством (если не всеми) поисковыми системами с 80х годов, и 2) специфичный для Sphinx фактор веса фразы.

Про BM25


BM25 представляет собой вещественное число в диапазоне от 0 до 1, которое зависит от частот ключевых слов в текущем документе с одной стороны и общем наборе документов (коллекции) с другой, и только от частот. Текущая реализация BM25 в Sphinx рассчитывается исходя из общей частоты слова в документе, а не только частоты фактических совпадений с запросом. Например, для запроса @title hello (который матчит 1 копию слова hello в заголовке) фактор BM25 будет рассчитан такой же, как и для запроса @(title,content) keyword. Упрощенная реализация сделана намеренно: в стандартном режиме ранжирования фактор BM25 вторичен, отличия в ранжировании при тестировании получались несущественные, а вот скорость отличалась вполне себе измеримо. Точный алгоритм расчета выглядит так:

BM25 = 0
foreach ( keyword in matching_keywords )
{
   n = total_matching_documents ( keyword )
   N = total_documents_in_collection
   k1 = 1.2

   TF = current_document_occurrence_count ( keyword )
   IDF = log((N-n+1)/n) / log(1+N)
   BM25 = BM25 + TF*IDF/(TF+k1)
}
BM25 = 0.5 + BM25 / ( 2*num_keywords ( query ) )

TF означает Term Frequency (частота ключевого слова, в ранжируемом документе). IDF означает Inverse Document Frequency (обратная частота документов, во всей коллекции). IDF для часто встречающихся в коллекции ключевых слов получается меньше, для редких слов побольше. Пиковые значения выходят IDF=1 для слова, которое встречается ровно в одном документе, и IDF~=-1 для слова, которое есть в каждом документе. TF теоретически варьируется от 0 до 1, в зависимости от k1; при выбранном k1=1.2 оно фактически варьируется от 0.4545… до 1.

BM25 растет, когда ключевые слова редкие и входят в документ много раз, и падает, когда ключевые слова частые. Наибольшее значение BM25 достигается, когда все ключевые слова совпадают с документом, причем слова сверх-редкие (только в этом 1 документе из всей коллекции и встречаются), и еще входят в него много раз. Наименьшее, соответственно, когда с документом совпадает только 1 сверх-частое слово, которое встречается вообще во всех документов, и… тоже входит в документ много раз.

Надо отметить, что слишком частые слова (встречающиеся чаще, чем в половине документов) уменьшают BM25! В самом деле, если какое-то слово встречается во всех документах, кроме одного, то этот один документ без сверх-частого слова таки особенный, заслуживает веса побольше.

Про вес фразы


Вес фразы (он же степень близости с запросом, он же query proximity) считается совершенно иначе. Этот фактор совсем не учитывает частоты, зато учитывает взаимное расположение ключевых слов запросе и документе. Для его расчета, Sphinx анализирует позиции ключевых слов в каждом поле документа, находит самое длинное непрерывное совпадение с запросом, и считает его, совпадения, длину в ключевых словах. Формально говоря, находит наибольшую общую подпоследовательность (Longest Common Subsequence, LCS) ключевых слов между запросом и обрабатываемым полем, и назначает вес фразы для этого поля равным длине LCS. В переводе обратно на русский, вес (под)фразы — это число ключевых слов, которые в поле появились ровно в таком же порядке, как и в запросе. Вот несколько примеров:

1) query = one two three, field = one and two three
field_phrase_weight = 2 (совпала подфраза "two three", длиной 2 ключевых слова)

2) query = one two three, field = one and two and three
field_phrase_weight = 1 (отдельные слова совпадают, но подфразы нет)

3) query = one two three, field = nothing matches at all
field_phrase_weight = 0

Чтобы получить окончательный вес фразы для документа, веса фразы в каждом поле перемножаются на указанные пользователем веса полей (см. метод SetFieldWeights()) и результаты умножения складываются вместе. (Кстати, по умолчанию веса полей равны 1, и не могут быть выставлены ниже 1.) Псевдокод выглядит так:

doc_phrase_weight = 0
foreach ( field in matching_fields )
{
   field_phrase_weight = max_common_subsequence_length ( query, field )
   doc_phrase_weight += user_weight ( field ) * field_phrase_weight
}

Например:

doc_title = hello world
doc_body = the world is a wonderful place

query = hello world
query_title_weight = 5
query_body_weight = 3

title_phrase_weight = 2
body_phrase_weight = 1
doc_phrase_weight = 2*5+3*1 = 13

Про светлое будущее


Описанные два фактора на сегодня основные, но вообще говоря не единственные. Технически вполне возможно считать другие текстовые факторы. Например, считать «правильный» BM25 с учетом фактических совпадений; считать веса подфраз похитрее, с учетом частот входящих слов; дополнительно учитывать количество совпавших в поле слов; итд итп. Еще можно учитывать всякие внетекстовые факторы на уровне самого ранкера; иными словами, использовать какие-то атрибуты в процессе расчета @weight, а не вдобавок к расчету.

Про ранкеры


Наконец, про режимы ранжирования, для краткости ранкеры. Именно они собирают из всяких разных факторов конечный вес. Веса на выходе из ранкеров целочисленные.

Ранкер по умолчанию (в режимах extended/extended2) называется SPH_RANK_PROXIMITY_BM25 и комбинирует вес фразы с BM25. Фактор BM25 располагается в нижних 3 десятичных знаках, вес фразы начиная с 4го и выше. Есть два связанных ранкера, SPH_RANK_PROXIMITY и SPH_RANK_BM25. Первый возвращает в качестве веса просто сам фактор веса фразы. Второй честно считает только BM25, а веса фразы в каждом совпавшем поле вместо долгих расчетов быстро принимает равными единице.

// ранкер SPH_RANK_PROXIMITY_BM25 (по умолчанию)
rank_proximity_bm25 = doc_phrase_weight*1000 + doc_bm25*999

// ранкер SPH_RANK_PROXIMITY (форсируется в режиме SPH_MATCH_ALL)
rank_proximity = doc_phrase_weight

// ранкер SPH_RANK_BM25 (быстрый, тк. не нужно считать дорогие веса подфраз)
rank_bm25 = sum ( matching_field_weight )*1000 + doc_bm25*999

SPH_RANK_PROXIMITY_BM25 выбран по умолчанию, тк. есть мнение, что из имеющихся именно он дает наиболее релевантный результат. Умолчание в будущем может и поменяться; планы делать более умные и в целом более хорошие ранкеры вполне себе есть. Ранкер SPH_RANK_PROXIMITY как бы эмулирует режим SPH_MATCH_ALL (самый, кстати, первый, с которого в 2001-м году все начиналось). SPH_RANK_BM25 нужно использовать, если вес фразы по каким-либо причинам не важен; либо просто если хочется подускорить запросы. Кстати говоря, выбор ранкера существенно влияет на скорость запроса! Типично наиболее дорогая часть вычислений это анализ позиций слов внутри документа. Поэтому SPH_RANK_PROXIMITY_BM25 всегда будет медленнее SPH_RANK_BM25, а тот в свою очередь всегда медленнее SPH_RANK_NONE (который вообще ничего не считает).

Еще один ранкер, используемый для обработки исторических режимов поиска, это SPH_RANK_MATCHANY. Он тоже считает веса подфраз, как и два других ранкера про proximity. Но этот вдобавок считает число совпавших ключевых слов в каждом поле, и комбинирует его с весами подфраз так, чтобы а) более длинная подфраза в любом поле отранжировала весь документ выше; б) при одинаковой длине подфразы, выше ранжировался документ с бОльшим количеством совпавших слов. На пальцах, если в документе A есть более точная (длинная) подфраза запроса, чем в документе B, надо выше ранжировать именно документ A. Иначе (если подфразы одинаковой длины) смотрим просто на число слов. Алгоритм такой:

k = 0
foreach ( field in all_fields )
   k += user_weight ( field ) * num_keywords ( query )

rank = 0
foreach ( field in matching_fields )
{
   field_phrase_weight = max_common_subsequence_length ( query, field )
   field_rank = ( field_phrase_weight * k + num_matching_keywords ( field ) )
   rank += user_weight ( field ) * field_rank
}

Ранкер SPH_RANK_WORDCOUNT тупо суммирует количество совпавших в каждом поле вхождений ключевых слов, помноженное на вес поля. Проще разве что SPH_RANK_NONE, который вообще ничего не считает.

rank = 0
foreach ( field in matching_fields )
   rank += user_weight ( field ) * num_matching_occurrences ( field )

Наконец, SPH_RANK_FIELDMASK возвращает битовую маску полей, совпавших с запросом. (Тоже несложный, да...)

rank = 0
foreach ( field in matching_fields )
   rank |= ( 1<<index_of ( field ) )

Про звездочки


Регулярно всплывает вопрос, чему таки равны максимальные возможные веса, и как их правильно пересчитать в звездочки (обычно 5-конечные, но возможны варианты), проценты, или баллы по интуитивно понятной шкале от 1 до 17. Как видим, максимальный вес сильно зависит и от ранкера, и от запроса. Например, абсолютный максимум веса на выходе из SPH_RANK_PROXIMITY_BM25 зависит от числа ключевых слов и весов полей:

max_proximity_bm25 = num_keywords * sum ( field_weights ) * 1000 + 999

Но этот абсолютный максимум будет достигнут только когда все поля содержат точную копию запроса во-1х, плюс запрос ищет во всех полях во-2х. А если запрос составлен с ограничением по одному конкретному полю (например, @title hello world)? Абсолютный максимум недостижим в принципе, для этого конкретного вида запроса максимальный практически возможный вес будет равен:

max_title_query_weight = num_keywords * title_field_weight * 1000 + 999

Так что точно аналитически посчитать «правильный» максимум веса довольно сложно. Если жизни нету без звездочек, можно либо сравнительно легко считать «абсолютный» максимум (который практически никогда не будет достигаться), либо вообще брать максимальный @weight по каждой конкретной выборке, и нормализовать все относительно него. Через multi-query механизм это делается почти бесплатно, но это уже тема для отдельной статьи.

Про полные совпадения (update)


В комментариях возник хороший вопрос, отчего полные совпадения поля с запросом не ранжируются выше, и как добиться.

Дело как раз в логике работы ранкеров. Дефолтные ранкеры proximity и proximity_bm25 в случае, когда запрос встретился в поле полностью, назначают такому полю максимальный вес фразы (а это у них основной фактор ранжирования). При этом длина самого поля не учитывается, факта полного совпадения поля с запросом не учитывается тоже. Такое вот исторически сложившееся поведение. Видимо, ситуация, когда запрос полностью совпадает с тем или иным полем, отчего-то ранее встречалась не особо часто.

В текущем транке (версия 0.9.10) работы уже ведутся, добавлен экспериментальный ранкер SPH_RANK_SPH04, который как раз ранжирует полные совпадения поля выше. Техническая возможность появилась в 0.9.9, тк. в 0.9.8 формат индекса не предусматривает нужных данных (для любопытных, там у сохраненных позиций нет флажка «это был конец поля»).

Кое-что можно сделать и без нового ранкера.

Например, можно пробовать вручную увеличивать вес в случае полного совпадения. Для этого ручками убираем всю пунктуацию и верхний регистр из самого поля (при индексации) и из запроса, считаем crc32 от поля, сохраняем егов атрибут индекса. Затем при поиске рассчитываем выражение @weight+if(fieldcrc==querycrc,1000,0) и сортируем результаты по этому выражению. Довольно криво, но в отдельных случаях может помочь.

Еще можно чуть изменить задачу, и учитывать не собственно факт полного совпадения, а длину документа (либо отдельного поля). Для этого при ндексации сохраняем LENGTH(myfield) в атрибут, при поиске ранжируем по выражению вида (это только пример!) @weight+ln(max(len,1))*1000

Еще в некоторых случаях (типа примера с индексациями названий песен из комментариев) может оказаться осмысленно индексировать поля не отдельно, а вместе — чтобы совпадение фразы «группа — песня» выдало больший вес фразы в «склеенном» поля. Иначе все поля считаются отдельными, совпадение «через границу поля» отранжировано выше не будет.

Про пространство для напильника


Есть ли куда всю эту кухню дорихтовывать дальше? Еще как. Поняв, как устроены имеющиеся ранкеры, какие факторы они считают и как комбинируют, сразу можно немедля осознанно подправлять @weight (точнее, задавать новое арифметическое выражение с участием @weight, и сортировать выдачу по нему). Что интереснее, технически можно добавлять новые специализированные ранкеры (минута рекламы на первом: ранкеры SPH_RANK_WORDCOUNT и SPH_RANK_FIELDMASK придумал не я, запросили коммерческие пользователи). Из C++ кода ранкеров есть немедленный доступ к запросу, ключевым слова, ранжируемому документу, и (самое главное) списку всех совпадающих с запросом вхождений ключевых слов вместе с позициями в документе… да, из этих деталей советского паровоза вполне таки можно собрать советский истребитель; главное, мастерски применять магию напильника.
+44
17 июня 2009, 15:22
90

комментарии (39)

+3
recoilme #
Не понял ни-че-го.
Можно для просто толковых немножко на пальцах пояснить??
У меня есть таблица с полем id, artist
Запрос — 'coil'
Сфинкс выдает:
Lacuna Coil, This Mortal Coil, The_Coil_of_sin и так далее и тому подобное.
Где то в самом конце — встречаются записи в которых в поле artist тупо написано Coil…

Я как то по простоте душевной думал поставлю сфинкс — уж он то поля на 100% совпадающие с запросом сверху выведет…

Можно что-то подкрутить?
+1
recoilme #
Полез на форум сфинкса. Чуть ли не каждый десятый запрос на форуме по низкой релевантности точных совпадений:

«Как сделать так, чтобы первым в результате было полное совпадение с полем?
Пример: soccer

из двух результатов:
dc soccer
soccer

Второй должен иметь больший вес.»

Ответы приводить не буду. Выглядят они мягко говоря дикими. Т.е. видимо не один я такой тупой. И этот вопрос волнует многих. Может быть автор пролшьет свет???
+2
shodan #
Щаз пролью…

Вот как раз логика работа ранкеров такая.
Проблема в том, что если запрос встретился в том поле полностью, уже получается максимальный вес фразы.
Безотносительно длины самого поля, и факта полного совпадения поля (!) с запросом.
Так исторически сложилось вот.

В версии 0.9.8 факт совпадения всего поля (!) вообще нельзя установить, не хватает данных в индексе.
Технически можно только факт совпадение-в-начале установить и забустить.
Но нужно приписать новый ранкер.

В версии 0.9.9 (текущая бета) добавилось еще всякое.
Ранкеров не добавилось, зато добавились модификаторы ^ и $, и соотв-но флажок «конец поля» в индексе.
Но все еще нужно приписать новый ранкер.

В версии 0.9.10 (текущий транк) добавился (барабанная) дробь как раз такой ранкер.
Называется sph04 (полностью SPH_RANK_SPH04), бустит совпадения в начале поля и в конце поля.
Возможно, бэкпортнем в 0.9.9 тоже.

Без ранкера сделать что-то непросто, но в определенной мере тоже можно.
Убирать ручками пунктуацию и верхний регистр из поля и запроса, посчитать crc32 поля, сохранить атрибутом.
Затем ранжировать по @weight+if(fieldcrc==querycrc,1000,0)
Криво, знаю, но может и пригодится таки кому.
0
recoilme #
Чего то не особо пролился свет… Можно по босяцки?

Скачать версию такую — то, тут то. В конфиге написать то-то. Если не поможет — то попробовать это.
Без барабанной дроби и фуршета.

Я правда хочу поставить, настроить и забыть. Если можно это сделать без изучения алгорима ранкера B52 c соответствующими модификаторами — то это было бы прекрасно.
Спасибо за ответ.
0
shodan #
Как объяснить проще, чем «в версии 0.9.10 добавился как раз такой ранкер, называется sph04» я не знаю :(
Нужно, чтобы

0
shodan #
Как объяснить проще, чем «в версии 0.9.10 добавился как раз такой ранкер, называется sph04» я не знаю :(
Нужно, чтобы кто-нибудь еще объяснил тогда.

> Если можно это сделать без изучения алгорима ранкера B52

:) Можно, наверное.
Статья таки скорее для тех, кому интересно изучить потроха.
+1
recoilme #
1. Где взять эту волшебную версию? я смотрю тут www.sphinxsearch.com/downloads.html, никаких транков не вижу:

Current beta release Sphinx 0.9.9-rc2 (r1785; Apr 08, 2009)
Latest stable release Sphinx 0.9.8.1 (r1533; Oct 30, 2008)

Если она ещё не вышла — можно бэкпортнуть в 0.9.9?

2. Что написать в конфиге чтобы включился ранкер sph04, который бустит совпадения в начале и в конце фразы? В доке ничего нет про него.
0
recoilme #
Решил проблему релевантности самостоятельно. Отказался от сфинкса, заюзал майэскюэлевский поиск — для меня самое то оказалось. Краткий хау ту моих действий для ленивых.

1. Сначала был лайк, и был он медленен и нерелевантен:
SELECT * FROM `wharrgarbl_mp3s_copy2905` WHERE `artist` LIKE '%coil%' LIMIT 0, 30// 1.7538 сек

2. Нужен индекс. Добавляем:
ALTER TABLE wharrgarbl_mp3s_copy2905 ADD FULLTEXT (artist);//ждем пару минут (зависит от того сколько лямов в таблице)

3. Преобразуем запрос:
SELECT * ,MATCH artist AGAINST ('coil') AS relev FROM wharrgarbl_mp3s_copy2905 WHERE MATCH artist AGAINST ('coil')>0//0.0019 сек.

И всё. По запросу coil — coil выше recoil, а Ария выше Дискотека авария.
Быстро. Просто. Вкусно. Удобно.
Мне хватает вобщем.
Слово-запрос кстати надо подготовить ещё, вырезать спец символы, разбить по словам и т.п.
Читать тут: phpclub.ru/detail/article/mysql_search?printVersion=1
0
shodan #
> заюзал майэскюэлевский поиск — для меня самое то оказалось.

:) Пока данные влазят в память, скорость не беспокоит, и поиск по уникальным словам — оно работает, да.
0
recoilme #
Осмелюсь добавить. Оно не просто работает. Оно охуительно работает.
1. Разобрался за пару часов.

2. Придал различные веса различным полям, причем довольно гибко
Например, есть поле артист/трек. индексы на обоих.

2.1 По запросу «coil» у группы койл с треком без слова coil — релевантность будет выше чем у группы lacuna coil даже если в названии трека будет слово coil.

2.2 По запросу coil — fire на первом месте будет coil — fire of the mind

2.3 По запросу Muse — bliss — будет соответствующий трек группы muse, а не говногруппа Bliss.

Не буду вас ругать, но по релевантности, в моем случае, мускул задвинул сфинкс на 100%
+1
shodan #
Ключевое слово «в моем случае» как раз.

Причем вполне понятно, что это за случай, и почему у Сфинкса «из коробки» не все гладко :)

Кстати, если статью читать внимательно, несложно добиться всего того же самого и на Сфинксе, в общем-то.

Другое дело, что на крохотных коллекциях это и не нужно, ага.
0
DmitryKoterov #
Поддержу Андрея. Мне приходилось работать с тремя полнотекстовыми движками: MySQL (ИМХО — ад кромешный: словоформ нет, работает медленно, в innodb не поддерживается), PostgreSQL (ИМХО — довольно хорош, однако сильно тормозит, в частности — при ранжировании, т.к. вначале делает всю выборку, а потом ее сортирует) и Sphinx. Последний лично мне больше всего нравится (особенно shebang-конфиги, это сказка), но у него есть большой недостаток: «без пол-литра не разберешься». В смысле, порог на вход очень высокий. Мне думается, что продукт можно значительно улучшить малой кровью, просто упростив работу с ним: добавить «коробочные» словоформы (всего-то в дистрибутив положить словарь и сделать в конфиге возможность указания относительного пути к файлам словаря), а также написать обертку для упрощения процесса инкрементного индексирования.

В общем, Андрей, — спасибо за отличную штуку!
0
mstarrr #
>shebang-конфиги,
Напишите пример, как вы их пользуете? Очень нужно, но честно не совсем понял из вики, что оно такое и как его заюзать в связке с сфинкс…

Разве что делать php "/path/to_config/sphinx.conf " — но это невероятный костыль и я честно не совсем понимаю, чему тут радоваться.

Буду очень благодарен за ответ — нужно в реальном проекте и «вчера».
0
recoilme #
Про «Muse — bliss» наврал. Придать бОльший вес первому слову не получилось. Точнее средствами пхп это разрулил
0
broderix #
Спасибо, shodan, все вполне понятно.
Для остальных могу посоветовать вернуться к этой статье, когда появится такая необходимость и базовые возможности sphinx исчерпаются себя для вас.
+1
david_mz #
Андрей, а есть ли надежда на ручное указание веса терма а) в документе при индексации и б) в запросе при поиске?

0
shodan #
а) Есть в транке (смотреть payloads)
б) В длинных планах есть, в коротких нет. Если надо спешно, welcome to sphinxsearch.com/consulting.html
0
stirbu #
расскажите по многопоточность запросов. сейчас возникла проблема что при большом количестве одновременных запросов, сфинкс стал выдавать через раз, нулевой результат.
0
mstarrr #
Я вот как для себя решил проблему многозапросности, код на пхп для phpapi:

foreach ($aPriceGroup as $aValue) {

$oSphinxClient->SetFilter('id_price_group', array($aValue['id']));
$iQuery = $oSphinxClient->AddQuery($sSphinxKeyword, 'price_group');
$oSphinxClient->ResetFilters();
$bAddedUnrunQuery=true;

$aPriceGroupAssoc[$iQuery+(32*$i)]=$aValue;

if ($iQuery && !($iQuery % 31) ) {
$aResultQuery=$oSphinxClient->RunQueries();
$aResultAll=array_merge($aResultAll,$aResultQuery);

$sLastError=$oSphinxClient->GetLastError();
$i++;
$bAddedUnrunQuery=false;
}
}
if ($bAddedUnrunQuery) {
$aResultQuery=$oSphinxClient->RunQueries();
$aResultAll=array_merge($aResultAll,$aResultQuery);
}



Костыль, признаю: пока полностью не разобрался в синтаксисе запросов — для моего случае рабоатет безотказно. На винде правда при девелоперском режиме почему-то приходится иногда перестраивать индекс, так как сервис не запускается. Но лечится удалением процесса, перестройкой индекса и рестартом сервиса после ошибки винды.
0
nod #
Отличная статья!
Без разбора потрахов, честно не хотел использовать сфинкс. Подумывал о создании своих весов и индексов тупо на соседнем сервере. (Полнотекстовый поиск по мускулу — это для амерекосов, может и пойдет. А так для нашего брата — без словоформ полная туфта).

Не отправляйте в ГУГЛ (пользоваться умеею) и в документацию (много букв). Посоветуйте хорошие статьи (если есть) на:
— как устроен индекс внутри;
— как устроены словоформы, и можно ли их тюнить;
— что-то хорошее про инкрементальный индекс, и его ньюансы (про то что это зло — понятно, но когда новых данных вагон, то инкрементальный это то что нужно, чтобы дожить до ночи);
— практика хорошего применения, или какие ошибки не стоит делать.
0
mkechinov #
Я, конечно, поздновато, но все же.

Есть ли возможность задать какой-то список документов, по которому можно выводить в самый верх документы по определенным запросам?

Поясню.

Есть документы:
1. «Приказ о вступлении в силу гражданского кодекса российской федерации».
2. «Комментарии к гражданскому кодексу российской федерации».
3. «Гражданский кодекс российской федерации».
4.… еще порядка пары тысяч документов, в названии которых встречается фраза «Гражданский кодекс российской федерации».

Так вот, при поисковом запросе «Гражданский кодекс российской федерации», собственно, самый важный документ (в списке под номером 3) появляется где-то очень-очень далеко, а должен идти первой же строкой.

Поэтому вопрос: есть ли какой-то способ, по которому для некоторых конкретных документов при некоторых конкретных запросах можно задать связи, благодаря которым документ с указанным таким поисковым запросом всегда будет на первом месте?
0
shodan #
Дефолтная ранжировалка различий между разными точными совпадениями фразы не делает.

Ранжировалка SPH04 из транка занимается примерно таким.
0
mkechinov #
Ну, т.е. в 9.9.9-rc2 средствами сфинкса мне такого добиться не получится?

А на когда планируется 9.9.10?
0
shodan #
> Ну, т.е. в 9.9.9-rc2 средствами сфинкса мне такого добиться не получится?

С определенными трюками разве (ручками считать и бустить точное совпадения поля).

> А на когда планируется 9.9.10?

Ряд клиентов уже использует в продакшне.

Публичную альфу скорее всего выкатим в декабре.
0
mkechinov #
Понятно, спасибо.
0
mkechinov #
Понимаю, что несколько не в тему, то все же хочу задать вопрос.

У документа есть разные свойства. Например "№ документа". "№ документа"- это не число, а строка. Например «ФЗ-1244» или «Приказ №33». Каждый документ может иметь несколько номеров. Соответственно, у нас связь «один-ко-многим».

По идее можно было бы использовать sql_attr_multi, но он работает только с числами. А тут строки. При этом нужно искать как по точному совпадению, так и по части строки, а также и по началу вхождения (например, «ФЗ-12%»).

Такое в текущем релизе на сфинксе можно сделать? Если нет, то планируется в каком-нибудь последующем релизе и, если да, то в каком?

Спасибо.
0
aristoc #
А можно сделать так, чтобы найденные слова большей длины получали больший итоговый вес?
0
shodan #
serious necro :)

больший вес «и так» получают более редкие слова.
0
aristoc #
К сожалению по моим наблюдениям очень часто портят выдачу записи с разного рода короткими предлогами и словами.

Например, по запросу «мини трактор» сначала идут записи со словами «мини», «мин» и где то уже в конце со словом трактор. Хотя по логике более длинное слово найденное должно быть выше. Аналогично со всякого рода предлогами типа «для», «под» и т.п.
0
shodan #
Длина не имеет значения. При прочих равных ранжироваться выше должны более редкие слова. Если слово «мини» в коллекции реже, чем «трактор», оно победит.
0
aristoc #
Ну вот в том то и дело что почему-то не срабатывает это. Слово мини, вместе со своими стемами более частое чем трактор, которое более редкое. Использую SPH_MATCH_ANY с заданными весами полей через SetFieldWeights.
+1
shodan #
> Использую SPH_MATCH_ANY

ыыыааа.

который вообще не смотрит на частоты слов — это вроде умеренно подробно как раз расписано в посте. нет?

(one | two | three) + extended2 + sph_rank_proximity_bm25 и вперед.
0
aristoc #
Спасибо. Раньше пробовал, но без разбиения по | слов запроса, а строгий поиск по всем словам не подходил, поэтому и пришлось вернуться к ANY.
0
shodan #
можно еще «one two three»/1, оно эквивалентно. (Именно так внутри эмулируется ANY.)
0
aristoc #
Да, так еще лучше, спасибо!

Возник еще вопрос. При использовании морфологии почему-то имеют одинаковый вес слова полностью совпадающие и слова похожие (обрезанные). Например, если ищем «лом», то в выдаче будут записи со словами «лом»,«ломаем»,«ломать» и т.п. При этом записи с полным совпадением оказываются иногда (и даже часто) ниже записей с похожими словами.

Можно ли это как-то еще подстроить? Так чтобы слова полностью совпадающие имели более высокий вес, чем морфологически близкие?
0
shodan #
При включении «просто» морфологии оно все слова приводит к одному и тому же стему внутри и более не отличает.

Можно попробовать index_exact_words=1 + expand_keywords=1. Первое сохранит исходные точные формы в индекс. Второе автоматом расширит запрос и заменит каждое слово на (=слово|слово). Что, теоретически, приведет к бусту веса точных совпадений.
0
aristoc #
К сожалению не помогло. (
0
shodan #
В тесте помогает.

— Query 1 (mode=extended2,ranker=(default),index=) — Query 'dog run': retrieved 4 of 4 matches in 0.001 sec.
Word stats:
'=dog' found 2 times in 2 documents
'dog' found 4 times in 4 documents
'=run' found 2 times in 2 documents
'run' found 4 times in 4 documents

Matches:
1. doc_id=4, weight=4430 body=«dog run»
2. doc_id=1, weight=3416 body=«dog runs»
3. doc_id=2, weight=3416 body=«dogs run»
4. doc_id=3, weight=2402 body=«dogs running»
0
aristoc #
Да, пардон. Для включения этих новых опций обновлял сфинкс до последней беты, и видимо после переиндексирования забыл демона перезапустить. Теперь действительно работает. Большое спасибо за помошь.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.