Bases de Données Vectorielles : Pourquoi et Comment les Utiliser avec le RAG
Regardez la vidéo!
ChatGPT est plutôt utile, mais on ne peut pas construire un produit complet avec seulement ça.
Cela fait plusieurs articles que je parle de RAG, et ce n'est pas pour rien. Comme on l’a expliqué dans notre livre et notre cours avec Towards AI, la récupération de documents ou de texte utile est nécessaire pour tous les produits basés sur des modèles de langage.
Il faut fournir des informations supplémentaires à votre modèle de langage pour qu’il soit réellement utile. Cela signifie qu’il doit être plus qu’un simple assistant Google. Il doit avoir un contexte à jour et des informations pertinentes à partager avec votre utilisateur. En gros, vous voulez votre propre modèle de langage sans le coût de l'entraînement. C’est pourquoi le RAG, ou la génération augmentée par récupération existe.
Mais que faire si votre base de données est immense, ou s'il y a une énorme quantité d’informations à comparer ? Comment s’assurer que la partie « récupération » de la chaîne ne nous ralentit pas ?
Pour commencer, il est important de savoir que lorsqu’on récupère des informations, on utilise un processus appelé indexation. Ainsi, toutes nos informations sont stockées dans une base de données, et on veut les « indexer » de la manière la plus efficace possible pour les récupérer plus tard. Voyons comment cela fonctionne. Au fait, j’assume ici que vous savez c’est quoi le RAG. Si ce n’est pas le cas, j’ai la vidéo parfaite pour vous!
Tout d’abord, dans les bases de données ou les requêtes de recherche classiques, les données sont stockées sous forme de lignes, avec des colonnes fournissant des informations supplémentaires sur le document, comme la date, les auteurs, etc. Avec une requête de l'utilisateur, qui est souvent une question, nous cherchions auparavant des correspondances exactes ou très proches. Cela a changé avec la révolution des grands modèles de langue. Au lieu de trouver seulement des correspondances précises, on utilise une base de données vectorielle. Notre base de données vectorielle est en fait l'ensemble de la base de données que nous avons alimentée dans notre modèle de langue pour qu’il en comprenne le sens des textes, ce qu’on appel des embeddings ou vecteur contextuel en français. Ces vecteur contextuel sont simplement de longues listes de nombres qui représentent le sens général de chaque partie de texte de notre base de données. Ensuite, pour chaque nouvelle requête, on la soumet à notre système de vecteurs contextuels et utilise cette liste de nombres pour comparer manuellement tous les vecteurs contextuels de notre base de données et récupérer les plus similaires. En bref, dans une base de données vectorielle, nous utilisons le contenu lui-même (recherche sémantique) pour obtenir du contenu similaire.
Mais on a toujours le problème du volume. Que se passe-t-il si nous avons des millions et des millions de vecteurs ? Est-ce que nous comparons encore chacun d’eux à notre vecteur de requête ? Pas vraiment ! C’est là qu’un bon index vectoriel est nécessaire.
Alors, qu'est-ce qu'un index vectoriel exactement ? Les vector index en anglais, ou index vectoriel, est une structure de données qui permet de rechercher et de récupérer un vecteur contextuel à partir d’un vaste ensemble de données. Il doit être à la fois rapide et précis. Comme on l’a vu, un vecteur contextuel est un objet converti en une liste de nombres. Chaque vecteur contextuel similaire est placé à proximité dans l’espace vectoriel. Comment sait-on que deux vecteurs contextuels sont similaires ? On utilise des mesures de distance comme la similarité cosinus (ou cosine similarity), le produit scalaire ou la distance euclidienne pour mesurer la distance et trouver les plus proches. Ce sont simplement des moyens de comparer de grandes listes de nombres, et cela nous donne un chiffre qui mesure cette comparaison.
Il existe plusieurs méthodes pour créer des index vectoriels. Regardons certaines des méthodes les plus populaires en détail.
La méthode la plus simple est l’index plat. Ici, nous convertissons le texte en un vecteur contextuel et le stockons tel quel. Nous utilisons une requête pour le rechercher et obtenir les informations les plus similaires. L'avantage est qu'il est très précis. Cependant, l'inconvénient est que cela rend la recherche très lente, car il faut comparer votre vecteur contextuel de requête avec toutes nos données.
La méthode suivante est l’index de hachage sensible à la localité (LSH). Dans cette approche, les vecteurs sont regroupés en « seaux » ou bucket en anglais, en fonction de leur similarité, à l’aide de fonctions de hachage sensibles à la localité (c’est-à-dire leur position spatiale dans l’espace vectoriel). Lorsqu'un vecteur de requête est introduit, il est haché de la même manière pour identifier le seau le plus similaire. Seuls les vecteurs de ce seau sont ensuite recherchés pour trouver une correspondance. Ce serait un peu comme une bibliothèque où les livres sont tous classés par fiction et genre. Si vous cherchez un livre spécifique, vous n’avez besoin de chercher que dans la catégorie correspondante, plutôt que dans toute la bibliothèque. Vous pouvez ensuite les ordonner par nom d’auteur, et vous trouvez tout en quelques secondes ! Cette méthode garantit que les éléments similaires sont regroupés, rendant le processus de récupération efficace et précis.
Un autre type d'indexation s'appelle l’index inversé. C'est similaire au LSH que nous venons de voir, mais au lieu de hachage, nous regroupons les vecteurs. Nous prenons un centroïde et calculons les groupes. Donc, nous calculons tous les vecteurs contextuels de notre ensemble de données et les regroupons en utilisant des algorithmes automatiques qui divisent nos vecteurs contextuels en groupes denses selon leurs similitudes. Dans notre exemple de bibliothèque, les livres de comédie pourraient tous être regroupés en fonction des rires ou d’autres traits similaires.
Un problème avec cette méthode est que lorsque la requête se trouve à la limite de plusieurs groupes, nous avons tendance à les chercher tous. Une version améliorée s'appelle l’index inversé avec quantification par produit (IVFPQ). Dans l'IVFPQ, chaque vecteur d’un groupe est ensuite décomposé en sous-vecteurs plus petits, et ces sous-vecteurs sont encodés en bits, un processus appelé quantification par produit, qui accélère vraiment le processus de comparaison. Nous comparons alors ces bits encodés plus petits au lieu des vecteurs contextuels d’origine pour plus d'efficacité.
Enfin, nous avons l'index HNSW (Hierarchical Navigable Small Worlds). C’est la méthode d'indexation la plus populaire, car elle est rapide et précise, réduisant la zone de recherche pour faciliter la recherche de ce dont vous avez besoin. Comment ?
Nous devons d’abord construire un graphe : vous commencez avec un graphe vide qui ressemble à ceci. Lorsqu'un document est ajouté, vous commencez par la couche supérieure et naviguez vers le bas, en vous connectant à des éléments similaires à chaque couche. Chaque nœud est relié à ses voisins les plus proches, créant une structure multi-couches.
Ensuite, la recherche : lorsque vous recherchez un vecteur, vous commencez à nouveau par la couche supérieure. Vous trouvez le voisin le plus proche et descendez à travers ses voisins les plus proches. Cela continue de couche en couche jusqu'à atteindre la couche inférieure, où vous trouvez les vecteurs les plus similaires
Ce processus élimine la grande majorité des données en descendant les couches à travers des vecteurs globalement similaires de manière progressive.
Nous savons maintenant à quoi ressemble un bon index. Récapitulons comment tout cela s’articule ensemble…
Tous nos textes ou données provenant de notre base de données sont divisés en petites parties. Nous appelons ces parties de texte nos nœuds. Ils sont essentiellement des segments individuels ou des unités des données d’origine, des fragments de texte dans notre cas simple ici. Ces nœuds sont ensuite convertis en vecteurs contextuels, des représentations numériques du texte, et stockés dans une base de données d’index vectoriel. Lorsque votre utilisateur pose une question, c’est notre requête, elle est convertie en un vecteur contextuel également. Grâce à la méthode d’indexation, la base de données recherche et renvoie les meilleurs résultats qui correspondent à la requête.
C’est une configuration basique, et il y a beaucoup de choses que vous pouvez faire pour l’améliorer. Une première amélioration rapide serait d’utiliser des techniques de prompting pour ajouter du contexte à la question de l’utilisateur et aider votre système de récupération à trouver l’information la plus pertinente possible, car certaines questions peuvent manquer de détails et être assez générales, ce qui donne lieu à des vecteurs contextuels déconnectés de ce que vous recherchez.
Ensuite, vous pouvez également implémenter d’autres méthodes de recherche avec des mots-clés ou une recherche hybride pour améliorer encore le système et aider votre index à récupérer les informations souhaitées. Il existe aussi d’autres nouvelles méthodes d’indexation comme le Multi-Scale Tree Graph (MSTG), que vous pouvez explorer. Mais tout ceci est pour de futures articles!
Bref, l’utilisation des index vectoriels dans les systèmes RAG permet de rendre la récupération d’informations pertinentes rapide, précise et évolutive. En convertissant le texte en vecteurs contextuels et en les stockant dans une base de données d’index vectoriel structurée, le système peut rapidement et efficacement trouver du contenu sémantiquement similaire.
Tout cela peut être fait avec d'excellentes plateformes de bases de données vectorielles comme ChromaDB, Milvus ou Pinecone en utilisant LlamaIndex, comme nous le voyons dans le livre “Building LLMs for Production” qu’on a sorti récemment avec Towards AI, qui est uniquement disponible en anglais.
J’espère que vous comprenez un peu mieux ce qui se passe en coulisses avec l’indexation et pourquoi il est important d’en implémenter une dans votre système de récupération de données!