Engineering

Gleich und doch anders: Mehr Elasticsearch-Power durch Synonyme

Die Nutzung von Synonymen ist zweifelsohne eines der wichtigsten Instrumente im Instrumentarium eines Search Engineers. Von Neulingen oftmals unterschätzt, kommt doch in der Praxis so gut wie kein Suchsystem ohne sie aus. Gleichzeitig weist ihre Nutzung aber auch Komplexitäten und Eigenheiten auf, die selbst von erfahrenen Nutzern nicht gebührend beachtet werden. Synonymfilter sind Teil des Analyseprozesses, der Eingabetext in durchsuchbare Begriffe umwandelt. Der Einstieg in die Synonymnutzung ist zumeist relativ problemlos, aber eine erfolgreiche Anwendung in einem Praxisszenario erfordert doch ein tieferes Eindringen in die Materie.

In letzter Zeit gab es einige deutliche Verbesserungen rund um das Thema Analyse in Elasticsearch. Die deutlichste dieser Verbesserungen dürfte die Einführung der Möglichkeit gewesen sein, Analyzer für die Synonymerweiterung bei der Suche erneut zu laden, was es möglich macht, Synonyme, die beim Suchen erweitert werden, zu ändern und neu zu laden. In diesem Blogpost wird diese neue API vorgestellt. Außerdem werden einige häufige Fragen zur Nutzung von Synonymen beantwortet und es wird auf wichtige Aspekte hingewiesen, die es bei ihrer Nutzung zu beachten gilt.

Was bringt die Nutzung von Synonymen?

Um die Nützlichkeit und Flexibilität von Synonymen zu verstehen, hilft es, einen kurzen Blick auf die interne Funktionsweise der meisten heute üblichen Suchmaschinen zu werfen. Suchmaschinen analysieren Dokumente und Abfragen und reduzieren sie auf ihre kleinsten Einheiten, zumeist „Token“ genannt. Bei diesen Token handelt es sich im Wesentlichen um abstrakte Symbole. Wenn beim Suchen nach Übereinstimmungen gesucht wird, wird einfach danach geschaut, wie ähnlich sich Zeichenfolgen sind, weshalb Abfragen mit nur einem kleinen Tippfehler („Sunde“) oder einem Plural („Stunden“) keine Treffer erbringen, wenn das Dokument nur den Singular („Stunde“) enthält. Methoden wie die Wortstammerkennung (Stemming) oder unscharfe Abfragen helfen, einige der häufigsten dieser Probleme zu beseitigen, aber sie können nicht die Lücke zwischen zusammengehörigen Konzepten und Ideen oder zwischen leicht unterschiedlicher Verwendung von Vokabular zwischen Dokumenten und Abfragen schließen.

Hier kommen Synonyme ins Spiel. Der Begriff „Synonym“ setzt sich aus zwei griechischen Bestandteilen zusammen: dem Präfix σύν (syn, „zusammen“) und dem Wort ὄνομα (ónoma, „Name“). Der Ursprung des Begriffs zeigt bereits, dass das Wort „Synonym“ unterschiedliche Wörter mit exakt oder beinahe derselben Bedeutung in derselben Sprache oder im selben Fachgebiet bezeichnet. In der Praxis kann das auf die verschiedensten Dinge zutreffen: allgemeine Synonyme („wach“ und „munter“), Abkürzungen („h“ und „Stunde“), unterschiedliche Schreibweisen von Produkten bei Suchen im E-Commerce-Bereich („iPod“ und „i-Pod“), kleinere sprachliche Unterschiede (wie „Abitur“ in Deutschland und „Matura“ in Österreich oder „lift“ im britischen und „elevator“ im amerikanischen Englisch), Unterschiede zwischen Fach- und Umgangssprache („Thorax“ und „Brustkorb“) oder einfach zwei unterschiedliche Bezeichnungen für das gleiche Konzept („Universum“ und „Kosmos“). Durch Bereitstellung entsprechender Synonymregeln kann der Search Engineer Informationen dazu bereitstellen, welche Wörter im jeweiligen Fachgebiet dasselbe bezeichnen und daher gleich behandelt werden sollten.

