System zur Ermittlung von Übereinstimmungen für Echtzeit-Einbettungen erstellen

Dieser Artikel gibt einen Überblick über die näherungsweise Ermittlung von Übereinstimmungen. Hierbei handelt es sich um eine Technik des maschinellen Lernens, um Elemente zu ermitteln, die einem bestimmten Element ähneln. Der Artikel beschreibt auch eine durchgängige Beispiellösung dafür, wie eine semantische Textsuche in Echtzeit durchgeführt wird, und zeigt verschiedene Aspekte des Verfahrens für die Durchführung der Beispiellösung auf. Die Beispiellösung befindet sich in einem zugehörigen GitHub-Repository zur Ermittlung von Echtzeit-Einbettungen.

In diesem Artikel wird davon ausgegangen, dass Sie mit den Konzepten für maschinelles Lernen sowie mit Google Cloud und Tools wie Apache Beam vertraut sind.

Einleitung

Das Auffinden von Elementen, die einer bestimmten Abfrage ähnlich sind, ist der Kernaspekt von Such-, Abruf- und Empfehlungssystemen. Beispielsweise hilft die Ermittlung von Übereinstimmungen Ihren Nutzern dabei, Folgendes zu finden:

  • Bilder, die dem ihres Haustiers ähneln
  • Nachrichtenartikel, die für ihre Suchanfrage relevant sind
  • Filme oder Songs, die denen ähneln, die sie sich angesehen oder angehört haben
  • Empfehlungen für Produkte und Dienstleistungen

Sie müssen Elemente erst in numerische Vektoren umwandeln, um ein System zur Ermittlung von Übereinstimmungen zu entwerfen. Diese Vektoren repräsentieren wiederum semantische Einbettungen der durch maschinelles Lernen (ML) ermittelten Elemente. Weitere Informationen finden Sie unter Übersicht: Merkmalseinbettungen für maschinelles Lernen extrahieren und bereitstellen.

Zweitens müssen Sie diese Einbettungen für die Suche nach dem nächsten Nachbarn auf Basis eines Werts für die Ähnlichkeit organisieren und speichern, um Elemente zu finden, die dem Einbettungsvektor der Nutzerabfrage ähnlich sind. Die Ermittlung von Übereinstimmungen muss jedoch schnell erfolgen, wenn Empfehlungen in Echtzeit gesucht, abgerufen und ausgeliefert werden sollen. Daher ist es praktischer, einen Algorithmus für die Annäherung an den nächsten Nachbarn anzuwenden, um einen Index der Elementeinbettungen zu erstellen. So finden Sie schneller ähnliche Elemente.

Die mit diesem Artikel verknüpfte Beispiellösung umfasst Folgendes:

  • Texteinbettungen von Wikipedia-Titeln extrahieren
  • Das Universal Sentence Encoder-Modul von tf.Hub verwenden
  • Einen Index mit der Ermittlung von näherungsweisen Übereinstimmungen mithilfe der Annoy-Bibliothek von Spotify erstellen
  • Index für die semantische Suche in Echtzeit in einer Webanwendung bereitstellen

Der Code für die Beispiellösung befindet sich im GitHub-Repository zur Ermittlung von Echtzeit-Einbettungen.

Ermittlung von näherungsweisen Übereinstimmungen

Im Folgenden finden Sie ein typisches Verfahren zum Abgleichen und Abrufen:

  1. Wandeln Sie die Elemente und die Abfrage in Vektoren in einem geeigneten Merkmalsbereich um. Diese Merkmale werden als Einbettungen bezeichnet.
  2. Definieren Sie einen Annäherungsmesswert für ein Paar von Einbettungsvektoren. Dieser Messwert kann eine Kosinus-Ähnlichkeit oder eine euklidische Entfernung sein.
  3. Finden Sie mit einer expliziten Suche im gesamten Objektsatz die nächsten Nachbarn.

Wenn Sie nur Hunderte oder ein paar tausend Elemente haben, dauert die Suche über den gesamten Elementsatz, um die Ähnlichkeit zwischen Ihrem Abfragevektor und dem Vektor jedes Elements zu berechnen, nicht sehr lange. Wenn Sie die Ergebnisse nicht online benötigen, können Sie auch eine annehmbare Leistung erzielen, wenn Sie die Ermittlung von Übereinstimmungen als Batchjob ausführen. Wenn Sie jedoch bei mehreren Millionen Elementen ein System für die Suche und das Abrufen in Echtzeit oder ein Empfehlungssystem verwenden, muss die Suche nach dem nächsten Nachbarn näherungsweise erfolgen. In diesem Fall müssen Sie den Prozess für eine Antwort mit niedriger Latenz optimieren.

Eine praktische Lösung besteht darin, eine Ermittlung von näherungsweisen Übereinstimmungen durchzuführen. Bei einer Ermittlung von näherungsweisen Übereinstimmungen werden Ihre Elementvektoren in einem Index organisiert. Dieser ist eine Datenstruktur, die das schnelle Abrufen von ähnlichen Elementen ermöglicht. Ein mögliches Problem besteht darin, dass die abgerufenen Elemente gegebenenfalls nicht den Elementen entsprechen, die der angegebenen Abfrage am ähnlichsten sind. Normalerweise können Sie jedoch Kompromisse zwischen der Genauigkeit des Index und seiner Latenz (und Größe) steuern.

Es gibt zwei Hauptansätze für die Ermittlung von näherungsweisen Übereinstimmungen: baumbasierte Methoden und hashingbasierte Methoden.

Baumbasierte Methoden

Die Idee hinter baumbasierten Methoden oder metrischen Baumdatenstrukturen besteht darin, die Daten rekursiv in einem Teile-und-herrsche-Verfahren zu partitionieren, das ähnliche Vektoren nahe beieinander in der Baumstruktur platziert. Die erwartete Abfragezeit ist O(log(n)), wobei n die Anzahl der vorhandenen Elemente (Vektoren) ist. Strukturindexe erfordern viel Arbeitsspeicher und die Leistung wird durch höherdimensionale Daten beeinträchtigt. Beispiele für baumbasierte Ansätze, auch als metrische Baumdatenstrukturen bezeichnet, umfassen:

Hashingbasierte Methoden

Eine Alternative zur baumbasierten Methode ist die hashbasierte Methode. Im Gegensatz zu Bäumen gibt es in Hashes keine rekursive Partitionierung. Dabei wird einem Modell beigebracht, ein Element in einen Code umzuwandeln, bei dem ähnliche Elemente den gleichen oder einen ähnlichen Code erzeugen (Hashingkollision). Durch diesen Ansatz wird der benötigte Speicher erheblich reduziert. Die erwartete Abfragezeit ist O(1), kann aber in n sublinear sein, wobei n die Anzahl der vorhandenen Elemente (Vektoren) ist. Beispiele für hashingbasierte Methoden sind:

