RAG Avanzado 01 Recuperación de Pequeño a Grande

RAG Avanzado 01 De lo Pequeño a lo Grande Recuperación y Transformación

Child-Parent RecursiveRetriever y Recuperación de Ventana de Oraciones con LlamaIndex

Los sistemas de RAG (Retrieval-Augmented Generation) recuperan información relevante de una base de conocimientos dada, lo que les permite generar información factual, relevante en términos de contexto y específica del dominio. Sin embargo, RAG enfrenta muchos desafíos cuando se trata de recuperar información relevante y generar respuestas de alta calidad. En esta serie de publicaciones/videos de blog, repasaré técnicas avanzadas de RAG con el objetivo de optimizar el flujo de trabajo de RAG y abordar los desafíos en los sistemas RAG ingenuos.

La primera técnica se llama recuperación de pequeño a grande. En los flujos de trabajo básicos de RAG, incrustamos un gran fragmento de texto para la recuperación, y este mismo fragmento de texto se utiliza para la síntesis. Pero a veces, la incrustación/recuperación de fragmentos de texto grandes puede sentirse subóptima. Puede haber mucho texto de relleno en un fragmento de texto grande que oculta la representación semántica, lo que lleva a una recuperación deficiente. ¿Qué pasaría si pudiéramos incrustar/recuperar en base a fragmentos más pequeños y más específicos, pero aún así tener suficiente contexto para que el LLM sintetice una respuesta? Específicamente, podría ser ventajoso desvincular los fragmentos de texto utilizados para la recuperación de los fragmentos de texto utilizados para la síntesis. El concepto detrás de la recuperación de pequeño a grande es utilizar fragmentos de texto más pequeños durante el proceso de recuperación y posteriormente proporcionar el fragmento de texto más grande al cual pertenece el texto recuperado al gran modelo de lenguaje.

Existen dos técnicas principales:

  1. Fragmentos de Hijo más Pequeños que Hacen Referencia a Fragmentos de Padre más Grandes: Recupere fragmentos más pequeños durante la recuperación primero, luego haga referencia a los IDs de los padres y devuelva los fragmentos más grandes.
  2. Recuperación de Ventana de Oraciones: Recupera una sola oración durante la recuperación y devuelve una ventana de texto alrededor de la oración.

En esta publicación de blog, profundizaremos en las implementaciones de estos dos métodos en LlamaIndex. ¿Por qué no lo hago en LangChain? Porque ya hay muchos recursos disponibles sobre RAG avanzado con LangChain. Prefiero no duplicar el esfuerzo. Además, uso tanto LangChain como LlamaIndex. Es mejor entender más herramientas y usarlas de manera flexible.

Puedes encontrar todo el código en esta notebook.

Revisión Básica de RAG

Comencemos con una implementación básica de RAG en 4 simples pasos:

Paso 1. Cargar Documentos

Usamos un PDFReader para cargar un archivo PDF y combinamos cada página del documento en un objeto Document.

loader = PDFReader()docs0 = loader.load_data(file=Path("llama2.pdf"))doc_text = "\n\n".join([d.get_content() for d in docs0])docs = [Document(text=doc_text)]

Paso 2. Analizar Documentos en Fragmentos de Texto (Nodos)

Luego dividimos el documento en fragmentos de texto, que se llaman “Nodos” en LlamaIndex, donde definimos el tamaño del fragmento como 1024. Los IDs de los nodos predeterminados son cadenas de texto aleatorias, posteriormente podemos dar formato a nuestro ID de nodo para seguir un cierto formato.

node_parser = SimpleNodeParser.from_defaults(chunk_size=1024)base_nodes = node_parser.get_nodes_from_documents(docs)for idx, node in enumerate(base_nodes):node.id_ = f"node-{idx}"

Paso 3. Seleccionar Modelo de Incrustación y LLM

Necesitamos definir dos modelos:

  • El modelo de incrustación se utiliza para crear incrustaciones vectoriales para cada uno de los fragmentos de texto. Aquí estamos llamando al modelo FlagEmbedding de Hugging Face.
  • LLM: la consulta del usuario y los fragmentos de texto relevantes se alimentan al LLM para que pueda generar respuestas con contexto relevante.

Podemos agrupar estos dos modelos juntos en el ServiceContext y usarlos más adelante en los pasos de indexación y consulta.

embed_model = resolve_embed_model(“local:BAAI/bge-small-en”)llm = OpenAI(model="gpt-3.5-turbo")service_context = ServiceContext.from_defaults(llm=llm, embed_model=embed_model)

Paso 4. Crear índice, recuperador y motor de consulta

Índice, recuperador y motor de consulta son tres componentes básicos para hacer preguntas sobre tus datos o documentos:

  • El índice es una estructura de datos que nos permite recuperar información relevante rápidamente para una consulta de usuario a partir de documentos externos. El índice del Almacenamiento Vectorial toma los fragmentos de texto/nodos y luego crea incrustaciones vectoriales del texto de cada nodo, listo para ser consultado por un LLM.
base_index = VectorStoreIndex(base_nodes, service_context=service_context)
  • El recuperador se utiliza para obtener y recuperar información relevante a partir de una consulta de usuario.
base_retriever = base_index.as_retriever(similarity_top_k=2)
  • El motor de consulta se construye sobre el índice y el recuperador, proporcionando una interfaz genérica para hacer preguntas sobre tus datos.
query_engine_base = RetrieverQueryEngine.from_args(    base_retriever, service_context=service_context)response = query_engine_base.query(    "¿Puedes decirme acerca de los conceptos clave para el ajuste fino de la seguridad?")print(str(response))

Método Avanzado 1: Fragmentos secundarios más pequeños que hacen referencia a fragmentos principales más grandes

En la sección anterior, utilizamos un tamaño fijo de fragmento de 1024 tanto para la recuperación como para la síntesis. En esta sección, vamos a explorar cómo utilizar fragmentos secundarios más pequeños para la recuperación y hacer referencia a fragmentos principales más grandes para la síntesis. El primer paso es crear fragmentos secundarios más pequeños:

Paso 1: Crear Fragmentos Secundarios más Pequeños

Para cada uno de los fragmentos de texto de tamaño 1024, creamos fragmentos de texto aún más pequeños:

  • 8 fragmentos de texto de tamaño 128
  • 4 fragmentos de texto de tamaño 256
  • 2 fragmentos de texto de tamaño 512

Agregamos el fragmento de texto original de tamaño 1024 a la lista de fragmentos de texto.

sub_chunk_sizes = [128, 256, 512]sub_node_parsers = [    SimpleNodeParser.from_defaults(chunk_size=c) for c in sub_chunk_sizes]all_nodes = []for base_node in base_nodes:    for n in sub_node_parsers:        sub_nodes = n.get_nodes_from_documents([base_node])        sub_inodes = [            IndexNode.from_text_node(sn, base_node.node_id) for sn in sub_nodes        ]        all_nodes.extend(sub_inodes)    # también agregar nodo original a los nodos    original_node = IndexNode.from_text_node(base_node, base_node.node_id)    all_nodes.append(original_node)all_nodes_dict = {n.node_id: n for n in all_nodes}

Cuando observamos todos los fragmentos de texto `all_nodes_dict`, podemos ver que muchos fragmentos más pequeños están asociados con cada uno de los fragmentos de texto originales, por ejemplo `node-0`. De hecho, todos los fragmentos más pequeños hacen referencia al fragmento grande en los metadatos con index_id que apunta al ID de índice del fragmento más grande.

Paso 2: Crear índice, recuperador y motor de consulta

  • Índice: Crea incrustaciones vectoriales de todos los fragmentos de texto.
vector_index_chunk = VectorStoreIndex(    all_nodes, service_context=service_context)
  • Recuperador: la clave aquí es usar un Recuperador Recursivo para recorrer las relaciones entre nodos y obtener nodos en función de las “referencias”. Este recuperador explorará de forma recursiva los enlaces de los nodos a otros recuperadores/motores de consulta. Para cualquier nodo recuperado, si alguno de los nodos es un Nodo de Índice, explorará el recuperador/motor de consulta vinculado y consultará eso.
vector_retriever_chunk = vector_index_chunk.as_retriever(similarity_top_k=2)retriever_chunk = RecursiveRetriever(    "vector",    retriever_dict={"vector": vector_retriever_chunk},    node_dict=all_nodes_dict,    verbose=True,)

Cuando hacemos una pregunta y recuperamos los fragmentos de texto más relevantes, en realidad recuperará el fragmento de texto con el id de nodo que apunta al fragmento padre y, por lo tanto, recuperará el fragmento padre.

  • Ahora, con los mismos pasos que antes, podemos crear un motor de consultas como una interfaz genérica para hacer preguntas sobre nuestros datos.
query_engine_chunk = RetrieverQueryEngine.from_args(    retriever_chunk, service_context=service_context)response = query_engine_chunk.query(    "¿Puedes decirme acerca de los conceptos clave para el ajuste de seguridad")print(str(response))

Método Avanzado 2: Recuperación de Ventanas de Frases

Para lograr una recuperación aún más detallada, en lugar de utilizar fragmentos de menor tamaño, podemos analizar los documentos en una sola frase por fragmento.

En este caso, las frases individuales serán similares al concepto de fragmento “hijo” que mencionamos en el método 1. La “ventana” de frases (5 frases a cada lado de la frase original) será similar al concepto de fragmento “padre”. En otras palabras, utilizamos las frases individuales durante la recuperación y pasamos la frase recuperada con la ventana de frases al LLM.

Paso 1: Crear analizador de nodos de ventana de frases

# crear el analizador de nodos de ventana de frases con configuración predeterminadanode_parser = SentenceWindowNodeParser.from_defaults(    window_size=3,    window_metadata_key="window",    original_text_metadata_key="original_text",)sentence_nodes = node_parser.get_nodes_from_documents(docs)sentence_index = VectorStoreIndex(sentence_nodes, service_context=service_context)

Paso 2: Crear motor de consultas

Cuando creamos el motor de consultas, podemos reemplazar la frase con la ventana de frases utilizando MetadataReplacementPostProcessor, para que la ventana de las frases se envíe al LLM.

query_engine = sentence_index.as_query_engine(    similarity_top_k=2,    # el objetivo predeterminado es `window` para que coincida con la configuración predeterminada del node_parser    node_postprocessors=[        MetadataReplacementPostProcessor(target_metadata_key="window")    ],)window_response = query_engine.query(    "¿Puedes decirme acerca de los conceptos clave para el ajuste de seguridad")print(window_response)

La Recuperación de Ventanas de Frases pudo responder la pregunta “¿Puedes decirme acerca de los conceptos clave para el ajuste de seguridad”:

Aquí puedes ver la frase real recuperada y la ventana de la frase, lo cual proporciona más contexto y detalles.

Conclusión

En este blog, exploramos cómo utilizar la recuperación de pequeño a grande para mejorar RAG, centrándonos en el Recuperador Recursivo Hijo-Padre y la Recuperación de Ventanas de Frases con LlamaIndex. En futuras publicaciones de blog, profundizaremos en otros trucos y consejos. ¡Estén atentos para más en este emocionante viaje hacia técnicas avanzadas de RAG!

Referencias:

Por Sophia Yang el 4 de noviembre de 2023

Conéctate conmigo en LinkedIn, Twitter y YouTube y únete al Club de Libros DS/ML ❤️

We will continue to update Zepes; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Inteligencia Artificial

Meta lanza Code Llama la última herramienta de IA para programar

En un increíble salto tecnológico, Meta ha lanzado su última creación, Code Llama, una herramienta impulsada por IA c...

Inteligencia Artificial

Un nuevo conjunto de datos de imágenes del Ártico impulsará la investigación en inteligencia artificial

El conjunto de datos, recopilado como parte de una misión científica de la Guardia Costera de Estados Unidos, se publ...

Inteligencia Artificial

Libre de Limitaciones La Validación de las Alucinaciones de la Máquina en el MoMA

El científico de datos principal en Refik Anadol Studio, Christian Burke, relata su experiencia trabajando en la exhi...

Inteligencia Artificial

Kinara presenta el procesador Ara-2 revolucionando el procesamiento de IA en dispositivos para un rendimiento mejorado

Kinara, una entidad pionera en inteligencia artificial energéticamente eficiente en el dispositivo, ha presentado su ...

Inteligencia Artificial

¿Qué es la Hiperpersonalización de IA? Ventajas, Estudios de Caso y Preocupaciones Éticas

Explora el concepto de hiperpersonalización de IA, sus mecanismos y estudios de caso. Aprende sobre sus ventajas e im...