Estrazione degli argomenti dei documenti con modelli linguistici di grandi dimensioni (LLM) e l’algoritmo di allocazione latente di Dirichlet (LDA)

Estrazione argomenti documenti LLM e algoritmo LDA

Una guida su come estrarre efficientemente argomenti da documenti grandi utilizzando Grandi Modelli Linguistici (LLM) e l’algoritmo di allocazione della distribuzione di Dirichlet latente (LDA).

Foto di Henry Be su Unsplash

Introduzione

Stavo sviluppando un’applicazione web per chattare con file PDF, in grado di elaborare documenti grandi, oltre 1000 pagine. Ma prima di iniziare una conversazione con il documento, volevo che l’applicazione desse all’utente un breve riassunto degli argomenti principali, in modo da facilitare l’interazione.

Un modo per farlo è riassumere il documento utilizzando LangChain, come mostrato nella sua documentazione. Il problema, però, è il costo computazionale elevato e, di conseguenza, 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, ad esempio con il metodo map-reduce. Una stima conservativa del costo utilizzando gpt-3.5 Turbo con un contesto di 4k supera 1$ per documento, solo per il riassunto. Anche utilizzando risorse gratuite, come l’API Unofficial HuggingChat, il numero di chiamate API richieste sarebbe un abuso. Quindi, avevo bisogno di un approccio diverso.

LDA alla riscossa

L’algoritmo di allocazione della distribuzione di Dirichlet latente era una scelta naturale per questa attività. Questo algoritmo prende un insieme di “documenti” (in questo contesto, un “documento” si riferisce a un pezzo di testo) e restituisce un elenco di argomenti per ogni “documento” insieme a un elenco di parole associate a ciascun argomento. Quello che è importante per il nostro caso è l’elenco di parole associate a ciascun argomento. Questi elenchi di parole codificano il contenuto del file, quindi possono essere inseriti nel LLM per ottenere un riassunto. Raccomando questo articolo per una spiegazione dettagliata dell’algoritmo.

Ci sono due considerazioni fondamentali da affrontare prima di ottenere un risultato di alta qualità: la scelta degli iperparametri per l’algoritmo LDA e la determinazione del formato dell’output. L’iperparametro più importante da considerare è il numero di argomenti, poiché ha la maggior influenza sul risultato finale. Per quanto riguarda il formato dell’output, un formato che ha funzionato molto bene è la lista puntata nidificata. In questo formato, ogni argomento è rappresentato come una lista puntata con sottoelementi che descrivono ulteriormente l’argomento. Perché funziona, penso che, utilizzando questo formato, il modello possa concentrarsi nell’estrazione del contenuto dalle liste senza la complessità di articolare paragrafi con connettori e relazioni.

Implementazione

Ho implementato il codice su Google Colab. Le librerie necessarie erano gensim per LDA, pypdf per l’elaborazione dei PDF, nltk per l’elaborazione delle parole e LangChain per i suoi modelli di prompt e la sua interfaccia con l’API di OpenAI.

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

Successivamente, ho definito una funzione di utilità, preprocess, per assistere nell’elaborazione del testo di input. Rimuove le stopwords e i token brevi.

def preprocess(testo, stop_words):    """    Tokenizza e elabora il testo di input, rimuovendo le stopwords e i token brevi.    Parametri:        testo (str): Il testo di input da elaborare.        stop_words (set): Un insieme di stopwords da rimuovere dal testo.    Returns:        list: Una lista di token preelaborati.    """    risultato = []    for token in simple_preprocess(testo, deacc=True):        if token not in stop_words and len(token) > 3:            risultato.append(token)    return risultato