Es gibt mehrere Open-Source-Bibliotheken, die Techniken zur näherungsweisen Ermittlung von Übereinstimmungen implementieren, mit unterschiedlichen Kompromissen zwischen Genauigkeit, Abfragelatenz, Speichereffizienz, Zeit zum Erstellen des Index, Funktionen und Nutzerfreundlichkeit.

Die in diesem Artikel beschriebene Beispiellösung verwendet Annoy (Approximate Nearest Neighbours Oh Yeah), eine von Spotify für Musikempfehlungen erstellte Bibliothek. Annoy ist eine C++-Bibliothek mit Python-Bindungen, die Random Projection-Trees erstellt. Ein Index wird mit einer Gesamtheit von k Bäumen erstellt, wobei k ein einstellbarer Parameter ist, der einen Kompromiss zwischen Präzision und Leistung darstellt. Es werden auch große, schreibgeschützte, dateibasierte Datenstrukturen erstellt, die im Speicher zugeordnet werden, sodass viele Prozesse die Daten gemeinsam nutzen können.

Andere weit verbreitete Bibliotheken sind NMSLIB (Non-Metric Space Library) und Faiss (Facebook AI Similarity Search). Welche Bibliothek Sie für die Implementierung der Ermittlung von näherungsweisen Übereinstimmungen verwenden, sollte sich nicht auf die allgemeine Lösungsarchitektur oder den in diesem Artikel beschriebenen Arbeitsablauf auswirken.

Die in diesem Artikel beschriebene Beispiellösung veranschaulicht, wie Sie die Ermittlung von Übereinstimmungen in Einbettungen in der semantischen Textsuche anwenden können. Das Ziel der Lösung besteht darin, semantisch relevante Dokumente (z. B. Nachrichtenartikel, Blogposts oder Forschungsberichte) für eine Suchanfrage in Echtzeit abzurufen.

Tokenbasierte Suchtechniken rufen Dokumente basierend auf Messwerten wie Aktualität oder Häufigkeit ab oder abhängig davon, wie oft die Suchwörter einzeln oder kombiniert in den Dokumenten vorkommen. Im Gegensatz dazu verwendet die semantische Suche die Einbettungen der Anfrage und der Dokumente für die Übereinstimmung. Wie zum Beispiel später im Abschnitt Webbasierte Suchanwendung gezeigt, könnte eine Suchanfrage "tropische wilde Tiere" lauten und die Ergebnisse könnten einen Titel wie "Im afrikanischen Dschungel kämpfen alle Löwen, Gnus und Krokodile für sich selbst! BBC Wildlife" enthalten. Beachten Sie, dass keines der Abfragewörter im Ergebnis enthalten ist, das Ergebnis jedoch ein Artikel ist, in dem tropische wilde Tiere behandelt werden.

BigQuery-Datensatz von Wikipedia

In diesem Beispiel ist die Datenquelle das Dataset bigquery-samples:wikipedia_benchmark.Wiki100B in BigQuery. Dabei handelt es sich um ein öffentliches Dataset, das 100 Milliarden Einträge basierend auf Wikipedia-Titeln enthält. In diesem Beispiel sind die Daten auf eindeutige Titel mit mehr als zwei Aufrufen beschränkt, die aus mindestens fünf Wörtern und weniger als 500 Zeichen bestehen. Dieser Filter ergibt etwa 10,5 Millionen eindeutige Titel.

Technische Anforderungen an das System

Das beispielhafte semantische Suchsystem hat die folgenden technischen Anforderungen:

  • Der Aufwand, eine Vektordarstellung der Einbettungen zu finden, die die Semantik bzw. die Wikipedia-Titel codiert, soll minimiert werden. Daher muss im Beispiel ein vorab trainiertes Text-Einbettungsmodell verwendet werden, anstatt ein Sprachmodell von Grund auf zu trainieren.
  • Die Notwendigkeit einer dedizierten Computerinfrastruktur, die Einbettungen extrahiert und den Index erstellt, soll minimiert werden. In diesem Beispiel müssen daher vollständig verwaltete On-demand-Rechendienste verwendet werden, die genügend Ressourcen (Speicher und CPU) für den Job beschaffen und diese nach Beendigung des Jobs freigeben.
  • Der Einbettungsextraktionsvorgang soll automatisch skaliert werden. Daher muss im Beispiel ein paralleler Datenverarbeitungsdienst verwendet werden.
  • Die Wartezeit, um ähnliche Einbettungen im Index für eine angegebene Anfrage zu finden, soll minimiert werden. Daher muss der Index vollständig in den Speicher geladen sein.
  • Die Latenz, um die Wikipedia-Titel für Vektoren ähnlicher Einbettungen in Echtzeit abzurufen, soll minimiert werden. Daher muss das Beispiel die Wikipedia-Titel in einer Lesedatenbank mit niedriger Latenz speichern.
  • Der Aufwand von DevOps für die Bereitstellung des Suchdienstes als Webanwendung soll minimiert werden. Daher muss das Beispiel vollständig verwaltete Dienste verwenden.
  • Die Arbeitslast für die Webanwendung soll gesteigert werden können, bis hin zu Tausenden von Abfragen pro Sekunde mit einer durchschnittlichen Latenz von unter einer Sekunde. Daher muss das Beispiel in der Lage sein, mehrere Knoten der webbasierten Suchanwendung sowie einen Load-Balancer bereitzustellen.

Lösungsarchitektur

Abbildung 1 zeigt eine Übersicht über das System zur semantischen Textsuche in Echtzeit. Das System extrahiert die Einbettungen aus den Wikipedia-Titeln, erstellt mit Annoy einen Index zur Ermittlung von näherungsweisen Übereinstimmungen und stellt den Build-Index für die semantische Suche und den Abruf in Echtzeit bereit.

Architektur einer Beispiellösung

Abbildung 1. Lösungsarchitektur für das System zur semantischen Textsuche – Übersicht

Schlüsselkomponenten der Architektur

In der folgenden Tabelle werden die in Abbildung 1 dargestellten Schlüsselkomponenten erläutert.

