Conceção de modelos de dados para pesquisa
Os sistemas de recuperação de informação, também conhecidos como motores de pesquisa, são essenciais para várias aplicações de IA, como a geração aumentada por recuperação (RAG), a pesquisa visual e a recomendação de produtos. No centro destes sistemas está um modelo de dados cuidadosamente concebido para organizar, indexar e recuperar a informação.
Milvus permite-lhe especificar o modelo de dados de pesquisa através de um esquema de coleção, organizando dados não estruturados, as suas representações vectoriais densas ou esparsas, e metadados estruturados. Quer esteja a trabalhar com texto, imagens ou outros tipos de dados, este guia prático ajudá-lo-á a compreender e a aplicar os principais conceitos de esquema para conceber um modelo de dados de pesquisa na prática.
Anatomia do modelo de dados
Modelo de dados
O design do modelo de dados de um sistema de pesquisa envolve a análise das necessidades do negócio e a abstração da informação num modelo de dados expresso por um esquema. Um esquema bem definido é importante para alinhar o modelo de dados com os objectivos comerciais, garantindo a consistência dos dados e a qualidade do serviço. Além disso, a escolha de tipos de dados e índices adequados é importante para atingir o objetivo comercial de forma económica.
Analisar as necessidades da empresa
A resposta eficaz às necessidades da empresa começa com a análise dos tipos de consultas que os utilizadores irão efetuar e com a determinação dos métodos de pesquisa mais adequados.
Consultas do utilizador: Identificar os tipos de consultas que se espera que os utilizadores efectuem. Isto ajuda a garantir que o esquema suporta casos de utilização reais e optimiza o desempenho da pesquisa. Estas podem incluir:
Recuperar documentos que correspondam a uma consulta de linguagem natural
Encontrar imagens semelhantes a uma imagem de referência ou que correspondam a uma descrição de texto
Pesquisa de produtos por atributos como nome, categoria ou marca
Filtragem de itens com base em metadados estruturados (por exemplo, data de publicação, etiquetas, classificações)
Combinação de vários critérios em consultas híbridas (por exemplo, na pesquisa visual, considerando a semelhança semântica das imagens e das suas legendas)
Métodos de pesquisa: Escolha as técnicas de pesquisa adequadas que se alinham com os tipos de consultas que os seus utilizadores irão realizar. Diferentes métodos servem diferentes objectivos e podem frequentemente ser combinados para obter resultados mais poderosos:
Pesquisa semântica: Utiliza a semelhança de vectores densos para encontrar itens com significado semelhante, ideal para dados não estruturados como texto ou imagens.
Pesquisa de texto completo: Complementa a pesquisa semântica com correspondência de palavras-chave. A pesquisa de texto completo pode utilizar a análise léxica para evitar a quebra de palavras longas em tokens fragmentados, captando os termos especiais durante a recuperação.
Filtragem de metadados: No topo da pesquisa vetorial, aplicando restrições como intervalos de datas, categorias ou etiquetas.
Traduzir os requisitos comerciais num modelo de dados de pesquisa
O próximo passo é traduzir os seus requisitos comerciais num modelo de dados concreto, identificando os principais componentes da sua informação e os respectivos métodos de pesquisa:
Definir os dados que precisa de armazenar, tais como conteúdo em bruto (texto, imagens, áudio), metadados associados (títulos, etiquetas, autoria) e atributos contextuais (carimbos de data/hora, comportamento do utilizador, etc.)
Determine os tipos e formatos de dados adequados para cada elemento. Por exemplo:
Descrições de texto → string
Embeddings de imagens ou documentos → vectores densos ou esparsos
Categorias, etiquetas ou bandeiras → cadeia de caracteres, matriz e bool
Atributos numéricos como preço ou classificação → integer ou float
Informação estruturada, como detalhes do autor -> json
Uma definição clara destes elementos garante a consistência dos dados, resultados de pesquisa exactos e facilidade de integração com lógicas de aplicação a jusante.
Conceção do esquema
No Milvus, o modelo de dados é expresso através de um esquema de coleção. A conceção dos campos certos dentro de um esquema de coleção é fundamental para permitir uma recuperação eficaz. Cada campo define um tipo particular de dados armazenados na coleção e desempenha um papel distinto no processo de pesquisa. Em termos gerais, Milvus suporta dois tipos principais de campos: campos vectoriais e campos escalares.
Agora, pode mapear o seu modelo de dados para um esquema de campos, incluindo vectores e quaisquer campos escalares auxiliares. Certifique-se de que cada campo está correlacionado com os atributos do seu modelo de dados, prestando especial atenção ao seu tipo de vetor (denso ou spase) e à sua dimensão.
Campo vetorial
Os campos vectoriais armazenam embeddings para tipos de dados não estruturados, como texto, imagens e áudio. Esses embeddings podem ser densos, esparsos ou binários, dependendo do tipo de dados e do método de recuperação utilizado. Normalmente, os vectores densos são utilizados para a pesquisa semântica, enquanto os vectores esparsos são mais adequados para a correspondência de texto completo ou lexical. Os vectores binários são úteis quando os recursos de armazenamento e computacionais são limitados. Uma coleção pode conter vários campos vectoriais para permitir estratégias de recuperação multimodais ou híbridas. Para um guia detalhado sobre este tema, consulte a Pesquisa híbrida multi-vetorial.
O Milvus suporta os tipos de dados vectoriais: FLOAT_VECTOR para Vetor Denso, SPARSE_FLOAT_VECTOR para Vetor Esparso e BINARY_VECTOR para Vetor Binário
Campo escalar
Os campos escalares armazenam valores primitivos e estruturados - geralmente chamados de metadados - como números, cadeias de caracteres ou datas. Esses valores podem ser retornados juntamente com os resultados da pesquisa vetorial e são essenciais para filtragem e classificação. Permitem-lhe limitar os resultados da pesquisa com base em atributos específicos, como limitar os documentos a uma determinada categoria ou a um intervalo de tempo definido.
Milvus suporta tipos escalares como BOOL, INT8/16/32/64, FLOAT, DOUBLE, VARCHAR, JSON, e ARRAY para armazenar e filtrar dados não vectoriais. Estes tipos aumentam a precisão e a personalização das operações de pesquisa.
Tirar partido de funcionalidades avançadas na conceção de esquemas
Ao conceber um esquema, não basta mapear os dados para os campos utilizando os tipos de dados suportados. É essencial ter um conhecimento profundo das relações entre campos e das estratégias disponíveis para configuração. Ter em mente as principais caraterísticas durante a fase de conceção garante que o esquema não só satisfaz os requisitos imediatos de tratamento de dados, como também é escalável e adaptável a necessidades futuras. Ao integrar cuidadosamente estas caraterísticas, pode construir uma arquitetura de dados forte que maximiza as capacidades do Milvus e apoia a sua estratégia e objectivos de dados mais amplos. Aqui está uma visão geral das principais caraterísticas que criam um esquema de coleção:
Chave primária
Um campo de chave primária é um componente fundamental de um esquema, uma vez que identifica de forma única cada entidade dentro de uma coleção. A definição de uma chave primária é obrigatória. Deve ser um campo escalar de tipo inteiro ou de cadeia de caracteres e marcado como is_primary=True. Opcionalmente, pode ativar auto_id para a chave primária, à qual são atribuídos automaticamente números inteiros que crescem monoliticamente à medida que mais dados são ingeridos na coleção.
Para obter mais detalhes, consulte Campo primário e AutoID.
Particionamento
Para acelerar a pesquisa, pode opcionalmente ativar o particionamento. Ao designar um campo escalar específico para o particionamento e ao especificar critérios de filtragem com base neste campo durante as pesquisas, o âmbito da pesquisa pode ser efetivamente limitado apenas às partições relevantes. Este método aumenta significativamente a eficiência das operações de recuperação, reduzindo o domínio de pesquisa.
Para mais pormenores, consulte Utilizar chave de partição.
Analisador
Um analisador é uma ferramenta essencial para o processamento e transformação de dados de texto. A sua principal função é converter texto em bruto em tokens e estruturá-los para indexação e recuperação. Fá-lo através da tokenização da cadeia de caracteres, eliminando as palavras de paragem e transformando as palavras individuais em tokens.
Para mais pormenores, consulte Descrição geral do analisador.
Funções
O Milvus permite-lhe definir funções incorporadas como parte do esquema para derivar automaticamente determinados campos. Por exemplo, pode adicionar uma função BM25 incorporada que gera um vetor esparso a partir de um campo VARCHAR para suportar a pesquisa de texto integral. Estes campos derivados de funções simplificam o pré-processamento e asseguram que a coleção permanece autónoma e pronta a ser consultada.
Para mais pormenores, consulte Pesquisa de texto integral.
Um exemplo do mundo real
Nesta secção, vamos delinear a conceção do esquema e o exemplo de código para uma aplicação de pesquisa de documentos multimédia mostrada no diagrama acima. Este esquema foi concebido para gerir um conjunto de dados que contém artigos com mapeamento de dados para os seguintes campos:
Campo |
Fonte de dados |
Utilizado pelos métodos de pesquisa |
Chave primária |
Chave de partição |
Analisador |
Função de entrada/saída |
|---|---|---|---|---|---|---|
article_id ( |
gerado automaticamente com ativado |
Y |
N |
N |
N |
|
título ( |
título do artigo |
N |
N |
Y |
N |
|
carimbo de data/hora ( |
data de publicação |
N |
Y |
N |
N |
|
texto ( |
texto em bruto do artigo |
N |
N |
Y |
entrada |
|
text_dense_vector ( |
vetor denso gerado por um modelo de incorporação de texto |
N |
N |
N |
N |
|
text_sparse_vector ( |
vetor esparso gerado automaticamente por uma função BM25 incorporada |
N |
N |
N |
saída |
Para mais informações sobre esquemas e orientações pormenorizadas sobre a adição de vários tipos de campos, consulte Schema Explained.
Inicializar o esquema
Para começar, precisamos de criar um esquema vazio. Esta etapa estabelece uma estrutura básica para definir o modelo de dados.
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
Adicionar campos
Depois que o esquema é criado, a próxima etapa é especificar os campos que incluirão seus dados. Cada campo está associado aos seus respectivos tipos de dados e atributos.
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
}"
Neste exemplo, os seguintes atributos são especificados para os campos:
Chave primária: o
article_idé usado como chave primária, permitindo a atribuição automática de chaves primárias para entidades de entrada.Chave de partição: o
timestampé atribuído como uma chave de partição que permite a filtragem por partições. Esta pode serAnalisador de texto: o analisador de texto é aplicado a 2 campos de cadeia
titleetextpara suportar a correspondência de texto e a pesquisa de texto completo, respetivamente.
(Opcional) Adicionar funções
Para melhorar as capacidades de consulta de dados, podem ser incorporadas funções no esquema. Por exemplo, pode ser criada uma função para processar dados relacionados com campos específicos.
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
}"
Este exemplo adiciona uma função BM25 incorporada no schema, utilizando o campo text como entrada e armazenando os vectores esparsos resultantes no campo text_sparse_vector.