Für eine Suchmaschine ist es wichtig zu wissen, welche Begriffe in Dokumenten und Abfragen trotz unterschiedlichen Aussehens als Übereinstimmung gelten sollen. Da dies sehr fachgebietsspezifisch ist, müssen die Nutzer die entsprechenden Regeln bereitstellen. Synonymfilter, die in benutzerdefinierten Analyzern verwendet werden können, ersetzen anhand benutzerdefinierter Regeln Token oder fügen zusätzliche Token hinzu. Dies geschieht entweder beim Indexieren, zum Beispiel um beide Varianten eines Wortes in einem indexierten Dokument zu speichern, oder beim Abfragen (Suchen), um die Abfragebegriffe zu erweitern und mehr relevante Dokumente finden zu können. Wir werden uns weiter unten mit den Vor- und Nachteilen dieser beiden Ansätze beschäftigen.

Situationen, in denen Synonyme nur mit Vorsicht verwendet werden sollten

Synonymfilter sind ein sehr flexibles Instrument, was dazu führen kann, dass sie in bestimmten Situationen etwas zu viel zur Anwendung kommen. So werden sie zuweilen unbedacht als Ersatz für die Stammformenerkennung eingesetzt, was große Synonymdateien mit grammatischen Varianten von Verben und Substantiven zur Folge hat. Diese Herangehensweise ist zwar nicht grundfalsch, aber sie kann, verglichen mit der Verwendung von Stammformenerkennung oder Lemmatisierung, die Prozesse verlangsamen und die Wartung erschweren. Das gleiche gilt für die Korrektur von Rechtschreibfehlern. Wo es nur eine Handvoll häufiger Rechtschreibfehler gibt, wie dies bei E-Commerce-Texten oft der Fall ist, kann es mitunter angeraten sein, eine Korrektur anhand von Synonymen zu versuchen. Handelt es sich aber um ein eher allgemeines Problem, ist die Nutzung von unscharfen Abfragen oder von Zeichen-N-Gramm-Verfahren der nachhaltigere Weg. Auch die Alternativen zur Synonymerweiterung in der Analysekette sollten in Betracht gezogen werden. Es gibt Fälle, in denen die Anreicherung von Dokumenten in einer Ingestions-Pipeline oder einem anderen Client-seitigen Prozess flexibler und einfacher zu handhaben ist als die Verwendung von Synonymen im stärker eingeschränkten Analyseprozess. So könnten beispielsweise gängige Verfahren zur Entitätenerkennung (Named Entity Recognition, NER) zum Einsatz kommen, um Eigennamen zu erkennen, die in der Vorabverarbeitungs-Pipeline oder beim Ingestieren in eindeutigen Kennungen verschlüsselt werden. Wenn Sie dann denselben Prozess auf die Abfragen der Nutzer anwenden, bevor diese an Elasticsearch gesendet werden, ist der Effekt identisch, aber Sie erhalten in der Regel mehr Einflussmöglichkeiten.

Die Versuchung ist groß, Synonyme auch für andere Aspekte der „Sameness“ zu verwenden, zum Beispiel, um bestimmte Tierarten unter einem gemeinsamen Begriff zusammenzufassen oder sogar für Taxonomie-Unterstützung für Ihr Fachgebiet zu sorgen. An dieser Stelle wird das Ganze richtig interessant und es gibt hier viel zu erkunden, aber es sollte nicht vergessen werden, dass Synonyme nicht immer die beste Wahl sind und ihr unbedachter Einsatz dazu führen kann, dass das System nicht so reagiert, wie es von ihm erwartet wird.

Synonymerweiterung beim Indexieren vs. beim Suchen

Synonyme kommen in Analyzern zur Anwendung, die beim Indexieren oder beim Suchen verwendet werden können. Eine der häufigsten Fragen im Zusammenhang mit der Nutzung von Synonymfiltern in Elasticsearch ist die, ob Synonyme beim Indexieren, beim Suchen oder in beiden Fällen verwendet werden sollten. Sehen wir uns zuerst das Anwenden von Synonymfiltern beim Indexieren an. Das bedeutet, dass Begriffe in indexierten Dokumenten dauerhaft ersetzt oder erweitert werden und das Ergebnis im Suchindex gespeichert wird.