Komponente Beschreibung
BigQuery BigQuery ist das vollständig verwaltete, kostengünstige Data Warehouse für Analysen im Petabyte-Bereich von Google. In der Beispiellösung werden die Quelltitel aus Wikipedia in BigQuery gespeichert.
Apache Beam Apache Beam ist ein einheitliches Open-Source-Programmiermodell, mit dem sowohl Streaming- als auch Batchdatenverarbeitungsjobs ausgeführt werden können. In der Beispiellösung wird mit Apache Beam eine Pipeline implementiert, um die Einbettungen zu extrahieren und eine ID für die Titelsuche im Datastore zu speichern.
Dataflow Dataflow ist ein vollständig verwalteter, serverloser, zuverlässiger Dienst zum Ausführen umfangreicher Apache Beam-Pipelines in Google Cloud. Mit Dataflow wird die Verarbeitung des Eingabetexts und die Extraktion der Einbettungen skaliert.
tf.Hub TensorFlow Hub ist eine Bibliothek wiederverwendbarer Module für maschinelles Lernen. Die Beispiellösung verwendet das vortrainierte Texteinbettungsmodul Universal Sentence Encoder, um jeden Titel in einen Einbettungsvektor umzuwandeln.
Cloud Storage Cloud Storage ist ein hochverfügbarer und dauerhafter Speicher für binäre große Objekte. In der Beispiellösung werden die extrahierten Einbettungen in Cloud Storage als TFRecords gespeichert. Nachdem der Index mit der Ermittlung von näherungsweisen Übereinstimmungen erstellt wurde, wird er außerdem serialisiert und in Cloud Storage gespeichert.
Datastore Datastore ist eine NoSQL-Dokumentdatenbank, die auf automatische Skalierung, hohe Leistung und einfache Anwendungsentwicklung ausgelegt ist. In der Beispiellösung werden die Wikipedia-Titel und ihre IDs in Datastore gespeichert, sodass sie mit geringer Latenz in Echtzeit abgerufen werden können.
AI Platform AI Platform ist ein serverloser Dienst, um ML-Modelle in großem Maßstab zu trainieren. Die Beispiellösung verwendet die AI Platform, um mithilfe der Annoy-Bibliothek den Index zur Ermittlung von näherungsweisen Übereinstimmungen zu erstellen, ohne eine dedizierte Recheninfrastruktur zu benötigen.
App Engine Mit App Engine können Sie skalierbare, zuverlässige Anwendungen auf einer vollständig verwalteten Plattform erstellen und bereitstellen. Die Beispiellösung verwendet App Engine, um eine Flask-Webanwendung zur Suche nach Wikipedia-Titeln zu liefern, die für eine Nutzeranfrage semantisch relevant sind. Mit App Engine können Sie viele Instanzen der Anwendung mit Load-Balancing bereitstellen, indem Sie zur Bewältigung von zunehmenden Abfragen pro Sekunde nur eine einfache Konfiguration verwenden.

Gesamter Workflow

Der Workflow des Systems zur semantischen Textsuche in Echtzeit, das in Abbildung 1 dargestellt ist, kann in die folgenden Schritte unterteilt werden:

  1. Einbettungen mit Dataflow extrahieren

    1. Die Wikipedia-Titel aus BigQuery abfragen
    2. Die Titeleinbettungen mithilfe des Moduls Universal Sentence Encoder extrahieren
    3. Die extrahierten Einbettungen als TFRecords in Cloud Storage speichern
    4. Titel und ihre IDs in Datastore speichern, um sie später in Echtzeit abzurufen
  2. Index mit AI Platform erstellen

    1. Die Einbettungen aus den Dateien in Cloud Storage in den Annoy-Index laden
    2. Den Index im Speicher erstellen
    3. Den Index auf dem Laufwerk speichern
    4. Gespeicherten Index in Cloud Storage hochladen
  3. Suchanwendung mit App Engine bereitstellen

    1. Den Annoy-Index aus Cloud Storage herunterladen
    2. Die Nutzeranfrage abrufen
    3. Die Anfrageeinbettung mit dem Modul Universal Sentence Encoder extrahieren
    4. Mithilfe des Annoy-Index nach Einbettungen suchen, die der Einbettung der Suchanfrage ähneln
    5. Die Artikel-IDs der ähnlichen Einbettungen abrufen
    6. Wikipedia-Titel mithilfe der IDs aus Datastore abrufen
    7. Die Ergebnisse zurückgeben

Suchsysteme in der Praxis

In der Praxis kombinieren Such- und Abrufsysteme häufig semantische Suchtechniken mit tokenbasierten Techniken wie dem umgekehrten Index. Die Ergebnisse beider Techniken werden kombiniert und klassifiziert, bevor sie dem Nutzer bereitgestellt werden. Möglicherweise sind Sie für diese Aufgabe bereits mit Elasticsearch, das auf dem Google Cloud Marketplace verfügbar ist, vertraut. Elasticsearch ist ein weit verbreitetes Framework für die Volltextsuche, das auf der Bibliothek Apache Lucene für die umgekehrte Indexierung basiert.

Eine weitere Optimierung, die häufig in realen Systemen implementiert, in dieser Lösung aber nicht behandelt wird, ist das Zwischenspeichern von Abfragen und deren entsprechenden Titel-IDs mithilfe von Memorystore. Wenn die Abfrage zuvor angezeigt wurde, können die Titel-IDs direkt aus Memorystore abgerufen werden. Dadurch werden die beiden teuren Vorgänge übersprungen, mit denen der Universal Sentence Encoder aufgerufen wird, um die Einbettung der Suchanfrage zu generieren, und der Index für näherungsweise Übereinstimmungen auf ähnliche Elemente hin durchsucht wird. Durch das Zwischenspeichern der Abfragen kann abhängig von der Redundanzstufe der Abfrage häufig die durchschnittliche Latenz Ihres Systems verbessert werden. Abbildung 2 zeigt den Workflow mit einem Abfragecache.

Architektur einer Lösung mit einem Cache

Abbildung 2. Lösungsarchitektur für die semantische Textsuche mit Abfragecache – Übersicht

Abbildung 2 zeigt den folgenden Ablauf:

  1. Suchanfrage erhalten
  2. Abfrage im Cache nachschlagen
  3. Wenn die Abfrage nicht gefunden wird:
    1. Einbettung aus der Abfrage extrahieren
    2. Nach ähnlichen Elementen im Index suchen
    3. Cache aktualisieren
  4. Zugriff über IDs von Datastore anfordern
  5. Ergebnisse zurückgeben

Dienste und Zugriffsberechtigungen aktivieren

Für die in Abbildung 1 beschriebene End-to-End-Lösung müssen die folgenden Dienst-APIs in der Cloud Console aktiviert sein:

Darüber hinaus müssen die folgenden Berechtigungen für die Dienstkonten erteilt werden. Die Standarddienstkonten haben ausreichende Zugriffsberechtigungen für die erforderlichen Ressourcen, wenn sie zum selben Google Cloud-Projekt gehören. Wenn die Dienstkontoberechtigungen jedoch geändert wurden, müssen Sie möglicherweise Änderungen vornehmen. Die erforderlichen Berechtigungen sind:

  • Dataflow
    • Leseberechtigung für das BigQuery-Dataset
    • Lese-/Schreibberechtigung für den Cloud Storage-Bucket, in dem die TFRecords gespeichert sind
    • Schreibberechtigung für Datastore
  • AI Platform
    • Schreib-/Leseberechtigung für den Cloud Storage-Bucket, in dem der Index gespeichert ist
  • App Engine
    • Leseberechtigung für den Cloud Storage-Bucket, in dem der Index gespeichert ist
    • Leseberechtigung für Datastore

Die Code-Snippets in den folgenden Abschnitten veranschaulichen die in diesem Artikel erläuterten Konzepte. In der Datei README.md im zugehörigen GitHub-Repository erfahren Sie, wie Sie das End-to-End-Beispiel ausführen.

Einbettungen mit Dataflow extrahieren

Die Pipeline zum Extrahieren der Einbettung aus den Wikipedia-Titeln ist in pipeline.py mit Apache Beam implementiert. Die gesamte Pipeline wird im folgenden Code-Snippet dargestellt:

def run(pipeline_options, known_args):

 pipeline = beam.Pipeline(options=pipeline_options)
 gcp_project = pipeline_options.get_all_options()['project']

 with impl.Context(known_args.transform_temp_dir):
   articles = (
       pipeline
       | 'Read from BigQuery' >> beam.io.Read(beam.io.BigQuerySource(
     project=gcp_project, query=get_source_query(known_args.limit),
     use_standard_sql=True)))

   articles_dataset = (articles, get_metadata())
   embeddings_dataset, _ = (
       articles_dataset
       | 'Extract embeddings' >> impl.AnalyzeAndTransformDataset(
preprocess_fn))

   embeddings, transformed_metadata = embeddings_dataset

   embeddings | 'Write embeddings to TFRecords' >> beam.io.tfrecordio.WriteToTFRecord(
     file_path_prefix='{0}'.format(known_args.output_dir),
     file_name_suffix='.tfrecords',
     coder=tft_coders.example_proto_coder.ExampleProtoCoder(
transformed_metadata.schema))

   (articles
       | "Convert to entity" >> beam.Map(
lambda input_features: create_entity(input_features, known_args.kind))
       | "Write to Datastore" >> WriteToDatastore(project=gcp_project))

...

 job = pipeline.run()

 if pipeline_options.get_all_options()['runner'] == 'DirectRunner':
   job.wait_until_finish()

Aus BigQuery abrufen

Der erste Schritt in der Pipeline besteht darin, die Titel aus dem BigQuery-Dataset von Wikipedia mit der Methode beam.io.Read und einem beam.io.BigQuerySource-Objekt zu lesen. Die Methode get_source_query in pipeline.py bereitet das SQL-Skript vor, das zum Abrufen der Daten verwendet wird. Die Anzahl der Wikipedia-Titel, die aus BigQuery abgerufen werden sollen, ist über den Parameter limit der Funktion get_source_query konfigurierbar.

def get_source_query(limit=1000000):
 query = """
   SELECT
     GENERATE_UUID() as id,
     text
   FROM
   (
       SELECT
         DISTINCT LOWER(title) text
       FROM
         `bigquery-samples.wikipedia_benchmark.Wiki100B`
       WHERE
         ARRAY_LENGTH(split(title,' ')) >= 5
       AND
         language = 'en'
       AND
         LENGTH(title) < 500
    )
   LIMIT {0}
 """.format(limit)
 return query

Mit der integrierten BigQuery-Funktion GENERATE_UUID wird dem Titel (hier id) eine ID hinzugefügt. Dieser Wert wird dazu verwendet, in Datastore einen Wikipedia-Titel nach ID zu suchen und einem Wikipedia-Titel dessen Einbettung zuzuordnen.

Dieser Schritt in der Beam-Pipeline gibt ein PCollection-Objekt zurück, wobei jedes Element in der Sammlung zwei Elemente enthält: den String id und den String title.

Einbettungen extrahieren

Der zweite Schritt in der Pipeline besteht darin, mit dem Universal Sentence Encoder-Modul tf.Hub für alle Wikipedia-Titel, die aus BigQuery gelesen wurden, einen Einbettungsvektor zu extrahieren. Zur Ausführung des Moduls wird im Beispiel die API TensorFlow Transform (tf.Transform) verwendet.

TensorFlow Transform ist eine Bibliothek, mit der Daten mit Apache Beam vorverarbeitet werden. In diesem Beispiel wird die Methode AnalyzeAndTransformDataset von tf.Transform als Kontext verwendet, um das Modul tf.Hub zum Extrahieren von Texteinbettungen aufzurufen.

Die Methode AnalyzeAndTransformDataset führt die Funktion preprocess_fn aus, die die Transformationslogik enthält, wie im folgenden Snippet gezeigt:

def preprocess_fn(input_features):
 import tensorflow_transform as tft
 embedding = tft.apply_function(embed_text, input_features['text'])
 output_features = {
   'id': input_features['id'],
   'embedding': embedding
 }
 return output_features

def embed_text(text):
 import tensorflow_hub as hub
 global encoder
 if encoder is None:
   encoder = hub.Module(
'https://tfhub.dev/google/universal-sentence-encoder/2')
 embedding = encoder(text)
 return embedding

In diesem Schritt der Pipeline wird ein weiteres PCollection-Objekt erstellt, bei dem jedes Element in der Sammlung den Wert id (String) des Wikipedia-Titels und den Wert embedding (numerisches Array) enthält, der aus dem Universal Sentence Encoder mit 512 Dimensionen extrahiert wurde.

Einbettungen in TFRecords schreiben

Nachdem die Einbettungen für die Wikipedia-Titel extrahiert wurden, speichert die Lösung sie zusammen mit den Titel-IDs als TFRecords in Cloud Storage und verwendet dazu die Methode beam.io.tfrecordio.WriteToTFRecord.

Das einfache TFRecord-Format speichert eine Reihe von Binärdatensätzen. Jeder Datensatz in einer TFRecord-Datei ist ein tf.Example-Protokollpuffer, der einen flexiblen Nachrichtentyp als Schlüssel/Wert-Paar bei der Zuordnung darstellt. Dieser Typ eignet sich für die Serialisierung von strukturierten Daten.
Sie können festlegen, wie viele Einbettungsdateien erstellt werden. Dafür müssen Sie den Parameter num_shards in der Methode WriteToTFRecord festlegen.