La seconda funzione, get_topic_lists_from_pdf, implementa la parte LDA del codice. Accetta il percorso del file PDF, il numero di argomenti e il numero di parole per argomento e restituisce una lista. Ogni elemento in questa lista 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):    """    Estrae argomenti e le relative parole associate da un documento PDF utilizzando l'algoritmo Latent Dirichlet Allocation (LDA).    Parametri:        file (str): Il percorso del file PDF per l'estrazione degli argomenti.        num_topics (int): Il numero di argomenti da scoprire.        words_per_topic (int): Il numero di parole da includere per ogni argomento.    Ritorna:        list: Una lista di sottoliste di num_topics, ognuna contenente parole rilevanti per un argomento.    """    # Carica il file pdf    loader = PdfReader(file)    # Estrai il testo da ogni pagina in una lista. Ogni pagina è considerata un documento    documents= []    for page in loader.pages:        documents.append(page.extract_text())    # Pre-elabora i documenti    nltk.download('stopwords')    stop_words = set(stopwords.words(['inglese','spagnolo']))    processed_documents = [preprocess(doc, stop_words) for doc in documents]    # Crea un dizionario e un corpus    dictionary = corpora.Dictionary(processed_documents)    corpus = [dictionary.doc2bow(doc) for doc in processed_documents]    # Costruisci il modello LDA    lda_model = LdaModel(        corpus,         num_topics=num_topics,         id2word=dictionary,         passes=15        )    # Recupera gli argomenti e le relative parole    topics = lda_model.print_topics(num_words=words_per_topic)    # Memorizza ogni lista di parole da ogni argomento in una lista    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 prossima funzione, topics_from_pdf, richiama il modello LLM. Come detto in precedenza, il modello è stato configurato per formattare l’output come una lista annidata con segni di elenco puntato.

def topics_from_pdf(llm, file, num_topics, words_per_topic):    """    Genera prompt descrittivi per LLM basati su parole argomento estratte da un documento PDF.    Questa funzione prende in input l'output della funzione `get_topic_lists_from_pdf`,    che consiste in una lista di parole relative all'argomento per ogni argomento e     genera una stringa di output nel formato di una tabella dei contenuti.    Parametri:        llm (LLM): Un'istanza del Large Language Model (LLM) per generare risposte.        file (str): Il percorso del file PDF per l'estrazione delle parole relative all'argomento.        num_topics (int): Il numero di argomenti da considerare.        words_per_topic (int): Il numero di parole per argomento da includere.    Ritorna:        str: Una risposta generata dal modello linguistico basata sulle parole argomento fornite.    """    # Estrai argomenti e convertili in stringa    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"    # Crea il template    template_string = '''Descrivi l'argomento di ognuna delle {num_topics}         liste delimitate dalle virgolette con una semplice frase e scrivi anche         tre possibili sottotemi differenti. Le liste sono il risultato di un         algoritmo per la scoperta degli argomenti.        Non fornire un'introduzione o una conclusione, descrivi solo gli         argomenti. Non menzionare la parola "argomento" quando descrivi gli argomenti.        Utilizza il seguente template per la risposta.        1: <<<(frase che descrive l'argomento)>>>        - <<<(Frase che descrive il primo sottotema)>>>        - <<<(Frase che descrive il secondo sottotema)>>>        - <<<(Frase che descrive il terzo sottotema)>>>        2: <<<(frase che descrive l'argomento)>>>        - <<<(Frase che descrive il primo sottotema)>>>        - <<<(Frase che descrive il secondo sottotema)>>>        - <<<(Frase che descrive il terzo sottotema)>>>        ...        n: <<<(frase che descrive l'argomento)>>>        - <<<(Frase che descrive il primo sottotema)>>>        - <<<(Frase che descrive il secondo sottotema)>>>        - <<<(Frase che descrive il terzo sottotema)>>>        Liste: """{string_lda}""" '''    # Chiamata LLM    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, la lista di parole viene convertita in una stringa. Successivamente, viene creato un prompt utilizzando l’oggetto ChatPromptTemplate da LangChain; si nota che il prompt definisce la struttura per la risposta. Infine, la funzione chiama il modello chatgpt-3.5 Turbo. Il valore di ritorno è la risposta data dal modello LLM.

Ora è il momento di chiamare le funzioni. Prima impostiamo la chiave API. Questo articolo offre istruzioni su come ottenerne una.

openai_key = "sk-p..."llm = OpenAI(openai_api_key=openai_key, max_tokens=-1)

