LMQL — Языковые модели в SQL

LMQL — Языковые модели в SQL улучшаем запросы и результаты

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

Изображение от DALL-E 3

Я уверен, что вы слышали о SQL и даже овладели им. SQL (Structured Query Language) – это декларативный язык, широко используемый для работы с данными базы данных.

Согласно ежегодному опросу StackOverflow, SQL по-прежнему является одним из самых популярных языков в мире. Для профессиональных разработчиков SQL находится в топ-3 языков (после Javascript и HTML/CSS). Более половины профессионалов его используют. Удивительно, но SQL даже популярнее Python.

График от автора, данные из опроса StackOverflow

SQL – это общий способ взаимодействия с данными в базе данных. Поэтому неудивительно, что существуют попытки использовать похожий подход для LLM. В этой статье я хотел бы рассказать вам о таком подходе, как LMQL.

Что такое LMQL?

LMQL (Language Model Query Language) – это язык программирования с открытым исходным кодом для языковых моделей. LMQL выпущен под лицензией Apache 2.0, что позволяет использовать его в коммерческих целях.

LMQL был разработан исследователями ETH Цюриха. Они предложили новую идею LMP (Language Model Programming). LMP комбинирует естественные и программные языки: текстовую подсказку и инструкции скриптового языка.

В оригинальной статье “Программирование через подсказки: язык запросов для больших языковых моделей” Луки Бойрера-Келлнера, Марка Фишера и Мартина Вечева авторы обозначили следующие вызовы текущего использования LLM:

  • Взаимодействие. Например, мы можем использовать мета подсказку, попросив LM расширить исходную подсказку. В качестве практического примера мы могли бы сначала попросить модель определить язык исходного вопроса, а затем отвечать на этом языке. Для такой задачи нам понадобится отправить первую подсказку, извлечь язык из вывода, добавить его в шаблон второй подсказки и сделать еще один запрос к LM. Для этого нам потребуется управлять множеством взаимодействий. С помощью LMQL вы можете определить несколько переменных ввода и вывода в одной подсказке. Более того, LMQL будет оптимизировать общую вероятность при множественных вызовах, что может привести к лучшим результатам.
  • Ограничения и представление токенов. Текущие LLM не предоставляют функциональности для ограничения вывода, что является важным, если мы используем LLM в производстве. Представьте себе создание анализа настроения в производственной среде для отметки негативных отзывов в нашем интерфейсе для операторов службы поддержки. Наша программа должна получать от LLM “положительное”, “отрицательное” или “нейтральное”. Однако часто можно получить что-то вроде “Настроение для предоставленного отзыва клиента положительное” от LLM, что не так легко обработать в вашем API. Вот почему ограничения могут быть очень полезными. LMQL позволяет вам контролировать вывод с использованием понятных человеку слов (а не токенов, с которыми работают LLM).
  • Эффективность и стоимость. LLM – это большие сети, поэтому они довольно дорогие, независимо от того, используете ли вы их через API или в своем локальном окружении. LMQL может использовать предопределенное поведение и ограничение пространства поиска (введенное ограничениями) для сокращения числа вызовов к LM.

Как вы можете видеть, LMQL может решить эти вызовы. Он позволяет объединять несколько вызовов в одной подсказке, контролировать вывод и даже снижать затраты.

Влияние на стоимость и эффективность может быть довольно существенным. Ограничения на пространство поиска могут значительно снизить затраты для LLM. Например, в случае из статьи LMQL было на 75–85% меньше токенов с оплатой по сравнению с стандартной декодировкой, что означает, что это существенно сократит ваши затраты.

Рисунок из статьи от Beurer-Kellner и др. (2023)

Я считаю, что наиболее важным преимуществом LMQL является полный контроль над вашим выводом. Однако, при таком подходе, у вас будет еще один уровень абстракции над LLM (подобный LangChain, о котором мы ранее говорили). Это позволит вам легко переключаться с одного бэкэнда на другой при необходимости. LMQL может работать с разными бэкэндами: OpenAI, HuggingFace Transformers или llama.cpp.

Вы можете установить LMQL локально или использовать онлайн “игровую площадку” (Playground). Playground может быть очень удобным для отладки, но здесь вы можете использовать только бэкэнд OpenAI. Для всех остальных случаев использования вам придется использовать локальную установку.

Как и обычно, у этого подхода есть некоторые ограничения:

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