Schreibzugriff auf Datastore

Im nächsten Schritt wird in Datastore geschrieben. Dieser Schritt wird parallel zum Schritt "Extraktion einbetten" ausgeführt. Der Zweck besteht darin, die Wikipedia-Titel in Datastore zu speichern, damit sie anhand ihrer IDs abgerufen werden können. Die IDs der Wikipedia-Titel werden auch mit der Einbettung in den TFRecord-Dateien gespeichert. Damit können sie als Kennungen für die Elemente (Einbettungsvektoren) verwendet werden, die dem Annoy-Index hinzugefügt wurden.

Die Lösung muss zuerst jedes Element in eine Datastore-Entität konvertieren, um die Elemente in Datastore speichern zu können, die im Schritt Aus BigQuery abrufen erzeugt wurden. Dazu wird der Code aus dem folgenden Snippet in pipeline.py verwendet:

def create_entity(input_features, kind):
 entity = entity_pb2.Entity()
 datastore_helper.add_key_path(
   entity.key, kind, input_features['id'])
 datastore_helper.add_properties(
   entity, {
     "text": unicode(input_features['text'])
   })
 return entity

Nachdem dieser Code ausgeführt wurde, speichert die Methode WriteToDatastore die Elemente in Datastore. Abbildung 3 zeigt einige der in Datastore geschriebenen Entitäten, nachdem die Pipeline mit dem Parameter kind von Datastore ausgeführt wurde. Der Parameter wurde dabei auf wikipedia gesetzt.

Bild

Abbildung 3. Datastore-Entitäten nach Ausführung der Pipeline

Pipeline in Dataflow ausführen

Sie können die Apache Beam-Pipeline ausführen. Dazu rufen Sie das Skript run.py auf, übergeben die erforderlichen Argumente und setzen das Argument --runner auf DataflowRunner. Dazu können Sie Konfigurationsparameter in der Skriptdatei run.sh festlegen und dann das Skript run.py ausführen.

Der folgende Befehl zeigt, wie die Pipeline ausgeführt wird. Das Skript enthält eine Reihe von Variablen (z. B. $OUTPUT_PREFIX), die festgelegt werden, wenn Sie das Skript run.sh ausführen.

python run.py \
 --output_dir=$OUTPUT_PREFIX \
 --transform_temp_dir=$TRANSFORM_TEMP_DIR \
 --transform_export_dir=$TRANSFORM_EXPORT_DIR \
 --project=$PROJECT \
 --runner=$RUNNER \
 --region=$REGION \
 --kind=$KIND \
 --limit=$LIMIT \
 --staging_location=$STAGING_LOCATION \
 --temp_location=$TEMP_LOCATION \
 --setup_file=$(pwd)/setup.py \
 --job_name=$JOB_NAME \
 --worker_machine_type=$MACHINE_TYPE \
 --enable_debug \
 --debug_output_prefix=$DEBUG_OUTPUT_PREFIX

Den Ablauf der Dataflow-Pipeline sehen Sie in der Cloud Console, wie in Abbildung 4 dargestellt.

Cloud Dataflow-Pipeline, wie in der Cloud Console angezeigt

Abbildung 4. Dataflow-Ausführungsgrafik der Pipeline, wie in der Cloud Console angezeigt

Index mit AI Platform erstellen

In der Beispiellösung wird nach dem Extrahieren der Einbettungsvektoren aus den Wikipedia-Titeln mithilfe der Annoy-Bibliothek ein Index erstellt, der die Ermittlung von näherungsweisen Übereinstimmungen für diese Vektoren abbildet. In der Beispiellösung enthält der Ordner index_builder den Code, den Sie für diese Aufgabe verwenden können.

Implementieren Sie erst eine Aufgabe, mit der der Index erstellt und gespeichert wird. Senden Sie danach die Aufgabe, die auf AI Platform ausgeführt werden soll. Mit diesem Ansatz können Sie den Index ohne eine dedizierte Computerinfrastruktur erstellen.

Indexerstellungsaufgabe implementieren

Die Datei task.py ist der Einstiegspunkt für den Index-Builder, der die folgenden Schritte ausführt:

  • Annoy-Index erstellen
  • (Optional) Index komprimieren
  • Erzeugte Artefakte in Cloud Storage hochladen

Die Logik zum Erstellen des Annoy-Index wird im folgenden Code-Snippet aus dem Modul index.py gezeigt.

def build_index(embedding_files_pattern, index_filename,
                num_trees=100):

 annoy_index = AnnoyIndex(VECTOR_LENGTH, metric=METRIC)
 mapping = {}

 embed_files = tf.gfile.Glob(embedding_files_pattern)
 logging.info('{} embedding files are found.'.format(len(embed_files)))

 item_counter = 0
 for f, embed_file in enumerate(embed_files):
   logging.info('Loading embeddings in file {} of {}...'.format(f, len(embed_files)))
   record_iterator = tf.python_io.tf_record_iterator(path=embed_file)

   for string_record in record_iterator:
     example = tf.train.Example()
     example.ParseFromString(string_record)
     string_identifier = example.features.feature['id'].bytes_list.value[0]
     mapping[item_counter] = string_identifier
     embedding = np.array(example.features.feature['embedding'].float_list.value)
     annoy_index.add_item(item_counter, embedding)
     item_counter += 1

   logging.info('Loaded {} items to the index'.format(item_counter))

 logging.info('Start building the index with {} trees...'.format(num_trees))
 annoy_index.build(n_trees=num_trees)
 logging.info('Index is successfully built.')
 logging.info('Saving index to disk...')
 annoy_index.save(index_filename)
 logging.info('Index is saved to disk.')
 logging.info('Saving mapping to disk...')
 with open(index_filename + '.mapping', 'wb') as handle:
   pickle.dump(mapping, handle, protocol=pickle.HIGHEST_PROTOCOL)
 logging.info('Mapping is saved to disk.')

Dazu sind die folgenden Schritte auszuführen:

  1. Alle eingebetteten Dateinamen abrufen, die mit einem bestimmten Muster übereinstimmen
  2. Für jede Einbettungsdatei:
    1. Durch die Instanzen von tf.Example in der TFRecord-Datei iterieren
    2. Den string_identifier, also die ID, abrufen und sie dem Wörterbuch mapping als Wert hinzufügen, wobei der Schlüssel der aktuelle Wert für item_counter ist
    3. Den Vektor embedding abrufen und ihn dem annoy_index hinzufügen, wobei der Wert item_id auf den aktuellen Wert von item_counter gesetzt wird
  3. Die Methode annoy_index.build aufrufen, wobei der Wert num_trees festgelegt wird
  4. Den Index durch Aufruf der Methode annoy_index.save speichern
  5. Das Wörterbuch mapping mithilfe der Methode pickle.dump serialisieren

