Dans la plupart des cas, les entreprises restent à un niveau de recherche très basique en interrogeant simplement directement les bases de données OLTP. Apache Lucene, Apache Solr, Elasticsearch, Sphinx, MeiliSearch, Typesense, etc. ont tendance à être relativement plus rapides et bien meilleurs pour traiter les requêtes complexes et travailler avec des filtres.
Les applications modernes intègrent souvent des solutions de recherche pour permettre aux utilisateurs d'accéder rapidement au contenu existant à la demande. Il est difficile d'imaginer toute autre fonctionnalité qui pourrait récupérer efficacement ces informations, faisant de la recherche une fonctionnalité essentielle dans la plupart des applications.
Options de solution de recherche sur mesure
Simultanément, même si le besoin d'interroger et de rechercher est très courant, différentes applications adoptent des approches radicalement différentes.
Dans la plupart des cas, les entreprises restent à un niveau de recherche très basique en interrogeant simplement directement les bases de données OLTP. Les requêtes peuvent ressembler à ceci : SELECT id, title FROM, entities WHERE, description LIKE '%bow% .
Cependant, plus fréquemment, ils sont représentés dans des structures de jointure de table complexes à plusieurs niveaux qui sont impossibles à lire, lentes et primitives. Ils sont incapables de comprendre le contexte, nécessitent de nombreuses personnalisations et sont très difficiles à mettre en œuvre correctement.
Bien qu'il soit possible d'améliorer le temps d'exécution des requêtes grâce aux vues matérialisées, à la mise en cache des requêtes et à d'autres techniques, la complexité supplémentaire entraîne un décalage considérable entre les mises à jour principales et les résultats de recherche cohérents.
Des alternatives plus efficaces aux solutions de recherche primitives basées sur la base de données peuvent constituer des moteurs de recherche open source tels que Apache Lucene, Apache Solr, Elasticsearch, Sphinx, MeiliSearch, Typesense, etc.
Celles-ci ont tendance à être relativement plus rapides et bien meilleures pour traiter les requêtes complexes et travailler avec des filtres. Mais une fois que ces moteurs de recherche sont comparés à des homologues comme Google Search ou DuckDuckGo, il devient clair que les solutions open source ne parviennent pas à créer un contexte de recherche et des modalités de requête appropriés - ils sont incapables de comprendre une requête si l'utilisateur fournit une demande de recherche vague.
Extraire le sens de la requête
Imaginez que vous ne vous souveniez tout simplement pas du nom de cet agrume jaune au goût acidulé ! Mais vous souhaitez rechercher dans l'application un article sur la façon de cultiver ce fruit mystérieux. Comment procédez-vous pour cette recherche ?
Votre question peut être "Comment faire pousser des agrumes aigres jaunes à l'intérieur". N'importe lequel des moteurs de recherche open source susmentionnés peut avoir beaucoup de mal à renvoyer des résultats pertinents pour cette requête, même si la base de données contient des articles sur la culture de « citrons ».
En effet, l'extraction du sens d'une requête est une tâche en langage naturel et il est peu probable qu'elle soit résolue sans composants d'IA. GPT-3 est bon dans cette tâche.
Offres OpenAI basé sur GPT-3 qui convertit le texte en langage naturel en un vecteur de nombres à virgule flottante. Un encastrement est essentiellement un espace de faible dimension dans lequel des vecteurs de grande dimension peuvent être traduits. Dans ce cas, le vecteur de grande dimension est du texte et le vecteur de faible dimension est un vecteur de sortie de taille fixe. La distance entre les vecteurs représente leur degré de corrélation. Plus la distance est petite, plus la corrélation est élevée. En redéfinissant le texte comme une forme vectorielle, la tâche est réduite à un problème de recherche ML classique.
Choisir le bon modèle
La conversion du texte du document en un vecteur représentatif peut se produire en arrière-plan, tandis que la vectorisation de la requête de recherche doit se produire pendant l'exécution. Il existe plusieurs familles de modèles GPT-3 proposées par OpenAI :
Des dimensions vectorielles plus élevées entraînent davantage d'informations intégrées et, par conséquent, des coûts plus élevés et des recherches plus lentes.
Les documents sont généralement longs et les requêtes sont généralement courtes et incomplètes. Par conséquent, la vectorisation de tout document diffère considérablement de la vectorisation de toute requête compte tenu de la densité et de la taille du contenu. OpenAI le sait, et propose donc deux modèles jumelés, -doc et -query :
Il est important de noter que la requête et le document doivent tous deux utiliser la même famille de modèles et avoir la même longueur de vecteur de sortie.
Exemple de jeu de données
Il peut être plus facile d'observer et de comprendre la puissance de cette solution de recherche à travers des exemples. Pour cet exemple, partons du qui contient des métadonnées sur environ 5 000 films de TMDb. Nous allons construire une solution de recherche basée uniquement sur les documents de films, pas sur les critiques.
L'ensemble de données contient de nombreuses colonnes, mais notre processus de vectorisation sera construit uniquement autour des colonnes de titre et de vue d'ensemble.
Exemple de fiche :
Title: Harry Potter and the Half-Blood Prince Overview: As Harry begins his sixth year at Hogwarts, he discovers an old book marked as 'Property of the Half-Blood Prince', and begins to learn more about Lord Voldemort's dark past.
Mappons l'ensemble de données en texte prêt à indexer :
Ce bloc de code génère une liste dont la taille est égale aux paramètres avec lesquels le modèle fonctionne, qui dans le cas de text-search-babbage-doc-001 est 2048.
Un processus d'intégration similaire doit être appliqué à tous les documents sur lesquels nous souhaitons effectuer une recherche :
La colonne combined_info_search contiendra une représentation vectorielle du Combined_text.
Et, étonnamment, c'est déjà ça ! Enfin, nous sommes prêts à effectuer un exemple de requête de recherche :
from openai.embeddings_utils import get_embedding, cosine_similarity def search_movies(df, query, n=3, pprint=True): embedding = get_embedding( query, engine="text-search-babbage-query-001" ) df["similarities"] = df.combined_info_search.apply(lambda x: cosine_similarity(x, embedding)) res = ( df.sort_values("similarities", ascending=False) .head(n) .combined_info ) if pprint: for r in res: print(r[:200]) print() return res res = search_movies(df, "movie about the wizardry school", n=3)
Voici les résultats que nous obtenons :
Title: Harry Potter and the Philosopher's StoneOverview: Harry Potter has lived under the stairs at his aunt and uncle's house his whole life. But on his 11th birthday, he learns he's a powerful wizard — with a place waiting for him at the Hogwarts School of Witchcraft and Wizardry. As he learns to harness his newfound powers with the help of the school's kindly headmaster, Harry uncovers the truth about his parents' deaths — and about the villain who's to blame. Title: Harry Potter and the Goblet of FireOverview: Harry starts his fourth year at Hogwarts, competes in the treacherous Triwizard Tournament and faces the evil Lord Voldemort. Ron and Hermione help Harry manage the pressure — but Voldemort lurks, awaiting his chance to destroy Harry and all that he stands for. Title: Harry Potter and the Prisoner of AzkabanOverview: Harry, Ron and Hermione return to Hogwarts for another magic-filled year. Harry comes face to face with danger yet again, this time in the form of an escaped convict, Sirius Black — and turns to sympathetic Professor Lupin for help.
L'aperçu de « » contient les mots « sorcellerie » et « école » et apparaît en premier dans le résultat de la recherche. Le deuxième résultat ne contient plus le mot 'école', mais conserve toujours des mots proches de 'sorcellerie', 'Triwizard'. Le troisième résultat ne contient qu'un synonyme de 'sorcellerie' — magie.
Il y a, bien sûr, une multitude d'autres films dans cette base de données qui présentent des écoles ou des sorciers (ou les deux), mais ceux ci-dessus sont les seuls qui nous sont retournés. C'est une preuve évidente que la solution de recherche fonctionne et a réellement compris le contexte de notre requête.
Nous avons utilisé le modèle de Babbage avec seulement 2048 paramètres. Davinci a six fois plus de paramètres (12 288) et peut donc être beaucoup plus performant en ce qui concerne les requêtes très complexes.
La solution de recherche peut parfois ne pas produire de sortie pertinente pour certaines requêtes. Par exemple, la requête "films sur les sorciers à l'école" produit :
Title: Harry Potter and the Philosopher's StoneOverview: Harry Potter has lived under the stairs at his aunt and uncle's house his whole life. But on his 11th birthday, he learns he's a powerful wizard — with a place waiting for him at the Hogwarts School of Witchcraft and Wizardry. As he learns to harness his newfound powers with the help of the school's kindly headmaster, Harry uncovers the truth about his parents' deaths — and about the villain who's to blame. Title: Dumb and Dumberer: When Harry Met LloydOverview: This wacky prequel to the 1994 blockbuster goes back to the lame-brained Harry and Lloyd's days as classmates at a Rhode Island high school, where the unprincipled principal puts the pair in remedial courses as part of a scheme to fleece the school. Title: Harry Potter and the Prisoner of AzkabanOverview: Harry, Ron and Hermione return to Hogwarts for another magic-filled year. Harry comes face to face with danger yet again, this time in the form of an escaped convict, Sirius Black — and turns to sympathetic Professor Lupin for help.
Que fait « Dumb and Dumberer: When Harry Met Lloyd » ici, vous vous demandez peut-être ? Heureusement, ce problème n'a pas été reproduit sur les paramètres avec plus de paramètres.
Calcul de la distance entre la requête et les documents
Le résultat de la recherche doit être composé de documents triés par ordre décroissant de pertinence. Pour ce faire, nous devons être conscients de la distance entre la requête en cours et chaque document. Plus la longueur est courte, plus la sortie est relativement pertinente. Ensuite, après une portée maximale définie, nous devrions cesser de considérer la pertinence des documents restants.
Dans l'exemple ci-dessus, nous avons utilisé pour calculer la distance en raison de la haute dimensionnalité de l'espace vectoriel. Avec le modèle de babbage, nous avons 2048 paramètres.
Les algorithmes de calcul de distance tendent à représenter cette similarité (différence) entre une requête et un document par un seul numéro. Cependant, nous ne pouvons pas compter sur la en raison de l — les distances seront trop similaires. En effet, la distance euclidienne devient très peu pratique au-delà d'environ sept dimensions - à ce stade, les distances tombent dans les mêmes seaux et deviennent presque identiques.
Si vous le souhaitez, vous pouvez consulter le référentiel résultant ici : Alternativement, vous pouvez jouer avec Google Colab .
Complexité temporelle
Nous avons utilisé l'approche de la force brute pour trier les documents. Déterminons : ● n : nombre de points dans l'ensemble de données d'entraînement ● d : dimensionnalité des données
La complexité du temps de recherche pour les solutions de force brute est O(n * d * n * log(n)) . Le paramètre d dépend du modèle (dans le cas de Babbage, il est égal à 2048) alors que nous avons un bloc O(nlog(n)) dû à l'étape de tri.
Il est essentiel de se rappeler à ce stade que les modèles plus petits sont plus rapides et moins chers. Par exemple, dans l'étape de calcul de la similarité des cas de recherche, le modèle Ada est deux fois plus rapide, tandis que le modèle Davinci est six fois plus lent.
Les calculs de similarité cosinus entre la requête et 4803 documents de 2048 dimensions ont pris 1260 ms sur mon M1 Pro. Dans l'implémentation actuelle, le temps nécessaire au calcul augmenterait de manière linéaire par rapport au nombre total de documents. Simultanément, cette approche prend en charge la parallélisation des calculs.
Alternatives à la solution de la force brute
Dans les solutions de recherche, les requêtes doivent être traitées aussi rapidement que possible. Et ce prix est généralement payé du côté de la formation et du temps de pré-cache. Nous pouvons utiliser des structures de données comme un arbre kd, r-tree ou ball tree. Considérez l'article de sur l'analyse de la complexité de calcul de ces méthodes : elles conduisent toutes à une complexité de calcul proche de O(k * log(n)) , où k est le nombre d'éléments que nous aimerions renvoyer dans un seul lot.
Les arbres Kd, les arbres à billes et les arbres r constituent des structures de données utilisées pour stocker et rechercher efficacement des points dans un espace à N dimensions, tels que nos vecteurs de sens.
Les arbres Kd et ball sont des structures de données arborescentes qui utilisent un schéma de partitionnement binaire itératif pour diviser l'espace en régions, chaque nœud de l'arbre représentant une sous-région. Les arbres Kd sont particulièrement efficaces pour rechercher des points dans une plage spécifique ou pour trouver le voisin le plus proche d'un point donné.
De même, les r-arbres sont également utilisés pour stocker des points dans un espace à N dimensions, cependant, ils sont beaucoup plus efficaces pour rechercher des points dans une région spécifique ou pour trouver tous les points à une certaine distance d'un point donné. Il est important de noter que les r-trees utilisent un schéma de partitionnement différent des kd trees et des ball trees ; ils divisent l'espace en "rectangles" plutôt qu'en partitions binaires.
Les implémentations d'arborescence sortent du cadre de cet article et différentes implémentations conduiront à des résultats de recherche différents.
Heure de la requête
L'inconvénient le plus important de la solution de recherche actuelle est peut-être que nous devons appeler une API OpenAI externe pour récupérer le vecteur d'incorporation de requête. Quelle que soit la rapidité avec laquelle notre algorithme est capable de trouver les voisins les plus proches, une étape de blocage séquentielle sera nécessaire.
Text-search-babbage-query-001 Number of dimensions: 2048 Number of queries: 100 Average duration: 225ms Median duration: 207ms Max duration: 1301ms Min duration: 176ms
Text-search-ada-query-002 Number of dimensions: 1536 Number of queries: 100 Average duration: 264ms Median duration: 250ms Max duration: 859ms Min duration: 215ms
Text-search-davinci-query-001 Number of dimensions: 12288 Number of queries: 100 Average duration: 379ms Median duration: 364ms Max duration: 1161ms Min duration: 271ms
Si nous prenons la médiane comme point de référence, nous pouvons voir que ada-002 est +28% plus lent et davinci-001 est +76% plus lent.
Limites de l'intégration de la recherche GPT-3
Se référant à , nous pouvons conclure que GPT-3 ne fournit pas une performance ou une qualité de sortie exceptionnelle et nécessite une dépendance à l'API externe qui est plutôt lente. GPT-3 a la capacité de prendre en charge des entrées allant jusqu'à 4096 jetons (environ 3072 mots), cependant, il n'y a pas de service de troncation disponible via l'API et tenter d'encoder du texte plus long que 4096 jetons entraînera une erreur. Ainsi, il est de la responsabilité de l'utilisateur - vous - de déterminer la quantité de texte qui peut réellement être encodée.
De plus, les coûts de formation sont relativement élevés avec OpenAI.
Alternativement, vous pouvez envisager d'essayer ou .
Recherche approximative du voisin le plus proche dans Elasticsearch
Elasticsearch 8.0 prend en charge une recherche efficace du plus proche voisin (ANN) qui peut être utilisée pour résoudre nos problèmes d'ampleur plus rapidement que n'importe quel KNN linéaire. Elasticsearch 8.0 utilise un algorithme ANN appelé Hierarchical Navigable Small World graphs (HNSW) pour organiser les vecteurs en graphiques basés sur la similarité. Testés sur un ensemble de données de 10 millions de vecteurs intégrés, nous avons atteint une performance impressionnante de 200 requêtes par seconde avec ANN sur une seule machine axée sur le calcul, alors que seulement 2 requêtes par seconde en utilisant KNN. Les deux ont été fournis par Elasticsearch.
Selon ElasticSearch et le , le coût de cette performance est de 5% du rappel. Cela signifie qu'environ 1 sur 10 des vecteurs les plus proches trouvés ne sont pas véritablement relatifs. Nous avons appliqué une approche hybride pour améliorer ce résultat : demander k+l au lieu de k et appliquer un filtrage pour supprimer les valeurs aberrantes. Malheureusement, il n'y a pas de capacités de pagination. Ainsi, cette approche ne fonctionnera que dans certains cas d'utilisation.
Comme vous l'aurez peut-être vu, GPT-3 Embeddings n'est pas la solution parfaite pour tous les problèmes de recherche en raison de sa complexité d'indexation, de son coût, ainsi que de la grande complexité de calcul de l'opération de recherche, même des approximations. Néanmoins, GPT-3 Embeddings reste un excellent choix pour tous ceux qui recherchent une colonne vertébrale puissante pour une solution de recherche avec des requêtes peu fréquentes et des exigences d'indexation modestes.
De plus, il convient également d'ajouter que Microsoft a récemment __ __que le moteur de recherche Bing utilise désormais la nouvelle version mise à jour de GPT 3.5, appelée "Prometheus" et développée initialement pour la recherche. Selon l'annonce, le nouveau modèle de langage Prometheus permet à Bing d'augmenter la pertinence, d'annoter plus précisément les extraits, de fournir des résultats plus récents, de comprendre la géolocalisation et d'améliorer la sécurité. Cela peut ouvrir de nouvelles possibilités d'utilisation du modèle de langage autorégressif pour les solutions de recherche, que nous garderons certainement à l'œil à l'avenir.