Книга: Анализ данных с LLM. Текст, таблицы, изображения и аудио
Назад: 3. Библиотека OpenAI для Python
Дальше: 5. Анализ структурированных данных

4. Анализ текстовых данных

В этой главе

• Классификация текста.

• Извлечение информации.

• Кластеризация документов.

Текстовые данные используются повсеместно и содержат ценную информацию. Пример таких данных — газетные статьи, электронные письма, отзывы или книга, которую вы читаете. Однако до недавнего времени анализировать текст с по­мощью компьютера было сложно. В конце концов, в отличие от формальных языков наподобие Python, естественный язык изначально не был рассчитан на то, чтобы компьютеры могли легко его обрабатывать. Новейшее поколение языковых моделей позволяет для решения многих популярных задач выполнять анализ текста почти так же хорошо, как это сделал бы человек. В некоторых случаях было показано, что производительность языковых моделей в области анализа и генерации текста в среднем даже превосходит возможности людей [1].

В этой главе мы рассмотрим, как использовать большие языковые модели для анализа текста. В определенном смысле анализ текстовых данных — весьма естественное применение языковых моделей. Они обучались на больших объемах текста, и их можно использовать непосредственно для анализа текста (то есть не обращаясь к внешним инструментам для фактического анализа данных). В этой главе описываются несколько популярных видов анализа текста: классификация документов, извлечение данных из текста и кластеризация — объединение документов в группы на основе смыслового сходства. Для каждого из этих сценариев использования мы рассмотрим пример кода и обсудим варианты и дополнительные возможности.

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

4.1. Подготовка

Сначала убедитесь, что ваша система правильно настроена для работы с примерами проектов. В примерах этой главы используется серия моделей GPT от OpenAI, доступ к которым осуществляется через библиотеку Python от OpenAI. Мы подробно рассматривали ее в главе 3. Удостоверьтесь, что выполнили инструкции из главы 3 и имеете возможность запускать примеры кода.

ПРЕДУПРЕЖДЕНИЕ Библиотека Python от OpenAI быстро меняется. Код, представленный в этой главе, был протестирован в версии 1.29 этой библиотеки, но, возможно, он не будет работать с другими версиями.

Помимо библиотеки OpenAI, мы будем пользоваться популярной библиотекой pandas. Она широко применяется для работы с табличными данными (именно в этом формате у нас будет выполняться ввод и вывод данных). Мы будем работать только с базовой функциональностью этой библиотеки и разбирать соответствующие команды по мере их появления в коде. Убедитесь, что библиотека pandas установлена (например, попробуйте выполнить команду import pandas в интерпретаторе Python). В противном случае установите ее, введя в терминале следующую команду:

pip install pandas==2.2

Наконец, для работы с последним разделом главы вам понадобятся алгоритмы кластеризации из библиотеки scikit-learn. Чтобы установить соответствующую ее версию, наберите в терминале команду:

pip install scikit-learn==1.3

В следующих разделах содержится код трех мини-проектов, в которых языковые модели используются для анализа текста. Вводить код вручную не нужно — его можно найти по ссылкам, размещенным на сайте книги, в разделе дополнительных материалов для этой главы. Конечно, вы можете выполнять код на собственных данных, но в книге есть несколько наборов, которые используются в примерах (ссылки на наборы также есть на сайте книги). А теперь пришло время применить языковые модели для классификации текста!

4.2. Классификация

Итак, вы строите планы на субботний вечер и думаете, не посмотреть ли новую часть любимой кинофраншизы. Но стоит ли тратить время? В ваших лентах в социальных сетях появляется все больше комментариев друзей (и их друзей), которые делятся своими впечатлениями от фильма. Эти отзывы можно просмотреть вручную, читая каждый, чтобы лучше понять, каких мнений больше: положительных или отрицательных. Вот только у кого есть на это время? Не могут ли языковые модели помочь автоматизировать эту задачу?

Вообще-то, могут. Перед нами классический случай, одна из самых типичных задач по обработке текста: есть текст и его нужно классифицировать, сопоставив с категорией из фиксированного набора вариантов. В данном случае текст для классификации — это отзыв о фильме. Его нужно определить как положительный (то есть автор отзыва считает, что фильм отличный и его стоит посмотреть!) или отрицательный («Cэкономьте деньги!»). Значит, у нас есть две категории. В табл. 4.1 приведены фрагменты нескольких примеров отзывов с соответствующими метками классов. Отзыв, восхваляющий фильм как «хорошо снятый», явно положительный, тогда как отзыв, описывающий его как «явно слабый, дешевый», — отрицательный. Эти и другие отзывы можно найти в соответствующем файле, доступном по ссылке Reviews.csv, размещенной на сайте книги, в разделе дополнительных материалов к этой главе.

Таблица 4.1. Фрагменты отзывов о фильмах и соответствующие метки классов

Отзыв

Класс

Прежде всего этот фильм — кусочек реальности, мастерски воплощенный художественно

Положительный

Переименованный «Банды Инкорпорейтед» — откровенно слабая, дешевая мелодрама о гангстерах

Отрицательный

Это лишь один из многих сценариев использования классификации текста.

Еще один пример: представьте, что вы пытаетесь разобраться во входящих электронных письмах. Не правда ли, было бы замечательно автоматически классифицировать их на основе содержания (например, используя пользовательские категории, такие как «Работа», «Хобби», «Уход за детьми» и т.д.)? И снова перед нами задача классификации текста, на этот раз с несколькими категориями.

И последний пример: представьте, что вы создаете сайт, который позволяет пользователям оставлять текстовые комментарии в свободной форме. Конечно, вы не хотите показывать потенциально оскорбительные комментарии, и хорошо бы их автоматически отфильтровывать. Опять же, это означает, что текстовые комментарии классифицируются как относящиеся к одной из двух категорий («Оскорбительные» и «Не оскорбительные»). Теперь посмотрим, как легко языковые модели справляются с каждым из этих сценариев.

4.2.1. Обзор

Здесь мы сосредоточимся на разделении отзывов о фильмах (или, по сути, любых отзывов) на положительные («Отличный фильм!») и отри­цательные («Оставайтесь дома!»). Для этого используем языковые модели OpenAI. Предположим, что мы собрали необходимые отзывы и поместили в файл на диске. Разработанный нами код будет перебирать все отзывы, классифицировать каждый с помощью языковой модели и возвращать результат классификации для каждого отзыва.