Das Wörterbuch mapping basiert auf der Annahme, dass die ID für die in Datastore gespeicherten Wikipedia-Titel ein String ist, der beim Abrufen von Daten aus BigQuery mit der Methode GENERATE_UUID erzeugt wird. Die ID für ein Element, also der Einbettungsvektor, im Annoy-Index kann jedoch nur eine Ganzzahl sein. Daher erstellt der Code ein Wörterbuch für die Zuordnung zwischen einem ganzzahligen Ersatzindex und der String-ID für das Wikipedia-Element.

Der in den Konstruktor AnnoyIndex übergebene Wert METRIC ist ein Winkel, der eine Variation der Kosinus-Ähnlichkeit ist. Der Wert VECTOR_LENGTH wird auf 512 gesetzt. Das ist die Länge der Texteinbettung, die vom Universal Sentence Encoder-Modul erzeugt wird.

Die Größe des gespeicherten Index kann je nach Anzahl der Einbettungsvektoren und dem Wert des Parameters num_trees mehrere Gigabyte betragen. Daher muss die Lösung APIs verwenden, die Aufteilungen unterstützen, damit der Index in Cloud Storage hochgeladen werden kann. In der Beispiellösung wird die Methode googleapiclient.http.MediaFileUpload anstelle von google.cloud.storage verwendet, wie im folgenden Code-Snippet in task.py gezeigt:

media = MediaFileUpload(
 local_file_name, mimetype='application/octet-stream', chunksize=CHUNKSIZE,
 resumable=True)
request = gcs_services.objects().insert(
 bucket=bucket_name, name=gcs_location, media_body=media)
response = None
while response is None:
 progress, response = request.next_chunk()

Die Aufgabe des Indexerstellungstools an die AI Platform senden

In der Beispiellösung umfasst das Ausführen der Indexerstellungsaufgabe als AI Platform-Job die folgenden Dateien:

  • submit.sh. Diese Datei muss aktualisiert werden, um Variablen für das Projekt, den Bucket-Namen und die Region für die Indexausgabe festzulegen.
  • config.yaml. Diese Datei verwendet den Parameter scale_tier, um die Maschinengröße anzugeben, die zum Ausführen des Jobs verwendet wird.
  • setup.py. Diese Datei gibt die Pakete an, die für den Job erforderlich sind. Die Beispiellösung benötigt Annoy und den google-api-python-client.

Nachdem diese Dateien aktualisiert wurden, kann eine Builder-Aufgabe als AI Platform-Job gesendet werden. Dazu muss das Skript submit.sh ausgeführt werden. Das Skript enthält den folgenden Befehl:

gcloud ml-engine jobs submit training ${JOB_NAME} \
    --job-dir=${JOB_DIR} \
    --runtime-version=1.12 \
    --region=${REGION} \
    --scale-tier=${TIER} \
    --module-name=builder.task \
    --package-path=${PACKAGE_PATH}  \
    --config=config.yaml \
    -- \
    --embedding-files=${EMBED_FILES} \
    --index-file=${INDEX_FILE} \
    --num-trees=${NUM_TREES}

Je nach Größe des Indexes kann der Job mehrere Stunden dauern. Die Zeit hängt von der Anzahl der Vektoren und ihrer Dimensionalität sowie von der Anzahl der Bäume ab, die zum Erstellen des Indexes verwendet werden.

Nachdem der AI Platform-Job zum Erstellen des Indexes abgeschlossen ist, sind die folgenden Artefakte im angegebenen Cloud Storage-Bucket verfügbar:

  • gs://your_bucket/wikipedia/index/embeds.index
  • gs://your_bucket/wikipedia/index/embeds.index.mapping

Semantischen Suchdienst implementieren

In diesem Abschnitt wird die Implementierung der Dienstprogramme für die semantische Suche beschrieben, die den zuvor erstellten Annoy-Index verwenden, um relevante Wikipedia-Titel aus Datastore abzurufen. Der semantische Suchdienst verwendet die folgenden Dienstprogramme:

  • Dienstprogramm zum Einbetten von Abfragen
  • Dienstprogramm zum Einbetten von Übereinstimmungen
  • Dienstprogramm zum Suchen in Datastore
  • Suchdienst-Wrapper

Dienstprogramm zum Einbetten von Abfragen

Wenn der Nutzer eine Suchanfrage eingibt, muss die Lösung die Einbettung der Abfrage extrahieren, um im Index nach ähnlichen Abfragen zu suchen. Das folgende Code-Snippet in embedding.py führt die Aufgabe aus:

class EmbedUtil:

 def __init__(self):
   logging.info("Initialising embedding utility...")
   embed_module = hub.Module(
"https://tfhub.dev/google/universal-sentence-encoder/2")
   placeholder = tf.placeholder(dtype=tf.string)
   embed = embed_module(placeholder)
   session = tf.Session()
   session.run([tf.global_variables_initializer(), tf.tables_initializer()])
   logging.info('tf.Hub module is loaded.')

   def _embeddings_fn(sentences):
     computed_embeddings = session.run(
       embed, feed_dict={placeholder: sentences})
     return computed_embeddings

   self.embedding_fn = _embeddings_fn
   logging.info("Embedding utility initialised.")

 def extract_embeddings(self, query):
   return self.embedding_fn([query])[0]

Der Code führt Folgendes aus:

  1. Lädt den Universal Sentence Encoder aus tf.Hub
  2. Stellt die Methode extract_embeddings bereit, die den Text der Nutzeranfrage akzeptiert
  3. Gibt die Satzcodierung der Einbettungen für die Abfrage zurück

Der Code sorgt dafür, dass die Methode EmbedUtil das Modul tf.Hub nur einmal im Konstruktor der Klasse lädt und nicht jedes Mal, wenn die Methode extract_embeddings aufgerufen wird. Der Grund dafür ist, dass das Laden des Moduls Universal Sentence Encoder einige Sekunden dauern kann.

Dienstprogramm zum Einbetten von Übereinstimmungen

Die Klasse MatchingUtil, die in matching.py implementiert ist, ist für das Laden des Annoy-Index aus der Datei im lokalen Speicher sowie für das Laden des Zuordnungswörterbuchs verantwortlich. Das folgende Code-Snippet zeigt die Implementierung der Klasse MatchingUtil.

