Datenmodellentwurf für die Suche
Information-Retrieval-Systeme, auch bekannt als Suchmaschinen, sind für verschiedene KI-Anwendungen wie Retrieval-augmented Generation (RAG), visuelle Suche und Produktempfehlungen unerlässlich. Das Herzstück dieser Systeme ist ein sorgfältig entworfenes Datenmodell zum Organisieren, Indizieren und Abrufen von Informationen.
Milvus ermöglicht es Ihnen, das Suchdatenmodell durch ein Sammlungsschema zu spezifizieren, das unstrukturierte Daten, ihre dichten oder spärlichen Vektordarstellungen und strukturierte Metadaten organisiert. Unabhängig davon, ob Sie mit Text, Bildern oder anderen Datentypen arbeiten, wird Ihnen diese praktische Anleitung helfen, wichtige Schemakonzepte zu verstehen und anzuwenden, um ein Suchdatenmodell in der Praxis zu entwerfen.
Datenmodell Anatomie
Datenmodell
Der Entwurf eines Datenmodells für ein Suchsystem umfasst die Analyse der Geschäftsanforderungen und die Abstraktion der Informationen in ein schemaexprimiertes Datenmodell. Ein gut definiertes Schema ist wichtig, um das Datenmodell an den Geschäftszielen auszurichten und die Konsistenz der Daten und die Qualität der Dienste sicherzustellen. Darüber hinaus ist die Auswahl geeigneter Datentypen und Indizes wichtig, um das Geschäftsziel wirtschaftlich zu erreichen.
Analyse des Geschäftsbedarfs
Die effektive Erfüllung der geschäftlichen Anforderungen beginnt mit der Analyse der Abfragetypen, die die Benutzer durchführen werden, und der Bestimmung der am besten geeigneten Suchmethoden.
Benutzerabfragen: Identifizieren Sie die Arten von Abfragen, die die Benutzer voraussichtlich durchführen werden. So können Sie sicherstellen, dass Ihr Schema reale Anwendungsfälle unterstützt und die Suchleistung optimiert. Dazu können gehören:
Abrufen von Dokumenten, die einer natürlichsprachlichen Abfrage entsprechen
Suche nach Bildern, die einem Referenzbild ähnlich sind oder einer Textbeschreibung entsprechen
Suche nach Produkten anhand von Attributen wie Name, Kategorie oder Marke
Filtern von Artikeln auf der Grundlage strukturierter Metadaten (z. B. Veröffentlichungsdatum, Tags, Bewertungen)
Kombinieren mehrerer Kriterien in hybriden Abfragen (z. B. bei der visuellen Suche unter Berücksichtigung der semantischen Ähnlichkeit von Bildern und ihren Beschriftungen)
Suchmethoden: Wählen Sie die geeigneten Suchtechniken, die sich an den Abfragetypen Ihrer Nutzer orientieren. Verschiedene Methoden dienen unterschiedlichen Zwecken und können oft kombiniert werden, um bessere Ergebnisse zu erzielen:
Semantische Suche: Verwendet dichte Vektorähnlichkeit, um Elemente mit ähnlicher Bedeutung zu finden, ideal für unstrukturierte Daten wie Text oder Bilder.
Volltextsuche: Ergänzt die semantische Suche durch den Abgleich von Schlüsselwörtern. Die Volltextsuche kann die lexikalische Analyse nutzen, um zu vermeiden, dass lange Wörter in fragmentierte Token zerlegt werden, und erfasst die speziellen Begriffe während des Abrufs.
Filterung von Metadaten: Zusätzlich zur Vektorsuche können Beschränkungen wie Datumsbereiche, Kategorien oder Tags angewendet werden.
Übersetzt Geschäftsanforderungen in ein Suchdatenmodell
Der nächste Schritt besteht darin, Ihre Geschäftsanforderungen in ein konkretes Datenmodell zu übersetzen, indem Sie die Kernkomponenten Ihrer Informationen und deren Suchmethoden identifizieren:
Definieren Sie die Daten, die Sie speichern müssen, wie z. B. Rohinhalte (Text, Bilder, Audio), zugehörige Metadaten (Titel, Tags, Urheberschaft) und kontextbezogene Attribute (Zeitstempel, Nutzerverhalten usw.)
Bestimmen Sie die geeigneten Datentypen und -formate für jedes Element. Zum Beispiel:
Textbeschreibungen → String
Bild- oder Dokumenteneinbettungen → dichte oder spärliche Vektoren
Kategorien, Tags oder Flaggen → String, Array und bool
Numerische Attribute wie Preis oder Bewertung → Integer oder Float
Strukturierte Informationen wie z.B. Autorendetails -> json
Eine klare Definition dieser Elemente gewährleistet Datenkonsistenz, genaue Suchergebnisse und eine einfache Integration in nachgelagerte Anwendungslogiken.
Schema-Entwurf
In Milvus wird das Datenmodell durch ein Sammlungsschema ausgedrückt. Die Gestaltung der richtigen Felder innerhalb eines Sammlungsschemas ist der Schlüssel zur Ermöglichung eines effektiven Abrufs. Jedes Feld definiert einen bestimmten Typ von Daten, die in der Sammlung gespeichert sind, und spielt eine bestimmte Rolle im Suchprozess. Milvus unterstützt zwei Haupttypen von Feldern: Vektorfelder und Skalarfelder.
Nun können Sie Ihr Datenmodell in ein Feldschema abbilden, das Vektoren und alle skalaren Hilfsfelder enthält. Vergewissern Sie sich, dass jedes Feld mit den Attributen Ihres Datenmodells korreliert, und achten Sie insbesondere auf den Vektortyp (dicht oder spärlich) und seine Dimension.
Vektorfeld
Vektorfelder speichern Einbettungen für unstrukturierte Datentypen wie Text, Bilder und Audio. Diese Einbettungen können dicht, spärlich oder binär sein, je nach Datentyp und verwendeter Abrufmethode. Typischerweise werden dichte Vektoren für die semantische Suche verwendet, während spärliche Vektoren besser für die Volltextsuche oder den lexikalischen Abgleich geeignet sind. Binäre Vektoren sind nützlich, wenn die Speicher- und Rechenressourcen begrenzt sind. Eine Sammlung kann mehrere Vektorfelder enthalten, um multimodale oder hybride Abfragestrategien zu ermöglichen. Eine detaillierte Anleitung zu diesem Thema finden Sie in der Multi-Vector Hybrid Search.
Milvus unterstützt die folgenden Vektordatentypen: FLOAT_VECTOR für Dense Vector, SPARSE_FLOAT_VECTOR für Sparse Vector und BINARY_VECTOR für Binary Vector
Skalares Feld
Skalare Felder speichern primitive, strukturierte Werte - üblicherweise als Metadaten bezeichnet - wie Zahlen, Zeichenketten oder Daten. Diese Werte können zusammen mit Vektorsuchergebnissen zurückgegeben werden und sind für das Filtern und Sortieren wichtig. Sie ermöglichen es Ihnen, die Suchergebnisse auf der Grundlage bestimmter Attribute einzugrenzen, z. B. die Beschränkung von Dokumenten auf eine bestimmte Kategorie oder einen bestimmten Zeitraum.
Milvus unterstützt skalare Typen wie BOOL, INT8/16/32/64, FLOAT, DOUBLE, VARCHAR, JSON und ARRAY zur Speicherung und Filterung von Nicht-Vektordaten. Diese Typen verbessern die Präzision und Anpassung von Suchvorgängen.
Nutzung erweiterter Funktionen beim Schemadesign
Beim Entwerfen eines Schemas reicht es nicht aus, Ihre Daten einfach nur mit Hilfe der unterstützten Datentypen auf Felder abzubilden. Es ist wichtig, die Beziehungen zwischen den Feldern und die für die Konfiguration verfügbaren Strategien genau zu kennen. Durch die Berücksichtigung der wichtigsten Funktionen in der Entwurfsphase wird sichergestellt, dass das Schema nicht nur die unmittelbaren Anforderungen an die Datenverarbeitung erfüllt, sondern auch skalierbar und für künftige Anforderungen anpassbar ist. Durch die sorgfältige Integration dieser Funktionen können Sie eine starke Datenarchitektur aufbauen, die die Fähigkeiten von Milvus maximiert und Ihre breitere Datenstrategie und Ziele unterstützt. Im Folgenden finden Sie einen Überblick über die wichtigsten Funktionen zur Erstellung eines Sammelschemas:
Primärschlüssel
Ein Primärschlüsselfeld ist eine grundlegende Komponente eines Schemas, da es jede Entität innerhalb einer Sammlung eindeutig identifiziert. Die Definition eines Primärschlüssels ist obligatorisch. Es muss ein skalares Feld vom Typ Ganzzahl oder String sein und als is_primary=True gekennzeichnet sein. Optional können Sie auto_id für den Primärschlüssel aktivieren, dem automatisch ganzzahlige Nummern zugewiesen werden, die monolithisch wachsen, wenn mehr Daten in die Sammlung aufgenommen werden.
Weitere Einzelheiten finden Sie unter Primärfeld & AutoID.
Partitionierung
Um die Suche zu beschleunigen, können Sie optional die Partitionierung aktivieren. Indem Sie ein bestimmtes Skalarfeld für die Partitionierung festlegen und bei der Suche Filterkriterien auf der Grundlage dieses Feldes angeben, kann der Suchumfang effektiv auf die relevanten Partitionen beschränkt werden. Diese Methode erhöht die Effizienz der Suchvorgänge erheblich, indem sie den Suchbereich reduziert.
Weitere Einzelheiten finden Sie unter Partitionsschlüssel verwenden.
Analysator
Ein Analyzer ist ein wichtiges Werkzeug für die Verarbeitung und Umwandlung von Textdaten. Seine Hauptfunktion ist die Umwandlung von Rohtext in Token und deren Strukturierung für die Indizierung und den Abruf. Dies geschieht durch die Tokenisierung der Zeichenfolge, das Entfernen von Stoppwörtern und das Stemming der einzelnen Wörter in Token.
Weitere Einzelheiten finden Sie unter Analyzer Overview.
Funktion
Milvus ermöglicht es Ihnen, integrierte Funktionen als Teil des Schemas zu definieren, um bestimmte Felder automatisch abzuleiten. Sie können zum Beispiel eine integrierte BM25-Funktion hinzufügen, die einen Sparse-Vektor aus einem VARCHAR -Feld erzeugt, um die Volltextsuche zu unterstützen. Diese von Funktionen abgeleiteten Felder rationalisieren die Vorverarbeitung und gewährleisten, dass die Sammlung in sich geschlossen und abfragebereit bleibt.
Weitere Einzelheiten finden Sie unter Volltextsuche.
Ein Beispiel aus der Praxis
In diesem Abschnitt werden das Schema-Design und das Code-Beispiel für eine Anwendung zur Suche nach Multimedia-Dokumenten beschrieben, wie im obigen Diagramm dargestellt. Dieses Schema wurde entwickelt, um einen Datensatz zu verwalten, der Artikel mit Daten enthält, die den folgenden Feldern zugeordnet sind:
Feld |
Datenquelle |
Verwendet von Suchmethoden |
Primärschlüssel |
Partitionsschlüssel |
Analyzer |
Funktion Input/Output |
|---|---|---|---|---|---|---|
artikel_id ( |
automatisch generiert mit aktiviert |
Y |
N |
N |
N |
|
Titel ( |
Titel des Artikels |
N |
N |
Y |
N |
|
Zeitstempel ( |
Veröffentlichungsdatum |
N |
Y |
N |
N |
|
Text ( |
Rohtext des Artikels |
N |
N |
Y |
Eingabe |
|
text_dichter_vektor ( |
dichter Vektor, der durch ein Texteinbettungsmodell erzeugt wurde |
N |
N |
N |
N |
|
text_sparse_vector ( |
automatisch durch eine eingebaute BM25-Funktion erzeugter Sparse-Vektor |
N |
N |
N |
Ausgabe |
Weitere Informationen zu Schemata und eine detaillierte Anleitung zum Hinzufügen verschiedener Feldtypen finden Sie unter Schema erklärt.
Schema initialisieren
Zu Beginn müssen wir ein leeres Schema erstellen. Mit diesem Schritt wird eine grundlegende Struktur für die Definition des Datenmodells geschaffen.
from pymilvus import MilvusClient
schema = MilvusClient.create_schema()
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.service.collection.request.CreateCollectionReq;
// 1. Connect to Milvus server
ConnectConfig connectConfig = ConnectConfig.builder()
.uri("http://localhost:19530")
.build();
MilvusClientV2 client = new MilvusClientV2(connectConfig);
// 2. Create an empty schema
CreateCollectionReq.CollectionSchema schema = client.createSchema();
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";
//Skip this step using JavaScript
import "github.com/milvus-io/milvus/client/v2/entity"
schema := entity.NewSchema()
# Skip this step using cURL
Felder hinzufügen
Sobald das Schema erstellt ist, müssen im nächsten Schritt die Felder festgelegt werden, die Ihre Daten enthalten sollen. Jedes Feld ist mit den entsprechenden Datentypen und Attributen verbunden.
from pymilvus import DataType
schema.add_field(field_name="article_id", datatype=DataType.INT64, is_primary=True, auto_id=True, description="article id")
schema.add_field(field_name="title", datatype=DataType.VARCHAR, enable_analyzer=True, enable_match=True, max_length=200, description="article title")
schema.add_field(field_name="timestamp", datatype=DataType.INT32, description="publish date")
schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=2000, enable_analyzer=True, description="article text content")
schema.add_field(field_name="text_dense_vector", datatype=DataType.FLOAT_VECTOR, dim=768, description="text dense vector")
schema.add_field(field_name="text_sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR, description="text sparse vector")
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;
schema.addField(AddFieldReq.builder()
.fieldName("article_id")
.dataType(DataType.Int64)
.isPrimaryKey(true)
.autoID(true)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("title")
.dataType(DataType.VarChar)
.maxLength(200)
.enableAnalyzer(true)
.enableMatch(true)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("timestamp")
.dataType(DataType.Int32)
.build())
schema.addField(AddFieldReq.builder()
.fieldName("text")
.dataType(DataType.VarChar)
.maxLength(2000)
.enableAnalyzer(true)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("text_dense_vector")
.dataType(DataType.FloatVector)
.dimension(768)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("text_sparse_vector")
.dataType(DataType.SparseFloatVector)
.build());
const fields = [
{
name: "article_id",
data_type: DataType.Int64,
is_primary_key: true,
auto_id: true
},
{
name: "title",
data_type: DataType.VarChar,
max_length: 200,
enable_analyzer: true,
enable_match: true
},
{
name: "timestamp",
data_type: DataType.Int32
},
{
name: "text",
data_type: DataType.VarChar,
max_length: 2000,
enable_analyzer: true
},
{
name: "text_dense_vector",
data_type: DataType.FloatVector,
dim: 768
},
{
name: "text_sparse_vector",
data_type: DataType.SparseFloatVector
}
]
schema.WithField(entity.NewField().
WithName("article_id").
WithDataType(entity.FieldTypeInt64).
WithIsPrimaryKey(true).
WithIsAutoID(true).
WithDescription("article id"),
).WithField(entity.NewField().
WithName("title").
WithDataType(entity.FieldTypeVarChar).
WithMaxLength(200).
WithEnableAnalyzer(true).
WithEnableMatch(true).
WithDescription("article title"),
).WithField(entity.NewField().
WithName("timestamp").
WithDataType(entity.FieldTypeInt32).
WithDescription("publish date"),
).WithField(entity.NewField().
WithName("text").
WithDataType(entity.FieldTypeVarChar).
WithMaxLength(2000).
WithEnableAnalyzer(true).
WithDescription("article text content"),
).WithField(entity.NewField().
WithName("text_dense_vector").
WithDataType(entity.FieldTypeFloatVector).
WithDim(768).
WithDescription("text dense vector"),
).WithField(entity.NewField().
WithName("text_sparse_vector").
WithDataType(entity.FieldTypeSparseVector).
WithDescription("text sparse vector"),
)
export fields='[
{
"fieldName": "article_id",
"dataType": "Int64",
"isPrimary": true
},
{
"fieldName": "title",
"dataType": "VarChar",
"elementTypeParams": {
"max_length": 200,
"enable_analyzer": true,
"enable_match": true
}
},
{
"fieldName": "timestamp",
"dataType": "Int32"
},
{
"fieldName": "text",
"dataType": "VarChar",
"elementTypeParams": {
"max_length": 2000,
"enable_analyzer": true
}
},
{
"fieldName": "text_dense_vector",
"dataType": "FloatVector",
"elementTypeParams": {
"dim": 768
}
},
{
"fieldName": "text_sparse_vector",
"dataType": "SparseFloatVector",
}
]'
export schema="{
\"autoID\": true,
\"fields\": $fields
}"
In diesem Beispiel werden die folgenden Attribute für Felder angegeben:
Primärschlüssel:
article_idwird als Primärschlüssel verwendet und ermöglicht die automatische Zuweisung von Primärschlüsseln für eingehende Entitäten.Partitionsschlüssel:
timestampwird als Partitionsschlüssel zugewiesen und ermöglicht die Filterung nach Partitionen. Dies kann seinTextanalyzer: Der Textanalyzer wird auf die beiden String-Felder
titleundtextangewendet, um eine Textübereinstimmung bzw. eine Volltextsuche zu unterstützen.
(Optional) Funktionen hinzufügen
Um die Möglichkeiten der Datenabfrage zu verbessern, können Funktionen in das Schema aufgenommen werden. So kann zum Beispiel eine Funktion erstellt werden, die bestimmte Felder verarbeitet.
from pymilvus import Function, FunctionType
bm25_function = Function(
name="text_bm25",
input_field_names=["text"],
output_field_names=["text_sparse_vector"],
function_type=FunctionType.BM25,
)
schema.add_function(bm25_function)
import io.milvus.common.clientenum.FunctionType;
import io.milvus.v2.service.collection.request.CreateCollectionReq.Function;
import java.util.*;
schema.addFunction(Function.builder()
.functionType(FunctionType.BM25)
.name("text_bm25")
.inputFieldNames(Collections.singletonList("text"))
.outputFieldNames(Collections.singletonList("text_sparse_vector"))
.build());
import FunctionType from "@zilliz/milvus2-sdk-node";
const functions = [
{
name: 'text_bm25',
description: 'bm25 function',
type: FunctionType.BM25,
input_field_names: ['text'],
output_field_names: ['text_sparse_vector'],
params: {},
},
];
function := entity.NewFunction().
WithName("text_bm25").
WithInputFields("text").
WithOutputFields("text_sparse_vector").
WithType(entity.FunctionTypeBM25)
schema.WithFunction(function)
export myFunctions='[
{
"name": "text_bm25",
"type": "BM25",
"inputFieldNames": ["text"],
"outputFieldNames": ["text_sparse_vector"],
"params": {}
}
]'
export schema="{
\"autoID\": true,
\"fields\": $fields
\"functions\": $myFunctions
}"
In diesem Beispiel wird eine integrierte BM25-Funktion in das Schema eingefügt, die das Feld text als Eingabe verwendet und die resultierenden spärlichen Vektoren im Feld text_sparse_vector speichert.