Статьи

Используйте префиксные операторы вместо логических операторов

Нижеследующее написано для пользователей Solr, но принципы применимы и к пользователям Lucene.

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

Фон: логическая логика делает для ужасных результатов

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

Когда пользователь ищет «все документы, содержащие слово« Аллигатор »», он не будет очень доволен, если поисковая система применяет простую логическую логику, чтобы просто идентифицировать неупорядоченный набор всех соответствующих документов. Вместо этого используются алгоритмы, такие как TF / IDF, чтобы попытаться идентифицировать упорядоченный список совпадающих документов, так что «лучшие» совпадения идут первыми. Аналогичным образом, если пользователь ищет «все документы, содержащие слова« Аллигатор »или« Крокодил »), простое логическое объединение наборов документов из отдельных запросов не даст результатов, столь же хороших, как запрос, который ЦГ / IDF десятка документов для отдельных запросов, а также принимая во внимание , какие документы матчей какзапросы. (Вероятно, пользователь больше интересуется документом, в котором обсуждаются сходства и различия между аллигаторами и крокодилами, чем документами, которые упоминают один или другой очень много раз).

Это подводит нас к сути того, почему я считаю плохой идеей использовать «булевы операторы» в строках запросов: потому что это не то, как работают базовые структуры запросов, и это не так выразительно, как альтернатива для описания того, что вы хотите.

BooleanQuery: Отличный класс, плохое имя

Чтобы действительно понять, почему логические операторы уступают префиксным операторам, вы должны начать с рассмотрения базовой реализации. Класс BooleanQuery, вероятно, является одним из самых вводящих в заблуждение имен классов во всей базе кода Lucene, поскольку он вообще не моделирует простые операции запроса логической логики. Основная функция BooleanQuery:

  1. BooleanQuery состоит из одного или нескольких BooleanClauses , каждое из которых содержит две части информации:

    • Вложенный запрос
    • Occur флаг, который имеет одно из трех значений

      • ДОЛЖЕН — указывать, что документы должны соответствовать этому вложенному запросу, чтобы документ соответствовал BooleanQuery, и оценка этого подзапроса должна способствовать оценке BooleanQuery.
      • MUST_NOT — указывает, что документам, которые соответствуют этому вложенному запросу, запрещено соответствовать BooleanQuery
      • СЛЕДУЕТ — указывать, что документы, которые соответствуют этому вложенному запросу, должны иметь свою оценку из вложенного запроса, чтобы внести вклад в оценку из BooleanQuery, но документы могут совпадать с BooleanQuery, даже если они не соответствуют вложенному запросу.
  2. Если BooleanQuery не содержит обязательных BooleanClauses, то документ считается совпадающим с BooleanQuery только в том случае, если одно или несколько из SHOULD BooleanClauses совпадают.
  3. Окончательная оценка документа, который соответствует BooleanQuery, основывается на сумме оценок всех соответствующих BooleanClauses MUST и SHOULD, умноженных на «коэффициент координирования», основанный на отношении количества совпадающих BooleanClauses к общему количеству BooleanClauses. в логическом запросе.

Эти правила не совсем просты для понимания. Они, безусловно, более сложны, чем таблицы истинности логической логики, но это потому, что они более мощные. Приведенные ниже примеры показывают, как легко реализовать «чистую» булеву логику с объектами BooleanQuery, но они лишь демонстрируют то, что возможно с классом BooleanQuery:

  • Соединение: (X ∧ Y)

    BooleanQuery q = new BooleanQuery();
    q.add(X, Occur.MUST);
    q.add(Y, Occur.MUST);
    
  • Дизъюнкция: (X ∨ Y)

    BooleanQuery q = new BooleanQuery();
    q.add(X, Occur.SHOULD);
    q.add(Y, Occur.SHOULD);
    
  • Отрицание: (X¬Z)

    BooleanQuery q = new BooleanQuery();
    q.add(X, Occur.SHOULD);
    q.add(Z, Occur.MUST_NOT);
    