class MatchingUtil:

 def __init__(self, index_file):
   logging.info("Initialising matching utility...")
   self.index = AnnoyIndex(VECTOR_LENGTH)
   self.index.load(index_file, prefault=True)
   logging.info("Annoy index {} is loaded".format(index_file))
   with open(index_file + '.mapping', 'rb') as handle:
     self.mapping = pickle.load(handle)
   logging.info("Mapping file {} is loaded".format(index_file + '.mapping'))
   logging.info("Matching utility initialised.")

 def find_similar_items(self, vector, num_matches):
   item_ids = self.index.get_nns_by_vector(
     vector, num_matches, search_k=-1, include_distances=False)
   identifiers = [self.mapping[item_id]
                 for item_id in item_ids]
   return identifiers

Der Index wird in den Klassenkonstruktor geladen. Der Code setzt den Parameter prefault in der Methode index.load auf True, sodass die gesamte Indexdatei in den Arbeitsspeicher geladen wird.

Die Klasse gibt auch die Methode find_similar_items frei, die:

  1. einen Vektor erhält, d. h., den Einbettungsvektor einer Nutzerabfrage,
  2. die item_ids als Integer-IDs der ähnlichsten Einbettung im Annoy index für den angegebenen Vektor sucht,
  3. die identifiers als GUID String-IDs aus dem mapping-Wörterbuch abruft,
  4. das Objekt identifiers zurückgibt, das zum Abrufen der Wikipedia-Titel aus Datastore verwendet werden soll.

Dienstprogramm zum Suchen in Datastore

Das folgende Snippet zeigt die Klasse DatastoreUtil in lookup.py, die die Logik zum Abrufen der Wikipedia-Titel aus Datastore implementiert. Der Konstruktor übernimmt den Wert kind von Datastore, der beschreibt, zu welchen Entitäten die Titel gehören.

class DatastoreUtil:

 def __init__(self, kind):
   logging.info("Initialising datastore lookup utility...")
   self.kind = kind
   self.client = datastore.Client()
   logging.info("Datastore lookup utility initialised.")

 def get_items(self, keys):
   keys = [self.client.key(self.kind, key)
           for key in keys]
   items = self.client.get_multi(keys)
   return items

Die Methode get_items akzeptiert einen keys-Parameter, d. h. eine Liste von IDs, und gibt das Datastore-Objekt items im Zusammenhang mit diesen Schlüsseln zurück.

Suchdienst-Wrapper

Das folgende Snippet zeigt die Klasse SearchUtil in search.py, die als Wrapper für die zuvor beschriebenen Module fungiert.

class SearchUtil:

 def __init__(self):
   logging.info("Initialising search utility...")
   dir_path = os.path.dirname(os.path.realpath(__file__))
   service_account_json = os.path.join(dir_path, SERVICE_ACCOUNT_JSON)
   index_file = os.path.join(dir_path, INDEX_FILE)
   download_artifacts(index_file, GCS_BUCKET, GCS_INDEX_LOCATION)
   self.match_util = matching.MatchingUtil(index_file)
   self.embed_util = embedding.EmbedUtil()
   self.datastore_util = lookup.DatastoreUtil(KIND, service_account_json)
   logging.info("Search utility is up and running.")

 def search(self, query, num_matches=10):
   query_embedding = self.embed_util.extract_embeddings(query)
   item_ids = self.match_util.find_similar_items(query_embedding, num_matches)
   items = self.datastore_util.get_items(item_ids)
   return items

Im Konstruktor von SearchUtil werden die Annoy-Indexdatei und das serialisierte Zuordnungswörterbuch mithilfe der Methode download_artifacts von Cloud Storage auf das lokale Laufwerk heruntergeladen. Anschließend werden die Objekte match_util, embed_util und datastore_util initialisiert.

Die Methode search akzeptiert einen query-Parameter der Nutzersuche und den Parameter num_matches, der die Anzahl der abzurufenden Übereinstimmungen angibt. Die Methode search ruft die folgenden Methoden auf:

  • Die Methode embed_util.extract_embeddings ruft mithilfe des Universal Sentence Encoder-Moduls den Einbettungsvektor der Abfrage ab.
  • Die Methode match_util.find_similar_items findet die Element-IDs von Übereinstimmungen, die der Abfrageeinbettung im Annoy-Index ähneln.
  • Die Methode datastore_util.get_items ruft die Elemente mit den angegebenen item_ids aus Datastore ab, einschließlich der Wikipedia-Titel.

Ein typischer Schritt nach dem Abruf besteht darin, die durch den Index erzeugten Artikel in Bezug auf die Höhe der Übereinstimmung einzustufen, bevor die Artikel zurückgegeben werden.

Suche mit App Engine bereitstellen

In diesem Abschnitt wird beschrieben, wie Sie den semantischen Suchdienst als Webanwendung ausführen und für App Engine bereitstellen.

Flask-Webanwendung implementieren

Das folgende Code-Snippet in main.py implementiert eine Flask-Webanwendung zur semantischen Suche nach Wikipedia-Titeln.

...
search_util = utils.search.SearchUtil()
app = Flask(__name__)

@app.route('/search', methods=['GET'])
def search():
   try:
       query = request.args.get('query')
       show = request.args.get('show')
       is_valid, error = validate_request(query, show)

       if is_valid:
           results = search_util.search(query, show)
       else:
           results = error

   except Exception as error:
       results = "Unexpected error: {}".format(error)

   response = jsonify(results)
   return response

if __name__ == '__main__':
 app.run(host='127.0.0.1', port=8080)

Das Objekt search_util wird auf Modulebene nur einmal initialisiert. Der Endpunkt RESTful /search leitet die HTTP GET-Anfrage an die Methode search weiter. Die Methode liest die Nutzersuche query als String aus und gibt an, wie viele Ergebnisse anhand von show als Integer angezeigt werden sollen, ruft die Methode search_util.search auf und gibt die abgerufenen Treffer zurück.

Webanwendung in App Engine bereitstellen

Die Flask-Webanwendung wird in einer flexiblen App Engine-Umgebung mit gunicorn als HTTP Web Server Gateway Interface (WSGI) bereitgestellt. Für die Bereitstellung in App Engine sind Konfigurationseinstellungen in den folgenden Dateien erforderlich:

  • app.yaml. Diese Datei definiert Konfigurationseinstellungen für die Python-Laufzeit sowie allgemeine Anwendungs-, Netzwerk- und Ressourceneinstellungen. In dieser Datei müssen Sie folgende Änderungen vornehmen:

    • app_start_timeout_sec im Abschnitt readiness_check einstellen, damit genügend Zeit zum Herunterladen des Index und zum Laden der Dienstprogrammobjekte bleibt
    • memory im Abschnitt resources auf einen Wert setzen, der größer als Ihre Indexgröße ist, damit der Index vollständig in den Speicher geladen werden kann
    • gunicorn --timeout einstellen, damit genügend Zeit zum Herunterladen und Laden des Index und zum Laden der Dienstprogrammobjekte bleibt
    • gunicorn --threading auf die zwei- bis vierfache Anzahl der CPU-Kerne festlegen, die im Abschnitt resources der Datei app.yaml angefordert werden, um die Gleichzeitigkeit zu erhöhen
  • requirement.txt. Die Laufzeit sucht im Quellverzeichnis der Anwendung nach einer requirements.txt-Datei und installiert Abhängigkeiten mithilfe von pip, bevor die Anwendung gestartet wird.

