Boolesche Operatoren für Solr-Benutzer

Die folgenden Ausführungen richten sich an Solr-Benutzer, aber die Grundsätze gelten auch für Lucene-Benutzer. BEARBEITET: 2017-05-19 – Codebeispiele und Beispiele…

Die folgenden Ausführungen richten sich an Solr-Benutzer, aber die Grundsätze gelten auch für Lucene-Benutzer.

BEARBEITET: 2017-05-19 – Codebeispiele und Beispiele wurden aktualisiert, um die Lucene 6.5 APIs und die neue Standardähnlichkeit zu berücksichtigen.

Ich mag die so genannten „Booleschen Operatoren“ („AND“, „OR“ und „NOT“) wirklich nicht und rate generell davon ab, sie zu verwenden. Es ist verständlich, dass unerfahrene Benutzer dazu neigen, die Abfragen, die sie ausführen möchten, in diesen Begriffen zu denken, aber wenn Sie mit IR-Konzepten im Allgemeinen und den Möglichkeiten von Solr im Besonderen vertrauter werden, sollten Sie versuchen, „kindische Dinge beiseite zu lassen“ und anfangen, in den Begriffen der überlegenen „Präfix-Operatoren“ („+“, „-„) zu denken (und Ihre Benutzer dazu zu ermutigen).

Hintergrund: Boolesche Logik sorgt für furchtbare Ergebnisse

Boolesche Algebra ist (wie mein Vater es ausdrücken würde) „ziemlich tolles Zeug“ und die Welt, wie wir sie kennen, würde ohne sie ganz sicher nicht existieren. Aber wenn es um die Entwicklung einer Suchmaschine geht, ist die boolesche Logik nicht sehr hilfreich. Je nachdem, wie Sie es betrachten, geht es bei der booleschen Logik um Wahrheitswerte und/oder um Schnittmengen. In beiden Fällen gibt es kein Konzept der „Relevanz“ – entweder ist etwas wahr oder falsch; entweder ist es in einer Menge enthalten oder nicht in der Menge.

Wenn ein Benutzer nach „allen Dokumenten, die das Wort ‚Alligator‘ enthalten“ sucht, wird er nicht sehr glücklich sein, wenn ein Suchsystem eine einfache boolesche Logik anwendet, um einfach die ungeordnete Menge aller übereinstimmenden Dokumente zu ermitteln. Stattdessen werden Algorithmen wie TF/IDF verwendet, um die geordnete Liste der übereinstimmenden Dokumente zu ermitteln, so dass die „besten“ Treffer zuerst erscheinen. Wenn ein Benutzer nach „allen Dokumenten, die die Wörter ‚Alligator‘ oder ‚Krokodil‘ enthalten“ sucht, würde eine einfache boolesche Verknüpfung der Dokumentensätze aus den einzelnen Abfragen nicht so gute Ergebnisse liefern wie eine Abfrage, die die Begriffs- und Dokumentstatistiken für die einzelnen Abfragen berücksichtigt und auch berücksichtigt, welche Dokumente zu beiden Abfragen passen. (Der Benutzer ist wahrscheinlich mehr an einem Dokument interessiert, in dem die Ähnlichkeiten und Unterschiede zwischen Alligatoren und Krokodilen erörtert werden, als an Dokumenten, in denen nur das eine oder das andere sehr oft erwähnt wird).

Damit kommen wir zum Kern der Sache, warum ich es für eine schlechte Idee halte, die „Booleschen Operatoren“ in Abfragezeichenfolgen zu verwenden: weil die zugrunde liegenden Abfragestrukturen so nicht funktionieren und es nicht so aussagekräftig ist wie die Alternative, um zu beschreiben, was Sie wollen.

BooleanQuery: Tolle Klasse, schlechter Name

Um wirklich zu verstehen, warum die booleschen Operatoren den Präfix-Operatoren unterlegen sind, müssen Sie zunächst die zugrunde liegende Implementierung betrachten. Die Klasse BooleanQuery ist wahrscheinlich einer der irreführendsten Klassennamen in der gesamten Lucene-Codebasis, denn sie modelliert überhaupt keine einfachen Abfrageoperationen mit boolescher Logik. Die grundlegende Funktion einer BooleanQuery ist:

  1. Eine BooleanQuery besteht aus einer oder mehreren BooleanClauses, von denen jede zwei Informationen enthält:
    • Eine verschachtelte Abfrage
    • Ein Occur-Flag, das einen von vier Werten hat
      • MUST / FILTER – gibt an, dass Dokumente mit dieser verschachtelten Abfrage übereinstimmen müssen, damit das Dokument mit der BooleanQuery übereinstimmt. (Der Unterschied zwischen diesen beiden Abfragen besteht darin, dass die Punktzahl der MUST-Unterabfragen zur endgültigen Punktzahl für die BooleanQuery beiträgt, die Punktzahlen der FILTER-Unterabfragen jedoch nicht verwendet werden)
      • MUST_NOT – die angibt, dass Dokumente, die mit dieser verschachtelten Abfrage übereinstimmen, nicht mit der BooleanQuery übereinstimmen dürfen
      • SHOULD – die angibt, dass bei Dokumenten, die mit dieser verschachtelten Abfrage übereinstimmen, die Punktzahl aus der verschachtelten Abfrage zur Punktzahl aus der BooleanQuery beitragen sollte, aber Dokumente können auch dann eine Übereinstimmung für die BooleanQuery sein, wenn sie nicht mit der verschachtelten Abfrage übereinstimmen
  2. Wenn eine BooleanQuery keine MUST BooleanClauses enthält, dann wird ein Dokument nur dann als Übereinstimmung mit der BooleanQuery betrachtet, wenn eine oder mehrere der SHOULD BooleanClauses eine Übereinstimmung darstellen.
  3. Die endgültige Punktzahl eines Dokuments, das mit einer BooleanQuery übereinstimmt, wird hauptsächlich aus der Summe der Punktzahlen aller übereinstimmenden MUST und SHOULD BooleanClauses berechnet.

Diese Regeln sind nicht gerade einfach zu verstehen. Sie sind sicherlich komplexer als die Wahrheitstabellen der booleschen Logik, aber das liegt daran, dass sie viel leistungsfähiger sind. Die folgenden Beispiele zeigen, wie einfach es ist, „reine“ boolesche Logik mit BooleanQuery-Objekten zu implementieren, aber sie kratzen nur an der Oberfläche dessen, was mit der Klasse BooleanQuery möglich ist:

  • Verneinung: (X ¬ Z)
    BooleanQuery q = new BooleanQuery.Builder()
      .add(X, Occur.SHOULD)
      .add(Z, Occur.MUST_NOT)
      .build();
    
  • Disjunktion: (X ∨ Y)
    BooleanQuery q = new BooleanQuery.Builder()
      .add(X, Occur.SHOULD)
      .add(Y, Occur.SHOULD)
      .build();
    
  • Konjunktion: (X ∧ Y)
    BooleanQuery q = new BooleanQuery.Builder()
      .add(X, Occur.MUST)
      .add(Y, Occur.MUST)
      .build();
    

Abfrage-Parser: Präfix-Operatoren

Im Lucene QueryParser (und allen anderen Parsern, die darauf basieren, wie DisMax und EDisMax) werden die „Präfix“-Operatoren „+“ und „-“ direkt den Flags Occur.MUST und Occur.MUST_NOT zugeordnet, während das Fehlen eines Präfixes standardmäßig dem Flag Occur.SHOULD zugeordnet wird. (Wenn Sie Vorschläge für eine einstellige Präfix-Syntax haben, die verwendet werden könnte, um explizit auf Occur.SHOULD hinzuweisen, kommentieren Sie bitte Ihre Vorschläge, ich versuche schon seit Jahren, einen guten Vorschlag zu finden). Mit der Präfix-Syntax können Sie also alle Permutationen ausdrücken, die die Klasse BooleanQuery unterstützt – nicht nur einfache boolesche Logik:

  • (+X -Z) … Negation, d.h.: (X ¬ Z)
    BooleanQuery q = new BooleanQuery.Builder()
      .add(X, Occur.MUST)
      .add(Z, Occur.MUST_NOT)
      .build()
    
  • (+X +Y) … Konjunktion, d.h.: (X ∧ Y)
    BooleanQuery q = new BooleanQuery().Builder()
      .add(X, Occur.MUST)
      .add(Y, Occur.MUST)
      .build();
    
  • (X Y) … Disjunktion, d.h.: (X ∨ Y)
    BooleanQuery q = new BooleanQuery.Builder()
      .add(X, Occur.SHOULD)
      .add(Y, Occur.SHOULD)
      .build()
    
  • (X +Y) … Nicht in einfacher boolescher Logik ausdrückbar
    BooleanQuery q = new BooleanQuery.Builder()
      .add(X, Occur.SHOULD)
      .add(Y, Occur.MUST)
      .build()
    

Beachten Sie vor allem die Unterschiede zwischen den letzten drei Beispielen. (X +Y) erfordert, dass Dokumente mit Y übereinstimmen. Wenn sie auch mit X übereinstimmen, wird ihre Punktzahl erhöht. Dies ist nicht dasselbe wie eine einfache Konjunktion (bei der beide Unterabfragen übereinstimmen müssen) oder eine Disjunktion (bei der jedes Dokument mit einer der beiden Unterabfragen übereinstimmt). Die ähnlichste Abfrage im Sinne der reinen booleschen Logik ist einfach Y, die auf die gleiche ungeordnete Menge von Dokumenten zutrifft, aber die Punktzahlen sind sehr unterschiedlich.

Die folgende Tabelle zeigt, wie einige hypothetische Dokumente bei einigen hypothetischen X- und Y-Abfragen abschneiden würden (der Einfachheit halber nehmen wir an, dass X und Y Abfragen mit konstanter Punktzahl sind, die nicht von Begriffs- oder Dokumentstatistiken beeinflusst werden), sowie die endgültigen Punktzahlen, die von verschiedenen BooleanQuery-Strukturen erzeugt werden, die sich aus X und Y zusammensetzen. Wie Sie sehen können, gibt es kein rein boolesches Logikäquivalent zu (X +Y), das ebenfalls dieselben Punktzahlen für jedes Dokument erzeugt….

Dokument X Y +X +Y
X ∧ Y
X Y
X ∨ Y
X +Y
doc1: { X } 1.0 k.A. k.A. 1.0 k.A.
doc2: { Y } k.A. 1.0 k.A. 1.0 1.0
doc3: { X Y } 1.0 1.0 2.0 2.0 2.0

Abfrage-Parser: „Boolesche Operatoren“

Der Abfrageparser unterstützt auch die so genannten „booleschen Operatoren“, mit denen Sie auch boolesche Logik ausdrücken können, wie in diesen Beispielen gezeigt wird:

  • (X AND Y) … Konjunktion, d.h.: (X ∧ Y)
    BooleanQuery q = new BooleanQuery.Builder()
      .add(X, Occur.MUST)
      .add(Y, Occur.MUST)
      .build();
    
  • (X OR Y) … Disjunktion, d.h.: (X ∨ Y)
    BooleanQuery q = new BooleanQuery.Builder()
      .add(X, Occur.SHOULD)
      .add(Y, Occur.SHOULD)
      .build();
    
  • (X NOT Z) … Negation, d.h.: (X ¬ Z)
    BooleanQuery q = new BooleanQuery.Builder()
      .add(X, Occur.SHOULD)
      .add(Z, Occur.MUST_NOT)
      .build();
    
  • ((X AND Y) OR Z) … ((X ∧ Y) ∨ Z)
    BooleanQuery inner = new BooleanQuery.Builder()
      .add(X, Occur.MUST)
      .add(Y, Occur.MUST)
      .build();
    BooleanQuery q = new BooleanQuery().Builder()
      .add(inner, Occur.SHOULD)
      .add(Z, Occur.SHOULD)
      .build();
    
  • ((X OR Y) AND Z) … ((X ∨ Y) ∧ Z)
    BooleanQuery inner = new BooleanQuery.Builder()
      .add(X, Occur.SHOULD)
      .add(Y, Occur.SHOULD)
      .build();
    BooleanQuery q = new BooleanQuery.Builder()
      .add(inner, Occur.MUST)
      .add(Z, Occur.MUST)
      .build();
    
  • (X AND (Y NOT Z)) … (X ∧ (Y ¬ Z))
    BooleanQuery inner = new BooleanQuery.Builder()
      .add(Y, Occur.MUST)
      .add(Z, Occur.MUST_NOT)
      .build();
    BooleanQuery q = new BooleanQuery.Builder()
      .add(X, Occur.MUST)
      .add(inner, Occur.MUST)
      .build();
    

Bitte beachten Sie, dass es wichtig ist, mehrere Operatoren in Klammern zu kombinieren, um Abfragen zu erzeugen, die boolesche Logik korrekt modellieren. Wie bereits erwähnt, unterstützt die Klasse BooleanQuery eine oder mehrere Klauseln. Das bedeutet, dass (X OR Y OR Z) eine einzelne BooleanQuery mit drei Klauseln erstellt – streng genommen ist dies nicht gleichbedeutend mit ((X OR Y) OR Z) oder (X OR (Y OR Z)), da diese eine BooleanQuery mit zwei Klauseln ergeben, von denen eine eine verschachtelte BooleanQuery ist. Während die Ergebnisse aller drei Abfragen bei Verwendung der Standard-Similarity-Klasse von Lucene in der Regel gleich sind, sind diese Abfragen strukturell unterschiedlich und andere Verwendungen (oder andere Similarity-Funktionen) können zu subtil unterschiedlichen Ergebnissen führen.

Die Dinge werden definitiv sehr verwirrend, wenn diese „booleschen Operatoren“ auf andere als die oben beschriebenen Arten verwendet werden. In einigen Fällen liegt das daran, dass der Abfrageparser versucht, die Verwendung von Operatoren im Stil der „natürlichen Sprache“ zu verzeihen, die viele boolesche Logiksysteme als Parse-Fehler ansehen würden. In anderen Fällen ist das Verhalten bizarr und esoterisch:

  • Abfragen werden von links nach rechts geparst
  • NOT setzt das Occurs-Flag der Klausel nach rechts auf MUST_NOT
  • AND ändert das Occurs-Flag der Klausel auf MUST, sofern es nicht bereits auf gesetzt wurde. MUST_NOT
  • AND setzt das Occurs-Flag der Klausel nach rechts auf MUST
  • Wenn der Standardoperator des Abfrageparsers auf „Und“ gesetzt wurde: OR ändert das Occurs-Flag der Klausel auf SHOULD, sofern es nicht bereits auf MUST_NOT
  • OR setzt das Occurs-Flag der Klausel nach rechts auf SHOULD

Praktisch gesehen bedeutet dies, dass NOT Vorrang vor AND hat, das wiederum Vorrang vor OR hat – aber nur, wenn der Standardoperator für den Abfrageparser nicht von der Vorgabe („Oder“) abweicht. Wenn der Standardoperator auf „Und“ eingestellt ist, ist das Verhalten einfach nur seltsam.

Fazit

Ich werde nicht versuchen, die Art und Weise zu verteidigen oder zu rechtfertigen, wie sich der Query Parser verhält, wenn er auf diese „booleschen Operatoren“ trifft, denn in vielen Fällen verstehe ich das Verhalten selbst nicht oder bin nicht damit einverstanden – aber darum geht es in diesem Artikel auch nicht. Mein Ziel ist es nicht, Sie davon zu überzeugen, dass das Verhalten dieser Operatoren sinnvoll ist. Ganz im Gegenteil, mein Ziel ist es, darauf hinzuweisen, dass diese Operatoren, unabhängig davon, wie sie geparst werden, keine gute Darstellung der zugrunde liegenden Funktionalität der Klasse BooleanQuery sind.

Tun Sie sich selbst einen Gefallen und betrachten Sie BooleanQuery als einen Container mit beliebig verschachtelten Abfragen, die mit MUST, MUST_NOT oder SHOULD annotiert sind, und entdecken Sie die Möglichkeiten, die Ihnen jenseits einfacher boolescher Logik zur Verfügung stehen.

Vielen Dank an Bill Dueber, der mich in seinem kürzlich erschienenen Blog daran erinnert hat, dass ich einige Entwürfe zu diesem Thema auf meinem Laptop herumliegen hatte, die darauf warteten, fertiggestellt und online gestellt zu werden.

You Might Also Like

Analytics Studio: Verwandeln Sie Ihre E-Commerce-Daten in verwertbare Einblicke

Entdecken Sie, wie Analytics Studio Teams in die Lage versetzt, datengestützte Entscheidungen...

Read More

Wenn KI schief geht: Fehlschläge in der realen Welt und wie man sie vermeidet

Lassen Sie nicht zu, dass Ihr KI-Chatbot einen 50.000 Dollar teuren Tahoe...

Read More

Lucidworks Kernpakete: Branchenoptimierte KI-Such- und Personalisierungslösungen

Entdecken Sie unsere umfassenden Core Packages, die Analytics Studio, Commerce Studio und...

Read More

Quick Links

Diese Site ist auf wpml.org als Entwicklungssite registriert. Wechseln Sie zu einer Produktionssite mit dem Schlüssel remove this banner.