Отчасти близкой альтернативой LMQL является Guidance. Он также позволяет ограничивать генерацию и контролировать вывод ЯП.

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

Если вас интересует узнать больше о LMQL от его авторов, посмотрите это видео.

Синтаксис LMQL

Теперь, когда мы немного знаем о LMQL, давайте рассмотрим пример LMQL-запроса, чтобы познакомиться с его синтаксисом.

beam(n=3)    "Q: Скажите 'Привет, {name}!'"     "A: [RESPONSE]" from "openai/text-davinci-003" where len(TOKENS(RESPONSE)) < 20

Я надеюсь, вы можете догадаться, что это значит. Но давайте обсудим это подробнее. Вот схема для LMQL-запроса:

Рисунок из статьи от Beurer-Kellner и др. (2023)

Любая программа LMQL состоит из 5 частей:

  • Decoder определяет процедуру декодирования. Простыми словами, это описывает алгоритм выбора следующего токена. У LMQL есть три разных типа декодеров: argmax, beam и sample. Вы можете узнать о них подробнее из статьи.
  • Фактический запрос похож на классический промпт, но в синтаксисе Python, что означает, что вы можете использовать такие конструкции, как циклы или условные операторы.
  • В операторе from мы указали модель для использования (openai/text-davinci-003 в нашем примере).
  • Оператор where определяет ограничения.
  • Оператор distribution используется, когда вы хотите видеть вероятности для токенов в возврате. Мы не использовали распределение в этом запросе, но мы будем его использовать, чтобы получить вероятности классов для анализа настроений позже.

Также вы могли заметить особые переменные в нашем запросе {name} и [RESPONSE]. Давайте обсудим, как они работают:

  • {name} – это входной параметр. Это может быть любая переменная из вашей области. Такие параметры помогают создавать удобные функции, которые легко могут быть повторно использованы для разных входных данных.
  • [RESPONSE] – это фраза, которую LM сгенерирует. Ее также можно назвать отверстием или заполнителем. Весь текст до [RESPONSE] отправляется в LM, и затем вывод модели присваивается переменной. Удобно то, что вы можете легко повторно использовать этот вывод позже в промпте, обращаясь к нему как {RESPONSE}.

Мы кратко рассмотрели основные концепции. Давайте попробуем сами. Практика делает совершенство.

Начало работы

Настройка окружения

Прежде всего, нам нужно настроить наше окружение. Чтобы использовать LMQL в Python, сначала нам нужно установить пакет. Не удивительно, мы можем просто использовать pip. Вам понадобится среда с Python ≥ 3.10.

pip install lmql

Если вы хотите использовать LMQL с локальным GPU, следуйте инструкциям в документации.

Для использования моделей OpenAI вам необходимо установить API-ключ для доступа к OpenAI. Проще всего указать переменную среды OPENAI_API_KEY.

import osos.environ['OPENAI_API_KEY'] = '<ваш_ключ_апи>'

Однако у моделей OpenAI есть много ограничений (например, вы не сможете получить распределение с более чем пятью классами). Поэтому мы будем использовать Llama.cpp для тестирования LMQL с локальными моделями.

Сначала вам нужно установить связывание Python для Llama.cpp в той же среде, что и LMQL.

pip install llama-cpp-python

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

CMAKE_ARGS="-DLLAMA_METAL=on" pip install llama-cpp-python

Затем нам нужно загрузить веса модели в виде файлов .gguf. Модели вы можете найти на HuggingFace Models Hub.

Мы будем использовать две модели:

Llama-2–7B – это самая маленькая версия обученных моделей генерации текста от Meta. Это довольно базовая модель, поэтому ожидать от нее выдающихся результатов не стоит.

Zephyr – это обученная версия модели Mistral с хорошей производительностью. Она лучше справляется с некоторыми аспектами, чем 10 раз более масштабная открытая модель Llama-2–70b. Однако между Zephyr и собственными моделями, такими как ChatGPT или Claude, все еще есть небольшая разница.

Изображение из статьи Тансталла и др. (2023)

Согласно лидерборду LMSYS ChatBot Arena, Zephyr – это модель с наилучшей производительностью с 7 миллиардами параметров. Она находится на уровне гораздо более крупных моделей.

Скриншот доски лидеров | источник

Давайте загрузим файлы .gguf для наших моделей.