Sie können das Skript deploy.sh ausführen, um die Anwendung in App Engine bereitzustellen. Dazu gehört der folgende Befehl:

gcloud --verbosity=info -q app deploy app.yaml --project=${PROJECT}

Webbasierte Suchanwendung abrufen

Nachdem die Webanwendung für App Engine bereitgestellt wurde, kann über folgende URL eine Suche aufgerufen werden:

https://service_name-dot-project_name.appspot.com/search?query=query

Der Wert service_name entspricht dem Namen in der Datei app.yaml. Wenn die in query übergebene Abfrage Leerzeichen enthält, müssen diese in %20 konvertiert werden. Durch Hinzufügen von show=num_results zum Abfragestring wird angegeben, wie viele Übereinstimmungen abgerufen werden sollen. Der Standardwert ist 10.

Die folgenden Beispiele zeigen Suchabfragen und die passenden Wikipedia-Titel basierend auf dem Beispieldatensatz.

Abfrage Beispielergebnisse
Tropische wilde Tiere "Im afrikanischen Dschungel kämpfen alle Löwen, Gnus und Krokodile für sich selbst! BBC Wildlife"
Bedenken globale Technologie "Weltweites Risiko künstlicher Intelligenz"
Frische Sommergetränke "Tolle Ideen für alkoholfreien Mojito"
Wintersport "Langlaufen bei den Nordischen Skiweltmeisterschaften 2007"

Volumetrics und Lasttests

Nachdem die Beispiellösung erstellt wurde, wurde ein Beispieldurchlauf ausgeführt, um Leistungsinformationen abzurufen. In den folgenden Tabellen sehen Sie die Einstellungen, die zum Ausführen des End-to-End-Beispiels mit dem Dataset bigquery-samples.wikipedia_benchmark.Wiki100B verwendet wurden.

Einbettungen extrahieren

In der folgenden Tabelle sehen Sie die Konfigurationen des Dataflow-Jobs, der zum Extrahieren der Einbettungen verwendet wird, und die daraus resultierende Ausführungszeit.

Konfiguration
  • Aufzeichnungsgrenze: 5 Millionen
  • Größe des Einbettungsvektors: 512
  • vCPUs: 64 (32 Worker)
  • Worker-Maschinentyp: n1-highmem-2
Ergebnisse
  • Ausführungsdauer: 32 Minuten

Index erstellen

In der folgenden Tabelle sind die Konfiguration und die Ergebnisinformationen für das Erstellen des Index mit einem AI Platform-Job aufgeführt.

Konfiguration
  • Anzahl der Bäume im Annoy-Index: 100
  • Skalierungsstufe von AI Platform: large_model (n1-highmem-8)
Ergebnisse
  • Ausführungsdauer: 2 Stunden, 56 Minuten
  • Indexdateigröße: 19,28 GB
  • Größe der Zuordnungsdatei: 263,21 MB

Suchanwendung bereitstellen

Die folgende Tabelle enthält Informationen zur Konfiguration und zu den Ergebnissen für die Bereitstellung der Suchanwendung mit App Engine. Der Lasttest wurde 180 Sekunden lang mit dem Tool ab – Apache HTTP server benchmarking durchgeführt.

Konfiguration
  • vCPUs: 6
  • Arbeitsspeicher: 24 GB
  • Speicherplatz: 50 GB
  • Skalierung: 10 Instanzen manuell
Ergebnisse
  • Bereitstellungszeit (in Betrieb): ca. 19 Minuten
  • Container-Image erstellen und hochladen: ca. 6 Minuten
  • Bereitstellung der Anwendung: ca. 13 Minuten
  • Gleichzeitigkeitsstufe: 1.500
  • Anfragen pro Sekunde: ca. 2.500
  • Latenz (95. Perzentil): ca. 903 Millisekunden
  • Latenz (50. Perzentil): ca. 514 Millisekunden

Weitere Verbesserungen

Die folgenden Verbesserungen können am aktuellen System vorgenommen werden:

  • GPUs zum Bereitstellen verwenden. Der Universal Sentence Encoder kann vom Einsatz eines Beschleunigers profitieren. Die Annoy-Bibliothek unterstützt keine GPUs. Eine Bibliothek wie Faiss hingegen, die GPUs unterstützt, kann die Suchzeit für den Ermittlungsindex von näherungsweisen Übereinstimmungen verbessern. Da App Engine die Verwendung von GPUs jedoch nicht unterstützt, müssen Sie anstelle von App Engine Compute Engine oder Google Kubernetes Engine (GKE) verwenden, um GPUs nutzen zu können.

  • Index vom Datenträger auslesen. Zur Kostenoptimierung können Sie den Index statt über einen Knoten mit großem Arbeitsspeicher (im Beispiel 26 GB RAM) über kleinere Speicherknoten bereitstellen, z. B. 4 GB RAM, und den Index vom Datenträger auslesen. Wenn Sie den Index vom Datenträger auslesen, müssen Sie eine SSD angeben, da sonst die Leistung möglicherweise nicht ausreicht. Wenn Sie den Index auf dem Datenträger belassen, können Sie die Anzahl der Bereitstellungsknoten erhöhen, was wiederum den Systemdurchsatz erhöht. Außerdem werden dadurch die Kosten des Systems gesenkt. Wenn Sie jedoch den Index auf dem Datenträger belassen möchten, müssen Sie Compute Engine oder GKE verwenden, da App Engine keine SSDs als nichtflüchtigen Speicher unterstützt.

  • Index im Live-System aktualisieren. Wenn neue Daten empfangen werden, in diesem Beispiel neue Wikipedia-Artikel, muss der Index aktualisiert werden. Dies wird normalerweise als täglicher oder wöchentlicher Batchprozess ausgeführt. Nach dem Update muss die Suchanwendung aktualisiert werden, damit der neue Index ohne Ausfallzeiten verwendet werden kann.

Weitere Informationen