Utiliser une base de données vectorielle pour rechercher des discours de la Maison-Blanche
Cet article a été initialement publié sur The New Stack.
La saison de campagne approche pour la campagne présidentielle américaine. C’est le bon moment pour revenir sur certains des discours prononcés par l’administration Biden au cours de ses deux premières années au pouvoir. Ne serait-il pas formidable de rechercher dans certaines transcriptions de discours afin d’en savoir plus sur les messages de la Maison-Blanche concernant certains sujets jusqu’à présent ?
Disons que nous voulons rechercher le contenu d’un discours. Comment ferions-nous cela ? Nous pourrions utiliser la recherche sémantique. La recherche sémantique est l’un des sujets les plus en vogue dans l’intelligence artificielle (IA) en ce moment. Elle est devenue plus importante avec la montée en popularité des applications de traitement du langage naturel (NLP) comme ChatGPT. Au lieu d’interroger GPT à répétition, ce qui est coûteux à la fois sur le plan économique et écologique, nous pouvons utiliser une base de données vectorielle pour mettre en cache les résultats (comme avec GPTCache).
Dans ce tutoriel, nous allons lancer localement une base de données vectorielle afin de pouvoir rechercher les discours de Biden de 2021 à 2022 par contenu. Le jeu de données que nous utilisons est le jeu de données “The White House (Speeches and Remarks) 12/10/2022”, que nous avons trouvé sur Kaggle et rendu disponible au téléchargement via Google Drive pour cet exemple. Un notebook pas à pas de ce tutoriel est disponible sur GitHub.
Avant de nous plonger dans le code, veuillez vous assurer de télécharger les prérequis. Nous avons besoin de quatre bibliothèques : PyMilvus, Milvus, Sentence-Transformers et gdown. Vous pouvez obtenir les bibliothèques nécessaires depuis PyPi en exécutant : pip3 install pymilvus==2.2.5 sentence-transformers gdown milvus.
Préparation du jeu de données des discours de la Maison-Blanche
Comme pour presque tout projet d’IA/ML basé sur des jeux de données réels, nous devons d’abord préparer les données. Nous utilisons gdown pour télécharger le jeu de données et zipfile pour l’extraire dans un dossier local. Après avoir exécuté le code ci-dessous, nous nous attendons à voir un fichier intitulé “The white house speeches.csv” dans un dossier intitulé “white_house_2021_2022”.
import gdown
url = 'https://drive.google.com/uc?id=10_sVL0UmEog7mczLedK5s1pnlDOz3Ukf'
output = './white_house_2021_2022.zip'
gdown.download(url, output)
import zipfile
with zipfile.ZipFile("./white_house_2021_2022.zip","r") as zip_ref:
zip_ref.extractall("./white_house_2021_2022")
Nous utilisons pandas pour charger et inspecter les données CSV.
import pandas as pd
df = pd.read_csv("./white_house_2021_2022/The white house speeches.csv")
df.head()
Lorsque nous examinons le head des données, que remarquez-vous ? La première chose que je remarque est que les données comportent quatre colonnes : un titre, une date et heure, un lieu et une colonne de discours. La deuxième chose est qu’il y a des valeurs nulles. Les valeurs nulles ne sont pas toujours un problème, mais elles le sont pour nos données.
Nettoyage du jeu de données
Les discours sans aucun contenu (valeurs nulles dans la colonne “Speech”) nous sont totalement inutiles. Supprimons nos valeurs nulles et réexaminons les données.
df = df.dropna()
df
Nous voyons maintenant qu’il existe en réalité un deuxième problème qui n’était pas immédiatement évident en regardant seulement le head des données. Si vous regardez la dernière entrée, vous verrez que cette entrée n’est qu’une heure. “12:18 P.M. EST” n’est guère un discours. Cela n’a pas de sens d’enregistrer cette entrée. Nous ne pouvons tirer aucune valeur de l’enregistrement d’un plongement vectoriel.
Débarrassons-nous de tous les discours qui sont inférieurs à une certaine longueur. Pour cet exemple, j’ai choisi 50, mais vous pouvez choisir ce qui vous semble pertinent. J’ai choisi 50 après avoir exploré de nombreux nombres différents. Si vous cherchez des transcriptions de discours entre 20 et 50 caractères, vous verrez que beaucoup sont des lieux ou des horaires avec quelques phrases aléatoires ajoutées.
cleaned_df = df.loc[(df["Speech"].str.len() > 50)]cleaned_df
Une fois les discours courts et sans substance traités, nous examinons à nouveau nos données pour voir un autre problème. Beaucoup de discours contiennent des valeurs \r\n - des retours à la ligne et des retours chariot. Ces caractères sont utilisés pour la mise en forme, mais ne contiennent aucune valeur sémantique. L’étape suivante de notre processus de nettoyage des données consiste à les supprimer.
cleaned_df["Speech"] = cleaned_df["Speech"].str.replace("\r\n", "")
cleaned_df
C’est beaucoup mieux. La dernière étape consiste à convertir la colonne “Date_time” dans un meilleur format pour la stocker dans notre base de données vectorielle et la comparer à d’autres datetimes. Nous utilisons la bibliothèque datetime pour convertir simplement ce format datetime en un format universel YYYY-MM-DD.
import datetime
# Convert the 'date' column to datetime objects
cleaned_df["Date_time"] = pd.to_datetime(cleaned_df["Date_time"], format="%B %d, %Y")
cleaned_df
Configurer une base de données vectorielle pour la recherche sémantique
Nos données sont maintenant propres et prêtes à être utilisées. L’étape suivante consiste à lancer une base de données vectorielle pour rechercher réellement les discours par leur contenu. Pour cet exemple, nous utilisons Milvus Lite, une version légère de Milvus que vous pouvez faire fonctionner sans Docker, Kubernetes ni avoir à gérer quelque fichier YAML que ce soit.
La première chose que nous faisons est de définir certaines de nos constantes. Nous avons besoin d’un nom de collection (pour la base de données vectorielle), du nombre de dimensions dans notre vecteur intégré, d’une taille de lot et d’un nombre qui définit combien de résultats nous voulons récupérer lors de la recherche. Cet exemple utilise le sentence transformer MiniLM L6 v2, qui produit des vecteurs d’embedding de 384 dimensions.
COLLECTION_NAME = "white_house_2021_2022"
DIMENSION = 384
BATCH_SIZE = 128
TOPK = 3
Nous utilisons le default_server de Milvus. Ensuite, nous utilisons le SDK PyMilvus pour nous connecter à notre serveur Milvus local. S’il existe dans notre base de données vectorielle une collection qui porte le même nom que celui que nous avons défini précédemment, nous supprimons cette collection afin de nous assurer de repartir d’une page blanche.
from milvus import default_server
from pymilvus import connections, utility
default_server.start()
connections.connect(host="127.0.0.1", port=default_server.listen_port)
if utility.has_collection(COLLECTION_NAME):
utility.drop_collection(COLLECTION_NAME)
Comme pour la plupart des autres bases de données, nous avons besoin d’un schéma pour charger les données dans la base de données vectorielle Milvus. Tout d’abord, nous définissons les champs de données que nous voulons que chaque objet possède. Heureusement que nous avons examiné les données plus tôt. Nous utilisons cinq champs de données, les quatre colonnes que nous avions précédemment et une colonne d’ID. Sauf que cette fois, nous utilisons l’embedding vectoriel du discours au lieu du texte réel.
from pymilvus import FieldSchema, CollectionSchema, DataType, Collection
# object should be inserted in the format of (title, date, location, speech embedding)
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name="title", dtype=DataType.VARCHAR, max_length=500),
FieldSchema(name="date", dtype=DataType.VARCHAR, max_length=100),
FieldSchema(name="location", dtype=DataType.VARCHAR, max_length=200),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=DIMENSION)
]
schema = CollectionSchema(fields=fields)
collection = Collection(name=COLLECTION_NAME, schema=schema)
La dernière chose que nous devons définir avant d’être prêts à charger des données dans la base de données vectorielle est l’index. Il existe de nombreux index et modèles vectoriels, mais pour cet exemple, nous utilisons l’index IVF_FLAT avec 128 clusters. Les applications plus importantes utilisent généralement plus de 128 clusters, mais nous n’avons de toute façon qu’un peu plus de 600 entrées. Pour notre distance, nous utilisons la norme L2 comme mesure. Une fois nos paramètres d’index définis, nous créons l’index dans notre collection et le chargeons pour l’utiliser.
index_params = {
"index_type": "IVF_FLAT",
"metric_type": "L2",
"params": {"nlist": 128},
}
collection.create_index(field_name="embedding", index_params=index_params)
collection.load()
Obtenir des embeddings vectoriels à partir de discours
Une grande partie de ce que nous avons vu jusqu’à présent s’applique lorsque l’on travaille avec presque n’importe quelle base de données. Nous avons nettoyé certaines données, lancé une instance de base de données et défini un schéma pour notre base de données. Outre la définition d’un index, une autre chose que nous devons faire pour les bases de données vectorielles en particulier est d’obtenir les embeddings.
Tout d’abord, nous récupérons le modèle de transformeur de phrases MiniLM L6 v2 comme mentionné ci-dessus. Ensuite, nous créons une fonction qui effectue une transformation sur les données et les insère dans la collection. Cette fonction prend un lot de données, obtient les embeddings des transcriptions de discours, crée un objet à insérer et l’insère dans la collection.
Pour le contexte, cette fonction effectue une mise à jour par lots. Dans cet exemple, nous insérons par lots 128 entrées à la fois. La seule transformation de données que nous effectuons lors de notre insertion consiste à transformer le texte du discours en embedding.
from sentence_transformers import SentenceTransformer
transformer = SentenceTransformer('all-MiniLM-L6-v2')
# expects a list of (title, date, location, speech)
def embed_insert(data: list):
embeddings = transformer.encode(data[3])
ins = [
data[0],
data[1],
data[2],
[x for x in embeddings]
]
collection.insert(ins)
Remplir votre base de données vectorielle
Avec une fonction qui crée des embeddings par lots et effectue les insertions terminée, nous sommes prêts à remplir la base de données. Pour cet exemple, nous parcourons chaque ligne de notre dataframe et l’ajoutons à une liste de listes que nous utilisons pour regrouper nos données en lots. Une fois que nous atteignons la taille de notre lot, nous appelons la fonction embed_insert et réinitialisons notre lot.
S’il reste des données dans le lot de données après la fin de la boucle, nous intégrons et insérons les données restantes. Enfin, pour terminer le remplissage de notre base de données vectorielle, nous appelons flush afin de nous assurer que la base de données est mise à jour et indexée.
data_batch = [[], [], [], []]
for index, row in cleaned_df.iterrows():
data_batch[0].append(row["Title"])
data_batch[1].append(str(row["Date_time"]))
data_batch[2].append(row["Location"])
data_batch[3].append(row["Speech"])
if len(data_batch[0]) % BATCH_SIZE == 0:
embed_insert(data_batch)
data_batch = [[], [], [], []]
# Embed and insert the remainder
if len(data_batch[0]) != 0:
embed_insert(data_batch)
# Call a flush to index any unsealed segments.
collection.flush()
Recherche sémantique de discours de la Maison-Blanche à partir de descriptions
Supposons que je souhaite trouver un discours dans lequel le président a parlé de l’impact de l’énergie renouvelable au National Renewable Energy Lab (NREL) et un discours dans lequel la vice-présidente et le premier ministre du Canada s’expriment. Je peux trouver les titres des discours les plus similaires prononcés par les membres de la Maison-Blanche en 2021-2022 en utilisant la base de données vectorielle que nous venons de créer.
Nous pouvons rechercher dans notre base de données vectorielle les discours les plus similaires à nos descriptions. Ensuite, tout ce que nous avons à faire est de convertir la description en embedding vectoriel en utilisant le même modèle que celui que nous avons utilisé pour obtenir les embeddings des discours, puis de rechercher dans la base de données vectorielle.
Une fois que nous avons converti les descriptions en un embedding vectoriel, nous utilisons la fonction search sur notre collection. Nous transmettons les embeddings comme données de recherche, indiquons le champ que nous recherchons, ajoutons quelques paramètres sur la façon d’effectuer la recherche, une limite pour le nombre de résultats et le champ que nous voulons retourner. Dans cet exemple, les paramètres de recherche que nous devons transmettre sont le type de métrique, qui doit être du même type que celui utilisé lors de la création de l’index (norme L2), et le nombre de clusters que nous voulons rechercher (en définissant nprobe sur 10).
import time
search_terms = ["The President speaks about the impact of renewable energy at the National Renewable Energy Lab.", "The Vice President and the Prime Minister of Canada both speak."]
# Search the database based on input text
def embed_search(data):
embeds = transformer.encode(data)
return [x for x in embeds]
search_data = embed_search(search_terms)
start = time.time()
res = collection.search(
data=search_data, # Embeded search value
anns_field="embedding", # Search across embeddings
param={"metric_type": "L2",
"params": {"nprobe": 10}},
limit = TOPK, # Limit to top_k results per search
output_fields=["title"] # Include title field in result
)
end = time.time()
for hits_i, hits in enumerate(res):
print("Title:", search_terms[hits_i])
print("Search Time:", end-start)
print("Results:")
for hit in hits:
print( hit.entity.get("title"), "----", hit.distance)
print()
Lorsque nous recherchons les phrases de cet exemple, nous nous attendons à obtenir une sortie comme l’image ci-dessous. Il s’agissait d’une recherche réussie, car les titres correspondent à ce que nous nous attendions à voir. La première description renvoie le titre d’un discours prononcé par le président Biden au NREL, et la deuxième description renvoie un titre reflétant un discours prononcé par la vice-présidente Harris et le premier ministre Trudeau.
Résumé
Dans ce tutoriel, nous avons appris à utiliser une base de données vectorielle pour effectuer une recherche sémantique dans les discours prononcés par l’administration Biden avant les élections de mi-mandat de 2022. La recherche sémantique nous permet de prendre un court texte et de rechercher du texte sémantiquement similaire, et pas seulement syntaxiquement similaire. Cela nous permet de rechercher une description générale d’un discours au lieu de rechercher un discours à partir de phrases ou de citations spécifiques. Pour la plupart d’entre nous, cela rend beaucoup plus facile la recherche de discours susceptibles de nous intéresser.
Continuer à lire

Introducing Zilliz Cloud Global Cluster: Region-Level Resilience for Mission-Critical AI
Zilliz Cloud Global Cluster delivers multi-region resilience, automatic failover, and fast global AI search with built-in security and compliance.

Build for the Boom: Why AI Agent Startups Should Build Scalable Infrastructure Early
Explore strategies for developing AI agents that can handle rapid growth. Don't let inadequate systems undermine your success during critical breakthrough moments.

Building RAG Pipelines for Real-Time Data with Cloudera and Milvus
explore how Cloudera can be integrated with Milvus to effectively implement some of the key functionalities of RAG pipelines.



