Una guida su come estrarre in modo efficiente argomenti da documenti di grandi dimensioni utilizzando Large Language Models (LLM) e l’algoritmo Latent Dirichlet Allocation (LDA).
introduzione
Stavo sviluppando un’applicazione web per chattare con file PDF, in grado di elaborare documenti di grandi dimensioni, superiori a 1000 pagine. Ma prima di iniziare una conversazione con il documento, volevo che l’applicazione fornisse all’utente un breve riassunto degli argomenti principali, in modo che fosse più semplice avviare l’interazione.
Un modo per farlo è riassumere il documento utilizzando LangChaincome mostrato nel suo documentazione. Il problema, tuttavia, è l’elevato costo computazionale e, per estensione, il costo monetario. Un documento di mille pagine contiene circa 250.000 parole e ogni parola deve essere inserita nel LLM. Inoltre, i risultati devono essere ulteriormente elaborati, come nel caso del metodo map-reduce. Una stima conservativa del costo utilizzando gpt-3.5 Turbo con contesto 4K è superiore a 1$ per documento, solo per il riepilogo. Anche quando si utilizzano risorse gratuite, come API HuggingChat non ufficialel’enorme numero di chiamate API richieste sarebbe un abuso. Quindi avevo bisogno di un approccio diverso.
LDA in soccorso
L’algoritmo Latent Dirichlet Allocation è stata una scelta naturale per questo compito. Questo algoritmo prende una serie di “documenti” (in questo contesto, un “documento” si riferisce a una porzione di testo) e restituisce un elenco di argomenti per ciascun “documento” insieme a un elenco di parole associate a ciascun argomento. Ciò che è importante nel nostro caso è l’elenco delle parole associate a ciascun argomento. Questi elenchi di parole codificano il contenuto del file, quindi possono essere forniti a LLM per richiedere un riepilogo. raccomando Questo articolo per una spiegazione dettagliata dell’algoritmo.
Ci sono due considerazioni chiave da affrontare prima di poter ottenere un risultato di alta qualità: selezionare gli iperparametri per l’algoritmo LDA e determinare il formato dell’output. L’iperparametro più importante da considerare è il numero di argomenti, poiché ha l’effetto più significativo sul risultato finale. Per quanto riguarda il formato dell’output, uno che ha funzionato abbastanza bene è l’elenco puntato nidificato. In questo formato, ogni argomento è rappresentato come un elenco puntato con voci secondarie che descrivono ulteriormente l’argomento. Per quanto riguarda il motivo per cui funziona, penso che, utilizzando questo formato, il modello possa concentrarsi sull’estrazione del contenuto dagli elenchi senza la complessità di articolare i paragrafi con connettori e relazioni.
Implementazione
Ho implementato il codice in Google Co. Le librerie necessarie erano gensim per LDA, pypdf per l’elaborazione di PDF, nltk per l’elaborazione di testi e LangChain per i suoi modelli di prompt e la sua interfaccia con l’API OpenAI.
import gensim
import nltk
from gensim import corpora
from gensim.models import LdaModel
from gensim.utils import simple_preprocess
from nltk.corpus import stopwords
from pypdf import PdfReader
from langchain.chains import LLMChain
from langchain.prompts import ChatPromptTemplate
from langchain.llms import OpenAI
Successivamente, ho definito una funzione di utilità, preprocessoper assistere nell’elaborazione del testo immesso. Rimuove le stop word e i token brevi.
def preprocess(text, stop_words):
"""
Tokenizes and preprocesses the input text, removing stopwords and short
tokens.Parameters:
text (str): The input text to preprocess.
stop_words (set): A set of stopwords to be removed from the text.
Returns:
list: A list of preprocessed tokens.
"""
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 seconda funzione, get_topic_lists_from_pdfimplementa il LDA porzione del codice. Accetto il percorso del file PDF, il numero di argomenti e il numero di parole per argomento e restituisce un elenco. Ogni elemento in questo elenco contiene un elenco di parole associate a ciascun argomento. Qui consideriamo ogni pagina del file PDF come un “documento”.
def get_topic_lists_from_pdf(file, num_topics, words_per_topic):
"""
Extracts topics and their associated words from a PDF document using the
Latent Dirichlet Allocation (LDA) algorithm.Parameters:
file (str): The path to the PDF file for topic extraction.
num_topics (int): The number of topics to discover.
words_per_topic (int): The number of words to include per topic.
Returns:
list: A list of num_topics sublists, each containing relevant words
for a topic.
"""
# Load the pdf file
loader = PdfReader(file)
# Extract the text from each page into a list. Each page is considered a document
documents= ()
for page in loader.pages:
documents.append(page.extract_text())
# Preprocess the documents
nltk.download('stopwords')
stop_words = set(stopwords.words(('english','spanish')))
processed_documents = (preprocess(doc, stop_words) for doc in documents)
# Create a dictionary and a corpus
dictionary = corpora.Dictionary(processed_documents)
corpus = (dictionary.doc2bow(doc) for doc in processed_documents)
# Build the LDA model
lda_model = LdaModel(
corpus,
num_topics=num_topics,
id2word=dictionary,
passes=15
)
# Retrieve the topics and their corresponding words
topics = lda_model.print_topics(num_words=words_per_topic)
# Store each list of words from each topic into a list
topics_ls = ()
for topic in topics:
words = topic(1).split("+")
topic_words = (word.split("*")(1).replace('"', '').strip() for word in words)
topics_ls.append(topic_words)
return topics_ls
La funzione successiva, argomenti_da_pdfinvoca il modello LLM. Come affermato in precedenza, al modello è stato richiesto di formattare l’output come elenco puntato nidificato.
def topics_from_pdf(llm, file, num_topics, words_per_topic):
"""
Generates descriptive prompts for LLM based on topic words extracted from a
PDF document.This function takes the output of `get_topic_lists_from_pdf` function,
which consists of a list of topic-related words for each topic, and
generates an output string in table of content format.
Parameters:
llm (LLM): An instance of the Large Language Model (LLM) for generating
responses.
file (str): The path to the PDF file for extracting topic-related words.
num_topics (int): The number of topics to consider.
words_per_topic (int): The number of words per topic to include.
Returns:
str: A response generated by the language model based on the provided
topic words.
"""
# Extract topics and convert to string
list_of_topicwords = get_topic_lists_from_pdf(file, num_topics,
words_per_topic)
string_lda = ""
for list in list_of_topicwords:
string_lda += str(list) + "\n"
# Create the template
template_string = '''Describe the topic of each of the {num_topics}
double-quote delimited lists in a simple sentence and also write down
three possible different subthemes. The lists are the result of an
algorithm for topic discovery.
Do not provide an introduction or a conclusion, only describe the
topics. Do not mention the word "topic" when describing the topics.
Use the following template for the response.
1: <<<(sentence describing the topic)>>>
- <<<(Phrase describing the first subtheme)>>>
- <<<(Phrase describing the second subtheme)>>>
- <<<(Phrase describing the third subtheme)>>>
2: <<<(sentence describing the topic)>>>
- <<<(Phrase describing the first subtheme)>>>
- <<<(Phrase describing the second subtheme)>>>
- <<<(Phrase describing the third subtheme)>>>
...
n: <<<(sentence describing the topic)>>>
- <<<(Phrase describing the first subtheme)>>>
- <<<(Phrase describing the second subtheme)>>>
- <<<(Phrase describing the third subtheme)>>>
Lists: """{string_lda}""" '''
# LLM call
prompt_template = ChatPromptTemplate.from_template(template_string)
chain = LLMChain(llm=llm, prompt=prompt_template)
response = chain.run({
"string_lda" : string_lda,
"num_topics" : num_topics
})
return response
Nella funzione precedente, l’elenco di parole viene convertito in una stringa. Quindi, viene creato un prompt utilizzando il file ChatPromptTemplate oggetto da LangChain; si noti che il prompt definisce la struttura della risposta. Infine, la funzione richiama il modello chatgpt-3.5 Turbo. Il valore restituito è la risposta data dal modello LLM.
Ora è il momento di chiamare le funzioni. Per prima cosa impostiamo la chiave API. Til suo articolo offre istruzioni su come ottenerne uno.
openai_key = "sk-p..."
llm = OpenAI(openai_api_key=openai_key, max_tokens=-1)
Successivamente, chiamiamo il argomenti_da_pdf funzione. Scelgo i valori per il numero di argomenti e il numero di parole per argomento. Inoltre, ho selezionato a dominio pubblico libro, La Metamorfosi di Franz Kafka, per prova. Il documento viene archiviato nel mio disco personale e scaricato utilizzando la libreria gdown.
!gdown https://drive.google.com/uc?id=1mpXUmuLGzkVEqsTicQvBPcpPJW0aPqdLfile = "./the-metamorphosis.pdf"
num_topics = 6
words_per_topic = 30
summary = topics_from_pdf(llm, file, num_topics, words_per_topic)
Il risultato è visualizzato di seguito:
1: Exploring the transformation of Gregor Samsa and the effects on his family and lodgers
- Understanding Gregor's metamorphosis
- Examining the reactions of Gregor's family and lodgers
- Analyzing the impact of Gregor's transformation on his family2: Examining the events surrounding the discovery of Gregor's transformation
- Investigating the initial reactions of Gregor's family and lodgers
- Analyzing the behavior of Gregor's family and lodgers
- Exploring the physical changes in Gregor's environment
3: Analyzing the pressures placed on Gregor's family due to his transformation
- Examining the financial strain on Gregor's family
- Investigating the emotional and psychological effects on Gregor's family
- Examining the changes in family dynamics due to Gregor's metamorphosis
4: Examining the consequences of Gregor's transformation
- Investigating the physical changes in Gregor's environment
- Analyzing the reactions of Gregor's family and lodgers
- Investigating the emotional and psychological effects on Gregor's family
5: Exploring the impact of Gregor's transformation on his family
- Analyzing the financial strain on Gregor's family
- Examining the changes in family dynamics due to Gregor's metamorphosis
- Investigating the emotional and psychological effects on Gregor's family
6: Investigating the physical changes in Gregor's environment
- Analyzing the reactions of Gregor's family and lodgers
- Examining the consequences of Gregor's transformation
- Exploring the impact of Gregor's transformation on his family
L’output è abbastanza decente e ci sono voluti solo pochi secondi! Ha estratto correttamente le idee principali dal libro.
Questo approccio funziona anche con i libri tecnici. Per esempio, I fondamenti della geometria di David Hilbert (1899) (anche di pubblico dominio):
1: Analyzing the properties of geometric shapes and their relationships
- Exploring the axioms of geometry
- Analyzing the congruence of angles and lines
- Investigating theorems of geometry2: Studying the behavior of rational functions and algebraic equations
- Examining the straight lines and points of a problem
- Investigating the coefficients of a function
- Examining the construction of a definite integral
3: Investigating the properties of a number system
- Exploring the domain of a true group
- Analyzing the theorem of equal segments
- Examining the circle of arbitrary displacement
4: Examining the area of geometric shapes
- Analyzing the parallel lines and points
- Investigating the content of a triangle
- Examining the measures of a polygon
5: Examining the theorems of algebraic geometry
- Exploring the congruence of segments
- Analyzing the system of multiplication
- Investigating the valid theorems of a call
6: Investigating the properties of a figure
- Examining the parallel lines of a triangle
- Analyzing the equation of joining sides
- Examining the intersection of segments
Conclusione
La combinazione dell’algoritmo LDA con LLM per l’estrazione di argomenti di documenti di grandi dimensioni produce buoni risultati riducendo significativamente sia i costi che i tempi di elaborazione. Siamo passati da centinaia di chiamate API a una sola e da minuti a secondi.
La qualità dell’output dipende molto dal suo formato. In questo caso, un elenco puntato nidificato ha funzionato perfettamente. Inoltre, il numero di argomenti e il numero di parole per argomento sono importanti per la qualità del risultato. Consiglio di provare diversi suggerimenti, numero di argomenti e numero di parole per argomento per trovare ciò che funziona meglio per un determinato documento.
Il codice potrebbe essere trovato in questo link.
Fonte: towardsdatascience.com