tutoriales.com

Construcción de un Sistema de Preguntas y Respuestas sobre Documentos con FAISS y Hugging Face

Descubre cómo implementar una arquitectura de Generación Aumentada por Recuperación (RAG) utilizando FAISS como base de datos vectorial y modelos de Hugging Face para procesar y responder consultas basadas exclusivamente en tus documentos privados.

Intermedio12 min de lectura7 views
Reportar error

🚀 Introducción al Procesamiento de Documentos con IA

En la era de la información masiva, las organizaciones y los investigadores se enfrentan a un desafío constante: ¿cómo extraer respuestas precisas de miles de páginas de texto sin perderse en el intento? Los modelos de lenguaje grande (LLMs) son herramientas potentes, pero tienen limitaciones críticas, como las alucinaciones y la falta de conocimiento sobre datos privados o específicos de una empresa. Aquí es donde entra en juego la arquitectura RAG (Retrieval-Augmented Generation), combinando la búsqueda eficiente de información con la potencia de generación de los modelos de transformers.

En este tutorial completo, aprenderás a construir un sistema de Preguntas y Respuestas (Q&A) de extremo a extremo que procesa documentos PDF o de texto plano, genera representaciones vectoriales (embeddings), las almacena en una base de datos ultrarrápida utilizando FAISS (Facebook AI Similarity Search) y utiliza un modelo de Hugging Face para formular respuestas contextuales y precisas.

💡 Consejo: Este enfoque es ideal para crear asistentes virtuales internos, analizadores de contratos legales o buscadores inteligentes para bases de conocimiento empresariales.

🛠️ Requisitos Previos y Configuración del Entorno

Antes de escribir código, asegúrate de tener instalado Python 3.8 o superior en tu sistema. Vamos a utilizar un entorno virtual para mantener nuestras dependencias limpias y organizadas.

Abre tu terminal y ejecuta los siguientes comandos para crear y activar el entorno virtual:

python -m venv venv_rag
source venv_rag/bin/activate

Ahora, instalaremos las bibliotecas necesarias. Utilizaremos transformers para los modelos de lenguaje, sentence-transformers para la vectorización eficiente, faiss-cpu (o faiss-gpu si cuentas con una tarjeta gráfica compatible) para la indexación y pypdf para la lectura de documentos PDF.

pip install transformers sentence-transformers faiss-cpu pypdf torch

Verifica que la instalación se haya realizado correctamente ejecutando un script rápido de prueba en Python:

import torch
import transformers
import sentence_transformers
import faiss

print("Todas las librerías se han importado correctamente.")

📚 Arquitectura General del Sistema RAG

Para entender qué vamos a programar, es fundamental visualizar el flujo de datos. Un sistema RAG consta de dos fases principales: la indexación (preparación del documento) y la recuperación y generación (respuesta a la consulta del usuario).

Fase 1: Indexación Documentos Fragmentación Embeddings Base de Datos Vectorial (FAISS) Fase 2: Consulta Pregunta Usuario Embedding Consulta Búsqueda Similitud Contexto Recuperado Modelo LLM Respuesta

Como se observa en el diagrama, el proceso transforma texto plano en vectores matemáticos que capturan el significado semántico, permitiendo buscar conceptos similares y no solo coincidencias exactas de palabras.


📝 Paso 1: Carga y Fragmentación (Chunking) de Documentos

El primer paso consiste en leer nuestro archivo de texto o PDF y dividirlo en fragmentos más pequeños (chunks). Los modelos de lenguaje tienen límites en la cantidad de tokens que pueden procesar de una sola vez, y los fragmentos más pequeños permiten recuperar información mucho más específica.

Crea un archivo llamado rag_system.py y añade el siguiente código para leer un documento PDF y dividirlo en bloques manejables:

from pypdf import PdfReader

def cargar_y_dividir_pdf(ruta_pdf, tamaño_chunk=500, solapamiento=50):
    lector = PdfReader(ruta_pdf)
    texto_completo = ""
    
    for pagina in lector.pages:
        texto_pagina = pagina.extract_text()
        if texto_pagina:
            texto_completo += texto_pagina + " "
            
    # Dividir el texto en chunks con solapamiento para no perder contexto
    chunks = []
    inicio = 0
    while inicio < len(texto_completo):
        fin = inicio + tamaño_chunk
        chunk = texto_completo[inicio:fin]
        chunks.append(chunk)
        inicio += (tamaño_chunk - solapamiento)
        
    print(f"Documento procesado: {len(chunks)} fragmentos creados.")
    return chunks

