# Hybrid-Suche in MyScale

Dieser Leitfaden stellt die Vorteile der Verwendung von Hybrid-Suche zur Verbesserung Ihrer Textsucherfahrung vor und gibt Anweisungen zur Implementierung mit MyScale.

# Warum benötigen Sie Hybrid-Suche?

Vektor-Suche kann semantische Beziehungen zwischen Wörtern erfassen, komplexe und mehrdeutige Ausdrücke in natürlicher Sprache verarbeiten und multimodale und crossmodale Suchen unterstützen. Obwohl sie für viele Aufgaben leistungsstark und effizient ist, benötigt die Vektor-Suche möglicherweise Unterstützung, um die Semantik für Kurztext-Suchen korrekt zu verstehen.

Warum ist Hybrid-Suche notwendig?

Kurztext-Anfragen benötigen oft mehr Informationen und Kontext, um genaue und hoch relevante Ergebnisse zu liefern; stattdessen werden Ergebnisse mit geringer Präzision zurückgegeben. Zum Beispiel sind traditionelle Suchtechnologien (nicht Vektor-Suche) basierend auf Term-Übereinstimmung wie "BM25" und "TF-IDF" eine geeignetere Option, wenn Benutzer umfassende, präzise Übereinstimmungen für Phrasen wie Warenbezeichnungen, Produkt-Tags und Kleidergrößen durchführen müssen.

Hybrid-Suche, eine Kombination aus semantischer Suche und traditioneller Term-Übereinstimmung, überwindet die Herausforderungen unzureichender semantischer Abdeckung in vektorisierten Dokumenten. Wenn Sie beispielsweise in einer Datenbank eines Baumarkts mit Vektor-Suche nach einem Schraubenzieher suchen, enthält der Ergebnissatz alle Optionen, die in der Datenbank gespeichert sind. Term-Übereinstimmung ist jedoch nützlicher, wenn Sie nach einem bestimmten Schraubenzieher suchen, der genau dem Modell, der Länge und dem Material entspricht.

# Verwendung von Hybrid-Suche in MyScale

In diesem Tutorial erfahren Sie, wie Sie Hybrid-Suche in MyScale verwenden. Um dieses Tutorial abzuschließen, benötigen Sie lediglich ein MyScale-Konto und eine Python3-Umgebung auf Ihrem lokalen Rechner. Melden Sie sich bei MyScale an und erstellen Sie einen Cluster, den Sie während dieses Tutorials verwenden werden.

TIP

Anweisungen zur Erstellung eines Clusters finden Sie in unserer Schnellstart-Dokumentation (opens new window).

Sobald Sie einen Cluster erstellt haben, sind die nächsten Schritte wie folgt:

# Erstellen einer Tabelle in MyScale

Führen Sie den folgenden SQL-Befehl aus, um die Tabelle rd0 im SQL-Arbeitsbereich von MyScale zu erstellen:

CREATE TABLE default.rd0
(
    `id` UInt64,
    `body` String,
    `title` String,
    `url` String,
    `body_vector` Array(Float32),
    CONSTRAINT check_length CHECK length(body_vector) = 384
)
ENGINE = MergeTree
ORDER BY id;

Sie können den folgenden SQL-Befehl verwenden, um zu überprüfen, ob die Tabelle erstellt wurde:

SHOW tables;

Wenn die Tabelle erstellt wurde, gibt dieser SQL-Befehl den folgenden Ergebnissatz zurück:

name
rd0

# Daten aus Amazon S3 importieren

Wir haben den Wikipedia-Abstract-Datensatz (opens new window), der von RedisSearch gehostet wird, durch Hinzufügen von Vektordaten verbessert. Wir haben das Modell sentence-transformers/all-MiniLM-L6-v2 verwendet, um den Text in der Spalte body in 384-dimensionale Vektoren umzuwandeln. Diese Vektoren werden in der Spalte body_vector gespeichert, und der Kosinus wird verwendet, um den Abstand zwischen ihnen zu berechnen.

TIP

Weitere Informationen zur Verwendung von all-MiniLM-L6-v2 finden Sie in der Dokumentation von HuggingFace (opens new window).

