Extracción de temas de documentos con Modelos de Lenguaje Grandes (LLM) y el algoritmo de Asignación Latente de Dirichlet (LDA)
Extracción de temas con LLM y LDA
Una guía sobre cómo extraer eficientemente temas de documentos grandes utilizando Modelos de Lenguaje Grandes (LLM) y el algoritmo de Asignación Latente de Dirichlet (LDA).

Introducción
Estaba desarrollando una aplicación web para chatear con archivos PDF, capaz de procesar documentos grandes, de más de 1000 páginas. Pero antes de iniciar una conversación con el documento, quería que la aplicación le diera al usuario un breve resumen de los temas principales, para que fuera más fácil iniciar la interacción.
Una forma de hacerlo es resumir el documento utilizando LangChain, como se muestra en su documentación. El problema, sin embargo, es el alto costo computacional y, por extensión, el costo monetario. Un documento de mil páginas contiene aproximadamente 250,000 palabras y cada palabra debe ser ingresada al LLM. Además, los resultados deben procesarse aún más, como con el método map-reduce. Una estimación conservadora del costo utilizando gpt-3.5 Turbo con un contexto de 4k es superior a 1$ por documento, solo para el resumen. Incluso cuando se utilizan recursos gratuitos, como la API no oficial de HuggingChat, la gran cantidad de llamadas API requeridas sería un abuso. Así que necesitaba un enfoque diferente.
LDA al Rescate
El algoritmo de Asignación Latente de Dirichlet fue una elección natural para esta tarea. Este algoritmo toma un conjunto de “documentos” (en este contexto, un “documento” se refiere a un fragmento de texto) y devuelve una lista de temas para cada “documento” junto con una lista de palabras asociadas a cada tema. Lo importante para nuestro caso es la lista de palabras asociadas a cada tema. Estas listas de palabras codifican el contenido del archivo, por lo que se pueden ingresar al LLM para solicitar un resumen. Recomiendo este artículo para obtener una explicación detallada del algoritmo.
Hay dos consideraciones clave que abordar antes de obtener un resultado de alta calidad: la selección de los hiperparámetros para el algoritmo LDA y la determinación del formato de la salida. El hiperparámetro más importante a considerar es el número de temas, ya que tiene el mayor impacto en el resultado final. En cuanto al formato de la salida, uno que funcionó bastante bien es la lista con viñetas anidadas. En este formato, cada tema se representa como una lista con viñetas con subentradas que describen aún más el tema. En cuanto a por qué esto funciona, creo que, al utilizar este formato, el modelo puede centrarse en extraer contenido de las listas sin la complejidad de articular párrafos con conectores y relaciones.
Implementación
Implementé el código en Google Colab. Las bibliotecas necesarias fueron gensim para LDA, pypdf para el procesamiento de PDF, nltk para el procesamiento de palabras y LangChain por sus plantillas de promt y su interfaz con la API de OpenAI.
- Cómo humanizar el contenido y superar el plagio de IA
- MLCommons presenta una nueva prueba de velocidad de referencia para...
- De los Datos a las Ideas Aprovechando la IA Generativa para el Anál...
import gensimimport nltkfrom gensim import corporafrom gensim.models import LdaModelfrom gensim.utils import simple_preprocessfrom nltk.corpus import stopwordsfrom pypdf import PdfReaderfrom langchain.chains import LLMChainfrom langchain.prompts import ChatPromptTemplatefrom langchain.llms import OpenAI
A continuación, definí una función de utilidad, preprocess, para ayudar en el procesamiento del texto de entrada. Elimina las palabras vacías y los tokens cortos.
def preprocess(text, stop_words): """ Tokeniza y procesa el texto de entrada, eliminando las palabras vacías y los tokens cortos. Parámetros: text (str): El texto de entrada a procesar. stop_words (set): Un conjunto de palabras vacías que se eliminarán del texto. Retorna: list: Una lista de tokens procesados. """ result = [] for token in simple_preprocess(text, deacc=True): if token not in stop_words and len(token) > 3: result.append(token) return result
La segunda función, get_topic_lists_from_pdf, implementa la parte del código de LDA. Acepta la ruta del archivo PDF, el número de temas y el número de palabras por tema, y devuelve una lista. Cada elemento en esta lista contiene una lista de palabras asociadas a cada tema. Aquí consideramos que cada página del archivo PDF es un “documento”.
def obtener_listas_de_temas_desde_pdf(archivo, num_temas, palabras_por_tema): """ Extrae temas y sus palabras asociadas de un documento PDF utilizando el algoritmo de Asignación Latente de Dirichlet (LDA). Parámetros: archivo (str): La ruta al archivo PDF para la extracción de temas. num_temas (int): El número de temas a descubrir. palabras_por_tema (int): El número de palabras a incluir por tema. Retorna: list: Una lista de sublistas de num_temas, cada una conteniendo palabras relevantes para un tema. """ # Cargar el archivo PDF cargador = PdfReader(archivo) # Extraer el texto de cada página en una lista. Cada página se considera un documento documentos= [] for pagina in cargador.pages: documentos.append(pagina.extract_text()) # Preprocesar los documentos nltk.download('stopwords') palabras_vacias = set(stopwords.words(['english','spanish'])) documentos_procesados = [preprocesar(doc, palabras_vacias) for doc in documentos] # Crear un diccionario y un corpus diccionario = corpora.Dictionary(documentos_procesados) corpus = [diccionario.doc2bow(doc) for doc in documentos_procesados] # Construir el modelo LDA modelo_lda = LdaModel( corpus, num_topics=num_temas, id2word=diccionario, passes=15 ) # Obtener los temas y sus palabras correspondientes temas = modelo_lda.print_topics(num_words=palabras_por_tema) # Almacenar cada lista de palabras de cada tema en una lista listas_temas = [] for tema in temas: palabras = tema[1].split("+") palabras_tema = [palabra.split("*")[1].replace('"', '').strip() for palabra in palabras] listas_temas.append(palabras_tema) return listas_temas
La siguiente función, temas_desde_pdf, invoca al modelo LLM. Como se mencionó anteriormente, el modelo fue configurado para formatear la salida como una lista anidada con viñetas.
def temas_desde_pdf(llm, archivo, num_temas, palabras_por_tema): """ Genera indicaciones descriptivas para LLM basado en palabras de temas extraídas de un documento PDF. Esta función toma la salida de la función `obtener_listas_de_temas_desde_pdf`, que consiste en una lista de palabras relacionadas con el tema para cada tema, y genera una cadena de salida en formato de tabla de contenido. Parámetros: llm (LLM): Una instancia del Modelo de Lenguaje Grande (LLM) para generar respuestas. archivo (str): La ruta al archivo PDF para extraer palabras relacionadas con el tema. num_temas (int): El número de temas a considerar. palabras_por_tema (int): El número de palabras por tema a incluir. Retorna: str: Una respuesta generada por el modelo de lenguaje basada en las palabras de tema proporcionadas. """ # Extraer temas y convertir a cadena lista_de_palabras_de_tema = obtener_listas_de_temas_desde_pdf(archivo, num_temas, palabras_por_tema) cadena_lda = "" for lista in lista_de_palabras_de_tema: cadena_lda += str(lista) + "\n" # Crear la plantilla cadena_plantilla = '''Describe el tema de cada una de las {num_temas} listas delimitadas por comillas dobles en una oración simple y también anota tres posibles subtemas diferentes. Las listas son resultado de un algoritmo para descubrimiento de temas. No proporciones una introducción o una conclusión, solo describe los temas. No menciones la palabra "tema" al describir los temas. Utiliza la siguiente plantilla para la respuesta. 1: <<<(oración que describe el tema)>>> - <<<(Frase que describe el primer subtema)>>> - <<<(Frase que describe el segundo subtema)>>> - <<<(Frase que describe el tercer subtema)>>> 2: <<<(oración que describe el tema)>>> - <<<(Frase que describe el primer subtema)>>> - <<<(Frase que describe el segundo subtema)>>> - <<<(Frase que describe el tercer subtema)>>> ... n: <<<(oración que describe el tema)>>> - <<<(Frase que describe el primer subtema)>>> - <<<(Frase que describe el segundo subtema)>>> - <<<(Frase que describe el tercer subtema)>>> Listas: """{cadena_lda}""" ''' # Llamada a LLM plantilla_indicación = ChatPromptTemplate.from_template(cadena_plantilla) cadena_respuesta = LLMChain(llm=llm, prompt=plantilla_indicación) respuesta = cadena_respuesta.run({ "cadena_lda" : cadena_lda, "num_temas" : num_temas }) return respuesta
En la función anterior, la lista de palabras se convierte en una cadena de texto. Luego, se crea un mensaje utilizando el objeto ChatPromptTemplate de LangChain; ten en cuenta que el mensaje define la estructura para la respuesta. Finalmente, la función llama al modelo chatgpt-3.5 Turbo. El valor de retorno es la respuesta proporcionada por el modelo LLM.
Ahora es momento de llamar a las funciones. Primero configuramos la clave de la API. Este artículo ofrece instrucciones sobre cómo obtener una.
openai_key = "sk-p..."llm = OpenAI(openai_api_key=openai_key, max_tokens=-1)
A continuación, llamamos a la función topics_from_pdf. Elijo los valores para el número de temas y el número de palabras por tema. Además, seleccioné un libro de dominio público, La Metamorfosis de Franz Kafka, para realizar pruebas. El documento se almacena en mi unidad personal y se descarga utilizando la biblioteca gdown.
!gdown https://drive.google.com/uc?id=1mpXUmuLGzkVEqsTicQvBPcpPJW0aPqdLfile = "./the-metamorphosis.pdf"num_topics = 6words_per_topic = 30summary = topics_from_pdf(llm, file, num_topics, words_per_topic)
El resultado se muestra a continuación:
1: Explorando la transformación de Gregor Samsa y sus efectos en su familia e inquilinos- Comprendiendo la metamorfosis de Gregor- Examinando las reacciones de la familia y los inquilinos de Gregor- Analizando el impacto de la transformación de Gregor en su familia2: Examinando los eventos que rodean el descubrimiento de la transformación de Gregor- Investigando las reacciones iniciales de la familia y los inquilinos de Gregor- Analizando el comportamiento de la familia y los inquilinos de Gregor- Explorando los cambios físicos en el entorno de Gregor3: Analizando las presiones ejercidas sobre la familia de Gregor debido a su transformación- Examinando la carga financiera en la familia de Gregor- Investigando los efectos emocionales y psicológicos en la familia de Gregor- Examinando los cambios en la dinámica familiar debido a la metamorfosis de Gregor4: Examinando las consecuencias de la transformación de Gregor- Investigando los cambios físicos en el entorno de Gregor- Analizando las reacciones de la familia y los inquilinos de Gregor- Investigando los efectos emocionales y psicológicos en la familia de Gregor5: Explorando el impacto de la transformación de Gregor en su familia- Analizando la carga financiera en la familia de Gregor- Examinando los cambios en la dinámica familiar debido a la metamorfosis de Gregor- Investigando los efectos emocionales y psicológicos en la familia de Gregor6: Investigando los cambios físicos en el entorno de Gregor- Analizando las reacciones de la familia y los inquilinos de Gregor- Examinando las consecuencias de la transformación de Gregor- Explorando el impacto de la transformación de Gregor en su familia
El resultado es bastante decente, ¡y solo tomó segundos! Extrajo correctamente las ideas principales del libro.
Este enfoque también funciona con libros técnicos. Por ejemplo, Los fundamentos de la geometría de David Hilbert (1899) (también de dominio público):
1: Analizando las propiedades de las formas geométricas y sus relaciones- Explorando los axiomas de la geometría- Analizando la congruencia de ángulos y líneas- Investigando los teoremas de la geometría2: Estudiando el comportamiento de las funciones racionales y las ecuaciones algebraicas- Examinando las líneas rectas y los puntos de un problema- Investigando los coeficientes de una función- Examinando la construcción de una integral definida3: Investigando las propiedades de un sistema numérico- Explorando el dominio de un grupo verdadero- Analizando el teorema de segmentos iguales- Examinando el círculo de desplazamiento arbitrario4: Examinando el área de las formas geométricas- Analizando las líneas paralelas y los puntos- Investigando el contenido de un triángulo- Examinando las medidas de un polígono5: Examinando los teoremas de la geometría algebraica- Explorando la congruencia de segmentos- Analizando el sistema de multiplicación- Investigando los teoremas válidos de una llamada6: Investigando las propiedades de una figura- Examinando las líneas paralelas de un triángulo- Analizando la ecuación de la unión de los lados- Examinando la intersección de segmentos
Conclusión
Combinar el algoritmo LDA con LLM para la extracción de temas de documentos grandes produce buenos resultados al tiempo que reduce significativamente tanto el costo como el tiempo de procesamiento. Hemos pasado de cientos de llamadas a la API a solo una y de minutos a segundos.
La calidad de la salida depende en gran medida de su formato. En este caso, una lista anidada con viñetas funcionó muy bien. Además, el número de temas y el número de palabras por tema son importantes para la calidad del resultado. Recomiendo probar diferentes mensajes, número de temas y número de palabras por tema para encontrar lo que funcione mejor para un documento dado.
El código se puede encontrar en este enlace.
Gracias por leer. Por favor, déjame saber cómo resultó con tus documentos. Espero pronto escribir sobre la implementación de la aplicación que mencioné al principio.
LinkedIn: Antonio Jiménez Caballero
GitHub: a-jimenezc