Но как классифицировать отзывы? С помощью библиотеки Python от OpenAI, представленной в главе 3. Для каждого отзыва, который нужно классифицировать, сначала сгенерируем промпт. Он описывает задачу для языковой модели. В случае нашего примера эта задача такова: модель должна отнести отзыв к одной из двух категорий («Положительный» или «Отрицательный»). Например, рассмотрим следующий промпт:

В этом промпте содержатся: отзыв для классификации , вопрос, описывающий задачу классификации , и окончательное утверждение, описывающее желаемый формат вывода . Мы составим промпты такого типа для каждого отзыва, отправим промпт языковой модели и (надеюсь) получим обратно один из двух возможных ответов («Положительный» или «Отрицательный»). На рис. 4.1 показана общая схема процесса классификации для каждого отзыва.

Рис. 4.1. Для каждого отзыва мы генерируем промпт, который содержит отзыв вместе с инструкциями, описывающими задачу классификации. Получив промпт в качестве входных данных, языковая модель выводит для отзыва метку класса

4.2.2. Составление промптов

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

Напоминание: что такое шаблон промпта

Шаблоны промптов упоминались в главе 1. Шаблон промпта — это текст, который содержит метки-заполнители. Подставляя вместо заполнителей реальный текст, мы получаем промпт, который можно отправить языковой модели. Мы также говорим, что создается экземпляр промпта по шаблону, если его можно получить путем подстановки значений в метки-заполнители шаблона.

Тестовый промпт, рассмотренный в предыдущем подразделе, является экземпляром следующего шаблона промпта:

Шаблон содержит только одну метку-заполнитель: текст отзыва для классификации . Для каждого отзыва эта метка-заполнитель заменяется фактическим текстом отзыва. Кроме того, мы даем модели инструкцию, что делать с текстом отзыва (проверить его тональность: положительный он или отрицательный) и определяем формат вывода . Последний шаг важен, ведь способов обозначить тональность отзыва бывает множество: например, использовать для положительного отзыва букву «П», а для отрицательного — «О» или дать более развернутый ответ, скажем «Отзыв положительный». Если мы явно не укажем языковой модели, что нужно использовать определенный формат вывода, то она может выбрать любой из этих вариантов! В нашем сценарии в конечном счете нужно будет агрегировать результаты классификации, чтобы узнать мнение большинства (нравится ли фильм большинству людей?), а агрегировать результаты каждого отзыва намного проще, если все классификации соблюдают один формат вывода.

Приведенная ниже функция генерирует промпт для заданного отзыва (указанного как входной параметр text), используя описанный шаблон:

def create_prompt(text):

    task = 'Отзыв положительный или отрицательный?'

    answer_format = 'Ответ ("Положительный"/"Отрицательный")'

    return f'{text}\n{task}\n{answer_format}:'

Функция возвращает готовый промпт — это конкретный экземпляр шаблона, заполненный данными из отзыва.

4.2.3. Вызов модели

Далее отправим сгенерированные промпты языковой модели, чтобы получить решение. А именно, используем модель GPT-4o компании OpenAI — последнюю модель на момент написания книги. Поскольку это одна из чат-моделей OpenAI, оптимизированная для многоэтапного взаимодействия с пользователями, то для связи с ней мы используем эндпоинт chat.completions. Как подробно рассказывалось в главе 3, этот эндпоинт ожидает в качестве входных данных историю предыдущих сообщений (в дополнение к конкретному имени модели). Здесь у нас есть только одно предыдущее «сообщение»: промпт. Мы классифицируем его как сообщение пользователя, побуждая модель решить любую задачу, описанную в сообщении. Например, мы можем отправлять промпты языковой модели и собирать ответы с помощью следующего фрагмента кода (при условии, что параметр prompt содержит ранее сгенерированный текст промпта):

import openai

client = openai.OpenAI()

 

response = client.chat.completions.create(

    model='gpt-4o',

    messages=[

        {'role':'user', 'content':prompt}

        ]

    )

Однако использовать этот код напрямую проблематично. Модели GPT от OpenAI размещены онлайн и доступны удаленно. Из-за этого могут возникнуть сбои при попытке достичь соответствующего эндпоинта: например, из-за временного разрыва соединения. В связи с этим при вызове модели рекомендуется разрешать выполнение нескольких повторных попыток. В частности, при обработке больших наборов данных, требующих множества последовательных вызовов моделей OpenAI, шансы, что как минимум один вызов завершится неудачей, возрастают. Вместо того чтобы прерывать вычисления, выводя сообщение об исключении, лучше подождать несколько секунд, прежде чем делать следующую попытку. Ниже приведена завершенная версия предыдущего кода — функция, которая вызывает языковую модель с автоматическими повторными попытками:

import openai

client = openai.OpenAI()

 

def call_llm(prompt):

    for nr_retries in range(1, 4):

        try:

            response = client.chat.completions.create(

                model='gpt-4o',

                messages=[

                    {'role':'user', 'content':prompt}

                    ]

                )

            return response.choices[0].message.content

        except:

            time.sleep(nr_retries * 2)

    raise Exception('Не удается выполнить запрос к модели OpenAI!')

Функция call_llm позволяет выполнить до трех повторных попыток с увеличивающимся периодом задержки между ними. Эта задержка реализуется путем вызова функции time.sleep (с использованием модуля time из Python-библиотеки) всякий раз при возникновении исключения (указывающего, например, на временный разрыв соединения). После трех повторных попыток функция завершается исключением (пессимистично предполагая, что, какая бы проблема ни мешала нам связаться с OpenAI, она не будет решена в ближайшее время). При успешном вызове функция каждый раз возвращает соответствующий результат.

4.2.4. Полный рабочий код для классификации данных

Пришло время все объединить! В листинге 4.1 показан код, соответствующий процессу классификации, который мы обсуждали в предыдущем подразделе. Кроме того, этот код содержит функцию для генерации промптов и функцию для вызова языковой модели .

Листинг 4.1. Классификация входного текста по тональности отзыва  (положительный, отрицательный)

import argparse                              

import openai

import pandas as pd

import time

 

client = openai.OpenAI()

 