Der endgültige Datensatz, wiki_abstract_with_vector.parquet (opens new window), ist 8,2 GB groß und enthält 5.622.309 Einträge. Sie können den Inhalt dieses Datensatzes unten anzeigen. Es ist nicht erforderlich, ihn auf Ihren lokalen Rechner herunterzuladen, da wir ihn direkt über S3 in MyScale importieren können.

id body title url body_vector
... ... ... ... ...
77 Jake Rodkin is an American .... and Puzzle Agent. Jake Rodkin https://en.wikipedia.org/wiki/Jake_Rodkin (opens new window) [-0.081793934,....,-0.01105572]
78 Friedlandpreis der Heimkehrer is ... of Germany. Friedlandpreis der Heimkehrer https://en.wikipedia.org/wiki/Friedlandpreis_der_Heimkehrer (opens new window) [0.018285718,...,0.03049711]
... ... ... ... ...

Führen Sie den folgenden SQL-Befehl im SQL-Arbeitsbereich aus, um diese Daten zu importieren.

INSERT INTO default.rd0 SELECT * FROM s3('https://myscale-datasets.s3.ap-southeast-1.amazonaws.com/wiki_abstract_with_vector.parquet','Parquet');

Note

Die geschätzte Zeit für den Datenimport beträgt etwa 10 Minuten.

Führen Sie den folgenden SQL-Befehl aus, um zu überprüfen, wann die importierten Daten 5.622.309 Zeilen erreicht haben.

SELECT COUNT(*) FROM default.rd0;

TIP

Sie können diesen SQL-Befehl mehrmals ausführen, bis die Daten vollständig importiert sind.

# Erstellen eines Vektor-Index

Der erste Teil der Erstellung eines Vektor-Index besteht darin, die Leistung der Vektor-Suche zu verbessern, indem die Datenabschnitte der Tabelle zu einem einzigen Abschnitt zusammengeführt werden, bevor der Vektor-Index zu dieser Tabelle hinzugefügt wird.

# Verbesserung der Leistung der Vektor-Suche

Um die Tabelle zu optimieren (die Leistung der Vektor-Suche zu verbessern), führen Sie den folgenden SQL-Befehl in Ihrem SQL-Arbeitsbereich aus:

OPTIMIZE TABLE default.rd0 FINAL;

Dieser Befehl kann einige Zeit in Anspruch nehmen.

Führen Sie den folgenden SQL-Befehl aus, um zu überprüfen, ob die Datenabschnitte dieser Tabelle zu 1 komprimiert wurden.

SELECT COUNT(*) FROM system.parts WHERE table='rd0' AND active=1;

Wenn die Datenabschnitte zu 1 komprimiert wurden, gibt dieser SQL-Befehl den folgenden Ergebnissatz zurück:

count()
1

# Erstellen des Vektor-Index

Führen Sie den folgenden Befehl aus, um einen Vektor-Index zu erstellen:

Note

MSTG ist ein Vektor-Index, der von MyScale entwickelt wurde.

ALTER TABLE default.rd0 ADD VECTOR INDEX RD0_MSTG body_vector
TYPE MSTG('metric_type=Cosine');

Das Erstellen eines Index erfordert Zeit. Führen Sie den folgenden SQL-Befehl aus, um den Fortschritt der Indexerstellung zu überprüfen. Wenn die Spalte "status" Built zurückgibt, wurde der Index erfolgreich erstellt. Während der Index noch erstellt wird, sollte die Spalte "status" InProgress zurückgeben.

SELECT * FROM system.vector_indices;

# Durchführen von Vektor-Suche und Hybrid-Suche

Dieser Leitfaden beschreibt sowohl die Vektor- als auch die Hybrid-Suche.

Bevor wir jedoch fortfahren, müssen wir einige vorbereitende Arbeiten erledigen.

# Vorbereitende Arbeiten

Verwenden Sie den folgenden Python-Code in Ihrer Anwendung, um Folgendes zu erreichen:

  • Ändern Sie den Host, den Benutzernamen und das Passwort, um eine Verbindung zu Ihrem MyScale-Cluster herzustellen.
  • Importieren Sie das Modell all-MiniLM-L6-v2, um Text in Vektoren umzuwandeln.
  • Erstellen Sie eine einfache Ausgabefunktion, um die Ergebnisse der SQL-Ausführung anzuzeigen.
