Привет! Меня зовут Анна Щеникова, я аналитик в Центре RnD в МТС Диджитал. Ко мне часто приходят задачи, где нужно использовать open-source LLM. Сразу же встает вопрос: а как адаптировать имеющуюся модель под конкретный кейс?
Мы выделяем четыре уровня адаптации. Для этого смотрим, какие потребуются навыки для решения этой задачи, сколько времени и человекочасов займет разработка. Поняв требуемый уровень, мы можем поставить себе дедлайны на проверку гипотезы и запланировать действия, если задача не решится выбранным способом. Ниже я расскажу, как мы разделяем разные уровни адаптации, что делаем на каждом из них и когда переходим на следующий.
Нулевой уровень адаптации: использование готовых технологий
Допустим, у нас есть задача: сгенерировать суммаризацию по тексту, код по описанию, сделать перевод и так далее. С чего мы начинаем? Как вариант, идем в ChatGPT, отправляем запрос и мгновенно получаем решение. Не факт, что хорошее, но это какой-никакой ответ на наш вопрос.
Тут мы с нуля используем готовые продукты. В технологиях GenAI у вас есть выбор между облачными продуктами, например, от OpenAI или open-source моделями (Mistral, LLama или Qwen), которые скачиваются и ставятся локально.
У каждого варианта есть свои плюсы и минусы:
OpenAI и Anthropic все время соревнуются между собой в качестве и выдают очень хороший результат, а вот open-source пока еще от них отстает. Например, недавно вышел релиз GPT-4o-mini, и у OpenAI снова появилось дешевое и топовое решение. Более того, совсем недавно вышла еще одна версию старшей модели GPT-4о с ценами, сниженными почти в два раза. Конкуренты тоже не сидят на месте: в лидерборде появились Gemini-1.5-Pro-Exp-0801 — новая модель на уровне с GPT-4o, а Claude 3.5 Sonnet не сильно отстает от GPT-4o-mini.
Увы, чаще всего готовых решений не хватает и модели приходится дополнительно адаптировать.
Легкий уровень адаптации: промпт-инжиниринг
На этом уровне мы помогаем модели решать задачи с помощью промптов. Для этого перебираем разные варианты и ищем, какой из них даст самый качественный инференс.
Промпт-инжиниринг — это управление поведением модели с помощью специфического запроса. Например, с системным сообщением для спецификации поведения («ты топовый маркетолог») и с сообщением от пользователя для определения задачи («придумай маркетинговую стратегию для повышения продаж кредитных карт»).
Он включает много техник и приемов, которые можно посмотреть тут:
-
Руководство по промпт-инжинирингу: все про промпты, параметры генерации и области применения. Максимально просто и доступно.
-
Как найти общий язык с LLM | Наталья Бруй и Алена Шувар | True Tech Day 2.0 — наши коллеги из MTS AI рассказали про задачи и возможности промпт-инжиниринга и поделились опытом jailbreak LLM.
Примеры техник
Вот так корректируется «личность» модели:
А вот так меняются данные и добавляются требования:
В этом примере видно, что без системной настройки мы получаем слишком подробный ответ. Стоит нам добавить требование в базовый промпт, как модель даст максимально релевантный короткий ответ.
Еще одна классная техника — chain-of-thoughts (CoT). Она безумно помогает в работе. С ее помощью можно заставить модель генерировать сложные рассуждения с использованием промежуточных шагов или решить задачу поэтапно, а не всю целиком:
Например, мы хотим создать отчет по практике на тему «Использование генеративных ИИ-моделей в задаче суммаризации». Можем отправить запрос в LLM без изменений и получим:
Отчет по практике на тему «Использование генеративных ИИ-моделей в задаче суммаризации»
Введение
В рамках практики было исследовано применение генеративных искусственных интеллект-моделей (ГИИ) для задачи автоматической суммаризации текста. Задача суммаризации состоит в создании краткого и информативного обзора основных идей и фактов, содержащихся в исходном тексте. Генеративные модели представляют собой мощный инструмент для автоматического формирования текстовых выводов на основе входных данных.
Цель практики
Целью данной практики было изучение возможностей и эффективности генеративных ИИ-моделей в контексте задачи суммаризации текста. Основной акцент делался на сравнении различных подходов и моделей, их применимости и результативности.
Методы и инструменты
-
Выбор моделей: в работе были выбраны различные генеративные модели, такие как Transformer-based модели (например, GPT, BERT), Seq2Seq модели с механизмом внимания (Attention) и другие.
-
Предобработка данных: исходные тексты подвергались предварительной обработке для подготовки к использованию моделями. Это включало токенизацию, очистку от шума и приведение к стандартному формату.
-
Обучение и тестирование: модели обучались на размеченных данных, затем их результаты сравнивались с реальными суммаризациями для оценки качества.
-
Оценка качества: для оценки качества сгенерированных суммаризаций использовались метрики ROUGE (Recall-Oriented Understudy for Gisting Evaluation), BLEU (Bilingual Evaluation Understudy) и другие.
Результаты и выводы
Практика показала, что генеративные ИИ-модели демонстрируют высокий потенциал в задаче автоматической суммаризации текста. Однако эффективность моделей может сильно варьироваться в зависимости от выбора конкретной модели, размера и качества обучающей выборки, а также от параметров обучения.
-
Преимущества: генеративные модели способны улавливать семантическую информацию и создавать качественные и краткие суммаризации, сохраняя ключевые аспекты текста.
-
Ограничения: некоторые модели могут страдать от проблемы «затухающего градиента», требуя больших объемов данных для обучения, что может сделать их применение затратным и трудоемким процессом.
Заключение
Использование генеративных ИИ-моделей в задаче суммаризации текста представляет собой перспективный исследовательский и практический направление. В дальнейшем планируется расширение экспериментов на более широкий спектр текстовых данных и моделей для получения более точных и обобщенных результатов.
Так как мы не обучали модель специально, в ответе нет подробного описания каждой из частей и есть фактические ошибки. Разобьем инференс на четыре этапа:
-
Попросим сгенерировать введение с указанием цели, задач и уровня актуальности решения.
-
Отдельно предложим написать основную часть с указанием методологии. Для генерации классного саммари можно использовать не только относительно маленькие обученные модели типа T5, но и более продвинутые подходы. Например, подход Retrieval: разделим текст на блоки и последовательно подадим их один за другим. После первого блока попросим сгенерировать черновик саммари, а следующие блоки будем использовать для улучшения. Или можно интегрировать CoT: так вместо суммаризации всего текста сразу LLM будет суммаризировать по отдельности каждый из блоков, а потом объединит их в единое саммари.
-
Отдельно сформируем выводы и результаты. Попросим модель указать, что мы сравнили использование классических моделей для суммаризации с промпт подходами, использовали не только стандартные метрики, но и, например, SBS-оценку человеком. И выяснили что промпт-подход лучше, но не когда нам нужно саммари по определенной структуре.
-
Создаем заключение из сгенерированных ответов.
Техника CoT помогает решать задачи генерации объемных текстов (написание отчета) или подробного анализа больших данных (суммаризация книги). Классный пример: в этой статье CoT использовался для сбора данных и решения задач по математике.
Есть много других полезных техник. В Few-shot inference перед самим запросом ставится пример ожидаемого результата, что дает более точные ответы:
Few-shot отличается и от single-shot, и от zero-shot. В single-shot мы уточняем ожидаемый результат, а в zero-shot отправляем запрос напрямую.
Все эти техники можно использовать для простых диалогов с ChatGPT. Они позволяют быстро получить желаемый результат. Надо учитывать, что промптинг — это итеративная история, на которую может уйти много попыток. Если нужный ответ никак не получается, нужно идти дальше.
Средний уровень: агенты
Есть крутой и перспективный фреймворк Langchain, который позволяет использовать LLM-модели на полную мощь. Он поддерживает разные функции: промпты, индексы цепочки, агенты и работу с памятью. В этой статье я остановлюсь на агентах, потому что они связывают все остальные компоненты в единый пайплайн.
Агенты — это глубокая промптовая настройка, которая «имитирует» у модели мыслительный процесс и дополнительно улучшает собственные ответы с помощью разных инструментов. Другими словами, промптами мы заставляем модель рассуждать. Это уже сложнее, чем шаги в методе CoT.
Как же оформляется промпт, чтобы модель могла «думать, как человек»? Агент задает LLM мыслительный процесс, заставляя ее генерировать цепочки вида «мысль — действие — результат». Покажу на примере:
Пользователь задал вопрос модели: «Сколько будет 2+2?»
У нас есть текущее состояние: запрос 2+2.
Мысль. Запрос можно посчитать калькулятором.
Действие. Считаем на калькуляторе 2+2.
Результат. Мы получаем новое текущее состояние: 4.
Мысль. Этого достаточно, чтобы ответить пользователю.
Действие. Возвратить ответ
Финальный ответ: 4.
Агент повторяет цикл из «состояние — мысль — действие», пока не дойдет до финального ответа, а LLM реализует шаги с помощью промптов.
Давайте разберем работу агента на практике:
-
У нас есть LLM, которую мы хотим использовать в этой задаче. Пусть это будет ChatGPT. Мы отправляем ему вопрос и получаем ответ:
llm = ChatOpenAI(
model_name= "gpt-4o",
max_tokens= 1024,
verbose=True,
api_key=os.getenv("API_KEY"),
base_url=os.getenv("API_BASE")
)
-
Мы можем добавить модели функциональности с помощью дополнительных инструментов (tools): калькулятора, выхода в интернет, генерации картинок по тексту и так далее. Эти функции принимают на вход данные и возвращают результат. В нашем случае подключим serpapi для выхода в интернет и калькулятор в утилите llm-math:
extra_tools = ["serpapi", "llm-math"]
tools = load_tools(extra_tools, llm=self.llm)
-
У нас есть промпты, которые позволяют имитировать мысленный процесс. Для этого мы просим, чтобы модель сначала генерировала цепочки рассуждений в виде «мысль — действие — результат», а потом возвращала json-разметку для совершения какого-то действия (action, вызов инструмента из tools):
SYSTEM_PROMPT = “You are helpful assistant”
AGENT_CHAIN_FORMAT_INSTRUCTIONS = """
TOOLS:
------
{tools}
Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input).
Valid "action" values: "Final Answer" or tool_names {tool_names}
Provide only ONE action per $JSON_BLOB, as shown:
```
{{
"action": $TOOL_NAME,
"action_input": $INPUT
}}
```
Follow this format:
Question: input question to answer
Thought: consider previous and subsequent steps
Action:
```
$JSON_BLOB
```
Observation: action result
... (repeat Thought/Action/Observation N times)
Thought: I know what to respond
Action:
```
{{
"action": "Final Answer",
"action_input": "Final response to human"
}}
```
Reminder to ALWAYS respond with a valid json blob of a single action. Use tools if necessary.
RETURN ONLY THIS JSON BLOB and nothing else!"""
HUMAN = '''{input}
{agent_scratchpad}
(remember return only json blob)'''
prompt = ChatPromptTemplate.from_messages(
[
("system", SYSTEM_PROMPT + AGENT_CHAIN_FORMAT_INSTRUCTIONS),
MessagesPlaceholder("chat_history", optional=True),
("human", HUMAN),
]
)
return prompt
-
Все это объединяется в одну структуру и становится агентом, который умеет парсить json-разметку и запускать необходимые действия (tools) в зависимости от запроса пользователя:
memory = ChatMessageHistory(session_id="test-session")
agent = create_structured_chat_agent(llm, tools, prompt=prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, return_intermediate_steps=True)
agent = RunnableWithMessageHistory(
agent_executor,
lambda session_id: memory,
input_messages_key="input",
history_messages_key="chat_history")
def _generate_answer(self, text, chat_id):
res = self.agent.invoke({"input": text}, config={"configurable": {"session_id": chat_id}})
return res["output"]
Полный код можно найти по ссылке на google colab.
Retrieval Augmented Generation (RAG)
RAG — это специфическая утилита для работы разными с источниками. Для ответов она использует релевантный вопросу контекст. Например, может выйти в интернет или в базу знаний с нашим запросом, найти подходящие ссылки и сформировать по ним точный ответ:
Структура работы RAG выглядит так:
-
Пользователь задает вопрос.
-
Вопрос перенаправляется в хранилище знаний, где с помощью алгоритма происходит поиск подходящей информации для ответа.
-
Формируется перечень полезной информации (релевантных знаний, например, нужных документов).
-
И вопрос, и релевантные знания отправляются в LLM.
-
На основе вопроса и релевантных знаний из базы данных LLM создает ответ.
При работе с RAG всегда возникают две сложности:
-
как сделать поиск? Есть более простые решения, такие как Elastic Search. Они дают хорошее качество поиска на легких запросах. Более сложные — Vector Search и Knowledge graph — лучше работают с семантическим поиском;
-
какую использовать LLM? Практика показывает, что быстро получить хорошее качество RAG можно только на GPT-моделях. Open-source (Mistral, LLama) придется дополнительно настраивать.
Время адаптации растягивается в зависимости от выбранных типов поиска и LLM. Чем сложнее их адаптировать, тем больше времени уходит на настройку пайплайна под бизнес-требования.
Полезные ссылки
-
Agents from LangСhain: быстрый старт работы с агентами.
-
AgentСhain: github-репозиторий, где мультимодальность реализуется через агент. По ссылке много видеопримеров.
-
Свой чат по документам на python с помощью LangChain и RAG: относительно простая видеоинструкция.
-
Создание приложения RAG с нуля с использованием Python, LangChain и API OpenAI: еще один туториал, но уже более сложный. Документация Langchain: подробно рассказывается про функции LangChain.Что такое RAG: обзорная статья на Хабре.
Максимальный уровень адаптации: дообучение моделей
К этому уровню мы подходим, когда попробовали все предыдущие, но так и не смогли выполнить свою задачу. Промпт-инжиниринга и интеграции агентов недостаточно, а open-source LLM плохо работают в агентах. Проявляются такие симптомы:
-
не хватает фактологической точности, то есть соответствия заранее определенным фактам. Например, искажается числовая информация (на вход было 15%, а модель сгенерировала 25%), перевираются факты (мы написали, что 2+2=4, а модель выдает, что 2+2=5) и т.д. Как бы мы ни настраивали наши промпты, результат будет нестабилен. Сегодня фактологическая точность 90%, завтра меняются условия инференса (окружение, карточки), и она падает до 50%. Да, даже если зафиксировать соответствующие гиперпараметры. Плавали, знаем.
Пример из жизни. С помощью различных техник Prompt-engineering (CoT, обьяснение терминов) на одном проекте по суммаризации я добилась фактологической точности около 90%. Но на следующий день у меня что-то случилось с докер-контейнером, и модель вместо адекватного саммари генерировала слово «живело» без остановки. Мы долго искали, в чем проблема, а потом еще три недели тюнили промпты, чтобы вернуть те заветные 90%. Через три недели ситуация повторилась, и результат снова пришлось развивать с нуля;
-
модель галлюцинирует. Отвечает русскими словами на латинице, переходит на английский язык, неправильно интерпретирует входную информацию и генерирует несвязный текст. Это можно пофиксить промптами, но часто их все равно не хватает;
-
отсутствуют специфичные навыки. Иногда нужно интегрировать модель в RAG, но она не может найти ответ по контексту. Мне приходилось обучать модель ответам на вопросы, иначе она плохо справлялась с двумя противоречащими друг другу документами. Например, у нас были разные даты публикации и модель не знала, что нужно использовать информацию из более позднего документа;
-
не может справиться с определенной задачей. Например, модель хорошо справляется с запросами на английском языке, а ее надо заставить говорить на русском. В этом случае промптами можно добиться небольшого успеха, но скорее всего вы не получите ожидаемый результат.
Полностью исправить эти проблемы не получится: фактологическая точность никогда не станет 100%, периодически будут проскакивать галлюцинации. Тут поможет только дообучение моделей. И это нормально. Если вам нужно идеальное решение, стандартные ML-алгоритмы подойдут лучше.
Что такое дообучение, или Fine-tuning
Например, у нас есть open-source LLama3 8B. Она уже умеет классно разговаривать в чате с пользователем, но не может решать специфичную задачу напрямую.
Эта модель прошла несколько этапов:
-
претрейн, где модели на обучение дали информацию из почти всего интернета, чтобы сформировать общее представление о мире;
-
Supervised Fine-tuning (SFT), где ее научили структуре чата с пользователем;
-
Reinforcement Learning from Human Feedback (RLHF), где ее натренировали отвечать качественно.
Мы берем модель и обучаем ее с помощью разных техник решать нужную задачу с помощью разных техник:
Есть бесконечное множество подходов, но вот самые популярные:
-
Full Fine-tuning: обучение модели целиком, то есть всех весов. Дает лучшее качество на конкретной задаче, но ведет к катастрофическому забыванию всего остального. А еще отнимает много ресурсов;
-
Low-Rank Adaptation (LoRA): обучение маленьких копий некоторых слоев модели. Тратится меньше ресурсов и можно подобрать гиперпараметры и получить результат на уровне Full Fine-tuning. Хотя в этой статье говорят, что Lora learns less and forget less.
Для дообучения нужны четыре составляющие:
-
данные;
-
тренировочный пайплайн (например, LoRA);
-
оценка качества;
-
сама модель.
Полезные ссылки для понимания дообучения моделей
-
Гайд, как использовать LoRa в python через Peft и transformers.
-
Статья на Хабре про разные методы обучения.
-
Статья на Medium про сложности и проблемы у LLM.
Как определить требуемый уровень адаптации
Чтобы лучше понять разницу между подходами, мы посчитали время на создание Proof-of-Concept (PoC). Для каждого уровня требуется свое количество ресурсов:
Самый сложный и длительный этап — последний. Проще не связываться с дообучением, а искать более быстрый и простой подход. Но не факт, что это решение будет лучшим. Если вы застряли на одном этапе дольше, чем требуется для проверки гипотезы, стоит признать: текущий метод не работает. Нужно усложнять решение или комбинировать техники с разных подходов — например, обучение и агентов. В любом случае не зацикливайтесь на одном уровне и не пытайтесь выжать из него невозможное.
Если вы сталкиваетесь с адаптацией моделей, то помните:
-
так или иначе придется адаптировать как облачные, так и опенсорс-модели;
-
часть возникающих проблем можно решить промпт-инжинирингом. Главное — не переусердствовать и вовремя остановиться;
-
с помощью агентов можно добавить новые функции, которых нет по умолчанию;
-
дообучение — сложный и дорогой процесс. Он необходим, если вы не можете справиться с задачей на предыдущих уровнях адаптации;
-
лучшее решение для специфичных задач — не зацикливаться на одном подходе, а комбинировать разные.
На этом у меня все, но я готова отвечать на ваши вопросы. Спасибо, что читали!
Автор: anna_schenikova