import osimport urllib.requestdef download_gguf(model_url, filename):    if not os.path.isfile(filename):        urllib.request.urlretrieve(model_url, filename)        print("файл успешно загружен")    else:        print("файл уже существует")download_gguf(    "https://huggingface.co/TheBloke/zephyr-7B-beta-GGUF/resolve/main/zephyr-7b-beta.Q4_K_M.gguf",     "zephyr-7b-beta.Q4_K_M.gguf")download_gguf(    "https://huggingface.co/TheBloke/Llama-2-7B-GGUF/resolve/main/llama-2-7b.Q4_K_M.gguf",     "llama-2-7b.Q4_K_M.gguf")

Нам понадобится скачать несколько гигабайт, поэтому это может занять некоторое время (10-15 минут для каждой модели). К счастью, вам нужно сделать это только один раз.

Вы можете взаимодействовать с локальными моделями двумя разными способами (документация):

  • Архитектура с двумя процессами, когда у вас есть отдельный долгосрочный процесс с вашей моделью и краткие вызовы вывода. Этот подход более подходит для производства.
  • Для случайных задач мы можем использовать загрузку моделей в процессе, указав local: перед именем модели. Мы будем использовать этот подход для работы с локальными моделями.

Теперь, когда мы настроили среду, пришло время обсудить, как использовать LMQL из Python.

Функции Python

Давайте кратко обсудим, как использовать LMQL в Python. Песочница может быть полезна для отладки, но если вы хотите использовать LM на производстве, вам нужно использовать API.

LMQL предоставляет четыре основных подхода к своей функциональности: lmql.F, lmql.run, декоратор @lmql.query и API генераций.

API генераций был недавно добавлен. Это простой Python API, который помогает делать выводы без написания собственного LMQL. Поскольку меня больше интересует концепция LMP, мы не будем рассматривать этот API в этой статье.

Давайте подробнее рассмотрим остальные три подхода и попробуем их использовать.

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

Мы можем указать и запрос, и ограничение для функции. Ограничение будет эквивалентно оператору where в запросе LMQL.

Поскольку мы не указали никакой модели, будет использована модель OpenAI text-davinci.

capital_func = lmql.F("Какая столица у {country}? [CAPITAL]",     constraints = "STOPS_AT(CAPITAL, '.')")capital_func('Великобритания') # Вывод - '\n\nСтолицей Великобритании является Лондон.'

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

import nest_asyncionest_asyncio.apply()

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

В этом случае мы определили ограничения в самой строке запроса в разделе where .

query_string = '''    "Q: Что является столицей {country}? \\n"    "A: [CAPITAL] \\n"    "Q: Что является главной достопримечательностью в {CAPITAL}? \\n"    "A: [ANSWER]" where (len(TOKENS(CAPITAL)) < 10) \      and (len(TOKENS(ANSWER)) < 100) and STOPS_AT(CAPITAL, '\\n') \      and STOPS_AT(ANSWER, '\\n')'''lmql.run_sync(query_string, country="the United Kingdom")

Кроме того, я использовал run_sync вместо run , чтобы получить результат синхронно.

В итоге мы получили объект LMQLResult со следующими полями:

  • prompt – включает всю подсказку с параметрами и ответами модели. Мы можем видеть, что ответ модели использовался для второго вопроса.
  • variables – словарь со всеми переменными, которые мы определили: ANSWER и CAPITAL .
  • distribution_variable и distribution_valuesNone , так как мы не использовали эту функциональность.
Изображение автора

Третий способ использования Python API – это декоратор @lmql.query , который позволяет определить функцию Python, которая будет удобна для использования в будущем. Это более удобно, если вы планируете вызывать эту подсказку несколько раз.

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

@lmql.querydef capital_sights(country):    '''lmql    "Q: Что является столицей {country}? \\n"    "A: [CAPITAL] \\n"    "Q: Что является главной достопримечательностью в {CAPITAL}? \\n"    "A: [ANSWER]" where (len(TOKENS(CAPITAL)) < 10) and (len(TOKENS(ANSWER)) < 100) \        and STOPS_AT(CAPITAL, '\\n') and STOPS_AT(ANSWER, '\\n')    # вернуть только ANSWER     return ANSWER    '''print(capital_sights(country="the United Kingdom"))# В Лондоне есть много знаменитых достопримечательностей, но одной из самых известных является # колокольня Биг Бен, расположенная в Палате Вестминстера. # Другие популярные достопримечательности включают Букингемский дворец, Лондонское окно # и Тауэрский мост.

Кроме того, вы можете использовать LMQL в сочетании с LangChain:

  • LMQL-запросы – это развернутые шаблоны подсказок и могут быть частью цепочек LangChain.
  • Вы можете использовать компоненты LangChain из LMQL (например, выборку). Примеры можно найти в документации.

Теперь мы знаем все основы синтаксиса LMQL и готовы перейти к нашей задаче – определить настроение для комментариев клиентов.

Анализ настроения

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

Для этой задачи давайте использовать локальные модели – Zephyr и Llama-2. Чтобы использовать их в LMQL, мы должны указать модель и токенизатор при вызове LMQL. Для моделей семейства Llama мы можем использовать токенизатор по умолчанию.

Первые попытки

Давайте выберем один отзыв клиента Еда была очень хорошей.и попробуем определить его настроение. Для отладки мы будем использовать lmql.run, так как это удобно для таких спонтанных вызовов.

Я начал с очень наивного подхода.

query_string = """"Q: Каково настроение данного отзыва: ```Еда была очень хорошей.```?\\n""A: [SENTIMENT]""""lmql.run_sync(    query_string,     model = lmql.model("local:llama.cpp:zephyr-7b-beta.Q4_K_M.gguf",         tokenizer = 'HuggingFaceH4/zephyr-7b-beta'))# [Ошибка во время генерации()] Запрашиваемое количество токенов превышает размер контекста модели llama.cpp. Пожалуйста, укажите более высокое значение n_ctx.

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

Код выглядит абсолютно просто. Однако, удивительно, что он не работает и возвращает следующую ошибку.

[Ошибка во время генерации()] Запрашиваемое количество токенов превышает размер контекста модели llama.cpp. Пожалуйста, укажите более высокое значение n_ctx.

Из сообщения мы можем предположить, что вывод не подходит под размер контекста. Наш запрос состоит примерно из 20 токенов. Так что странно, что мы достигли порога размера контекста. Давайте попробуем ограничить количество токенов для SENTIMENT и посмотрим на вывод.

query_string = """"Q: Каково настроение данного отзыва: ```Еда была очень хорошей.```?\\n""A: [SENTIMENT]" where (len(TOKENS(SENTIMENT)) < 200)"""print(lmql.run_sync(query_string,     model = lmql.model("local:llama.cpp:zephyr-7b-beta.Q4_K_M.gguf",         tokenizer = 'HuggingFaceH4/zephyr-7b-beta')).variables['SENTIMENT'])# Положительное настроение.# # Q: Каково настроение данного отзыва: ```Сервис был ужасным.```?# A: Негативное настроение.# # Q: Каково настроение данного отзыва: ```Отель был потрясающим, персонал был дружелюбным и расположение было идеальным.```?# A: Положительное настроение.# # Q: Каково настроение данного отзыва: ```Продукт оказался полным разочарованием.```?# A: Негативное настроение.# # Q: Каково настроение данного отзыва: ```Рейс задержался на 3 часа, еда была холодной, и система развлечений не работала.```?# A: Негативное настроение.# # Q: Каково настроение данного отзыва: ```Ресторан был заполнен, но официант был эффективным, и еда была вкусной.```?# A: Положительное настроение.# # Q:

Теперь мы можем увидеть причину проблемы – модель застряла в цикле, повторяя вариации вопросов и ответов снова и снова. Я не видел таких проблем с моделями OpenAI (допустим, они могут это контролировать), но они довольно типичны для локальных моделей с открытым исходным кодом. Мы можем использовать ограничение STOPS_AT, чтобы прекратить генерацию, если мы видим Q:или новую строку в ответе модели, чтобы избежать таких циклов.

query_string = """"Q: Каково настроение данного отзыва: ```Еда была очень хорошей.```?\\n""A: [SENTIMENT]" where STOPS_AT(SENTIMENT, 'Q:') \     and STOPS_AT(SENTIMENT, '\\n')"""print(lmql.run_sync(query_string,     model = lmql.model("local:llama.cpp:zephyr-7b-beta.Q4_K_M.gguf",         tokenizer = 'HuggingFaceH4/zephyr-7b-beta')).variables['SENTIMENT'])# Положительное настроение.

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

query_string = """"Q: Каково настроение данного отзыва: ```Еда была очень хорошей.```?\\n""A: [SENTIMENT]" where (SENTIMENT in ['положительный', 'негативный', 'нейтральный'])"""print(lmql.run_sync(query_string,     model = lmql.model("local:llama.cpp:zephyr-7b-beta.Q4_K_M.gguf",         tokenizer = 'HuggingFaceH4/zephyr-7b-beta')).variables['SENTIMENT'])# положительный

Нам не нужны фильтры с критериями остановки, так как мы уже ограничиваем вывод только тремя возможными вариантами, и LMQL не рассматривает другие возможности.

Давайте попробуем использовать метод рассуждений “цепочка мыслей”. Дать модели немного времени подумать обычно улучшает результаты. Используя синтаксис LMQL, мы могли бы быстро реализовать этот подход.

query_string = """"Q: Какой эмоциональный окрас у следующего отзыва: ```Еда была очень вкусной.```?\\n""A: Подумаем шаг за шагом. [АНАЛИЗ]. Следовательно, эмоциональный окрас [ЭМОЦИЯ]" где (len(TOKENS(АНАЛИЗ)) < 200) и STOPS_AT(АНАЛИЗ, '\\n') \    и (ЭМОЦИЯ in ['положительный', 'отрицательный', 'нейтральный'])"""print(lmql.run_sync(query_string,     model = lmql.model("local:llama.cpp:zephyr-7b-beta.Q4_K_M.gguf",         tokenizer = 'HuggingFaceH4/zephyr-7b-beta')).variables)

Результат от модели Zephyr довольно хороший.

Изображение от автора

Мы можем попробовать ту же самую подсказку с Llama 2.

query_string = """"Q: Какой эмоциональный окрас у следующего отзыва: ```Еда была очень вкусной.```?\\n""A: Подумаем шаг за шагом. [АНАЛИЗ]. Следовательно, эмоциональный окрас [ЭМОЦИЯ]" где (len(TOKENS(АНАЛИЗ)) < 200) и STOPS_AT(АНАЛИЗ, '\\n') \    и (ЭМОЦИЯ in ['положительный', 'отрицательный', 'нейтральный'])"""print(lmql.run_sync(query_string,     model = lmql.model("local:llama.cpp:llama-2-7b.Q4_K_M.gguf")).variables)

Рассуждение не имеет много смысла. Мы уже видели в таблице лидеров, что модель Zephyr гораздо лучше, чем Llama-2–7b.

Изображение от автора

В классическом машинном обучении обычно получаем не только метки классов, но и их вероятности. Мы могли бы получить те же данные, используя distribution в LMQL. Просто нужно указать переменную и возможные значения — distribution ЭМОЦИЯ in ['положительный', 'отрицательный', 'нейтральный'].

query_string = """"Q: Какой эмоциональный окрас у следующего отзыва: ```Еда была очень вкусной.```?\\n""A: Подумаем шаг за шагом. [АНАЛИЗ]. Следовательно, эмоциональный окрас [ЭМОЦИЯ]" distribution ЭМОЦИЯ in ['положительный', 'отрицательный', 'нейтральный']где (len(TOKENS(АНАЛИЗ)) < 200) и STOPS_AT(АНАЛИЗ, '\\n')"""print(lmql.run_sync(query_string,     model = lmql.model("local:llama.cpp:zephyr-7b-beta.Q4_K_M.gguf",         tokenizer = 'HuggingFaceH4/zephyr-7b-beta')).variables)

Теперь у нас есть вероятности в выводе, и мы можем видеть, что модель довольно уверена в положительном эмоциональном окрасе.

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

Изображение от автора

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

@lmql.query(model=lmql.model("local:llama.cpp:zephyr-7b-beta.Q4_K_M.gguf",    tokenizer = 'HuggingFaceH4/zephyr-7b-beta', n_gpu_layers=1000))# указан n_gpu_layers для использования GPU для повышения скоростиdef sentiment_analysis(review):    '''lmql    "Q: Какой эмоциональный окрас у следующего отзыва: ```{review}```?\\n"    "A: Подумаем шаг за шагом. [АНАЛИЗ]. Следовательно, эмоциональный окрас [ЭМОЦИЯ]" где (len(TOKENS(АНАЛИЗ)) < 200) и STOPS_AT(АНАЛИЗ, '\\n') \        и (ЭМОЦИЯ in ['положительный', 'отрицательный', 'нейтральный'])    '''@lmql.query(model=lmql.model("local:llama.cpp:zephyr-7b-beta.Q4_K_M.gguf",   tokenizer = 'HuggingFaceH4/zephyr-7b-beta', n_gpu_layers=1000))def sentiment_analysis_distribution(review):    '''lmql    "Q: Какой эмоциональный окрас у следующего отзыва: ```{review}```?\\n"    "A: Подумаем шаг за шагом. [АНАЛИЗ]. Следовательно, эмоциональный окрас [ЭМОЦИЯ]" distribution ЭМОЦИЯ in ['положительный', 'отрицательный', 'нейтральный']    где (len(TOKENS(АНАЛИЗ)) < 200) и STOPS_AT(АНАЛИЗ, '\\n')    '''

Затем мы можем использовать эту функцию для нового отзыва.

sentiment_analysis('Номер был грязным')

Модель решила, что это нейтрально.

Изображение автора

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

По умолчанию используется кодировщик argmax. Это самый прямой подход: на каждом шаге модель выбирает токен с наибольшей вероятностью. Мы можем попробовать поиграть с другими вариантами.

Давайте попробуем использовать подход beam search с n = 3 и довольно высокой температурой = 0.8. В результате мы получим три последовательности, отсортированные по вероятности, и мы можем взять первую (с наибольшей вероятностью).

sentiment_analysis('Номер был грязным', decoder = 'beam', n = 3, temperature = 0.8)[0]

Теперь модель смогла определить отрицательную тональность в этом отзыве.

Изображение автора

Стоит сказать, что beam search декодирование имеет свою цену. Поскольку мы работаем с тремя последовательностями (лучами), получение результата LLM занимает в среднем в 3 раза больше времени: 39,55 секунды против 13,15 секунды.

Теперь у нас есть наши функции и мы можем протестировать их на наших реальных данных.

Результаты на реальных данных

Я запустил все функции на 10% выборке из набора данных Yelp с 1 тыс. отзывов с разными параметрами:

  • модели: Llama 2 или Zephyr,
  • подход: использование распределения или ограниченной подсказки,
  • декодеры: argmax или поиск луча.

Сначала давайте сравним точность – долю отзывов с правильной тональностью. Мы видим, что модель Zephyr работает намного лучше, чем модель Llama 2. Кроме того, по какой-то причине мы получаем значительно более низкое качество с распределениями.

График автора

Если мы заглянем немного глубже, то заметим:

  • Для положительных отзывов точность обычно выше.
  • Самая частая ошибка – пометка отзыва как нейтрального,
  • Для модели Llama 2 с подсказкой мы видим высокий уровень критических проблем (положительные комментарии, помеченные как отрицательные).

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

График автора
График автора

Также интересно посмотреть на фактические вероятности:

  • 75-й процентиль положительных ярлыков для положительных комментариев составляет более 0,85 для модели Zephyr, в то время как для модели Llama 2 он значительно ниже.
  • Все модели показывают плохие результаты для отрицательных комментариев, где 75-й процентиль отрицательных ярлыков для отрицательных комментариев даже ниже 0,5.
График от автора
График от автора

Наше быстрое исследование показывает, что простая подсказка с использованием модели Zephyr и декодером argmax будет лучшим вариантом для анализа настроения. Однако стоит проверить разные подходы для вашего случая использования. Кроме того, часто можно добиться лучших результатов, настроив подсказки.

Полный код вы найдете на GitHub.

Итог

Сегодня мы обсудили концепцию LMP (Языковое моделирование) , которое позволяет смешивать подсказки на естественном языке и указания на скриптах. Мы попробовали его использовать для задач анализа настроения и получили неплохие результаты с использованием локальных моделей с открытым исходным кодом.

Хотя LMQL еще не широко распространено, этот подход может быть полезным и приобретать популярность в будущем, поскольку он объединяет естественный и программный языки в мощный инструмент для языковых моделей.

Большое спасибо за прочтение этой статьи. Я надеюсь, что она была для вас информативной. Если у вас есть дополнительные вопросы или комментарии, пожалуйста, оставьте их в разделе комментариев.

Набор данных

Kotzias,Dimitrios. (2015). Sentiment Labelled Sentences. UCI Machine Learning Repository (лицензия CC BY 4.0). https://doi.org/10.24432/C57604