import clickhouse_connect
from prettytable import PrettyTable
from sentence_transformers import SentenceTransformer
# Verwenden Sie das Transformatormodell all-MiniLM-L6-v2
model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
# MyScale-Informationen
host = "your_endpoint"
port = 443
username = "your_username"
password = "your_password"
database = "default"
table = "rd0"
# Initialisieren Sie den MyScale-Client
client = clickhouse_connect.get_client(host=host, port=port,
                                       username=username, password=password)
# Verwenden Sie eine Tabelle zur Ausgabe Ihres Inhalts
def print_results(result_rows, field_names):
    x = PrettyTable()
    x.field_names = field_names
    for row in result_rows:
        x.add_row(row)
    print(x)

# Vektorsuche

Wie der folgende Python-Codeausschnitt zeigt, läuft der Vektorsuchprozess, auch als neuronale Suche bekannt, wie folgt ab:

  • Verwenden Sie das Modell all-MiniLM-L6-v2, um den Text "Geschichte über Fernsehnachrichtendienst" in einen Einbettungsvektor umzuwandeln.
  • Verwenden Sie eine Vektorsuche, um die fünf ähnlichsten Wikipedia-Seiten des Datensatzes zurückzugeben.
# Vektorsuche ausprobieren
sentence = "history about television news service"
sentence_embedding = model.encode([sentence])[0]
sentence_result = client.query(query=f"SELECT id, title, body, distance('alpha=1')"
                                     f"(body_vector, {list(sentence_embedding)}) AS distance "
                                     f"FROM {database}.{table} ORDER BY distance ASC LIMIT 5")
print_results(sentence_result.result_rows, ["ID", "Title", "Body", "Distance"])

Die folgende Tabelle beschreibt die Suchergebnisse.

ID Title Body Distance
2341540 Television news in the United States Television news in the United States has evolved over many years. It has gone from a simple 10- to 15-minute format in the evenings, to a variety of programs and channels. 0.3019871711730957
4741891 United States cable news Cable news channels are television channels devoted to television news broadcasts, with the name deriving from the proliferation of such networks during the 1980s with the advent of cable television. In the United States, early networks included CNN in 1980, Financial News Network (FNN) in 1981 and CNN2 (now HLN) in 1982. 0.3059382438659668
4555265 News and Views (TV series) News and Views was an early American evening news program. Broadcast on ABC from 1948 to 1951, it was ABC's first evening news program and one of the first such programs on any television network; Both CBS and NBC also initiated their evening news programs (respectively CBS Television News and Camel News Caravan, called Camel Newsreel Theatre at first) that same year, both debuting a few months before the first broadcast of News and Views on August 11, 1948. 0.3165452480316162
185179 MediaTelevision Media Television was a Canadian television newsmagazine series, which aired weekly on Citytv from 1991 to 2004. It was also syndicated internationally, airing in over 100 countries around the world at some point during its run. 0.32938069105148315
1426832 News service News service may refer to: 0.3431185483932495

# Begrenzungen der Vektorsuche

Aus der obigen Beschreibung wird deutlich, dass die Verwendung einer reinen Vektorsuche für kurze Textphrasen ihre Grenzen hat.

Beispielsweise: Konvertieren wir den Ausdruck "BGLE Island" in einen Vektor, führen eine Vektorsuche durch und betrachten die Ergebnisse.

terms = "BGLE Island"
terms_embedding = model.encode([terms])[0]
stage1 = f"SELECT id, title, body, distance('alpha=1')" \
         f"(body_vector,{list(terms_embedding)}) AS distance FROM {database}.{table} " \
         f"ORDER BY distance ASC LIMIT 5"
sentence_result = client.query(query=stage1)
print_results(client.query(query=stage1).result_rows, ["ID", "Title", "Body", "Distance"])

Hier sind die fünf besten Suchergebnisse:

ID Title Body Distance
2625112 Bligh Island (Alaska) Bligh Island}} 0.227422833442688
2625120 Bligh Island (Canada) Bligh Island}} 0.227422833442688
4894492 Hedley (band) Island 0.3269183039665222
4708096 Blueberry Island Blueberry Island may refer to: 0.3446136713027954
5519217 Brown Island (Antarctica) Brown Island}} 0.35350120067596436

Note

Bei Betrachtung dieser Ergebnisse wird deutlich, dass die Top 5 Ergebnisse das Wort "BGLE" nicht enthalten.

# Hybride Suche