Die Verwendung von Synonymen beim Indexieren hat etliche Nachteile:

  • Der Index kann größer werden, weil alle Synonyme indexiert werden müssen.
  • Das Suchergebnis-Scoring, das auf statistischen Daten zum Begriff basiert, kann beeinträchtigt werden, weil auch die Synonyme in die Berechnung eingehen, was die Statistik für weniger häufige Wörter verfälschen kann.
  • Bei bestehenden Dokumenten können die Synonymregeln nicht geändert werden, ohne dass erneut indexiert wird.

Vor allem die beiden letztgenannten Punkte wirken sich sehr nachteilig aus. Der einzige potenzielle Vorteil der Verwendung von Synonymen beim Indexieren ist die Performance: Es fällt nur einmal Aufwand für den Erweiterungsprozess an, und zwar vorab, ohne dass der ganze Prozess bei jeder Abfrage wiederholt werden muss, wodurch die Zahl der Begriffe, für die eine Übereinstimmung gefunden werden muss, potenziell steigt. In der Praxis ist dies jedoch in der Regel kein Problem.

Bei der Verwendung von Synonymen zum Zeitpunkt der Suche treten viele der oben genannten Probleme nicht auf:

  • Die Indexgröße ändert sich nicht.
  • Die statistischen Werte zum Begriff im Korpus bleiben unverändert.
  • Änderungen bei den Synonymregeln erfordern kein erneutes Indexieren von Dokumenten.

Diese Vorteile wiegen in der Regel den einzigen Nachteil auf, der darin besteht, dass die Synonymerweiterung bei jeder Abfrage erneut durchgeführt werden muss und dass es potenziell mehr Begriffe gibt, für die Übereinstimmungen zu finden sind. Hinzu kommt, dass bei der Synonymerweiterung beim Suchen der leistungsfähigere Tokenfilter synonym_graph verwendet werden kann, der Synonyme aus mehreren Wörtern unterstützt und nur für den Einsatz in einem Suchergebnis-Analyzer entwickelt wurde.

Im Allgemeinen überwiegen die Vorteile der Synonymerweiterung beim Suchen bei Weitem die möglichen leichten Performance-Gewinne, die erzielt werden können, wenn Synonyme bereits beim Indexieren verwendet werden.

Allerdings war die Synonymerweiterung beim Suchen bis vor Kurzem noch mit einem weiteren Problem verbunden: Auch wenn eine Änderung der Synonymregeln nicht dazu führt, dass die Dokumente erneut indexiert werden müssen, war es notwendig, den Index vorübergehend zu schließen und ihn dann wieder zu öffnen. Der Grund: Analyzer werden nur bei der Indexerstellung, beim Neustart eines Knotens oder beim erneuten Öffnen eines zuvor geschlossenen Index instanziiert. Um Änderungen an einer Synonymregeldatei vornehmen zu können, die für den Index sichtbar war, war es notwendig, diese Datei erst auf allen Knoten zu aktualisieren und dann den Index zu schließen und wieder zu öffnen. Das ist aber inzwischen Geschichte.

Neue Möglichkeiten für Synonyme

Seit Elasticsearch 7.3 ist es nicht mehr nötig, Indizes erneut zu öffnen, um Änderungen in Synonymdateien sichtbar zu machen. Wir haben einen neuen Endpunkt hinzugefügt, der ein bedarfsgerechtes Neuladen von Analyzer-Ressourcen auslösen kann. Wenn dieser neue Endpunkt aufgerufen wird, werden alle Analyzer des Index neu geladen, die Komponenten enthalten, welche als aktualisierbar gekennzeichnet sind. Dadurch lassen sich diese Komponenten wiederum nur beim Suchen nutzen.

