Desenho de esquemas prático
Os sistemas de Recuperação de Informação (RI), também conhecidos como pesquisa, são essenciais para várias aplicações de IA, como Retrieval-augmented generation (RAG), pesquisa de imagens e recomendação de produtos. O primeiro passo no desenvolvimento de um sistema de RI é a conceção do modelo de dados, que envolve a análise dos requisitos comerciais, a determinação da forma de organizar a informação e a indexação dos dados para os tornar semanticamente pesquisáveis.
O Milvus suporta a definição do modelo de dados através de um esquema de coleção. Uma coleção organiza dados não estruturados como texto e imagens, juntamente com as suas representações vectoriais, incluindo vectores densos e esparsos em várias precisões utilizadas para a pesquisa semântica. Além disso, o Milvus suporta o armazenamento e a filtragem de tipos de dados não vectoriais denominados "Scalar". Os tipos escalares incluem BOOL, INT8/16/32/64, FLOAT/DOUBLE, VARCHAR, JSON e Array.
Exemplo de esquema de dados concebido para pesquisar artigos de notícias
A conceção do modelo de dados de um sistema de pesquisa envolve a análise das necessidades comerciais e a abstração da informação num modelo de dados expresso num esquema. Por exemplo, para pesquisar um pedaço de texto, este deve ser "indexado" convertendo a cadeia literal num vetor através de "embedding", permitindo a pesquisa vetorial. Para além deste requisito básico, pode ser necessário armazenar outras propriedades, como o carimbo temporal da publicação e o autor. Estes metadados permitem que as pesquisas semânticas sejam refinadas através de filtragem, devolvendo apenas textos publicados após uma data específica ou por um determinado autor. Também podem precisar de ser recuperados juntamente com o texto principal, para apresentar o resultado da pesquisa na aplicação. Para organizar estas partes de texto, deve ser atribuído a cada uma delas um identificador único, expresso como um número inteiro ou uma cadeia de caracteres. Estes elementos são essenciais para obter uma lógica de pesquisa sofisticada.
Um esquema bem concebido é importante porque abstrai o modelo de dados e decide se os objectivos comerciais podem ser alcançados através da pesquisa. Além disso, uma vez que cada linha de dados inserida na coleção tem de seguir o esquema, este ajuda muito a manter a consistência dos dados e a qualidade a longo prazo. De uma perspetiva técnica, um esquema bem definido leva a um armazenamento de dados de coluna bem organizado e a uma estrutura de índice mais limpa, o que pode aumentar o desempenho da pesquisa.
Um exemplo: Pesquisa de notícias
Digamos que queremos criar uma pesquisa para um site de notícias e temos um corpus de notícias com texto, imagens em miniatura e outros metadados. Primeiro, precisamos de analisar como queremos utilizar os dados para suportar o requisito comercial da pesquisa. Imaginemos que o requisito é obter as notícias com base na imagem em miniatura e no resumo do conteúdo, e utilizar os metadados, como a informação do autor e a hora de publicação, como critérios para filtrar o resultado da pesquisa. Estes requisitos podem ainda ser divididos em.
Para pesquisar imagens através de texto, podemos incorporar imagens em vectores através do modelo de incorporação multimodal que pode mapear dados de texto e imagem no mesmo espaço latente.
O texto do resumo de um artigo é incorporado em vectores através do modelo de incorporação de texto.
Para filtrar com base na hora de publicação, as datas são armazenadas como um campo escalar e é necessário um índice para o campo escalar para uma filtragem eficiente. Outras estruturas de dados mais complexas, como JSON, podem ser armazenadas num escalar e uma pesquisa filtrada pode ser efectuada no seu conteúdo (a indexação de JSON é uma funcionalidade futura).
Para recuperar os bytes da miniatura da imagem e apresentá-la na página de resultados da pesquisa, o URL da imagem também é armazenado. Da mesma forma, para o texto de resumo e o título. (Em alternativa, podemos armazenar o texto em bruto e os dados do ficheiro de imagem como campos escalares, se necessário).
Para melhorar o resultado da pesquisa no texto de resumo, concebemos uma abordagem de pesquisa híbrida. Para um caminho de recuperação, utilizamos um modelo de incorporação regular para gerar um vetor denso a partir do texto, como o
text-embedding-3-large
da OpenAI ou obge-large-en-v1.5
de código aberto. Estes modelos são bons para representar a semântica global do texto. O outro caminho consiste em utilizar modelos de incorporação esparsos, como o BM25 ou o SPLADE, para gerar um vetor esparso, semelhante à pesquisa de texto completo, que é bom para compreender os detalhes e os conceitos individuais do texto. O Milvus permite a utilização de ambos na mesma recolha de dados graças à sua funcionalidade multi-vetorial. A pesquisa em vários vectores pode ser efectuada numa única operação emhybrid_search()
.Por fim, precisamos também de um campo ID para identificar cada página de notícias individual, formalmente designada por "entidade" na terminologia do Milvus. Este campo é utilizado como chave primária (ou "pk", abreviadamente).
Nome do campo | article_id (Chave primária) | título | author_info | publish_ts | URL_da_imagem | vector_imagem | resumo | resumo_denso_vector | summary_sparse_vector |
---|---|---|---|---|---|---|---|---|---|
Tipo de vetor | INT64 | VARCHAR | JSON | INT32 | VARCHAR | FLOAT_VECTOR | VARCHAR | FLOAT_VECTOR | VECTOR_FLOAT_ESPARSO |
Índice de necessidade | N | N | N (Suporte em breve) | Y | N | Y | N | Y | Y |
Como implementar o esquema de exemplo
Criar esquema
Primeiro, criamos uma instância de cliente Milvus, que pode ser usada para se ligar ao servidor Milvus e gerir colecções e dados.
Para configurar um esquema, usamos create_schema()
para criar um objeto de esquema e add_field()
para adicionar campos ao esquema.
from pymilvus import MilvusClient, DataType
collection_name = "my_collection"
# client = MilvusClient(uri="http://localhost:19530")
client = MilvusClient(uri="./milvus_demo.db")
schema = MilvusClient.create_schema(
auto_id=False,
)
schema.add_field(field_name="article_id", datatype=DataType.INT64, is_primary=True, description="article id")
schema.add_field(field_name="title", datatype=DataType.VARCHAR, max_length=200, description="article title")
schema.add_field(field_name="author_info", datatype=DataType.JSON, description="author information")
schema.add_field(field_name="publish_ts", datatype=DataType.INT32, description="publish timestamp")
schema.add_field(field_name="image_url", datatype=DataType.VARCHAR, max_length=500, description="image URL")
schema.add_field(field_name="image_vector", datatype=DataType.FLOAT_VECTOR, dim=768, description="image vector")
schema.add_field(field_name="summary", datatype=DataType.VARCHAR, max_length=1000, description="article summary")
schema.add_field(field_name="summary_dense_vector", datatype=DataType.FLOAT_VECTOR, dim=768, description="summary dense vector")
schema.add_field(field_name="summary_sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR, description="summary sparse vector")
Pode reparar no argumento uri
em MilvusClient
, que é utilizado para estabelecer a ligação ao servidor Milvus. Pode definir os argumentos da seguinte forma.
Se apenas necessitar de uma base de dados vetorial local para dados de pequena escala ou protótipos, definir o uri como um ficheiro local, por exemplo,
./milvus.db
, é o método mais conveniente, uma vez que utiliza automaticamente o Milvus Lite para armazenar todos os dados neste ficheiro.Se tiver uma grande escala de dados, digamos mais de um milhão de vectores, pode configurar um servidor Milvus mais eficiente em Docker ou Kubernetes. Nesta configuração, use o endereço e a porta do servidor como seu uri, por exemplo,
http://localhost:19530
. Se ativar a funcionalidade de autenticação no Milvus, utilize "<your_username>:<your_password>" como token, caso contrário não defina o token.Se utilizar o Zilliz Cloud, o serviço de nuvem totalmente gerido para o Milvus, ajuste os endereços
uri
etoken
, que correspondem ao Public Endpoint e à chave API no Zilliz Cloud.
Relativamente a auto_id
em MilvusClient.create_schema
, AutoID é um atributo do campo primário que determina se deve ser ativado o incremento automático para o campo primário. Uma vez que definimos o campoarticle_id
como chave primária e pretendemos adicionar o ID do artigo manualmente, definimos auto_id
False para desativar esta funcionalidade.
Depois de adicionar todos os campos ao objeto de esquema, o nosso objeto de esquema está de acordo com as entradas da tabela acima.
Definir índice
Depois de definir o esquema com vários campos, incluindo metadados e campos vectoriais para dados de imagem e resumo, o passo seguinte envolve a preparação dos parâmetros do índice. A indexação é crucial para otimizar a pesquisa e a recuperação de vectores, garantindo um desempenho de consulta eficiente. Na secção seguinte, definiremos os parâmetros de índice para os campos vectoriais e escalares especificados na coleção.
index_params = client.prepare_index_params()
index_params.add_index(
field_name="image_vector",
index_type="AUTOINDEX",
metric_type="IP",
)
index_params.add_index(
field_name="summary_dense_vector",
index_type="AUTOINDEX",
metric_type="IP",
)
index_params.add_index(
field_name="summary_sparse_vector",
index_type="SPARSE_INVERTED_INDEX",
metric_type="IP",
)
index_params.add_index(
field_name="publish_ts",
index_type="INVERTED",
)
Uma vez definidos e aplicados os parâmetros de indexação, o Milvus é optimizado para tratar consultas complexas sobre dados vectoriais e escalares. Esta indexação melhora o desempenho e a precisão das pesquisas por semelhança dentro da coleção, permitindo a recuperação eficiente de artigos com base em vectores de imagem e vectores de resumo. Ao tirar partido da indexação AUTOINDEX
para vectores densos, o SPARSE_INVERTED_INDEX
para vectores esparsos e o INVERTED_INDEX
para escalares, o Milvus pode identificar e devolver rapidamente os resultados mais relevantes, melhorando significativamente a experiência geral do utilizador e a eficácia do processo de recuperação de dados.
Existem muitos tipos de índices e métricas. Para mais informações sobre eles, pode consultar Milvus index type e Milvus metric type.
Criar coleção
Com o esquema e os índices definidos, criamos uma "coleção" com estes parâmetros. A coleção para o Milvus é como uma tabela para uma base de dados relacional.
client.create_collection(
collection_name=collection_name,
schema=schema,
index_params=index_params,
)
Podemos verificar se a coleção foi criada com sucesso descrevendo a coleção.
collection_desc = client.describe_collection(
collection_name=collection_name
)
print(collection_desc)
Outras considerações
Carregamento do índice
Ao criar uma coleção no Milvus, pode optar por carregar o índice imediatamente ou adiá-lo até depois de ingerir alguns dados em massa. Normalmente, não é necessário fazer uma escolha explícita sobre isso, pois os exemplos acima mostram que o índice é automaticamente construído para qualquer dado ingerido logo após a criação da coleção. Isso permite a capacidade de pesquisa imediata dos dados ingeridos. No entanto, se tiver uma grande inserção em massa após a criação da coleção e não precisar de pesquisar quaisquer dados até um determinado ponto, pode adiar a construção do índice omitindo index_params na criação da coleção e construir o índice chamando explicitamente load depois de ingerir todos os dados. Este método é mais eficiente para construir o índice numa coleção grande, mas nenhuma pesquisa pode ser feita até chamar load().
Como definir o modelo de dados para vários locatários
O conceito de vários locatários é normalmente usado em cenários em que um único aplicativo ou serviço de software precisa atender a vários usuários ou organizações independentes, cada um com seu próprio ambiente isolado. Isso é frequentemente visto em computação em nuvem, aplicativos SaaS (Software as a Service) e sistemas de banco de dados. Por exemplo, um serviço de armazenamento em nuvem pode utilizar o multi-tenancy para permitir que diferentes empresas armazenem e gerenciem seus dados separadamente, compartilhando a mesma infraestrutura subjacente. Essa abordagem maximiza a utilização e a eficiência dos recursos, garantindo a segurança e a privacidade dos dados para cada locatário.
A forma mais fácil de diferenciar os locatários é isolando os seus dados e recursos uns dos outros. Cada locatário tem acesso exclusivo a recursos específicos ou partilha recursos com outros para gerir entidades Milvus, tais como bases de dados, colecções e partições. Existem métodos específicos alinhados com estas entidades para implementar o Milvus multi-tenancy. Para mais informações, consulte a página de multilocação do Milvus.