Verwenden wir eine hybride Suche, um die Genauigkeit der Ergebnisse zu verbessern, anstatt uns ausschließlich auf die Vektorsuche für kürzere Phrasen oder einzelne Wörter zu verlassen. Für den Begriff "BGLE Island" verwenden wir beispielsweise einen zweistufigen Ansatz:

  • Verwenden Sie eine Vektorsuche, um die Top 200 Kandidaten zu identifizieren.
  • Verwenden Sie die integrierten Funktionen von MyScale und eine vereinfachte TF-IDF (Termfrequenz-Inverse Dokumentfrequenz)-Methode, um diese Ergebnisse neu zu organisieren und zu verfeinern.

# Verwenden Sie eine Vektorsuche

Der folgende Codeausschnitt beschreibt, wie eine Vektorsuche durchgeführt wird, um die Top 200 Ergebnisse zu identifizieren:

# Stufe 1. Vektorabruf
terms = "BGLE Island"
terms_embedding = model.encode([terms])[0]
terms_pattern = [f'(?i){x}' for x in terms.split(' ')]
stage1 = f"SELECT id, title, body, distance('alpha=1')" \
         f"(body_vector,{list(terms_embedding)}) AS distance FROM {database}.{table} " \
         f"ORDER BY distance ASC LIMIT 200"

# Hilfsfunktionen

Bevor wir eine hybride Suche durchführen, müssen wir die folgenden beiden Funktionen verstehen, die von MyScale bereitgestellt werden:

multiMatchAllIndices(): Diese Funktion gibt den Startindex aller Teilzeichenketten in einer Zeichenkette zurück, die mit den angegebenen regulären Ausdrücken übereinstimmen. Sie nimmt zwei Parameter entgegen: die Quellzeichenkette und eine Liste von regulären Ausdrücken.

TIP

Dieser Index beginnt bei 1 und nicht bei 0.

Note

Weitere Informationen finden Sie in der ClickHouse-Dokumentation zu multiMatchAllIndices (opens new window).

Beispiel:

SELECT multiMatchAllIndices(
        'He likes to eat tomatoes.',
        ['(?i)\\blike\\b', '(?i)likes', '(?i)Tomatoes']) AS result

Bei Ausführung dieser SQL-Anweisung wird das folgende Ergebnis zurückgegeben:

result
[2, 3]

countMatches(): Diese Funktion zählt die Anzahl der angegebenen Teilzeichenketten in einer Zeichenkette. Sie nimmt zwei Parameter entgegen: die Quellzeichenkette und einen regulären Ausdruck, der die re2-Syntax verwendet.

Note

Weitere Informationen finden Sie in der ClickHouse-Dokumentation zu countMatches (opens new window).

Beispiel:

SELECT countMatches('He likes to eat tomatoes', '(?i)Tomatoes') AS result

Bei Ausführung dieser SQL-Anweisung wird das folgende Ergebnis zurückgegeben:

result
1

# Sortieren der Suchergebnisse

Wie der folgende Python-Codeausschnitt zeigt, sortiert diese nächste Stufe diese Suchergebnisse zweimal (Term-Neusortierung):

  • Sortieren Sie diese Ergebnisse basierend auf ihrer Beliebtheit. Je mehr Suchtreffer ein Ergebnis hat, desto höher ist seine Platzierung.
  • Sortieren Sie diese Ergebnisse erneut basierend auf der Anzahl der Suchtreffer (Termfrequenz). Je höher die Termfrequenz, desto höher die Platzierung.

TIP

Wir verwenden eine vereinfachte TF-IDF, um diese Ergebnisse ein zweites Mal zu sortieren.

# Stufe 2. Term-Neusortierung
stage2 = f"SELECT tempt.id, tempt.title,tempt.body, distance1, distance2 FROM ({stage1}) tempt " \
         f"ORDER BY length(multiMatchAllIndices(arrayStringConcat([body, title], ' '), {terms_pattern})) " \
         f"AS distance1 DESC, " \
         f"log(1 + countMatches(arrayStringConcat([title, body], ' '), '(?i)({terms.replace(' ', '|')})')) " \
         f"AS distance2 DESC limit 10"
sentence1_result = client.query(query=stage2)
print_results(sentence1_result.result_rows, ["ID", "Title", "Body", "distance1", "distance2"])