Bei Synonymfiltern führt die Kennzeichnung als aktualisierbar und das Aufrufen der API für das erneute Laden dazu, dass Änderungen an der Synonymkonfigurationsdatei auf jedem Knoten für den Analyseprozess sichtbar werden. Das Aktualisieren von Synonymregeln, die Teil der Filterdefinition sind (über den Parameter synonyms) ist nicht möglich, aber diese dürften im Wesentlichen für Ad-hoc-Testzwecke genutzt werden. Das Konfigurieren von Synonymen mittels einer Konfigurationsdatei hat jedenfalls viele Vorteile:

  • Sie lassen sich einfacher verwalten! In einem Produktionssystem kann es eine Vielzahl von Synonymregeln geben und da diese die Relevanz der Suche stark beeinflussen können, sollten sie als integraler Bestandteil der Konfiguration behandelt werden, der einer Versionskontrolle unterliegen und bei jeder Aktualisierung getestet werden muss.
  • Synonyme werden oft aus anderen Quellen hergeleitet oder durch einen Algorithmus erstellt, der über Ihre Daten läuft. Durch das Auslesen aus Dateien müssen sie nicht in die Filterkonfiguration einbezogen werden.
  • Eine Synonymdatei kann in Filtern wiederverwendet werden.
  • Größere Synonymregelsätze können im Elasticsearch-„Cluster State“, dem Speicherort für Metainformationen über die Indexeinstellungen, viel Arbeitsspeicher belegen. Zur Vermeidung einer unnötigen Vergrößerung des Clusters ist es ratsam, größere Synonymregelsätze in Konfigurationsdateien zu speichern.

Nehmen wir zur Illustration einmal an, Sie legen im Verzeichnis config Ihrer Elasticsearch-Knoten eine Datei namens my_synonyms.txt ab, die zunächst nichts weiter als die folgende Regel enthält:

universe, cosmos

Als Nächstes müssen wir einen Analyzer definieren, der in einem Synonymfilter auf diese Datei verweist:

PUT /synonym_test
{
  "settings": {
    "index": {
      "analysis": {
        "analyzer": {
          "synonym_analyzer": {
            "tokenizer": "whitespace",
            "filter": ["my_synonyms"]
          }
        },
        "filter": {
          "my_synonyms": {
            "type": "synonym",
            "synonyms_path": "my_synonyms.txt",
            "updateable": true
          }
        }
      }
    }
  }
}

Wie Sie sehen, haben wir den Synonymfilter mit dem Parameter updateable als aktualisierbar gekennzeichnet. Das ist deshalb wichtig, weil beim Aufrufen des neuen Neuladen-Endpunkts nur die Filter neu geladen werden, die aktualisierbar sind. Nebenher hat dies den Effekt, dass Analyzer, die aktualisierbare Filter enthalten, beim Indexieren nicht mehr berücksichtigt werden dürfen. Aber zunächst prüfen wir erst einmal mit einem schnellen Test unter Einbeziehung des Endpunkts _analyze, dass Synonyme korrekt angewendet werden:

GET /synonym_test/_analyze
{
  "analyzer": "synonym_analyzer",
  "text": "cosmos"
}

Im Ergebnis sollten wir zwei Token erhalten, von denen eines wie erwartet auch „universe“ ist. Jetzt fügen wir der Datei synonyms.txt eine zweite Zeile mit einer weiteren Regel hinzu:

lift, elevator

An dieser Stelle hätten wir früher den Index schließen und wieder öffnen müssen, damit diese Änderungen wirksam werden. Jetzt reicht es, den neuen Endpunkt aufzurufen:

POST /synonym_test/_reload_search_analyzers

Die Anforderung benötigt keinen Textkörper, kann aber mithilfe der typischen Indexplatzhaltermuster auf einen oder mehrere Indizes eingeschränkt werden. In der Antwort finden sich Informationen dazu, welche Analyzer neu geladen wurden und welche Knoten betroffen waren:

{
  [...],
  "reload_details": [{
    "index": "synonym_test",
    "reloaded_analyzers": ["synonym_analyzer"],
    "reloaded_node_ids": ["FXbmbgG_SsOrNRssrYcPow"]
  }]
}

Wenn wir die oben erwähnte _analyze-Anforderung für den Begriff „lift“ ausführen, erhalten wir jetzt auch als zweites Synonymtoken „elevator“.

Es gibt jedoch ein paar Punkte, auf die hingewiesen werden muss. Wie oben erwähnt, sollte beim Suchen ein Filter verwendet werden, der als aktualisierbar (updateable) gekennzeichnet ist. Der Synonym-Analyzer, den wir oben definiert haben, ist also wie folgt anzuwenden:

POST /synonym_test/_mapping
{
  "properties": {
    "text_field": {
      "type": "text",
      "analyzer": "standard",
      "search_analyzer": "synonym_analyzer"
    }
  }
}

Zudem funktioniert das erneute Laden nur bei Synonymen, die aus Dateien geladen werden. Das Ändern der Synonyme, die über Einstellungen in einem Filter definiert sind, wird nicht unterstützt. Schließlich ist auch darauf zu achten, dass Aktualisierungen von Synonymdateien auf alle Knoten im Cluster angewendet werden. Wenn der Analyzer auf einigen Knoten auf abweichende Versionen der Datei trifft, ergibt dies möglicherweise unterschiedliche Suchergebnisse, die davon abhängen, welcher Knoten in der Suche verwendet wird. Dort, wo dies im Zusammenhang mit einem Synonym geschieht, muss als Erstes sichergestellt werden, dass die Synonymdateien auf allen Knoten dieselben sind. Dann kann das Neuladen erneut gestartet werden.

Zusammenfassend lässt sich sagen, dass der neue Endpunkt _reload_search_analyzer eine schnelle Prüfung und Änderung von Synonymen beim Abfragen erlaubt, ohne dass Sie Ihre Indizes neu öffnen müssen. So können Sie zum Beispiel durch die Prüfung Ihrer Abfragelogs feststellen, ob Nutzer andere Suchbegriffe verwenden als die, die in den indexierten Dokumenten vorhanden sind, und sofort entsprechende Änderungen vornehmen. Das Hinzufügen von Synonymen kann jedoch unerwartete Nebenwirkungen haben, weshalb es ratsam ist, vor der Einführung von Änderungen in Produktionsumgebungen entsprechende Tests (wie A/B-Tests oder Tests mit der ranking evaluation-API) durchzuführen.

Synonyme und Analyseketten

Im Zusammenhang mit Synonymfiltern wird auch immer wieder nach ihrem Verhalten in komplexeren Analyseketten gefragt. In den meisten Fällen werden dem Synonymfilter gängige Zeichen- oder Tokenfilter, zum Beispiel ein lowercase-Filter für Kleinschreibung, vorangestellt. In unserem Beispiel werden somit alle Token, die die Analysekette passieren, vor der Anwendung des Synonymfilters in Kleinschreibung umgewandelt. Bedeutet das, dass die eingegebenen Synonyme in den Synonymregeln ebenfalls kleingeschrieben werden müssen, damit sie gefunden werden? Dazu ein einfaches Beispiel:

PUT /test_index
{
  "settings": {
    "index": {
      "analysis": {
        "analyzer": {
          "synonym_analyzer": {
            "tokenizer": "whitespace",
            "filter": ["lowercase", "my_synonyms"]
          }
        },
        "filter": {
          "my_synonyms": {
            "type": "synonym",
            "synonyms": ["Eins, Uno, One", "Cosmos => Universe"]
          }
        }
      }
    }
  }
}
GET /test_index/_analyze
{
  "analyzer": "synonym_analyzer",
  "text": "one"
}

Wie Sie sehen, wird der kleingeschriebene Eingabetext zu drei Token erweitert, was zeigt, dass die Kleinschreibung auch auf die Synonymfilterregeln angewendet wird. Auch die rechte Seite von Ersetzungsregeln, zum Beispiel der Regel „Cosmos => Universe“ oben, wird umgeschrieben, wie die Kleinschreibung in der folgenden Ausgabe zeigt:

GET /test_index/_analyze
{
  "analyzer": "synonym_analyzer",
  "text": "cosmos"
}