Парсер запросов: операторы префиксов

В Lucene QueryParser (и всех других основанных на нем синтаксических анализаторах, таких как DisMax и EDisMax) операторы «префикса» «+» и «-» отображаются непосредственно на флаги Occur.MUST и Occur.MUST_NOT, в то время как отсутствие префикса сопоставляется с флагом Occur.SHOULD по умолчанию. (Если у вас есть предложения по синтаксису односимвольного префикса, который можно использовать для явного указания Occur.SHOULD, пожалуйста, прокомментируйте ваши предложения, я пытался придумать хороший вариант в течение многих лет.) Поэтому, используя синтаксис префикса, Вы можете выразить все перестановки, которые поддерживает класс BooleanQuery, а не просто логическую логику:

  • (+ X + Y) … Соединение, т.е.: (X ∧ Y)

    BooleanQuery q = new BooleanQuery();
    q.add(X, Occur.MUST);
    q.add(Y, Occur.MUST);
    
  • (XY) … дизъюнкция, то есть: (X ∨ Y)

    BooleanQuery q = new BooleanQuery();
    q.add(X, Occur.SHOULD);
    q.add(Y, Occur.SHOULD);
    
  • (+ X -Z) … Отрицание, то есть: (X¬Z)

    BooleanQuery q = new BooleanQuery();
    q.add(X, Occur.MUST);
    q.add(Z, Occur.MUST_NOT);
    
  • ((XY) -Z) … ((X ∨ Y) ¬ Z)

    BooleanQuery q = new BooleanQuery();
    BooleanQuery inner = new BooleanQuery();
    inner.add(X, Occur.SHOULD);
    inner.add(Y, Occur.SHOULD);
    q.add(inner, Occur.SHOULD);
    q.add(Z, Occur.MUST_NOT);
    
  • (XY-Z) … Не выражается в простой логической логике

    BooleanQuery q = new BooleanQuery();
    q.add(X, Occur.SHOULD);
    q.add(Y, Occur.SHOULD);
    q.add(Z, Occur.MUST_NOT);
    

Обратите особое внимание на различия между двумя последними примерами. (XY -Z) — это один объект BooleanQuery, содержащий три предложения, а ((XY) -Z) — это BooleanQuery, содержащий два предложения, одно из которых является вложенным BooleanQuery, содержащим два предложения. В обоих случаях документ должен соответствовать либо «X», либо «Y», и он не может совпадать с «Z» (поэтому набор документов, сопоставленный каждому запросу, будет одинаковым), и в обоих случаях оценка документа будет выше, если совпадает с предложениями «X» и «Y»; но из-за различий в их структуре оценки для этих запросов будут разными для каждого документа. В частности, коэффициент координат приведет к тому, что документы, соответствующие только одному из «X» или «Y» (но не обоим), будут иметь чрезвычайно разные оценки по каждому из этих запросов.(Предполагается, что используется DefaultSogeneity ; можно было бы написать пользовательское подобие, чтобы заставить баллы быть эквивалентными)

Парсер запросов: «Булевы операторы»

Анализатор запросов также поддерживает так называемые «логические операторы», которые также могут использоваться для выражения логической логики, как показано в следующих примерах:

  • (X И Y) … Соединение, то есть: (X ∧ Y)

    BooleanQuery q = new BooleanQuery();
    q.add(X, Occur.MUST);
    q.add(Y, Occur.MUST);
    
  • (X ИЛИ Y) … дизъюнкция, то есть: (X ∨ Y)

    BooleanQuery q = new BooleanQuery();
    q.add(X, Occur.SHOULD);
    q.add(Y, Occur.SHOULD);
    
  • (X НЕ Z) … Отрицание, то есть: (X ¬ Z)

    BooleanQuery q = new BooleanQuery();
    q.add(X, Occur.SHOULD);
    q.add(Z, Occur.MUST_NOT);
    
  • ((X И Y) ИЛИ Z) … ((X ∧ Y) ∨ Z)

    BooleanQuery q = new BooleanQuery();
    BooleanQuery inner = new BooleanQuery();
    inner.add(X, Occur.MUST);
    inner.add(Y, Occur.MUST);
    q.add(inner, Occur.SHOULD);
    q.add(Z, Occur.SHOULD);
    
  • ((X ИЛИ Y) И Z) … ((X ∨ Y) ∧ Z)

    BooleanQuery q = new BooleanQuery();
    BooleanQuery inner = new BooleanQuery();
    inner.add(X, Occur.SHOULD);
    inner.add(Y, Occur.SHOULD);
    q.add(inner, Occur.MUST);
    q.add(Z, Occur.MUST);
    
  • (X И (Y НЕ Z)) … (X ∧ (Y ¬ Z))

    BooleanQuery q = new BooleanQuery();
    BooleanQuery inner = new BooleanQuery();
    inner.add(Y, Occur.MUST);
    inner.add(Z, Occur.MUST_NOT);
    q.add(X, Occur.MUST);
    q.add(inner, Occur.MUST);
    

Обратите внимание, как важно использовать скобки для объединения нескольких операторов, чтобы генерировать запросы, которые правильно моделируют логическую логику. Как упоминалось ранее, класс BooleanQuery поддерживает произвольное количество предложений, поэтому (X OR Y OR Z) представляет собой один BooleanQuery с тремя предложениями — он не эквивалентен ни ((X OR Y) OR Z), ни (X OR ( Y ИЛИ Z)), потому что они приводят к BooleanQuery с двумя предложениями, одно из которых является вложенным BooleanQuery. Как упомянуто выше при обсуждении префиксных операторов, оценки по каждому из этих запросов будут различаться в зависимости от того, какие пункты соответствуют каждому документу.

Ситуация определенно становится очень запутанной, когда эти «логические операторы» используются не так, как описано выше. В некоторых случаях это происходит из-за того, что анализатор запросов пытается простить использование операторов в стиле «естественный язык», что многие системы булевой логики считают ошибкой синтаксического анализа. В других случаях поведение странно эзотерическое:

  • Запросы анализируются слева направо
  • NOT устанавливает флаг Occurs в предложении, чтобы он был прав на MUST_NOT
  • И изменит флаг Occurs в предложении, оставив его ДОЛЖНЫ, если только он не был установлен в MUST_NOT.
  • И устанавливает флаг Occurs предложения, чтобы он был ДОЛЖЕН
  • Если оператор по умолчанию синтаксического анализа запросов был установлен в положение «А»: ИЛИ изменит Встречается флаг пункта , чтобы оно осталось SHOULD , если он не был уже установлен в MUST_NOT
  • ИЛИ устанавливает флаг Occurs в условии, что он ДОЛЖЕН

На практике это означает, что NOT имеет приоритет над AND, который имеет приоритет над OR — но только если оператор по умолчанию для анализатора запросов не был изменен со значения по умолчанию («Или»). Если оператор по умолчанию установлен на «И», то поведение просто странное.

В заключении

Я не буду пытаться защищать или оправдывать поведение синтаксического анализатора запросов, когда он встречает эти «логические операторы», потому что во многих случаях я сам не понимаю или не согласен с поведением — но на самом деле не в этом суть этой статьи. Моя цель не состоит в том, чтобы убедить вас в том, что поведение этих операторов имеет смысл, а наоборот, моя цель сегодня состоит в том, чтобы показать, что независимо от того, как эти операторы анализируются, они не являются хорошим представлением базовой функциональности, доступной в BooleanQuery класс.

Сделайте себе одолжение и начните думать о BooleanQuery как о контейнере произвольных вложенных запросов, помеченных MUST, MUST_NOT или SHOULD, и откройте для себя всю мощь, доступную вам за пределами простой логической логики.

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

Источник:  http://www.lucidimagination.com/blog/2011/12/28/why-not-and-or-and-not/