Respuesta a preguntas con Milvus y Hugging Face
Un sistema de respuesta a preguntas basado en la búsqueda semántica consiste en encontrar la pregunta más similar a partir de un conjunto de datos de pares pregunta-respuesta para una consulta determinada. Una vez identificada la pregunta más similar, la respuesta correspondiente del conjunto de datos se considera la respuesta a la consulta. Este enfoque se basa en medidas de similitud semántica para determinar la similitud entre las preguntas y recuperar las respuestas relevantes.
Este tutorial muestra cómo construir un sistema de respuesta a preguntas utilizando Hugging Face como cargador de datos y generador de incrustaciones para el procesamiento de datos y Milvus como base de datos vectorial para la búsqueda semántica.
Antes de empezar
Necesitas asegurarte de que todas las dependencias requeridas están instaladas:
pymilvus
: un paquete python funciona con el servicio de base de datos vectorial impulsado por Milvus o Zilliz Cloud.datasets
transformers
: los paquetes Hugging Face gestionan datos y utilizan modelos.torch
: una potente biblioteca proporciona herramientas eficientes de cálculo tensorial y aprendizaje profundo.
$ pip install --upgrade pymilvus transformers datasets torch
Si utiliza Google Colab, para habilitar las dependencias que acaba de instalar, es posible que tenga que reiniciar el tiempo de ejecución. (Haz clic en el menú "Tiempo de ejecución" en la parte superior de la pantalla y selecciona "Reiniciar sesión" en el menú desplegable).
Preparar los datos
En esta sección, cargaremos pares de pregunta-respuesta de ejemplo de los conjuntos de datos de Caras Abrazadas. Como demostración, sólo tomamos datos parciales de la división de validación 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
})
Para generar incrustaciones para las preguntas, puede seleccionar un modelo de incrustación de texto de Hugging Face Models. En este tutorial, utilizaremos como ejemplo un pequeño modelo de incrustación de sentencias all-MiniLM-L6-v2.
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()
Insertar datos
Ya tenemos listos los pares pregunta-respuesta con la incrustación de preguntas. El siguiente paso es insertarlos en la base de datos vectorial.
Primero tendremos que conectarnos al servicio Milvus y crear una colección 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
)
En cuanto al argumento de MilvusClient
:
- Establecer el
uri
como un archivo local, por ejemplo./milvus.db
, es el método más conveniente, ya que utiliza automáticamente Milvus Lite para almacenar todos los datos en este archivo. - Si tiene una gran escala de datos, puede configurar un servidor Milvus más eficiente en docker o kubernetes. En esta configuración, por favor utilice la uri del servidor, por ejemplo
http://localhost:19530
, como suuri
. - Si desea utilizar Zilliz Cloud, el servicio en la nube totalmente gestionado para Milvus, ajuste los
uri
ytoken
, que corresponden al Public Endpoint y a la clave Api en Zilliz Cloud.
Introduzca todos los datos en la colección:
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}
Hacer preguntas
Una vez insertados todos los datos en Milvus, podemos hacer preguntas y ver cuáles son las respuestas más cercanas.
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?'}