# Ejemplo de uso (asegúrate de tener un archivo 'documento.pdf' en tu directorio)
# chunks = cargar_y_dividir_pdf('documento.pdf')
⚠️ Advertencia: Un tamaño de chunk demasiado grande diluirá la relevancia de la búsqueda, mientras que uno muy pequeño puede fragmentar una idea clave dejándola sin el contexto necesario.

🧠 Paso 2: Generación de Embeddings Vectoriales

Una vez que tenemos nuestros fragmentos de texto, debemos convertirlos en vectores numéricos. Utilizaremos la biblioteca sentence-transformers con un modelo preentrenado optimizado para la tarea de representación semántica: all-MiniLM-L6-v2.

Este modelo es rápido, ligero y ofrece un rendimiento excepcional en tareas de búsqueda semántica.

from sentence_transformers import SentenceTransformer

def generar_embeddings(chunks):
    print("Cargando modelo de embeddings...")
    modelo = SentenceTransformer('all-MiniLM-L6-v2')
    
    print("Generando vectores para los fragmentos...")
    vectores = modelo.encode(chunks, show_progress_bar=True)
    
    return modelo, vectores

Los embeddings resultantes son matrices numéricas de alta dimensión (por ejemplo, 384 dimensiones para este modelo específico) que representan la semántica de cada fragmento de texto en un espacio vectorial.


🗄️ Paso 3: Indexación y Almacenamiento con FAISS

FAISS es una biblioteca desarrollada por Meta para la búsqueda eficiente de similitud y agrupamiento de vectores densos. Permite buscar entre millones de vectores en cuestión de milisegundos.

Vamos a crear el índice vectorial y a añadir nuestros embeddings:

import faiss
import numpy as np

def crear_indice_faiss(vectores):
    dimension = vectores.shape[1]
    # Creamos un índice basado en la distancia L2 (Euclidiana)
    indice = faiss.IndexFlatL2(dimension)
    
    # Añadimos los vectores al índice
    indice.add(np.array(vectores).astype('float32'))
    
    print(f"Índice FAISS creado con {indice.ntotal} vectores almacenados.")
    return indice
📌 Nota: Para bases de datos masivas (más de un millón de documentos), FAISS ofrece índices avanzados basados en cuantización como IndexIVFFlat o IndexHNSW que aceleran drásticamente las búsquedas a costa de una mínima pérdida de precisión.

🔍 Paso 4: Recuperación de Contexto y Generación de Respuestas

Con nuestro índice listo, el sistema ya puede recibir una pregunta, transformarla en un vector, buscar los fragmentos más similares en FAISS y pasárselos a un modelo generativo de Hugging Face.

Implementemos la función de consulta completa:

from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline

def configurar_generador():
    # Usaremos un modelo ligero de lenguaje para demostración
    nombre_modelo = "google/flan-t5-small"
    tokenizer = AutoTokenizer.from_pretrained(nombre_modelo)
    modelo = AutoModelForCausalLM.from_pretrained(nombre_modelo)
    
    generador = pipeline(
        "text2text-generation",
        model=modelo,
        tokenizer=tokenizer,
        max_length=256
    )
    return generador

def responder_pregunta(pregunta, indice, modelo_embeddings, chunks, generador, k=3):
    # 1. Vectorizar la pregunta
    vector_pregunta = modelo_embeddings.encode([pregunta])
    
    # 2. Buscar los k fragmentos más cercanos en FAISS
    distancias, indices = indice.search(np.array(vector_pregunta).astype('float32'), k)
    
    # 3. Recopilar el contexto de los fragmentos recuperados
    contexto = "\n".join([chunks[i] for i in indices[0]])
    
    # 4. Construir el prompt para el LLM
    prompt = f"Responde a la pregunta basándote únicamente en el siguiente contexto:\n\nContexto:\n{contexto}\n\nPregunta: {pregunta}\nRespuesta:"
    
    # 5. Generar la respuesta
    respuesta = generador(prompt, max_length=150, do_sample=False)
    
    return respuesta[0]['generated_text'], contexto

🧪 Script Completo de Ejecución

Para poner todo en marcha, unifiquemos los componentes en un script ejecutable que simule el flujo completo de trabajo:

import os
from pypdf import PdfReader
from sentence_transformers import SentenceTransformer
import faiss
import numpy as np
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM, pipeline

def pipeline_completo():
    # Simulamos un documento de texto si no hay PDF
    texto_prueba = """
    La inteligencia artificial (IA) es la inteligencia exhibida por máquinas. 
    En ciencia de la computación, un agente inteligente ideal es un sistema que percibe su entorno y toma acciones que maximizan sus posibilidades de éxito. 
    FAISS es una biblioteca creada por Facebook AI Research para la búsqueda eficiente de similitud de vectores.
    El aprendizaje automático es un subcampo de la IA que se centra en el desarrollo de sistemas que aprenden de los datos.
    """
    
    chunks = [c.strip() for c in texto_prueba.split(". ") if c.strip()]
    
    # 1. Embeddings
    modelo_emb = SentenceTransformer('all-MiniLM-L6-v2')
    vectores = modelo_emb.encode(chunks)
    
    # 2. FAISS
    indice = faiss.IndexFlatL2(vectores.shape[1])
    indice.add(np.array(vectores).astype('float32'))
    
    # 3. Generador
    print("Cargando modelo generativo...")
    tokenizer = AutoTokenizer.from_pretrained("google/flan-t5-small")
    modelo = AutoModelForSeq2SeqLM.from_pretrained("google/flan-t5-small")
    generador = pipeline("text2text-generation", model=modelo, tokenizer=tokenizer)
    
    # 4. Consulta
    pregunta = "¿Qué es FAISS?"
    vector_q = modelo_emb.encode([pregunta])
    _, indices = indice.search(np.array(vector_q).astype('float32'), k=2)
    
    contexto = " ".join([chunks[i] for i in indices[0]])
    prompt = f"Contexto: {contexto}\nPregunta: {pregunta}\nRespuesta:"
    
    resultado = generador(prompt, max_length=100)
    print(f"\nPregunta: {pregunta}")
    print(f"Respuesta generada: {resultado[0]['generated_text']}")

if __name__ == "__main__":
    pipeline_completo()

📊 Tabla Comparativa de Estrategias de Indexación en FAISS

Dependiendo del volumen de tus documentos, elegir el tipo de índice correcto marcará la diferencia entre una consulta instantánea y un cuellorazón de botella.

Tipo de Índice FAISSVelocidad de BúsquedaUso de MemoriaPrecisiónTamaño Recomendado de Dataset
IndexFlatL2Lenta (Búsqueda exacta)Alta100% (Exacta)< 100,000 vectores
IndexIVFFlatRápida (Aproximada)ModeradaAlta (~95%)100,000 - 1,000,000 vectores
IndexHNSWExtremadamente RápidaMuy AltaMuy Alta (~98%)Millones de vectores

❓ Preguntas Frecuentes (FAQ)

¿Puedo utilizar modelos en español en lugar de inglés? Sí, puedes reemplazar all-MiniLM-L6-v2 por modelos multilingües como paraphrase-multilingual-MiniLM-L12-v2, los cuales ofrecen un excelente rendimiento en español y otros idiomas.
¿Qué hago si mis documentos están en formato Word (.docx) o HTML? Simplemente debes utilizar bibliotecas adicionales como python-docx para archivos Word o BeautifulSoup para páginas web antes de aplicar la función de fragmentación de texto.
¿Es necesario contar con una GPU obligatoriamente? No para este tutorial. FAISS y los modelos pequeños como FLAN-T5 o MiniLM funcionan perfectamente en la CPU de cualquier ordenador moderno, aunque una GPU acelerará notablemente la fase de indexación de documentos masivos.

✨ Conclusión y Próximos Pasos

¡Felicidades! Has construido con éxito un sistema completo de Preguntas y Respuestas basado en recuperación vectorial utilizando FAISS, modelos de Hugging Face y la arquitectura RAG. Este sistema te permite consultar tus propios documentos privados con una precisión muy superior a la de un modelo de lenguaje genérico.

Como próximos pasos, te recomendamos explorar:

  • Integrar una interfaz de usuario interactiva utilizando Streamlit o Gradio.
  • Implementar persistencia en disco para guardar y cargar tu índice FAISS sin tener que recalcular los embeddings cada vez que inicies el programa.
  • Experimentar con modelos generativos más potentes como Llama-3 o Mistral si dispones de infraestructura con GPU.

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!