def create_prompt(text):                     

    """Сгенерировать промпт для классификации по тональности.

 

    Аргументы:

        text: классифицируй текст.

 

    Выводит:

        Входные данные для LLM.

    """

    task = 'Отзыв положительный или отрицательный?'

    answer_format = 'Ответ ("Положительный"/"Отрицательный")'

    return f'{text}\n{task}\n{answer_format}:'

 

def call_llm(prompt):                        

    """Выполнить запрос к большой языковой модели и вернуть ответ.

 

    Аргументы:

        prompt: введите запрос к языковой модели.

 

    Выводит:

        Ответ языковой модели.

    """

    for nr_retries in range(1, 4):

        try:

            response = client.chat.completions.create(

                model='gpt-4o',

                messages=[

                    {'role':'user', 'content':prompt}

                    ]

                )

            return response.choices[0].message.content

        except:

            time.sleep(nr_retries * 2)

    raise Exception('Не удается выполнить запрос к модели OpenAI!')

 

def classify(text):                          

    """Классифицировать входной текст.

 

    Аргументы:

        text: присвоить данному тексту метку класса.

 

    Выводит:

        Имя класса.

    """

    prompt = create_prompt(text)

    label = call_llm(prompt)

    return label

 

if __name__ == '__main__':    

 

    parser = argparse.ArgumentParser()       

    parser.add_argument('file_path', type=str, help='Путь к входному файлу')

    args = parser.parse_args()

 

    df = pd.read_csv(args.file_path)         

    df['class'] = df['text'].apply(classify)

    statistics = df['class'].value_counts()  

    print(statistics)

    df.to_csv('result.csv')

Сначала обсудим библиотеки, используемые в этом листинге . Мы будем повторно использовать их в следующих проектах, поэтому имеет смысл рассмотреть их подробнее (и понять, зачем они здесь нужны). Мы собираемся запускать код из командной строки, указывая в качестве аргументов соответствующие параметры (например, путь к входным данным). В модуле argparse есть удобные средства для описания и считывания подобных аргументов, переданных через командную строку. Далее нам нужна библиотека openai (см. главу 3), чтобы вызывать языковую модель OpenAI из Python. Библиотека pandas поддерживает стандартные операции с табличными данными. Конечно, в рамках этой главы табличные данные нас мало интересуют. Однако мы будем хранить текстовые документы и связанные метаданные в виде строк в таблицах, поэтому будет удобно использовать библиотеку pandas. Наконец, как обсуждалось ранее, мы используем модуль time для реализации повторных попыток в случае задержки при вызове языковой модели.

4.2.5. Классификация документов

Процесс классификации одного текстового документа объединяет две функции, которые мы обсуждали выше. При наличии входного текста для классификации код сначала создает соответствующий промпт (вызов функции create_prompt), а затем генерирует подходящий ответ, вызывая языковую модель (вызов функции call_llm). Предполагается, что в результате пользователь получит метку класса.

Теперь объединим все действия . Эта часть кода выполняется при вызове модуля Python из командной строки и использует введенные нами функции. Начальное условие if гарантирует, что следующий код выполняется только при прямом вызове модуля (а не при его импорте другого модуля).

Сначала определим аргументы командной строки. Здесь нужен только один аргумент: путь к файлу в формате .csv, содержащему данные для классификации. Мы предполагаем, что каждая строка содержит один текстовый документ и что текст для классификации находится в столбце text. Мы разбираем аргументы командной строки и делаем их значения доступными в переменной args.

Затем загрузим входные данные с диска . Мы предполагаем, что данные хранятся в виде файла в формате .csv (значения, разделенные запятыми): есть строка заголовка с именами столбцов, за которой следуют строки, содержащие данные (поля разделены запятыми, как следует из названия). Для этого удобно использовать библиотеку pandas, которая позволяет загружать такие данные одной командой. После этого в переменной df окажется объект DataFrame библиотеки pandas, содержащий данные из входного файла. Мы извлекаем столбец text из DataFrame и применяем ранее определенную функцию classify к каждой строке (используя метод apply библиотеки pandas). Наконец , мы генерируем и выводим агрегированную статистику (количество вхождений для каждого ответа, сгенерированного моделью) и записываем полученные классификации в файл (result.csv).

4.2.6. Запуск кода

Используя ссылку Reviews.csv (см. раздел дополнительных материалов к этой главе), скачайте файл reviews.csv. В нем содержится несколько отзывов о фильмах, которые можно использовать для классификации. В файле два столбца: текст отзыва и его тональность (neg — отрицательный и pos — положительный). Нашей целью, безусловно, является автоматическое определение тональности. Однако наличие эталонных данных также позволяет нам оценить качество классификаций.

Вы можете протестировать код для классификации, как описано ниже (команды были проверены в операционной системе Linux). В терминале перейдите в каталог, содержащий модуль Python (listing1.py) с кодом из листинга 4.1. Затем выполните следующую команду (заменив python на имя вашего интерпретатора Python, например python3, если необходимо):

python listing1.py reviews.csv

Здесь мы предполагаем, что входной файл (reviews.csv) хранится в том же репозитории, что и код (в противном случае необходимо прописать соответствующий путь к имени файла). Обычно выполнение кода не должно занимать более нескольких секунд (немного дольше, если соединение нестабильно и требуются повторные попытки). При успешном выполнении в выводе вы увидите только сумму меток, присвоенных каждому из двух возможных классов.

После выполнения кода файл result.csv будет сохранен в тот же репозиторий. Помимо столбцов входного файла, итоговый файл содержит новый столбец class. В нем хранятся результаты классификации (положительные и отрицательные). Сравните метку, присвоенную классификатором, с эталонным вариантом. Вы увидите, что в большинстве случаев классификации совпадают. Неплохо для нескольких строк кода на Python, не правда ли?

4.2.7. Проверка вариантов в работе

На этом этапе можно немного поэкспериментировать с кодом и данными, чтобы лучше понять, как он работает. Например, попробуйте сами написать несколько отзывов о фильмах! При каких отзывах классификация надежна, а при каких возникают затруднения? Кроме того, попробуйте ввести несколько вариантов промптов. Какие инструкции повышают точность, а какие снижают производительность? Для примера попробуйте удалить часть промпта, которая точно определяет формат вывода (строку Ответ ("Положительный"/"Отрицательный")). Теперь запустите программу с измененным промптом. Что произойдет? Скорее всего, в результате классификации (в выводе программы) вы получите больше двух вариантов меток, включая сокращения (например, «П» и «О»), а также излишне подробные ответы (к примеру, во время тестирования модель GPT-4o генерировала ответы типа «Тон этого отзыва положительный»). В главе 9 мы оценим влияние различных промптов на качество вывода модели.

Кроме того, можно попробовать использовать для извлечения другую модель. Что, если применить версию модели меньшего размера, скажем GPT-3.5 (стоимость одного обработанного ею токена гораздо ниже)? А как насчет конфигурации модели? В листинге 4.1 используются только два параметра (имя модели и история сообщений), и оба — обязательные. Однако в главе 3 описаны и другие параметры конфигурации, которые можно здесь применить. Например, попробуйте изменить параметр temperature (так, задав значение temperature, равное 0, вы получите более предсказуемые результаты) или ограничьте длину желаемого вывода. В редких случаях модели GPT способны генерировать текст вывода, превышающий по длине желаемый результат классификации (состоящий из одного токена). Этого можно избежать, ограничив длину вывода с помощью параметра max_tokens. Как вариант, можно не ограничивать формат вывода только инструкциями в промпте, а повысить вероятность двух возможных результатов (положительного и отрицательного) с помощью параметра logit_bias. Подробнее настройку модели мы обсудим в главе 9.

Еще один вариант — изменить задачу классификации. Например, относительно легко создать классификацию с использованием другого набора категорий. Потребуется всего лишь изменить инструкции в промпте (перечислив все варианты ответов, как и раньше). К тому же, переписав несколько строк кода, можно получить универсальный инструмент классификации, позволяющий пользователям указывать задачу классификации и соответствующие классы в качестве дополнительных аргументов командной строки. Например, помимо отзывов о фильмах, можно использовать этот инструмент для категоризации газетных статей по нескольким тематическим категориям или для разделения электронных писем на «Срочные» или «Несрочные». К моменту чтения этих строк, надеюсь, вы убедились, что языковые модели способны выполнять классификацию текста, выдавая относительно высокое качество, требуя при этом умеренных затрат на реализацию. А теперь перейдем к другим задачам.

4.3. Извлечение данных из текста

Этот вид анализа мы рассмотрим на примере уже знакомой вам компании Banana, производящей бытовую электронику. Напомню, что в главе 2 вы уже выступали в роли ее сотрудника. Добавлю, что работу в ней (очень престижную!) вы получили благодаря своему опыту в области анализа данных с помощью языковых моделей.

Представьте, что вам начали поступать электронные письма от студентов, которых интересует летняя стажировка. Обзавестись стажером на лето было бы неплохо, но как выбрать лучшего кандидата? В идеале вы хотели бы составить таблицу, сравнивающую всех кандидатов по их среднему баллу, степени, названию компании, в которой они проходили последнюю стажировку (если таковая была), и т.д. Но читать электронные письма самому, чтобы составить такую таблицу, — весьма трудоемкое занятие. Нельзя ли автоматизировать процесс?

Конечно, можно. Применим для анализа электронных писем языковые модели. Пусть они извлекут все релевантные факторы, которые позволят выбрать стажера. Здесь мы снова сталкиваемся со стандартной задачей анализа текста — извлечением информации. В рамках извлечения информации из текста обычно можно получить структурированную информацию (например, табличные данные). В нашем примере мы рассматриваем электронные письма (от соискателей) как текстовые документы. Из каждого письма требуется извлечь ряд атрибутов: имя, средний балл и научную степень (получаемую в данный момент или последнюю полученную). Возьмем такой фрагмент письма одного из соискателей:

Привет!

Меня зовут Мартин, я хотел бы пройти летнюю стажировку в Banana!

Немного о себе: в настоящее время я получаю степень бакалавра компьютерных наук в Стэнфордском университете, мой средний балл — 4,0.

Рассматривая три упомянутых ранее атрибута, мы можем извлечь имя со­искателя (Мартин), его средний балл (4,0) и научную степень (бакалавр компьютерных наук). Проанализировав письма от нескольких соискателей, можно представить результат, как показано в табл. 4.2.

Таблица 4.2. Извлеченная информация о соискателях на летние стажировки

Имя

Средний балл

Научная степень

Мартин

4,0

Бакалавр компьютерных наук

Алиса

4,0

Магистр программной инженерии

Боб

3,7

Бакалавр дизайна

Далее мы обсудим, как выполнить извлечение информации с помощью языковых моделей.

4.3.1. Обзор

И снова предположим, что электронные письма хранятся на диске (в файле табличных данных, где в каждой строке содержится по одному письму). С помощью языковой модели мы будем перебирать письма и извлекать все релевантные атрибуты. Вместо того чтобы строго прописывать релевантные атрибуты в коде, позволим пользователям указывать их в командной строке (тогда код можно будет использовать повторно, если критерии для отбора претендентов на летнюю стажировку изменятся). Поскольку для анализа текста используются языковые модели (которые хорошо интерпретируют естественный язык), то нет необходимости прописывать атрибуты на формальном языке. Просто перечислите имена атрибутов (или, как вариант, их краткие описания на естественном языке), а языковая модель должна суметь разобраться, что извлекать. Результатом вывода кода станет файл табличных данных (в формате .csv), содержащий информацию, аналогичную указанной в табл. 4.2: в таблице вывода будет по одному столбцу для каждого извлеченного атрибута и по одной строке для каждого проанализированного письма.

Как же извлечь атрибуты из заданного письма? Нам снова нужно сгенерировать промпт, который будет описывать языковой модели задачу извлечения. Например, вот такой промпт должен помочь нам извлечь все релевантные атрибуты из предыдущего письма:

Промпт состоит из трех частей:

• описания задачи, содержащего спецификацию извлекаемых атрибутов ;

• исходного текста, который необходимо извлечь ;

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

Если передать такой промпт модели, то она должна вернуть текст с нужными нам извлеченными данными.

Вывод, который возвращает языковая модель, — это прежде всего текстовая строка. В конечном счете нам в выводе нужны структурированные табличные данные. Следовательно, потребуется обработка для выделения из ответа значений всех интересующих атрибутов (имя, средний балл, научная степень). На рис. 4.2 показаны этапы процесса извлечения (для одного текстового документа).

Рис. 4.2. Для каждого электронного письма мы генерируем промпт, который содержит само письмо и описание задачи извлечения. Это описание ссылается на атрибуты для извлечения, указанные пользователем. Получив промпт в качестве входных данных, языковая модель генерирует текстовый ответ, содержащий извлеченные значения атрибутов

Применив последующую обработку, мы извлечем эти значения из необработанного текста ответа.

4.3.2. Генерация промптов

Необходимо генерировать промпты, являющиеся экземплярами следующего шаблона:

Шаблон промпта содержит описание задачи , исходный текст, который требуется извлечь , и описание формата вывода . Обратите внимание, что в промпте теперь содержатся две метки-заполнителя (шаблон из предыдущего подраздела содержал только одну): список атрибутов, которые нужно извлечь, и исходный текст для извлечения.

Мы будем генерировать промпты с помощью следующего кода:

Эта функция принимает в качестве входных данных текст для анализа (который мы, безусловно, хотим включить в промпт) вместе со списком атрибутов, которые требуется извлечь. После генерации описания задачи , в том числе списка атрибутов для извлечения, функция добавляет исходный текст , а также описание желаемого формата вывода . Промпт объединяет эти части.

4.3.3. Последующая обработка

По сравнению с предыдущим проектом (классификация текста) наш промпт изменился, адаптировавшись к новой задаче (извлечение текста). Даже при наличии другого промпта мы все еще можем повторно использовать ту же функцию, что и в предыдущем подразделе, чтобы получить ответ от языковой модели. С другой стороны, нам придется приложить чуть больше усилий, чем раньше, чтобы обработать сырой ответ с помощью языковой модели. В случае классификации мы брали ответ языковой модели и сразу считали его конечным результатом. В текущем сценарии (извлечение текста), как правило, требуется извлечь значения нескольких атрибутов из одного входного текста. Поскольку текст, полученный от языковой модели, содержит значения всех извлеченных атрибутов, нужно будет извлечь значения для конкретных атрибутов из необработанного текста ответа.

Например, в качестве ответа модели мы можем получить следующий необработанный текст:

| Мартин | 4.0 | бакалавр компьютерных наук |

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

Все перечисленное можно выполнить с помощью следующей функции:

Входными данными для функции служит необработанный текст, созданный языковой моделью для одного текстового документа. Вывод — это список строк (где каждая строка результата, в свою очередь, представлена в виде списка). Чтобы перейти от входных данных к выводу, сначала нужно извлечь часть необработанного ответа, содержащую фактические табличные данные . Ответы, сгенерированные языковой моделью GPT-4o, помимо извлеченной таблицы, могут содержать вступление или дополнительные пояснения (например, «Конечно, вот таблица, которая вам нужна: ...»). Необходимо отделить интересующие нас данные. К счастью, это несложно сделать при условии, что модель GPT-4o придерживается наших инструкций (а обычно так и происходит): нужные нам данные должны содержаться между двумя маркерами (<BeginTable> и <EndTable>). Следовательно, регулярное выражение '<BeginTable>(.*)<EndTable>' точно соответствует интересующей нас части вывода. Мы извлекаем ее с помощью функции Python re.findall, которая, получив в качестве входных данных строку и регулярное выражение, возвращает список соответствующих подстрок. Использование флага re.DOTALL позволяет гарантировать, что точка в регулярном выражении будет учитывать любые символы, в том числе символы разрыва строк (поскольку таблица может занимать несколько строк). Из полученных совпадений выбираем первое. Обратите внимание: предполагается, что в выводе модели GPT имеется хотя бы одна таблица. Обычно так и есть, однако стоит подумать, как сделать функцию более устойчивой к ответам языковой модели, не соответствующим инструкциям в промпте.

После извлечения табличных данных в текстовом представлении мы сначала разделяем их на данные, связанные с конкретными строками , и данные, связанные с конкретными ячейками . После очистки (функция Python strip удаляет символы пробела) мы добавляем полученные значения ячеек в список результатов. Возвращается список строк (где каждая строка, в свою очередь, представлена в виде списка).

4.3.4. Полный рабочий код извлечения данных

В листинге 4.2 показан полный код на языке Python. Структура похожа на представленную в листинге 4.1, и некоторые функции являются общими для обоих листингов (вместо того чтобы опускать повторяющиеся функции, я стараюсь предоставить готовый код, чтобы вам не пришлось собирать его по фрагментам с нескольких страниц). В частности, код использует те же библиотеки, что и раньше , и вызывает языковую модель через ту же функцию . Вы узнаете уже знакомые вам функции: для создания промптов и последующей обработки необработанного вывода языковой модели .

Листинг 4.2. Извлечение определенных пользователем атрибутов из текста

import argparse                          

import openai

import pandas as pd

import re

import time

 

client = openai.OpenAI()

 

def create_prompt(text, attributes):     

    """Сгенерировать промпт для извлечения информации.

 

    Аргументы:

        text: извлечь информацию из текста.

        attributes: список атрибутов.

 

    Выводит:

        Входные данные для LLM.

    """

    parts = []

    parts += ['Извлеки следующие свойства в таблицу:']

    parts += [','.join(attributes)]

    parts += [f'Источник текста: {text}']

    parts += [

        ('Пометь начало таблицы тегом <BeginTable> '

        'а конец — тегом <EndTable>.')]

    parts += [

        ('Раздели строки символами новой строки '

        'а поля — вертикальной чертой (|).')]

    parts += [

        ('Опусти заголовок таблицы и вставляй значения в порядке '

        'следования атрибутов, начиная сверху.')]

    parts += [

        ('Используй заполнитель <NA>, если значение '

        'атрибута недоступно.')]

    return '\n'.join(parts)

 

def call_llm(prompt):                    

    """Отправить запрос большой языковой модели и вывести ответ.

 

    Аргументы:

        prompt: введите запрос к языковой модели.

 

    Выводит:

        Ответ языковой модели.

    """

    for nr_retries in range(1, 4):

        try:

            response = client.chat.completions.create(

                model='gpt-4o',

                messages=[

                    {'role':'user', 'content':prompt}

                    ]

                )

            return response.choices[0].message.content

        except:

            time.sleep(nr_retries * 2)

    raise Exception('Не удается выполнить запрос к модели OpenAI!')

 

def post_process(raw_answer):            

    """Извлечь поля из неструктурированного текстового ответа.

 

    Аргументы:

        raw_answer: неструктурированный текст, сгенерированный LLM.

 

    Выводит:

        Список получившихся строк.

    """

    table_text = re.findall(

        '<BeginTable>(.*)<EndTable>',

        raw_answer, re.DOTALL)[0]

 

    results = []

    for raw_row in table_text.split('\n'):

        if raw_row:

            row = raw_row.split('|')

            row = [field.strip() for field in row]

            row = [field for field in row if field]

            results.append(row)

    return results

 

def extract_rows(text, attributes):      

    """Извлечь значения атрибутов из текста.

 

    Аргументы:

        text: извлечь информацию из данного текста.

        attributes: список атрибутов для извлечения.

 

    Выводит:

        Список строк со значениями атрибутов.

    """

 

    prompt = create_prompt(text, attributes)

    result_text = call_llm(prompt)

    result_rows = post_process(result_text)

    return result_rows

 

if __name__ == '__main__':               

 

    parser = argparse.ArgumentParser()

    parser.add_argument('file_path', type=str, help='Путь к входному файлу')

    parser.add_argument('attributes', type=str, help='Список атрибутов')

    args = parser.parse_args()

 

    input_df = pd.read_csv(args.file_path)

    attributes = args.attributes.split('|')

 

    extractions = []

    for text in input_df['text'].values:

        extractions += extract_rows(text, attributes)

 

    result_df = pd.DataFrame(extractions)

    result_df.columns = attributes

    result_df.to_csv('result.csv')

Функция main считывает два входных параметра из командной строки:

• путь к файлу в формате .csv, содержащему текст для анализа;

• список атрибутов для извлечения, разделенный вертикальными чертами.

После открытия входного файла (с помощью библиотеки pandas) мы перебираем все входные текстовые документы . Обратите внимание: ожидается, что входной текст находится в столбце text входного файла. Для выполнения фактического извлечения применяется функция extract_rows . Получив входной текст и список атрибутов для извлечения, эта функция генерирует соответствующий промпт, получает необработанный ответ от языковой модели и проводит его последующую обработку, чтобы сформировать структурированный вывод (который она и возвращает). После обработки всех входных текстов окончательный результат сохраняется в файл result.csv (если такой файл уже существует, то будет перезаписан).

4.3.5. Проверка

Код из листинга 4.2, можно найти в файле listing2.py, доступном по ссылке Listing 2, размещенной на сайте книги, в разделе дополнительных материалов к этой главе. Там же можно скачать файл biographies.csv, в котором хранится небольшой набор данных для экспериментов с вашим экстрактором (он немного отличается от нашего сценария, но общедоступных данных о заявках по электронной почте мало). В файле содержатся биографии пяти знаменитостей, а также их имена — для каждого выделена одна строка. Перейдите в каталог, содержащий файл listing2.py (и данные), и запустите код:

python listing2.py biographies.csv

    "имя|место рождения|дата рождения"

Первый параметр — набор данных (если он находится в другом каталоге, то скорректируйте путь соответственно). Второй параметр — список атрибутов для извлечения. Для разделения атрибутов снова используется вертикальная черта. Обратите внимание, что атрибуты идентифицируются только по их именам; нет необходимости ссылаться на предопределенные категории. Языковая модель способна понимать семантику атрибутов, основываясь только на имени.

После выполнения кода (которое не должно занимать больше минуты) результаты будут сохранены в файл result.csv. Например, выполнив код с данными из примера, вы получите следующую таблицу:

    имя     место рождения   дата рождения

0   Сергей Михайлович Брин      Москва     21 августа 1973

1   Мартин Лютер Кинг мл.     Атланта, Джорджия     15 января 1929

2   Энн Э. Воджицки     <N/A>     28 июля 1973

3   Мария Саломея Склодовская-Кюри     Варшава     7 ноября 1867

4   Алан Мэтисон Тьюринг     Мейда-Вейл, Лондон     23 июня 1912

Даже при запуске одного и того же кода с одними и теми же данными можно заметить небольшие различия (вызванные рандомизацией при генерации вывода модели). Каждая строка в этом файле (кроме строки заголовка) представляет собой извлеченные данные. Мы извлекаем имя, место и дату рождения. Следовательно, предполагается, что из каждой биографии будет извлечено по одной строке (именно так и происходит в пробном запуске). Обратите внимание, что некоторые значения пропущены: во фрагменте биографии Энн Э. Воджицки не указано место рождения. Языковая модель реагирует соответствующе и вставляет вместо конкретного значения метку-заполнитель (<N/A>).

4.4. Кластеризация

Представьте, что по-прежнему работаете в компании Banana. Работа отличная, но есть одна проблема: ваш электронный почтовый ящик все так же завален письмами! Это не только заявки от желающих пройти летнюю стажировку (с ними мы разобрались в предыдущем разделе). Темы писем самые разные, и на прочтение всей значимой корреспонденции вы тратите много времени. Изучив письма подробнее, вы замечаете, что многие из них избыточны. Например, вы обращаете внимание, что в разных письмах обсуждаются одни и те же события в компании. На мгновение вы задумываетесь, а не применить ли код классификации текста (рассмотренный в разделе 4.2), чтобы классифицировать письма по нескольким категориям (например, связанным с конкретными событиями в компании). После этого можно будет прочитать всего несколько писем из каждой категории и получить полное представление о том, что происходит в компании. Увы, есть одна проблема: исчерпывающий список тем сложно составить и поддерживать, поскольку в ходе вашей работы они будут постоянно меняться. Вместо этого хорошо было бы автоматически группировать разные письма, имеющие общие черты, например посвященные одному и тому же событию. Тогда вам не придется заранее придумывать список тем.

Таким образом, требуется сгруппировать похожие письма в кластеры. Это еще одна классическая задача обработки текста — его кластеризация. Если вы хотите объединить связанные текстовые документы, не указывая предварительно набор категорий, то можете использовать методы кластеризации.

Итак, в этом разделе вы увидите, как с помощью языковых моделей выполнять кластеризацию текста.

4.4.1. Обзор

Кластеризация — классический подход, используемый в информатике. Методы кластеризации появились намного раньше языковых моделей и расширенного анализа текста. Однако традиционно кластеризация фокусируется на элементах, которые выражены в виде векторов. Необходимо объединить (в один кластер) векторы, находящиеся на небольшом расстоянии друг от друга (и конечно, существуют различные показатели расстояния, которые можно применить к векторам). Но в нашем случае все немного иначе: в сценарии мы хотим собрать в один кластер похожие письма (или похожие текстовые документы в целом). Как же перейти от документов к векторам?

Ответ — использовать эмбеддинги. Эмбеддинг представляет собой текстовый документ в виде вектора (как правило, многомерного). Именно это нам и нужно! Разумеется, этот подход имеет смысл только в том случае, если мы преобразуем текстовые документы в векторы, которые отражают смысл содержания документов. В идеале нам нужно, чтобы документы с похожими векторами (то есть векторами с небольшим расстоянием согласно выбранному показателю расстояния) имели и схожее содержание. А значит, нам не удастся использовать наивные методы для отображения текстовых документов в виде векторов. Вместо этого потребуется применить подход, который будет учитывать семантику текста и принимать ее во внимание при генерации векторного представления.

К счастью, языковые модели могут помочь! Поставщики наподобие OpenAI предлагают языковые модели, которые принимают текст в качестве входных данных и выдают эмбеддинги на выходе. Итак, имея коллекцию текстовых документов для кластеризации, можно вычислить для них эмбеддинги и применить к полученным векторам любой классический алгоритм кластеризации. Этот процесс показан на рис. 4.3. Далее мы обсудим, как его реализовать.

Рис. 4.3. Кластеризация электронных писем. Сначала мы вычисляем эмбеддинги для всех писем. Затем кластеризуем эти векторы, чтобы собрать письма с похожим содержанием в один кластер

4.4.2. Вычисление эмбеддингов

В примерах, которые приводились ранее, мы использовали эндпоинт chat.com­pletions с OpenAI. Чтобы выполнить кластеризацию, вместо него возьмем эндпоинт embeddings OpenAI. Цель эмбеддинга — создать вектор, который сжимает семантику текста. Вычислить эмбеддинги можно с помощью разных моделей. Размерность вектора зависит от используемой модели.

Для кода, который приводится ниже, используется модель text-embedding-ada-002. Можете попробовать заменить ее другими моделями (список моделей OpenAI для вычисления эмбеддингов представлен на странице https://plat­form.openai.com/docs/guides/embeddings) и сравнить качество вывода.

Например, эмбеддинги для текстовых документов можно генерировать так:

import openai

client = openai.OpenAI()

 

response = client.embeddings.create(

    model='text-embedding-ada-002',

    input=text)

Вот фрагмент соответствующего ответа:

Фрагмент содержит значения только для первых нескольких размерностей вектора (тогда как полный вектор имеет более 1000 размерностей). Помимо эмбеддинга, в ответе есть название модели и статистика использования . В отличие от предыдущих случаев статистика использования относится только к количеству токенов в промпте (которое также является общим количеством обработанных токенов). В отличие от диалогового режима здесь языковая модель только считывает токены, но не генерирует их.

Самая важная часть для нас, конечно, сам эмбеддинг. Получить к нему доступ можно с помощью следующей команды:

response.data[0].embedding

В большинстве случаев одного вызова языковой модели должно быть достаточно для получения нужного эмбеддинга. Конечно, при вычислении эмбеддингов для большого количества писем время от времени придется сталкиваться с проблемами (то есть неудачными попытками соединения). Вот почему в окончательной версии функции эмбеддинга снова содержится механизм повторных попыток:

import openai

client = openai.OpenAI()

 

def get_embedding(text):

    for nr_retries in range(1, 4):

        try:

            response = client.embeddings.create(

                model='text-embedding-ada-002',

                input=text)

            return response.data[0].embedding

        except:

            time.sleep(nr_retries * 2)

    raise Exception('Не удается выполнить запрос к модели OpenAI!')

Имея в качестве входных данных текст, мы выполняем до трех попыток по­лучения соответствующего эмбеддинга (увеличивая задержку между попытками после каждого сбоя). Именно эту функцию мы и будем использовать.

4.4.3. Кластеризация векторов

Для кластеризации векторов (представляющих документы) мы будем использовать алгоритм кластеризации k-средних. Это очень популярный алгоритм, который работает путем итеративного уточнения, к какому кластеру относится каждый вектор. В отличие от других алгоритмов кластеризации, для него необходимо заранее указать количество кластеров. В нашем тестовом сценарии это означает, что придется указать, насколько детальным должно быть разделение писем по их содержанию.

Как работает алгоритм k-средних

Алгоритм k-средних принимает в качестве входных данных набор элементов для кластеризации и целевое количество кластеров. Затем он итеративно перебирает элементы и уточняет, к какому кластеру они относятся, — и так до тех пор, пока не выполнится условие остановки (например, максимальное количество итераций или минимальные изменения в назначениях кластеров между последовательными итерациями). Алгоритм связывает каждый кластер с вектором-центроидом (представляющим собой центр этого кластера). При каждой итерации он распределяет векторы данных по кластерам, к векторам-центроидам которых они расположены ближе всего. Затем он пересчитывает векторы, связанные с кластерами (путем усреднения по векторам всех элементов, в данный момент назначенных кластеру).

Мы будем использовать реализацию k-средних в библиотеке scikit-learn. Следуйте инструкциям в первом разделе этой главы и убедитесь, что би­блиотека установлена (импортируйте методы кластеризации, используя команду from sklearn.cluster import KMeans). После импорта библиотеки реализацию k-средних можно вызвать с помощью этого (краткого) фрагмента кода:

def get_kmeans(embeddings, k):

    kmeans = KMeans(n_clusters=k, init='k-means++')

    kmeans.fit(embeddings)

    return kmeans.labels_

В качестве входных данных функция принимает список эмбеддингов и количество целевых кластеров, а затем кластеризует эти векторы с использованием реализации k-средних. Результатом кластеризации являются метки, связанные с каждым эмбеддингом. Эти метки обозначают идентификатор связанного кластера.

4.4.4. Полный рабочий код кластеризации текста

В листинге 4.3 показан полный код для кластеризации текстовых документов с помощью эмбеддингов. Вы познакомитесь с функциями для вычисления эмбеддингов и их кластеризации .

Листинг 4.3. Кластеризация текстовых документов с использованием языковых моделей

import argparse

import openai

import pandas as pd

import time

 

from sklearn.cluster import KMeans

 

client = openai.OpenAI()

 

def get_embedding(text):                 

    """Вычислить эмбеддинг для входного текста.

 

    Аргументы:

        text: текст, для которого будет вычисляться эмбеддинг.

 

    Выводит:

        Векторное представление входного текста.

    """

    for nr_retries in range(1, 4):

        try:

            response = client.embeddings.create(

                model='text-embedding-ada-002',

                input=text)

            return response.data[0].embedding

        except:

            time.sleep(nr_retries * 2)

    raise Exception('Не удается выполнить запрос к модели OpenAI!')

 

def get_kmeans(embeddings, k):           

    """Кластеризовать эмбеддинги с использованием метода k-средних.

 

    Аргументы:

        embeddings: эмбеддинги.

        k: количество получившихся кластеров.

 

    Выводит:

        ID кластеров в порядке следования эмбеддингов.

    """

    kmeans = KMeans(n_clusters=k, init='k-means++')

    kmeans.fit(embeddings)

    return kmeans.labels_

 

if __name__ == '__main__':               

 

    parser = argparse.ArgumentParser()

    parser.add_argument('file_path', type=str, help='Путь к входному файлу')

    parser.add_argument('nr_clusters', type=int, help='Количество кластеров')

    args = parser.parse_args()

 

    df = pd.read_csv(args.file_path)

 

    embeddings = df['text'].apply(get_embedding)

    df['clusterid'] = get_kmeans(list(embeddings), args.nr_clusters)

 

    df.to_csv('result.csv')

Главная функция в этом листинге считывает данные из файла на диске. Мы снова предполагаем, что данные содержатся в файле формата .csv, и основное внимание уделяем столбцу text. Сначала мы перебираем текстовые документы и генерируем соответствующие эмбеддинги (вызывая уже знакомую нам функцию get_embedding). Затем кластеризуем эмбеддинги с помощью функции get_kmeans. Идентификаторы кластеров становятся дополнительным столбцом в получившейся таблице, которая записывается на диск.

4.4.5. Проверка в работе

Пришло время опробовать выполнить кластеризацию с помощью эмбеддингов! Код из листинга 4.3 (listing3.py), а также подходящий набор данных (textmix.csv) доступны по ссылкам (Listing 3 и Textmix.csv соответственно), размещенным на сайте книги в разделе дополнительных материалов к главе. В наборе данных содержится смесь текстовых фрагментов из двух источников: сборника стихов и репозитория писем. Мы попытаемся разделить их с помощью кластеризации: предполагается, что письма и стихи будут распределены по разным кластерам.

Перейдите в каталог, содержащий код и данные, и введите в терминале следующую команду:

python listing3.py textmix.csv 2

Здесь textmix.csv — имя входного файла, а 2 — количество целевых кластеров (в этом конкретном случае число 2 кажется логичным, хотя в других сценариях правильно определить количество кластеров может быть сложнее). Результат будет сохранен в файле result.csv. Он содержит все столбцы из входного файла, а также дополнительный столбец с идентификатором кластера (поскольку мы используем только два кластера, значением идентификатора будет 0 или 1). Если выполнить команду, то письма, скорее всего, попадут в один кластер, а стихи — в другой.

Возможно, вы захотите протестировать разные модели, чтобы увидеть различия во времени выполнения и в качестве результата. Вдобавок можно менять текст ввода и варьировать количество кластеров. Помимо этого, можно реализовать и другие сценарии использования эмбеддингов, упоминавшиеся в начале этого раздела. Например, как насчет реализации интерфейса поиска, который сопоставляет утверждение на естественном языке с наиболее тесно связанным с ним документом (сравнивая эмбеддинги вопросов и документов)?

4.4.6. Другие варианты применения эмбеддингов

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

Например, мы надеемся, что эмбеддинги для вопроса «Что такое модель-трансформер?» и текста «Трансформер — это архитектура нейронной сети, часто используемая для языковых моделей» будут похожи, поскольку их темы связаны. Если это так, то можно идентифицировать документ, наиболее релевантный вопросу, сравнив векторы. Точнее, эмбеддинги вычисляются один раз для каждого документа, который может быть полезен для ответа на вопросы. Затем всякий раз, когда поступает новый вопрос, вычисляется связанный с ним эмбеддинг и извлекаются документы с похожими векторами. Далее мы можем сгенерировать ответ на основе этих документов.

Другой вариант использования эмбеддингов — обнаружение аномалий. Чтобы выявить в наборе текстовые документы, разительно отличающиеся от других из того же набора, можно сравнить их эмбеддинги. Опять же, векторы потребуется вычислить только один раз для каждого документа. Поступая так, мы избегаем необходимости использовать языковые модели для сравнения документов. Вместо этого мы просто сравниваем эмбеддинги (что занимает очень мало времени).

В итоге, хотя мы и уделяем основное внимание кластеризации, существует множество сценариев использования эмбеддингов. Поэтому стоит разобраться, как их генерировать и применять.

Резюме

• Языковые модели можно использовать непосредственно для анализа текстовых данных.

• Промпты обычно содержат текст для анализа и инструкции. В инструкциях описывается решаемая задача, а также указывается формат вывода.

• Диалоговый режим можно использовать для классификации, извлечения информации и ответов на вопросы.

• Изменение формата необработанного вывода модели может потребовать последующей обработки.

• Языковые модели способны преобразовывать текст в эмбеддинги. Вы можете создавать эмбеддинги с помощью эндпоинта embeddings. Сравнение эмбеддингов — относительно эффективный метод.

• Эмбеддинги можно использовать для кластеризации, поиска и обнаружения аномалий.

Литература

1. Katz D.M., Bommarito M.J., Gao S. et al. GPT-4 Passes the Bar Exam // Philosophical Transactions of the Royal Society A: Mathematical, Physical and Engineering Sciences, 2024. — № 382 (2270). — Pp. 1–17.


Здесь и далее docstrings, строки вывода и комментарии переведены для удобства восприятия кода. — Примеч. ред.

Назад: 3. Библиотека OpenAI для Python
Дальше: 5. Анализ структурированных данных