Réponse aux questions à l'aide de Milvus et de Hugging Face
Un système de réponse aux questions basé sur la recherche sémantique consiste à trouver la question la plus similaire à partir d'un ensemble de paires question-réponse pour une question donnée. Une fois la question la plus similaire identifiée, la réponse correspondante de l'ensemble de données est considérée comme la réponse à la question. Cette approche s'appuie sur des mesures de similarité sémantique pour déterminer la similarité entre les questions et extraire les réponses pertinentes.
Ce tutoriel montre comment construire un système de réponse aux questions en utilisant Hugging Face comme chargeur de données et générateur d'intégration pour le traitement des données et Milvus comme base de données vectorielle pour la recherche sémantique.
Avant de commencer
Vous devez vous assurer que toutes les dépendances nécessaires sont installées :
pymilvus
un paquet python fonctionne avec le service de base de données vectorielles alimenté par Milvus ou Zilliz Cloud.datasets
Les paquets Hugging Face gèrent les données et utilisent les modèles :transformers
: une bibliothèque puissante fournit des tenseurs efficaces pour la recherche sémantique.torch
Les packages Hugging Face gèrent les données et utilisent les modèles : : une bibliothèque puissante fournit des outils efficaces de calcul tensoriel et d'apprentissage profond.
$ pip install --upgrade pymilvus transformers datasets torch
Si vous utilisez Google Colab, pour activer les dépendances qui viennent d'être installées, vous devrez peut-être redémarrer le runtime. (Cliquez sur le menu "Runtime" en haut de l'écran, et sélectionnez "Restart session" dans le menu déroulant).
Préparer les données
Dans cette section, nous chargerons des exemples de paires question-réponse provenant des ensembles de données Hugging Face. À titre de démonstration, nous ne prenons que des données partielles de la partie validation de SQuAD.
from datasets import load_dataset
DATASET = "squad" # Name of dataset from HuggingFace Datasets
INSERT_RATIO = 0.001 # Ratio of example dataset to be inserted
data = load_dataset(DATASET, split="validation")
# Generates a fixed subset. To generate a random subset, remove the seed.
data = data.train_test_split(test_size=INSERT_RATIO, seed=42)["test"]
# Clean up the data structure in the dataset.
data = data.map(
lambda val: {"answer": val["answers"]["text"][0]},
remove_columns=["id", "answers", "context"],
)
# View summary of example data
print(data)
Dataset({
features: ['title', 'question', 'answer'],
num_rows: 11
})
Pour générer des embeddings pour les questions, vous pouvez sélectionner un modèle d'embedding de texte à partir des modèles de Hugging Face. Dans ce tutoriel, nous utiliserons un petit modèle d'intégration de phrases , all-MiniLM-L6-v2, comme exemple.
from transformers import AutoTokenizer, AutoModel
import torch
MODEL = (
"sentence-transformers/all-MiniLM-L6-v2" # Name of model from HuggingFace Models
)
INFERENCE_BATCH_SIZE = 64 # Batch size of model inference
# Load tokenizer & model from HuggingFace Hub
tokenizer = AutoTokenizer.from_pretrained(MODEL)
model = AutoModel.from_pretrained(MODEL)
def encode_text(batch):
# Tokenize sentences
encoded_input = tokenizer(
batch["question"], padding=True, truncation=True, return_tensors="pt"
)
# Compute token embeddings
with torch.no_grad():
model_output = model(**encoded_input)
# Perform pooling
token_embeddings = model_output[0]
attention_mask = encoded_input["attention_mask"]
input_mask_expanded = (
attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
)
sentence_embeddings = torch.sum(
token_embeddings * input_mask_expanded, 1
) / torch.clamp(input_mask_expanded.sum(1), min=1e-9)
# Normalize embeddings
batch["question_embedding"] = torch.nn.functional.normalize(
sentence_embeddings, p=2, dim=1
)
return batch
data = data.map(encode_text, batched=True, batch_size=INFERENCE_BATCH_SIZE)
data_list = data.to_list()
Insérer des données
Nous avons maintenant des paires question-réponse prêtes avec des embeddings de questions. L'étape suivante consiste à les insérer dans la base de données vectorielle.
Nous devons d'abord nous connecter au service Milvus et créer une collection Milvus.
from pymilvus import MilvusClient
MILVUS_URI = "./huggingface_milvus_test.db" # Connection URI
COLLECTION_NAME = "huggingface_test" # Collection name
DIMENSION = 384 # Embedding dimension depending on model
milvus_client = MilvusClient(MILVUS_URI)
if milvus_client.has_collection(collection_name=COLLECTION_NAME):
milvus_client.drop_collection(collection_name=COLLECTION_NAME)
milvus_client.create_collection(
collection_name=COLLECTION_NAME,
dimension=DIMENSION,
auto_id=True, # Enable auto id
enable_dynamic_field=True, # Enable dynamic fields
vector_field_name="question_embedding", # Map vector field name and embedding column in dataset
consistency_level="Strong", # To enable search with latest data
)
Comme pour l'argument de MilvusClient
:
- Définir
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 avez des données à grande échelle, vous pouvez configurer un serveur Milvus plus performant sur docker ou kubernetes. Dans cette configuration, veuillez utiliser l'uri du serveur, par exemple
http://localhost:19530
, comme votreuri
. - Si vous souhaitez utiliser Zilliz Cloud, le service cloud 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.
Insérez toutes les données dans la collection :
milvus_client.insert(collection_name=COLLECTION_NAME, data=data_list)
{'insert_count': 11,
'ids': [450072488481390592, 450072488481390593, 450072488481390594, 450072488481390595, 450072488481390596, 450072488481390597, 450072488481390598, 450072488481390599, 450072488481390600, 450072488481390601, 450072488481390602],
'cost': 0}
Poser des questions
Une fois toutes les données insérées dans Milvus, nous pouvons poser des questions et voir quelles sont les réponses les plus proches.
questions = {
"question": [
"What is LGM?",
"When did Massachusetts first mandate that children be educated in schools?",
]
}
# Generate question embeddings
question_embeddings = [v.tolist() for v in encode_text(questions)["question_embedding"]]
# Search across Milvus
search_results = milvus_client.search(
collection_name=COLLECTION_NAME,
data=question_embeddings,
limit=3, # How many search results to output
output_fields=["answer", "question"], # Include these fields in search results
)
# Print out results
for q, res in zip(questions["question"], search_results):
print("Question:", q)
for r in res:
print(
{
"answer": r["entity"]["answer"],
"score": r["distance"],
"original question": r["entity"]["question"],
}
)
print("\n")
Question: What is LGM?
{'answer': 'Last Glacial Maximum', 'score': 0.956273078918457, 'original question': 'What does LGM stands for?'}
{'answer': 'coordinate the response to the embargo', 'score': 0.2120140939950943, 'original question': 'Why was this short termed organization created?'}
{'answer': '"Reducibility Among Combinatorial Problems"', 'score': 0.1945795714855194, 'original question': 'What is the paper written by Richard Karp in 1972 that ushered in a new era of understanding between intractability and NP-complete problems?'}
Question: When did Massachusetts first mandate that children be educated in schools?
{'answer': '1852', 'score': 0.9709997177124023, 'original question': 'In what year did Massachusetts first require children to be educated in schools?'}
{'answer': 'several regional colleges and universities', 'score': 0.34164726734161377, 'original question': 'In 1890, who did the university decide to team up with?'}
{'answer': '1962', 'score': 0.1931006908416748, 'original question': 'When were stromules discovered?'}