Die Suchergebnisse lauten wie folgt:

ID Title Body distance1 distance2
4426976 Symington Islands Symington Islands () is a group of small islands lying west-northwest of Lahille Island, in the Biscoe Islands. Charted by the British Graham Land Expedition (BGLE) under Rymill, 1934-37. 2 1.945910148700207
4425283 Saffery Islands Saffery Islands () is a group of islands extending west from Black Head, off the west coast of Graham Land. Charted by the British Graham Land Expedition (BGLE) under Rymill, 1934–37. 2 1.6094379132876024
466090 The Narrows (Antarctica) The Narrows () is a narrow channel between Pourquoi Pas Island and Blaiklock Island, connecting Bigourdan Fjord and Bourgeois Fjord off the west coast of Graham Land. It was discovered and given this descriptive name by the British Graham Land Expedition (BGLE), 1934–37, under Rymill. 2 1.3862943611198906
79253 Boaz Island, Bermuda Boaz Island, formerly known as Gate's Island or Yates Island, is one of the six main islands of Bermuda. It is part of a chain of islands in the west of the country that make up Sandys Parish, lying between the larger Ireland Island and Somerset Island, and is connected to both by bridges. 1 2.1972245771389134
3886596 Moresby Island (Gulf Islands) Moresby Island is one of the Gulf Islands of British Columbia, located on the west side of Swanson Channel and east of the southern end of Saltspring Island. It is not to be confused with Moresby Island, the second largest of the Queen Charlotte Islands off the north coast of BC. 1 2.0794415416798357
5026601 Bazett Island Bazett Island is a small island close south of the west end of Krogh Island, in the Biscoe Islands. It was mapped from air photos by the Falkland Islands and Dependencies Aerial Survey Expedition (1956–57), and named by the UK Antarctic Place-Names Committee for Henry C. 1 1.945910148700207
5026603 Bazzano Island Bazzano Island () is a small island lying off the south end of Petermann Island, between Lisboa Island and Boudet Island in the Wilhelm Archipelago. It was discovered and named by the French Antarctic Expedition, 1908–10, under Jean-Baptiste Charcot. 1 1.945910148700207
5451889 Baudisson Island Baudisson Island is an island of Papua New Guinea, located south of New Hanover Island and west of the northern part of New Ireland. It is located between Selapiu Island and Manne Island. 1 1.945910148700207
4176021 Bluck's Island, Bermuda Bluck's Island (formerly Denslow['s] Island, Dyer['s] Island) is an island of Bermuda. It lies in the harbor of Hamilton in Warwick Parish. 1 1.7917594699409376
202822 Sorge Island Sorge Island () is an island lying just south of The Gullet in Barlas Channel, close east of Adelaide Island. Mapped by Falkland Islands Dependencies Survey (FIDS) from surveys and air photos, 1948-59. 1 1.7917594699409376

Die beiden Sortiervorgänge werden unten dargestellt:

neusortierung

# Erklärung von TF-IDF

TF-IDF ist ein statistisches Maß, das die Relevanz eines Wortes in einer Sammlung von Dokumenten bewertet. Dies geschieht durch Multiplikation von zwei Metriken: der Anzahl der Vorkommen eines Wortes in einem bestimmten Dokument und der inversen (gegensätzlichen) Dokumentfrequenz dieses Wortes in allen Dokumenten der Sammlung.

Beispiel:

In diesem Beispiel ist die Menge der Wörter und ​ die Häufigkeit aller Wörter/Terme, die im Dokument vorkommen. Die Standardberechnung von TF-IDF berechnet die Termfrequenz jedes Wortes einzeln, um die Relevanz zu messen:

Der nächste Schritt besteht darin, für jedes Wort eine andere Inverse Dokumentfrequenz zu berechnen. Wenn alle Wörter als Klasse betrachtet werden, kann die Berechnung von TF-IDF wie folgt vereinfacht werden:

Wobei

Wenn wir die vereinfachte TF-IDF-Berechnung verwenden, verwenden wir dieselbe Inverse Dokumentfrequenz (IDF) als Nenner, um die Relevanz aller Termfrequenzen zu berechnen. Daher können diese IDF-Nenner, die die Sortierungsergebnisse nicht beeinflussen, eliminiert werden, sodass das endgültige vereinfachte TF-IDF eine Form von TF wird: