Извлечение темы документа с помощью больших языковых моделей (LLM) и алгоритма латентного разбиения Дирихле (LDA)

Тема документа извлекается с помощью LLM и алгоритма LDA.

Руководство по эффективному извлечению тем из больших документов с использованием больших языковых моделей (LLM) и алгоритма латентного распределения Дирихле (LDA).

Фото Henry Be на Unsplash

Введение

Я разрабатывал веб-приложение для общения с PDF-файлами, способное обрабатывать большие документы, состоящие более чем из 1000 страниц. Но прежде чем начать разговор с документом, я хотел, чтобы приложение давало пользователю краткое описание основных тем, чтобы было легче начать взаимодействие.

Один из способов сделать это – суммировать документ с использованием LangChain, как показано в его документации. Однако проблема заключается в высокой вычислительной стоимости и, соответственно, в денежных затратах. Тысячестраницы документа содержат примерно 250 000 слов, и каждое слово должно быть подано на вход LLM. Более того, результаты должны быть дополнительно обработаны, как с помощью метода map-reduce. Консервативная оценка стоимости при использовании gpt-3.5 Turbo с контекстом 4k составляет более 1 доллара за документ, только за резюме. Даже при использовании бесплатных ресурсов, таких как Unofficial HuggingChat API, количество требуемых вызовов API будет являться злоупотреблением. Поэтому мне понадобился другой подход.

LDA на помощь

Алгоритм латентного распределения Дирихле был естественным выбором для этой задачи. Этот алгоритм принимает набор “документов” (в этом контексте “документ” означает фрагмент текста) и возвращает список тем для каждого “документа” вместе с списком слов, связанных с каждой темой. Для нашего случая важен список слов, связанных с каждой темой. Эти списки слов кодируют содержимое файла, поэтому их можно подать на вход LLM для запроса резюме. Рекомендую эту статью для подробного объяснения алгоритма.

Перед тем, как получить качественный результат, необходимо решить две ключевые проблемы: выбор гиперпараметров для алгоритма LDA и определение формата вывода. Самый важный гиперпараметр для рассмотрения – количество тем, поскольку оно имеет наибольшее влияние на конечный результат. Что касается формата вывода, один из способов, которые работали довольно хорошо, – это вложенный маркированный список. В этом формате каждая тема представлена в виде маркированного списка с подэлементами, которые дополнительно описывают тему. Почему это работает? Я думаю, что, используя этот формат, модель может сконцентрироваться на извлечении содержимого из списков без сложности формулирования абзацев с соединителями и связями.

Реализация

Я реализовал код в Google Colab. Необходимые библиотеки: gensim для LDA, pypdf для обработки PDF, nltk для обработки слов и LangChain для его шаблонов запросов и интерфейса с OpenAI API.

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

Затем я определил вспомогательную функцию preprocess для обработки входного текста. Она удаляет стоп-слова и короткие токены.

def preprocess(text, stop_words):    """    Токенизирует и предварительно обрабатывает входной текст, удаляя стоп-слова и короткие    токены.    Параметры:        text (str): Входной текст для предварительной обработки.        stop_words (set): Множество стоп-слов, которые должны быть удалены из текста.    Возвращает:        list: Список предварительно обработанных токенов.    """    result = []    for token in simple_preprocess(text, deacc=True):        if token not in stop_words and len(token) > 3:            result.append(token)    return result

Вторая функция, get_topic_lists_from_pdf, реализует часть кода для LDA. Она принимает путь к файлу PDF, количество тем и количество слов на каждую тему и возвращает список. Каждый элемент в этом списке содержит список слов, связанных с каждой темой. Здесь мы считаем каждую страницу из файла PDF “документом”.

def get_topic_lists_from_pdf(file, num_topics, words_per_topic):    """    Извлекает темы и связанные с ними слова из PDF-документа с использованием     алгоритма латентного размещения Дирихле (LDA).    Параметры:        file (str): Путь к PDF-файлу для извлечения темы.        num_topics (int): Количество тем для обнаружения.        words_per_topic (int): Количество слов для включения в каждую тему.    Возвращает:        list: Список из num_topics подсписков, каждый содержащий соответствующие слова         для темы.    """    # Загрузка PDF-файла    loader = PdfReader(file)    # Извлечение текста с каждой страницы в список. Каждая страница считается документом    documents= []    for page in loader.pages:        documents.append(page.extract_text())    # Предобработка документов    nltk.download('stopwords')    stop_words = set(stopwords.words(['english','spanish']))    processed_documents = [preprocess(doc, stop_words) for doc in documents]    # Создание словаря и корпуса    dictionary = corpora.Dictionary(processed_documents)    corpus = [dictionary.doc2bow(doc) for doc in processed_documents]    # Построение модели LDA    lda_model = LdaModel(        corpus,         num_topics=num_topics,         id2word=dictionary,         passes=15        )    # Получение тем и связанных с ними слов    topics = lda_model.print_topics(num_words=words_per_topic)    # Хранение каждого списка слов из каждой темы в списке    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

Следующая функция, topics_from_pdf, вызывает модель LLM. Как было сказано ранее, модель была настроена на форматирование вывода в виде вложенного маркированного списка.

def topics_from_pdf(llm, file, num_topics, words_per_topic):    """    Создает описательные подсказки для LLM на основе слов тем, извлеченных из     PDF-документа.    Эта функция принимает выходные данные функции `get_topic_lists_from_pdf`,     которая состоит из списка слов, связанных с каждой темой, и     генерирует строку вывода в формате оглавления.    Параметры:        llm (LLM): Экземпляр модели Large Language Model (LLM) для генерации         ответов.        file (str): Путь к PDF-файлу для извлечения слов, связанных с темой.        num_topics (int): Количество тем для рассмотрения.        words_per_topic (int): Количество слов на тему для включения.    Возвращает:        str: Ответ, сгенерированный моделью языка на основе предоставленных         слов темы.    """    # Извлечение тем и преобразование в строку    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"    # Создание шаблона    template_string = '''Опишите тему каждого из {num_topics}         списков, заключенных в двойные кавычки, простым предложением и также         запишите три возможных разных подтемы. Списки являются результатом         алгоритма обнаружения темы.        Не предоставляйте введение или заключение, только опишите         темы. Не упоминайте слово "тема" при описании тем.        Используйте следующий шаблон для ответа.        1: <<<(предложение, описывающее тему)>>>        - <<<(Фраза, описывающая первую подтему)>>>        - <<<(Фраза, описывающая вторую подтему)>>>        - <<<(Фраза, описывающая третью подтему)>>>        2: <<<(предложение, описывающее тему)>>>        - <<<(Фраза, описывающая первую подтему)>>>        - <<<(Фраза, описывающая вторую подтему)>>>        - <<<(Фраза, описывающая третью подтему)>>>        ...        n: <<<(предложение, описывающее тему)>>>        - <<<(Фраза, описывающая первую подтему)>>>        - <<<(Фраза, описывающая вторую подтему)>>>        - <<<(Фраза, описывающая третью подтему)>>>        Списки: """{string_lda}""" '''    # Вызов 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

В предыдущей функции список слов преобразуется в строку. Затем создается подсказка с использованием объекта ChatPromptTemplate из LangChain; обратите внимание, что подсказка определяет структуру для ответа. Наконец, функция вызывает модель chatgpt-3.5 Turbo. Возвращаемое значение – ответ, предоставленный моделью LLM.

Теперь пришло время вызвать функции. Сначала устанавливаем ключ API. В этой статье предлагаются инструкции по его получению.

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

Затем мы вызываем функцию topics_from_pdf. Я выбрал значения для количества тем и количества слов на тему. Также я выбрал общественное достояние книгу “Превращение” Франца Кафки для тестирования. Документ хранится в моем личном диске и загружается с использованием библиотеки 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)

Результат отображается ниже:

1: Исследование превращения Грегора Самсы и его влияния на семью и жильцов - Понимание превращения Грегора - Изучение реакций семьи и жильцов Грегора - Анализ последствий превращения Грегора для его семьи2: Исследование событий, связанных с обнаружением превращения Грегора - Исследование первоначальных реакций семьи и жильцов Грегора - Анализ поведения семьи и жильцов Грегора - Изучение физических изменений в окружении Грегора3: Анализ давления, оказываемого на семью Грегора из-за его превращения - Исследование финансовой нагрузки на семью Грегора - Исследование эмоционального и психологического влияния на семью Грегора - Изучение изменений в семейной динамике из-за превращения Грегора4: Исследование последствий превращения Грегора - Исследование физических изменений в окружении Грегора - Анализ реакций семьи и жильцов Грегора - Исследование эмоционального и психологического влияния на семью Грегора5: Исследование влияния превращения Грегора на его семью - Анализ финансовой нагрузки на семью Грегора - Изучение изменений в семейной динамике из-за превращения Грегора - Исследование эмоционального и психологического влияния на семью Грегора6: Исследование физических изменений в окружении Грегора - Анализ реакций семьи и жильцов Грегора - Исследование последствий превращения Грегора - Исследование влияния превращения Грегора на его семью

Результат довольно приличный, и это заняло всего несколько секунд! Он правильно извлек основные идеи из книги.

Этот подход также работает с техническими книгами. Например, “Основания геометрии” Давида Гильберта (1899 года) (также в общественном достоянии):

1: Анализ свойств геометрических фигур и их взаимосвязей - Исследование аксиом геометрии - Анализ конгруэнтности углов и линий - Исследование теорем геометрии2: Изучение поведения рациональных функций и алгебраических уравнений - Исследование прямых линий и точек задачи - Исследование коэффициентов функции - Анализ построения определенного интеграла3: Исследование свойств числовой системы - Исследование области истинной группы - Анализ теоремы равных отрезков - Исследование окружности произвольного смещения4: Исследование площади геометрических фигур - Анализ параллельных линий и точек - Исследование содержания треугольника - Анализ мер многоугольника5: Исследование теорем алгебраической геометрии - Изучение конгруэнтности отрезков - Анализ системы умножения - Исследование допустимых теорем вызова6: Исследование свойств фигуры - Анализ параллельных линий треугольника - Исследование уравнения соединения сторон - Исследование пересечения отрезков

Заключение

Комбинирование алгоритма LDA с LLM для извлечения тем больших документов дает хорошие результаты, существенно снижая затраты и время обработки. Мы перешли от сотен вызовов API к одному и от минут к секундам.

Качество вывода в значительной степени зависит от его формата. В данном случае вложенный маркированный список подходит прекрасно. Также количество тем и количество слов на тему важны для качества результата. Рекомендую попробовать разные подсказки, количество тем и количество слов на тему, чтобы найти оптимальное значение для данного документа.

Код можно найти по этой ссылке.

Спасибо за чтение. Пожалуйста, дайте мне знать, как это сработало с вашими документами. Надеюсь, скоро написать о реализации приложения, о котором я упоминал в начале.

LinkedIn: Antonio Jimenez Caballero

GitHub: a-jimenezc