Successivamente, chiamiamo la funzione topics_from_pdf. Ho scelto i valori per il numero di argomenti e il numero di parole per argomento. Inoltre, ho selezionato un libro di dominio pubblico, La metamorfosi di Franz Kafka, per il test. Il documento è archiviato nel mio drive personale e scaricato utilizzando la libreria 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)

Il risultato viene visualizzato di seguito:

1: Esplorazione della trasformazione di Gregor Samsa e gli effetti sulla sua famiglia e gli inquilini- Comprensione della metamorfosi di Gregor- Esame delle reazioni della famiglia di Gregor e degli inquilini- Analisi dell'impatto della trasformazione di Gregor sulla sua famiglia2: Esame degli eventi legati alla scoperta della trasformazione di Gregor- Indagine sulle reazioni iniziali della famiglia di Gregor e degli inquilini- Analisi del comportamento della famiglia di Gregor e degli inquilini- Esplorazione dei cambiamenti fisici nell'ambiente di Gregor3: Analisi delle pressioni esercitate sulla famiglia di Gregor a causa della sua trasformazione- Esame dello sforzo finanziario sulla famiglia di Gregor- Indagine sugli effetti emotivi e psicologici sulla famiglia di Gregor- Esame dei cambiamenti nella dinamica familiare a causa della metamorfosi di Gregor4: Esame delle conseguenze della trasformazione di Gregor- Indagine sui cambiamenti fisici nell'ambiente di Gregor- Analisi delle reazioni della famiglia di Gregor e degli inquilini- Indagine sugli effetti emotivi e psicologici sulla famiglia di Gregor5: Esplorazione dell'impatto della trasformazione di Gregor sulla sua famiglia- Analisi dello sforzo finanziario sulla famiglia di Gregor- Esame dei cambiamenti nella dinamica familiare a causa della metamorfosi di Gregor- Indagine sugli effetti emotivi e psicologici sulla famiglia di Gregor6: Indagine sui cambiamenti fisici nell'ambiente di Gregor- Analisi delle reazioni della famiglia di Gregor e degli inquilini- Esame delle conseguenze della trasformazione di Gregor- Esplorazione dell'impatto della trasformazione di Gregor sulla sua famiglia

Il risultato è abbastanza buono e ci sono voluti solo pochi secondi! Ha estratto correttamente le idee principali dal libro.

Questo approccio funziona anche con i libri tecnici. Ad esempio, I fondamenti della geometria di David Hilbert (1899) (anche nel dominio pubblico):

1: Analisi delle proprietà delle forme geometriche e delle loro relazioni- Esplorazione degli assiomi della geometria- Analisi della congruenza degli angoli e delle linee- Indagine sui teoremi di geometria2: Studio del comportamento delle funzioni razionali e delle equazioni algebriche- Esame delle rette e dei punti di un problema- Indagine sui coefficienti di una funzione- Esame della costruzione di un integrale definito3: Indagine sulle proprietà di un sistema numerico- Esplorazione del dominio di un vero gruppo- Analisi del teorema dei segmenti uguali- Esame del cerchio di spostamento arbitrario4: Esame dell'area delle forme geometriche- Analisi delle rette parallele e dei punti- Indagine del contenuto di un triangolo- Esame delle misure di un poligono5: Esame dei teoremi di geometria algebrica- Esplorazione della congruenza dei segmenti- Analisi del sistema di moltiplicazione- Indagine dei teoremi validi di una chiamata6: Indagine sulle proprietà di una figura- Esame delle rette parallele di un triangolo- Analisi dell'equazione di unione dei lati- Esame dell'intersezione dei segmenti

Conclusioni

Combinando l’algoritmo LDA con LLM per l’estrazione degli argomenti da documenti di grandi dimensioni si ottengono 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, una lista puntata nidificata ha funzionato bene. Inoltre, il numero di argomenti e il numero di parole per argomento sono importanti per la qualità del risultato. Consiglio di provare diversi prompt, 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.

Grazie per la lettura. Per favore, fatemi sapere come si è concluso con i vostri documenti. Spero presto di scrivere sull’implementazione dell’applicazione che ho menzionato all’inizio.

LinkedIn: Antonio Jimenez Caballero

GitHub: a-jimenezc