Разговариваем с BI на естественном языке
Всем привет! Искусственный интеллект уже научился писать простые запросы к базам данных, но можно ли совсем избавиться от кода в работе аналитиков? Мы расскажем про наши нейросетевые эксперименты, в которых мы научили BI-систему слушать, понимать и отрабатывать запросы аналитиков на естественном языке.
В команде R&D SberData мы ищем и разрабатываем технологии обработки, хранения и анализа данных Сбера. Мы исследуем все перспективные технологии, которые появляются на рынке, разрабатываем новые продукты, которые использует Сбер и его партнёры. Одно из приоритетных направлений для нас — это анализ данных. В Сбере более 100 тысяч пользователей BI (Business Intelligence). Естественно, что у такого количества аналитиков самые разные потребности и требования к сервису и продукту. И возможность сделать их работу проще и удобнее — это большой вызов и интересная задача для нашей команды. В этот раз мы пробовали научить LLM-модель написать правильный SQL-код по запросу на естественном языке.
Будущее BI? Или уже настоящее?
В 2018 году компания IBM выпустила «пророческое» исследование, в котором утверждалось, что в BI придёт искусственный интеллект и возьмёт на себя существенную часть работы и задач аналитиков.
И с тех пор этот тренд только набирает обороты, всё больше вендоров разрабатывает и предлагает ИИ-помощника в BI. Можно вдохновиться примерами Tableau GPT и Tableau Pulse. В компании Microsoft выпустили Copilot in Power BI Demo, выглядит очень круто и интересно. Анализируя тренды таких разработок, мы выделили три актуальные задачи для ИИ‑помощника в BI‑системе:
помощник для аналитика в написании SQL;
построение дашбордов по запросу на естественном языке;
ответы на вопросы на естественном языке.
Это серьёзные технологические задачи, но их можно попробовать решить. Мы начали с преобразования естественного языка в SQL (text2sql), тем более что появилось много LLM-моделей, которые можно использовать для этого. Судя по опыту Github Copilot, генеративные модели могут значительно ускорять написание кода: по статистике copilot ускоряет этот процесс на 55%! Вот, например, что думает об этом McKinsey. Мы попробовали применить такие модели на практике и далее расскажем, что из этого получилось.
Прототип на GPT. Проверяем гипотезы
Всем известный ChatGPT хорошо справляется с генерированием кода. Благодаря этой особенности мы впервые и узнали об этой модели: примерно год назад попробовали сгенерировать простенький Python-код и были сильно удивлены результатом. Поэтому для подтверждения гипотезы (PoC) мы с помощью ChatGPT попробовали оценить, насколько вся эта идея работоспособна. Взяли также streamlit, LangChainи таблицы из открытых источников. Это сразу позволило получать ответы на основе данных и показывать сами данные в табличном виде.
LangChain иLlamaIndex— это фреймворки для построения цепочек обращений к LLM-модели для решения различных задач. С их помощью можно использовать агентов для различных задач, в том числе и для генерации SQL (например, такой). Так можно легко сделать проект на основе уже готовой LLM. А если вы используете GigaChat, то можете обратить внимание на GigaChain.
В первом приближении результат кажется очень хорошим, но у такого решения есть нюансы. Во-первых, интерфейс streamlit не всегда удобен, так как сильно ограничен возможностями библиотеки. Кажется, что решение будет более полноценным, если будет встроено напрямую в BI. Там можно построить дашборды, самостоятельно написать или исправить SQL-запрос и так далее. Во-вторых, использование ChatGPT не всегда уместно. Мы передаём метаданные, и иногда могут быть переданы сами данные, а это недопустимо.
Теперь пробуем реализовать прототип непосредственно в BI. Вдохновившись видео о Superset и обсуждением в Github, мы сделали прототип в SDP Analytics (BI-система Сбера собственной разработки на основе Apache Superset).
Выглядит гораздо интереснее! Но самое важное, какая LLM-модель генерирует SQL-код?
Выбор LLM — он трудный самый
При разработке прототипа мы тщательно выбирали генеративную модель. Подразумевалось, что она должна уметь отвечать без дообучения на конкретных таблицах или базах. На момент запуска пилота Llama2 (эту линейку моделей разрабатывает META (признана в России экстремистской и запрещена)) ещё не вышла, поэтому выбор был довольно ограничен. Самой большой проблемой было то, что существовавшие на тот момент модели очень плохо понимали русский язык. При этом было множество LLM, обученных на английских текстах и коде. Для быстрого решения мы выбрали простую локальную модель-переводчик, которая обрабатывает вопрос на русском, а потом он в переводе на английский поступает в генерирующую модель.
Но у такого приёма есть очевидный недостаток: не во всех ситуациях нужно переводить запрос полностью. Например, мы хотим запросить город, который в таблице записан на русском языке. В этом случае часть запроса переводить не нужно, иначе на выходе в SQL этот город будет указан на английском языке, что приведёт к ошибке. Поэтому в дальнейшем отказались от варианта с переводчиком.
В качестве модели выбрали WizardCoder-15B. Конечно, сейчас уже есть другие модели, которые гораздо лучше понимают вопросы на русском языке. Но на момент разработки PoC именно эта модель показывала наилучшее качество и подходила для пилота. Кстати, а как понять, какая LLM-модель лучше всего подходит? Рассказываем, как мы измеряли качество.
Измеряем качество LLM-моделей. Бенчмарки
Задача по сравнению двух SQL-запросов в общем случае нетривиальна. Мы не можем сравнить их просто как строки — синтаксис запросов может быть очень разным, хотя запросы будут выполнять одну и ту же логику.
Пример разного синтаксиса одно запроса:
SELECT * FROM table-name WHERE date-column = '2013–12–12»
SELECT* FROM table-name where date-column < '2013-12-13' and date-column >= '2013–12–12»
SELECT * FROM table-name WHERE date-column BETWEEN »2013–12–12' and '2013–12–13»
SELECT* FROM table-name WHERE cast (datediff (day, 0, date-column) as datetime) = '2012–12–12'
Для оценки идентичности запросов мы воспользовались сравнением их AST. В библиотеке sqlglot есть реализация построения дерева по SQL и расчёт разности двух деревьев.
Пример сравнения двух AST SQL.
На основе количества разных узлов в деревьях рассчитывается степень схожести двух SQL-запросов. Эта информация позволяет, например, оценить качество моделей, которые имеют одинаковую точность по выполнению в базе данных. И в таком случае имеет смысл оценить среднюю степень схожести запросов: у какой модели она окажется больше, значит та модель лучше.
Также все запросы разбиваются по группам (бакетам) по степени схожести. Посмотрев на распределение запросов по группам, мы можем полностью оценить качество модели.
Дополнительно мы разметили все SQL по сложности, опираясь на количество ключевых слов. Ввели следующую градацию:
Лёгкие: до 4 ключевых слов.
Средние: от 5 до 7 ключевых слов.
Сложные: от 8 ключевых слов, а также если больше двух операторов JOIN или есть подзапросы.
В качестве метрики мы выбрали степень успешно выполненных запросов среди лёгких и средних. Далее все результаты всех моделей будут представлены именно по этой метрике.
Пример отчёта после прогона бенчмарка:
[0–60%): 9, exec success: 1
[60–80%): 15, exec success: 1
[80–90%): 4, exec success: 1
[90–100%]: 19, exec success: 17not select: 0
parse errors: 0
critical errors: 3Easy: 35.3%
Medium: 44.8%
Hard: 25.0%Total percent of successfully executed sql 40.0%
Metric (Easy+Medium): 41.3%
В качестве основного показателя мы выбрали точность (accuracy) по результатам выполнения предсказанного и эталонного SQL: оба запроса выполняются в синтетической базе данных, из запросов удаляются все псевдонимы, и затем сравниваются результаты. Для этого важно, чтобы все эталонные SQL после выполнения имели непустой результат, что отдельно проверяется в бенчмарке.
Бенчмарки для text2sql
Существует два довольно сложных бенчмарка, которые актуальны для нашей задачи: Spider и Bird. И там и там SQL-запросы сравниваются по результатам выполнения их в базе данных. Однако Bird ещё проверяет и эффективность запросов. Также стоит отметить, что он является сложным бенчмарком, судя по таблице лидеров даже специалисты по базам данных вместе со студентами справляются с ним примерно на 93%.
Таблица лидеров в бенчмарке Bird.
Но у Bird есть одна особенность: все вопросы в нём задаются на английском языке, а у наших пользователей должна быть возможность писать вопросы на русском. Поэтому мы взяли открытый набор PAUQ — это набор Spider, который Devices перевели на русский язык в 2022 году. Мы взяли его как dev-набор для оценки, так и как обучающий набор.
Но этого мало поэтому дополнительно мы взяли базу данных Vacancies, хранящую в себе информацию о различных вакансиях. На основе неё сделан ещё один бенчмарк. Преимущество этой базы в том, что, предположительно, ни одна модель не видела её до этого момента. А мы как раз хотим решать задачу text2sql в произвольной БД или таблице и для произвольного запроса на естественном языке.
Таким образом, у нас было разработано два бенчмарка (на самом деле четыре, ещё два создали на основе внутренних данных Сбера).
Сравнение бенчмарков:
Vacancies | PAUQ | |
Количество вопросов | 50 | 680 |
Среднее количество колонок в таблице | 20 | 5–6 |
Среднее количество таблиц в БД | 2 | 4–5 |
Результаты теста LLM-моделей
Первым делом мы оценили уже существующие open source-модели, а также прогнали бенчмарки на GPT 3.5 и GPT 4.
WizardCoder-15B — обучена преимущественно на написание кода по запросам на английском языке.
Llama2–13B — обучена преимущественно на английском, немного знает код.
NSQL‑llama2–7B — дообученная Llama2–7B на 300 тыс. примерах text2sql.
NSQL-6B — обучена генерировать SQL.
CodeLlama-13B‑Instruct — обучена генерировать код в целом, в том числе и SQL.
SQLCoder — обучена специально на генерацию SQL.
ruGPT-3.5–13B — об учена преимущественно на русском, основа для GigaChat, но не обучена писать код.
Получились следующие результаты, качество моделей указано по метрике:
Llama2–13B* | CodeLlama-13B* | SQLCoder-15B* | WizardCoder-15B* | NSQL-Llama2–7B* | NSQL-6B* | GPT 3.5 | GPT 4 | |
Vacancies | 37 | 23,9 | 30,4 | 54,3 | 19,6 | 21,7 | 65,2 | 71,7 |
PAUQ | 23,1 | 10,5 | 22,2 | 21,2 | 32,3 | 15,1 | - | - |
* — модели с открытыми весами.
Как видно из таблицы, модель WizardCoder-15Bпоказала наилучший результат среди open source-моделей в бенчмарке Vacancies. Мы предполагаем, что она могла видеть очень похожие таблицы, так как обучена на большом количестве кода, в том числе и SQL. На момент тестирования (июнь 2023 года) эта модель была закрыта для коммерческого использования (хотя на текущий момент уже имеет лицензию OpenRAIL-M), поэтому мы стали рассматривать другие аналоги, но их качество было слишком низким для полноценного и хорошего продукта.
И тогда мы задались новым вопросом: можно ли дообучить LLM-модель на SQL, чтобы она показывала лучшее качество?
Обучаем LLM-модель
Есть много способов дообучить модель. Про все (или почти все) эффективные методы дообучения (PEFT) есть страница на Hugging Face. Мы выбрали LoRA — этот метод позволяет сократить количество параметров обучения с помощью низкорангового разложения матрицы изменения весов. Это хорошо подходит под нашу довольно узкую языковую задачу.
Пример конвейера по обучению LoRA можно посмотреть тут. Также стоит учитывать, что LoRA как метод имеет множество параметров (например, ранг матриц r, alpha, bias и др.). Правильного рецепта тут, к сожалению, нет — чтобы получить оптимальную точность модели необходимо поэкспериментировать с большим количеством параметров.
Для первой итерации дообучения мы использовали для дообучения весь набор. Результаты замерили в двух бенчмарках:
VACANCIES | Llama2–13B | NSQL-Llama2–7B | CodeLlama-13B | SQLCoder |
После дообучения | 19,6 | 19,6 | 21,7 | 26,1 |
Насколько изменилось | -17,4 | 0 | -2,2 | -4,3 |
PAUQ | Llama2–13B | NSQL-Llama2–7B | CodeLlama-13B | SQLCoder |
После дообучения | 47,4 | 35 | 54,7 | 56,4 |
Насколько изменилось | +24,3 | +2,7 | +44,2 | +34,2 |
Как видно из таблицы, модель сильно прибавила в качестве в бенчмарке PAUQ. Это и не так удивительно, поскольку эти таблицы модель уже видела. Интереснее то, что она деградировала в бенчмарке Vacancies. Понятно, что если в обучающей выборке будут содержаться примеры с таблицами, которые есть в оценочном наборе, то качество будет расти, несмотря на то, что модели могут задаваться совершенно разные вопросы. Но наша цель — использовать модель в BI-системе, поэтому нам определённо надо учитывать тот факт, что в общем случае таблицы могут быть какими угодно. И именно поэтому для нас было важно добиться хорошего показателя в бенчмарке Vacancies.
Пробуем самокоррекцию
В качестве ещё одного приёма мы попробовали самокоррекцию. Это довольно простой способ, но он, тем не менее, позволяет немного повысить качество. После генерации SQL-запроса пробуем выполнить его в синтетической или разработческой базе данных, и в случае ошибки просим модель исправить её. Дополнительно о приёме самокоррекции можно почитать тут, а здесь отметим два важных момента.
Во-первых, далеко не каждая LLM-модель способна корректно исправить запрос. Это значит, что для самокоррекции необходимо выбирать дополнительную модель, которая будет хороша именно в этом навыке. Во-вторых, при применении метода самокоррекции практически удваивается длительность инференса, так как мы посылаем в модель два запроса.
Визуально самокоррекцию можно представить так:
Но мы отказались от этого способа, потому что он требует больших расходов на инференс и хранение моделей, при этом давая совсем небольшой прирост в качестве.
Обогащаем промпт
После анализа ошибок, которые совершает модель при генерации SQL, выяснилось, что она часто не знает, какие значения содержатся в наших таблицах. Например, в таблице может быть поле gender
, которое может иметь значения Мужчина/Женщина, или Male/Female. Если не посмотреть в таблицу, то даже человеку будет сложно выбрать какой-либо из вариантов. Что уж там говорить о языковых моделях.
Поэтому стали изучать, какие есть методики по обогащению промпта, которые можно применить в задаче text2sql, и наткнулись на полезную статью. В ней рассказано, как можно добавлять различные дополнительные подсказки для модели, примеры (few-shot prompting) и так далее:
Разные способы обогащения промпта.
Также мы провели исследование и выяснили, кто ещё специализируется на решении задачи text2sql. Нашли два интересных проекта: Dataherald и SQLCoder. Первый создан как надстройка над ChatGPT. Она сканирует базу данных для получения дополнительной информации: примеров значений из таблиц, релевантных колонок, примеров пар вопрос-SQL. Всё это наполняет входящий в модель промпт, что позволяет сгенерировать более качественный SQL. При необходимости код этого проекта можно адаптировать под свои нужды.
Нашли отдельные компании, которые больше делают упор на модели. Например, Defog учит свою модель SQLCoder писать качественный SQL-код по запросу. Для этого они применяют различные методы, даже собственный бенчмарк создали. Недавно они выпустили LLM на 34 миллиарда параметров, которая показывает хорошие результаты и по качеству сопоставима с GPT 4. Общие идеи их подходов можно почерпнуть в блоге компании.
В результате исследования, чтобы всё-таки исправить ошибку неправильных строковых констант, мы решили обогащать промпт статистикой. Перед отправлением запроса в модель мы сканируем нашу базу данных и вытаскиваем информацию о значениях в колонках. Такая функциональность уже реализована в проекте Dataherald, поэтому мы взяли его за основу в нашем эксперименте — добавление статистики показалось нам наиболее логичным решением задачи. Но для такой «фишки» контекст модели должен быть больше 2 тыс. токенов. В качестве модели для дальнейших экспериментов мы выбрали CodeLlama-13B с контекстом 16 тыс. Попробовали эту методику и получили серьёзный прирост в качестве:
CodeLlama-13B | |
Без обогащения промпта | 23,9 |
С добавлением статистики | 44 |
Неплохо, но для того, чтобы модель лучше понимала промпты со статистикой, мы стали обучать её таким промптам. Для этого нашли готовые базы для нескольких open source-наборов, к этому добавили ещё PAUQ. Просканировали и составили набор со статистикой, набралось 15 тыс. примеров. На всех этих данных дообучили CodeLlama-13B и получили отличные результаты:
СodeLlama-13B | CodeLlama-13B с добавлением статистики | CodeLlama-13B дообученная, с добавлением статистики | |
Vacancies | 24 | 44 | 65 |
PAUQ | 11 | 55 | 60 |
Такой результат подтверждает нашу гипотезу, что мы можем собрать ИИ-помощника для пользователей BI-системы, и разработка промышленного решения может показать хороший результат.
Собираем конструктор
Итак, собираем вместе все кубики нашего эксперимента. Архитектуру решения можно представить так:
Первым делом прогоняем наш скрипт по сбору статистики и записываем в базу данных. Далее при прогоне бенчмарка из базы подтягивается статистика, и формируется промпт, состоящий из: 1) DDL, 2) описания статистики, 3) вопроса на естественном языке. После генерации ответа модели мы парсим его для выделения только SQL-кода, затем этот запрос проверяем на корректность при помощи sqlglotи выполняем в синтетической базе.
Отдельной опцией может быть использование векторной базы данных. Надо внести в неё сгенерированные человеком вопросы и ответы на них в виде SQL. При генерации ответа сначала мы сходим в векторку и подтянем в промпт подходящие SQL. И это сильный чит, поскольку хорошие примеры в промте добавляют примерно 10% точности. При прогоне наших бенчмарков мы пока не пользуемся этим, потому что не уверены, что специально не подгоним связку под бенчмарк.
Итоговый промпт может выглядеть так (для CodeLlama-13B):
[INST]You are an expert in generating sqlite SQL queries.
Given an input question, create a syntactically correct sqlite query to run.
If there is a similar question among the sql examples, modify the example SQL query to fit the given question and return the sql. Use following information:
table schemas:
{ddl}
sql examples:
{examples}
Column information:
{column info}
Input question: {question}
[/INST] SELECT
Где:
Ddl
— ddl создания таблиц;Examples
— примеры SQL для БД (если таковых нет — пропускаем);column info
— информация о колонках. Пример:Table: Dogs, column: name, additional info: Description: None, Sample rows: Kacey, Hipolito, Mavis
, гдеDescription
— описание, если есть;Sample rows: Kacey, Hipolito, Mavis
— примеры колонок;question
— вопрос на русском.
Пример запроса из PAUQ
[INST]You are an expert in generating sqlite SQL queries.
Given an input question, create a syntactically correct sqlite query to run.
If there is a similar question among the sql examples, modify the example SQL query to fit the given question and return the sql. Use following information:
table schemas:
CREATE TABLE singer (
Singer_ID int,
Name text,
Birth_Year real,
Net_Worth_Millions real,
Citizenship text,
PRIMARY KEY (Singer_ID)
);
CREATE TABLE song (
Song_ID int,
Title text,
Singer_ID int,
Sales real,
Highest_Position real,
PRIMARY KEY (Song_ID),
FOREIGN KEY (Singer_ID) REFERENCES
singer
(Singer_ID));
column information:
Table: singer, column: Singer_ID, additional info: Description: None, Sample rows: 1, 2, 3
Table: singer, column: Name, additional info: Description: None, Sample rows: Liliane Bettencourt, Christy Walton, Alice Walton
Table: singer, column: Birth_Year, additional info: Description: None, Sample rows: 1944.0, 1948.0, 1949.0
Table: singer, column: Net_Worth_Millions, additional info: Description: None, Sample rows: 30.0, 28.8, 26.3
Table: singer, column: Citizenship, additional info: Description: None, Sample rows: France, United States, United States
Table: song, column: Song_ID, additional info: Description: None, Sample rows: 1, 2, 3
Table: song, column: Title, additional info: Description: None, Sample rows: Do They Know It’s Christmas, F**k It (I Don’t Want You Back), Cha Cha Slide
Table: song, column: Singer_ID, additional info: Description: None, Sample rows: 1, 1, 2
Table: song, column: Sales, additional info: Description: None, Sample rows: 1094000.0, 552407.0, 351421.0
Table: song, column: Highest_Position, additional info: Description: None, Sample rows: 1.0, 1.0, 1.0
Input question: Укажите имена певцов, чей год рождения — 1948 или 1949
[/INST] SELECT
И ещё немного докрутить: GigaChat
Отдельно стоит упомянуть про модель GigaChat-29B, которую мы решили протестировать. На момент нашего эксперимента по некоторым бенчмаркам она превосходила Llama2. Но главным её отличием было то, что она обучена на русском языке и у неё свой токенизатор. Более того, в обучающих данных у неё был код, в том числе и SQL. Поэтому модель подходила по всем параметрам: качество из коробки уже достигало 55+ %, что является безусловно хорошим результатом и уступало по нашим бенчмаркам только ChatGPT.
Как работает токенизатор дл визуального представления, можно посмотреть на картинках ниже (исходник). Получается, что когда мы спрашиваем на русском модель, которая больше обучалась на английском, мы тратим много токенов (поскольку её токенизатор изначально оптимизирован под англоязычный текст).
Выводы
Для SQL на лёгких и средних запросах мы добились точности 60+ %. При добавлении в векторную базу данных и дальнейшем подставлении в промпт написанных человеком SQL-запросов мы можем получить еще до 10%. Конечно, этого недостаточно для бизнес-пользователей, но зато достойный результат для продолжения экспериментов и инвестирования в разработку промышленного решения.
Мы планируем цикл статей по этой теме, расскажем вам о наших дальнейших экспериментах с использованием новейшей версии GigaChat Pro — следите за продолжением!
Авторы: Максим Радионов и Владимир Зворыгин, участники профессионального сообщества Сбера DWH/BigData. Профессиональное сообщество DWH/BigData отвечает за развитие компетенций в таких направлениях, как экосистема Hadoop, PostgreSQL, GreenPlum, а также BI‑инструментах Qlik, Apache SuperSet и др.