Conception de schémas Pratique
Les systèmes de recherche d'information (RI), également connus sous le nom de recherche, sont essentiels pour diverses applications d'IA telles que la génération augmentée par la recherche (RAG), la recherche d'images et la recommandation de produits. La première étape du développement d'un système de RI consiste à concevoir le modèle de données, ce qui implique d'analyser les besoins de l'entreprise, de déterminer comment organiser les informations et d'indexer les données pour les rendre sémantiquement consultables.
Milvus prend en charge la définition du modèle de données par le biais d'un schéma de collection. Une collection organise les données non structurées telles que le texte et les images, ainsi que leurs représentations vectorielles, y compris les vecteurs denses et épars dans diverses précisions utilisées pour la recherche sémantique. En outre, Milvus prend en charge le stockage et le filtrage de types de données non vectorielles appelés "Scalar". Les types scalaires comprennent BOOL, INT8/16/32/64, FLOAT/DOUBLE, VARCHAR, JSON et Array.
Exemple de schéma de données conçu pour la recherche d'articles d'actualité
La conception du modèle de données d'un système de recherche implique l'analyse des besoins de l'entreprise et l'abstraction des informations dans un modèle de données exprimé par un schéma. Par exemple, pour rechercher un morceau de texte, il faut l'"indexer" en convertissant la chaîne littérale en un vecteur par "encastrement", ce qui permet la recherche vectorielle. Au-delà de cette exigence de base, il peut être nécessaire de stocker d'autres propriétés telles que l'horodatage de la publication et l'auteur. Ces métadonnées permettent d'affiner les recherches sémantiques par filtrage, en ne renvoyant que les textes publiés après une date spécifique ou par un auteur particulier. Il peut également être nécessaire de les récupérer avec le texte principal, afin de restituer le résultat de la recherche dans l'application. Pour organiser ces éléments de texte, il convient d'attribuer à chacun un identifiant unique, exprimé sous la forme d'un nombre entier ou d'une chaîne de caractères. Ces éléments sont essentiels pour parvenir à une logique de recherche sophistiquée.
Un schéma bien conçu est important car il abstrait le modèle de données et détermine si les objectifs de l'entreprise peuvent être atteints par le biais de la recherche. En outre, étant donné que chaque ligne de données insérée dans la collection doit respecter le schéma, celui-ci contribue grandement à maintenir la cohérence des données et la qualité à long terme. D'un point de vue technique, un schéma bien défini permet un stockage des données en colonnes bien organisé et une structure d'index plus propre, ce qui peut améliorer les performances de recherche.
Un exemple : Recherche d'actualités
Supposons que nous voulions créer un moteur de recherche pour un site web d'actualités et que nous disposions d'un corpus d'actualités contenant du texte, des vignettes et d'autres métadonnées. Tout d'abord, nous devons analyser la manière dont nous voulons utiliser les données pour répondre aux besoins de recherche de l'entreprise. Imaginons qu'il s'agisse d'extraire les nouvelles sur la base de l'image de la vignette et du résumé du contenu, et de prendre les métadonnées telles que les informations sur l'auteur et l'heure de publication comme critères pour filtrer le résultat de la recherche. Ces exigences peuvent être décomposées comme suit.
Pour rechercher des images par le biais du texte, nous pouvons intégrer des images dans des vecteurs grâce à un modèle d'intégration multimodale qui peut mettre en correspondance des données textuelles et des données d'image dans le même espace latent.
Le résumé d'un article est intégré dans des vecteurs par le biais d'un modèle d'intégration de texte.
Pour filtrer sur la base de l'heure de publication, les dates sont stockées sous la forme d'un champ scalaire et un index est nécessaire pour le champ scalaire afin d'assurer un filtrage efficace. D'autres structures de données plus complexes, telles que JSON, peuvent être stockées dans un champ scalaire et une recherche filtrée peut être effectuée sur leur contenu (l'indexation de JSON est une fonctionnalité à venir).
Pour récupérer les octets de la vignette de l'image et l'afficher sur la page de résultats de la recherche, l'url de l'image est également stockée. Il en va de même pour le texte et le titre du résumé. (Nous pourrions également stocker le texte brut et les données du fichier image sous forme de champs scalaires si nécessaire).
Pour améliorer le résultat de la recherche sur le texte résumé, nous concevons une approche de recherche hybride. Pour un chemin de recherche, nous utilisons un modèle d'intégration régulier pour générer un vecteur dense à partir du texte, tel que le modèle
text-embedding-3-large
d'OpenAI ou le modèle open-sourcebge-large-en-v1.5
. Ces modèles représentent bien la sémantique globale du texte. L'autre voie consiste à utiliser des modèles d'intégration clairsemés tels que BM25 ou SPLADE pour générer un vecteur clairsemé, ressemblant à la recherche en texte intégral, qui permet de saisir les détails et les concepts individuels dans le texte. Milvus permet d'utiliser les deux dans la même collecte de données grâce à sa fonction multi-vecteur. La recherche sur plusieurs vecteurs peut être effectuée en une seule opérationhybrid_search()
.Enfin, nous avons également besoin d'un champ ID pour identifier chaque page d'information individuelle, formellement appelée "entité" dans la terminologie Milvus. Ce champ est utilisé comme clé primaire (ou "pk" en abrégé).
Nom du champ | article_id (clé primaire) | titre | author_info | publish_ts | image_url | vecteur_image | résumé | vecteur_dense_résumé | résumé_vecteur_sparse |
---|---|---|---|---|---|---|---|---|---|
Type d'image | INT64 | VARCHAR | JSON | INT32 | VARCHAR | FLOAT_VECTOR | VARCHAR | FLOAT_VECTOR | SPARSE_FLOAT_VECTOR |
Indice de besoin | N | N | N (support à venir) | Y | N | Y | N | Y | Y |
Comment mettre en œuvre l'exemple de schéma
Création du schéma
Nous commençons par créer une instance de client Milvus, qui peut être utilisée pour se connecter au serveur Milvus et gérer les collections et les données.
Pour configurer un schéma, nous utilisons create_schema()
pour créer un objet schéma et add_field()
pour ajouter des champs au schéma.
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")
Vous remarquerez peut-être l'argument uri
dans MilvusClient
, qui est utilisé pour se connecter au serveur Milvus. Vous pouvez définir les arguments comme suit.
Si vous n'avez besoin d'une base de données vectorielle locale que pour des données à petite échelle ou des prototypes, définir l'uri comme un fichier local, par exemple
./milvus.db
, est la méthode la plus pratique, car elle utilise automatiquement Milvus Lite pour stocker toutes les données dans ce fichier.Si vous disposez de données à grande échelle, par exemple plus d'un million de vecteurs, vous pouvez configurer un serveur Milvus plus performant sur Docker ou Kubernetes. Dans cette configuration, veuillez utiliser l'adresse et le port du serveur comme uri, par exemple
http://localhost:19530
. Si vous activez la fonction d'authentification sur Milvus, utilisez "<votre_nom_d'utilisateur>:<votre_mot_de_passe>" comme jeton, sinon ne définissez pas le jeton.Si vous utilisez Zilliz Cloud, le service en nuage entièrement géré pour Milvus, ajustez les adresses
uri
ettoken
, qui correspondent au point de terminaison public et à la clé API dans Zilliz Cloud.
Comme pour auto_id
dans MilvusClient.create_schema
, AutoID est un attribut du champ primaire qui détermine s'il faut activer l'incrémentation automatique pour le champ primaire. Comme nous avons défini le champarticle_id
comme clé primaire et que nous voulons ajouter l'identifiant de l'article manuellement, nous avons défini auto_id
False pour désactiver cette fonctionnalité.
Après avoir ajouté tous les champs à l'objet schéma, notre objet schéma correspond aux entrées du tableau ci-dessus.
Définir l'index
Après avoir défini le schéma avec différents champs, y compris les métadonnées et les champs vectoriels pour les images et les données de synthèse, l'étape suivante consiste à préparer les paramètres de l'index. L'indexation est cruciale pour optimiser la recherche et l'extraction des vecteurs, en garantissant des performances de requête efficaces. Dans la section suivante, nous allons définir les paramètres d'indexation pour les champs vectoriels et scalaires spécifiés dans la collection.
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",
)
Une fois les paramètres d'index configurés et appliqués, Milvus est optimisé pour traiter les requêtes complexes sur les données vectorielles et scalaires. Cette indexation améliore les performances et la précision des recherches de similarité dans la collection, permettant une récupération efficace des articles basés sur les vecteurs d'image et les vecteurs de résumé. En tirant parti de l'indexation AUTOINDEX
pour les vecteurs denses, le SPARSE_INVERTED_INDEX
pour les vecteurs épars et la fonction INVERTED_INDEX
pour les scalaires, Milvus peut rapidement identifier et renvoyer les résultats les plus pertinents, ce qui améliore considérablement l'expérience globale de l'utilisateur et l'efficacité du processus de recherche de données.
Il existe de nombreux types d'indices et de métriques. Pour plus d'informations à leur sujet, vous pouvez vous reporter à Type d'index Milvus et Type de métrique Milvus.
Création d'une collection
Une fois le schéma et les index définis, nous créons une "collection" avec ces paramètres. Pour Milvus, une collection est comparable à une table dans une base de données relationnelle.
client.create_collection(
collection_name=collection_name,
schema=schema,
index_params=index_params,
)
Nous pouvons vérifier que la collection a été créée avec succès en la décrivant.
collection_desc = client.describe_collection(
collection_name=collection_name
)
print(collection_desc)
Autres considérations
Chargement de l'index
Lors de la création d'une collection dans Milvus, vous pouvez choisir de charger l'index immédiatement ou de le différer jusqu'à l'ingestion en masse de certaines données. En général, il n'est pas nécessaire de faire un choix explicite à ce sujet, car les exemples ci-dessus montrent que l'index est automatiquement construit pour toutes les données ingérées juste après la création de la collection. Cela permet d'effectuer des recherches immédiates dans les données ingérées. Cependant, si vous avez une insertion en masse importante après la création de la collection et que vous n'avez pas besoin de rechercher des données jusqu'à un certain point, vous pouvez différer la construction de l'index en omettant les paramètres index_params dans la création de la collection et construire l'index en appelant explicitement load après avoir ingéré toutes les données. Cette méthode est plus efficace pour construire l'index sur une grande collection, mais aucune recherche ne peut être effectuée avant d'appeler load().
Comment définir le modèle de données pour le multi-locataire ?
Le concept de locataires multiples est couramment utilisé dans les scénarios où une application ou un service logiciel unique doit servir plusieurs utilisateurs ou organisations indépendants, chacun disposant de son propre environnement isolé. Cette situation est fréquemment observée dans l'informatique en nuage, les applications SaaS (Software as a Service) et les systèmes de base de données. Par exemple, un service de stockage en nuage peut utiliser la multilocation pour permettre à différentes entreprises de stocker et de gérer leurs données séparément tout en partageant la même infrastructure sous-jacente. Cette approche maximise l'utilisation des ressources et l'efficacité tout en garantissant la sécurité et la confidentialité des données pour chaque locataire.
La façon la plus simple de différencier les locataires est d'isoler leurs données et leurs ressources les unes des autres. Chaque locataire dispose d'un accès exclusif à des ressources spécifiques ou partage des ressources avec d'autres pour gérer les entités Milvus telles que les bases de données, les collections et les partitions. Il existe des méthodes spécifiques alignées sur ces entités pour mettre en œuvre le multi-tenant Milvus. Vous pouvez vous référer à la page Milvus multi-tenancy pour plus d'informations.