Im Allgemeinen schreiben die Synonymfilter ihre Eingaben gemäß dem Tokenizer und den Filtern um, die in der vorangegangenen Analysekette verwendet wurden. Es gibt aber ein paar wichtige Ausnahmen: Einige Filter, die gestapelte Token ausgeben (wie common_grams oder phonetic) dürfen nicht vor Synonymfiltern verwendet werden. Falls Sie es dennoch versuchen, kommt es zu Fehlermeldungen. Andere, wie die Filter für zusammengesetzte Wörter oder Synonymfilter selbst, werden übersprungen, wenn sie einem anderen Synonymfilter in der Kette vorangestellt sind. Das ist wichtig, um das Verketten von Synonymfiltern zu ermöglichen. Das folgende Beispiel zeigt, wie dies funktioniert.

Was passiert also, wenn man mehrere Synonymfilter hintereinander stellt? Wird die Ausgabe des vorhergehenden Filters als Eingabe für den nachstehenden Filter verwendet, sodass das Verketten von Synonymfiltern zu einer Art transitiven Operation wird? Sehen wir uns folgendes Beispiel an:

PUT /synonym_chaining
{
  "settings": {
    "index": {
      "analysis": {
        "filter": {
          "first_synonyms": {
            "type": "synonym",
            "synonyms": ["a => b", "e => f"]
          },
          "second_synonyms": {
            "type": "synonym",
            "synonyms": ["b => c", "d => e"]
          }
        },
        "analyzer": {
          "synonym_analyzer": {
            "filter": [
              "first_synonyms",
              "second_synonyms"
            ],
            "tokenizer": "whitespace"
          }
        }
      }
    }
  }
}
GET /synonym_chaining/_analyze
{
  "analyzer": "synonym_analyzer",
  "text": "a"
}

Als Token wird „c“ ausgegeben, was zeigt, dass beide Filter nacheinander angewendet werden, wobei der erste Filter „a“ durch „b“ ersetzt und der zweite Filter anstelle von „b“ ein „c“ scheibt. Wenn Sie stattdessen ein „d“ als Eingabe verwenden, wird „e“ zurückgegeben (die erste Regel findet keine Anwendung), aber wenn Sie „e“ verwenden, wird das Token im ersten Filter durch „f“ ersetzt, und dem zweiten Filter bleibt nichts, das er finden kann.

Erinnern Sie sich noch an die Ausnahmen für Änderungen bei vorausgehenden Tokenfiltern? Hätte der Filter second_synonyms im Beispiel oben auf seinen Regelsatz die Regeln des ersten Filters angewendet, hätte er seine eigene Regel d => e in d => f geändert (weil die Regel e => f des vorausgehenden Filters angewendet worden wäre). Dieses Verhalten hat in früheren Versionen von Elasticsearch zu Verwirrung geführt. Deshalb werden Synonymfilter beim Verarbeiten der Synonymregeln des folgenden Filters heute nicht mehr berücksichtigt. Dies gilt für die Version 6.6 und später.

Zurück in die Zukunft

In diesem kurzen Blogpost haben wir nur leicht an der Oberfläche dessen gekratzt, was sich mit Synonymen alles erreichen lässt, und versucht, einige häufige Fragen zu deren Nutzung zu beantworten. Synonyme sind ein mächtiges Hilfsmittel, das genutzt werden kann, um den Recall Ihres Suchsystems zu erhöhen. Gleichzeitig gibt es aber auch eine Reihe von Faktoren, die man kennen und ausprobieren muss, speziell was systematische Relevanztests anbelangt.

Die neue API zum Neuladen von Analyzern für die Synonymerweiterung beim Suchen in Elasticsearch 7.3 erleichtert derartige Experimente, da es nun nicht mehr notwendig ist, den Index zu schließen und wieder zu öffnen. Außerdem bietet sie Möglichkeiten, Synonymregeln zu aktualisieren, die beim Suchen angewendet werden, ohne die Indizes dazu offline schalten zu müssen. Dies ist jedoch nur ein Schritt in einer Reihe von Verbesserungen, mit denen wir die Verwaltung von Synonymen in großen Clustern benutzerfreundlicher gestalten möchten. Wie immer sind wir sehr an Ihrer Meinung interessiert. Geben Sie uns im Diskussionsforum Feedback und stellen Sie Ihre Fragen. Bis dahin wünschen wir fröhliches Analysieren!