Настройка крупных языковых моделей (LLM)

Конфигурация LLM

Концептуальный обзор с примером кода на Python

Это пятая статья в серии о применении больших языковых моделей (LLM) на практике. В этой статье мы рассмотрим, как настраивать (fine-tune, FT) предварительно обученную LLM. Мы начнем с введения основных понятий и техник FT, а затем приведем конкретный пример того, как настроить модель (локально) с использованием Python и экосистемы программного обеспечения Hugging Face.

Настройка языковой модели. Изображение от автора.

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

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

Что такое настройка модели?

Настройка модели заключается в обучении по крайней мере одного внутреннего параметра модели (т.е. весов) предварительно обученной модели. В контексте LLM это обычно приводит к преобразованию универсальной базовой модели (например, GPT-3) в специализированную модель для конкретного случая использования (например, ChatGPT) [1].

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

Хотя строго автономные базовые модели могут продемонстрировать впечатляющую производительность на широком спектре задач с помощью инженерии промптов [2], они все же предсказывают слова и могут генерировать завершения, которые не всегда полезны или точны. Например, давайте сравним завершения davinci (базовая модель GPT-3) и text-davinci-003 (модель, подвергнутая настройке).

Сравнение завершений модели davinci (базовая модель GPT-3) и text-davinci-003 (модель, подвергнутая настройке). Изображение от автора.

Заметьте, что базовая модель просто пытается завершить текст, перечисляя набор вопросов, как поиск в Google или домашнее задание, в то время как модель, подвергнутая настройке, дает более полезный ответ. В данном случае для модели text-davinci-003 использовалась настройка согласования, которая направлена на то, чтобы ответы LLM были более полезными, честными и безопасными, но об этом поговорим позже [3,4].

Зачем настраивать модель?

Настройка модели не только улучшает производительность базовой модели, но и меньшая (подвергнутая настройке) модель часто может превзойти более крупные (более дорогостоящие) модели в наборе задач, на которых она была обучена [4]. Это было продемонстрировано компанией OpenAI с их моделями первого поколения “InstructGPT”, где завершения модели InstructGPT с 1,3 миллиардами параметров оказались предпочтительнее базовой модели GPT-3 с 175 миллиардами параметров, несмотря на то, что они были в 100 раз меньше [4].

Хотя большинство LLM, с которыми мы взаимодействуем в наши дни, не являются строго автономными моделями, подобными GPT-3, все же есть недостатки в использовании существующей модели, подвергнутой настройке для конкретного случая использования.

Одной из проблем является ограниченное окно контекста в LLM. Таким образом, модель может работать неоптимально на задачах, требующих большой базы знаний или информации, специфичной для определенной области [1]. Модели, подвергнутые настройке, могут избежать этой проблемы, «учась» этой информации в процессе настройки. Это также позволяет избежать необходимости перегружать промпты дополнительным контекстом и, следовательно, может привести к снижению затрат на вывод.

3 Способа настройки

Существуют 3 общих способа настройки модели: самообучение, обучение с учителем и обучение с подкреплением. Эти способы не являются взаимоисключающими, поскольку любая комбинация из этих трех подходов может быть использована последовательно для настройки одной модели.

Самообучение

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

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

Обучение с учителем

Следующий, и, возможно, самый популярный способ настройки модели – это обучение с учителем. Это включает в себя обучение модели на входно-выходных парах для определенной задачи. Примером является настройка инструкций, которая направлена на улучшение производительности модели при ответах на вопросы или реагировании на запросы пользователя [1,3].

Ключевым шагом в обучении с учителем является создание обучающего набора данных. Простой способ сделать это – создать пары вопрос-ответ и интегрировать их в шаблон запроса [1,3]. Например, вопрос-ответная пара: Кто был 35-ым президентом Соединенных Штатов? – Джон Ф. Кеннеди может быть вставлена в следующий шаблон запроса. Больше примеров шаблонов запросов доступно в разделе A.2.1 в ссылке [4].

"""Пожалуйста, ответьте на следующий вопрос. В: {Вопрос} О: {Ответ}"""

Использование шаблона запроса важно, потому что базовые модели, такие как GPT-3, по сути, являются “заполнителями документов”. Это означает, что при заданном тексте модель генерирует дополнительный текст, который (статистически) имеет смысл в данном контексте. Это связано с предыдущим блогом этой серии и идеей «обмануть» языковую модель, чтобы она решала вашу проблему с помощью создания запросов.

Создание запросов – Как обмануть ИИ, чтобы он решал ваши проблемы

7 способов создания запросов, Langchain и пример кода на Python

towardsdatascience.com

Обучение с подкреплением

Наконец, можно использовать обучение с подкреплением (RL) для настройки моделей. RL использует модель вознаграждения для управления обучением базовой модели. Это может иметь различные формы, но основная идея заключается в обучении модели вознаграждения для оценки завершений языковой модели, которые отражают предпочтения людей, выполняющих разметку данных [3,4]. Модель вознаграждения затем может быть объединена с алгоритмом обучения с подкреплением (например, Proximal Policy Optimization (PPO)) для дальнейшей настройки предварительно обученной модели.

Пример использования RL для настройки модели демонстрируется моделями InstructGPT от OpenAI, которые были разработаны через 3 ключевых шага [4].

  1. Создание высококачественных пар запрос-ответ и настройка предварительно обученной модели с использованием обучения с учителем. (~13 тыс. обучающих запросов) Примечание: Можно (альтернативно) перейти к шагу 2 с предварительно обученной моделью [3].
  2. Использование настроенной модели для генерации завершений и оценка ответов людьми на основе их предпочтений. Используйте эти предпочтения для обучения модели вознаграждения. (~33 тыс. обучающих запросов)
  3. Использование модели вознаграждения и алгоритма обучения с подкреплением (например, PPO) для дальнейшей настройки модели. (~31 тыс. обучающих запросов)

Хотя стратегия выше, как правило, приводит к завершениям языковых моделей, которые значительно предпочтительнее базовой модели, это также может привести к снижению производительности в некотором подмножестве задач. Это снижение производительности также известно как налог на согласованность [3,4].

Шаги обучения с учителем (высокоуровнево)

Как мы видели выше, существует множество способов настройки существующей языковой модели. Однако в оставшейся части этой статьи мы сосредоточимся на настройке с помощью обучения с учителем. Ниже приведена высокоуровневая процедура настройки модели с учителем [1].

  1. Выберите задачу настройки (например, суммирование, ответы на вопросы, классификация текста)
  2. Подготовьте набор обучающих данных, т.е. создайте (100–10 тыс.) пар ввод-вывод и предварительно обработайте данные (токенизируйте, усекайте и заполняйте текст).
  3. Выберите базовую модель (протестируйте разные модели и выберите ту, которая лучше всего выполняет желаемую задачу).
  4. Настройте модель с помощью обучения с учителем
  5. Оцените производительность модели

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

3 варианта обучения параметров

Когда дело доходит до настройки модели с ~100 млн – 100 млрд параметров, нужно обдумывать вычислительные затраты. В этом контексте важный вопрос состоит в том, какие параметры мы (пере)обучаем?

Учитывая огромное количество параметров, у нас есть бесчисленное множество вариантов, какие из них мы обучаем. Здесь я сосредоточусь на трех общих вариантах, из которых можно выбрать.

Вариант 1: Переобучение всех параметров

Первый вариант – обучение всех внутренних параметров модели (называемое полным настройкой параметров) [3]. Хотя этот вариант прост (концептуально), он требует наибольших вычислительных затрат. Кроме того, известной проблемой полной настройки параметров является явление катастрофического забывания. Здесь модель “забывает” полезную информацию, которую она “выучила” в своем первоначальном обучении [3].

Один из способов смягчить недостатки Варианта 1 – это заморозить большую часть параметров модели, что переводит нас к Варианту 2.

Вариант 2: Обучение с передачей знаний

Основная идея обучения с передачей знаний (TL) заключается в сохранении полезных представлений/характеристик, которые модель изучила в прошлом обучении, при применении модели к новой задаче. Это обычно включает в себя отбрасывание “головы” нейронной сети (NN) и ее замену новой (например, добавление новых слоев с случайными весами). Примечание: Голова NN включает ее последние слои, которые преобразуют внутренние представления модели в выходные значения.

Хотя оставление большинства параметров без изменений смягчает огромные вычислительные затраты при обучении LLM, TL не обязательно решает проблему катастрофического забывания. Чтобы лучше справиться с обеими этими проблемами, мы можем обратиться к другому набору подходов.

Вариант 3: Эффективная настройка параметров (PEFT)

PEFT включает в себя дополнение базовой модели относительно небольшим числом обучаемых параметров. Главный результат заключается в методологии настройки, демонстрирующей сравнимую производительность с полной настройкой параметров при гораздо меньших вычислительных и хранилищных затратах [5].

PEFT включает в себя семейство техник, одной из которых является популярный метод LoRA (Low-Rank Adaptation) [6]. Основная идея LoRA заключается в выборе подмножества слоев в существующей модели и изменении их весов в соответствии с следующим уравнением.

Уравнение, показывающее, как изменяются матрицы весов при настройке с использованием LoRA [6]. Изображение автора.

Где h() = скрытый слой, который будет настраиваться, x = вход для h(), W₀ = исходная матрица весов для h и ΔW = матрица обучаемых параметров, вводимых в h. ΔW разлагается по формуле ΔW=BA, где ΔW – матрица размером d на k, B – матрица размером d на r, а A – матрица размером r на k. r – это предполагаемый “внутренний ранг” ΔW (который может быть таким маленьким, как 1 или 2) [6].

Извините за всю математику, но ключевой момент заключается в том, что веса (d * k) в W₀ заморожены и, следовательно, не участвуют в оптимизации. Вместо этого, ((d * r) + (r * k)) веса, составляющие матрицы B и A, являются единственными, которые обучаются.

Подставив некоторые вымышленные числа для d=1000, k=1000 и r=2, чтобы получить представление о повышении эффективности, количество обучаемых параметров снижается с 1 000 000 до 4 000 в этом слое. На практике авторы статьи о LoRA упоминают уменьшение размера контрольной точки параметров в 10 000 раз при использовании техники fine-tune GPT-3 с применением LoRA по сравнению с полной настройкой параметров [6].

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

Пример кода: Дообучение LLM с использованием LoRA

В этом примере мы будем использовать экосистему Hugging Face для дообучения модели языка для классификации текста как “позитивный” или “негативный”. Здесь мы дообучаем distilbert-base-uncased, модель с ~70M параметров, основанную на BERT. Поскольку базовая модель была обучена на языковое моделирование, а не на классификацию, мы используем перенос обучения, чтобы заменить голову базовой модели головой классификации. Кроме того, мы используем LoRA для эффективного дообучения модели так, чтобы она могла работать на моем Mac Mini (чип M1 с 16 ГБ памяти) в разумное время (~20 мин).

Код, а также файлы среды conda, доступны в репозитории GitHub. Финальная модель и набор данных [7] доступны на Hugging Face.

YouTube-Blog/LLMs/fine-tuning at main · ShawhinT/YouTube-Blog

Коды для дополнения видеороликов и блог-постов на VoAGI. – YouTube-Blog/LLMs/fine-tuning at main ·…

github.com

Импорт

Начнем с импорта полезных библиотек и модулей. Datasets, transformers, peft и evaluate – все это библиотеки от Hugging Face (HF).

from datasets import load_dataset, DatasetDict, Datasetfrom transformers import (    AutoTokenizer,    AutoConfig,     AutoModelForSequenceClassification,    DataCollatorWithPadding,    TrainingArguments,    Trainer)from peft import PeftModel, PeftConfig, get_peft_model, LoraConfigimport evaluateimport torchimport numpy as np

Базовая модель

Затем мы загружаем нашу базовую модель. Здесь базовая модель относительно небольшая, но есть несколько других (больших) моделей, которые мы могли бы использовать (например, roberta-base, llama2, gpt2). Полный список доступен здесь.

model_checkpoint = 'distilbert-base-uncased'# определить отображение метокid2label = {0: "Негативный", 1: "Позитивный"}label2id = {"Негативный":0, "Позитивный":1}# создать классификационную модель на основе model_checkpointmodel = AutoModelForSequenceClassification.from_pretrained(    model_checkpoint, num_labels=2, id2label=id2label, label2id=label2id)

Загрузка данных

Затем мы можем загрузить наши обучающие и проверочные данные из библиотеки datasets от HF. Это набор данных из 2000 обзоров фильмов (1000 для обучения и 1000 для проверки) с бинарными метками, указывающими, является ли обзор позитивным (или нет).

# загрузить набор данныхdataset = load_dataset("shawhin/imdb-truncated")dataset# dataset = # DatasetDict({#     train: Dataset({#         features: ['label', 'text'],#         num_rows: 1000#     })#     validation: Dataset({#         features: ['label', 'text'],#         num_rows: 1000#     })# }) 

Предобработка данных

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

# создаем токенизаторtokenizer = AutoTokenizer.from_pretrained(model_checkpoint, add_prefix_space=True)

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

# создаем функцию токенизацииdef tokenize_function(examples):    # извлекаем текст    text = examples["text"]    # токенизируем и усекаем текст    tokenizer.truncation_side = "left"    tokenized_inputs = tokenizer(        text,        return_tensors="np",        truncation=True,        max_length=512    )    return tokenized_inputs# добавляем токен пустого значения, если его нетif tokenizer.pad_token is None:    tokenizer.add_special_tokens({'pad_token': '[PAD]'})    model.resize_token_embeddings(len(tokenizer))# токенизируем тренировочный и валидационный наборы данныхtokenized_dataset = dataset.map(tokenize_function, batched=True)tokenized_dataset# tokenized_dataset = # DatasetDict({#     train: Dataset({#        features: ['label', 'text', 'input_ids', 'attention_mask'],#         num_rows: 1000#     })#     validation: Dataset({#         features: ['label', 'text', 'input_ids', 'attention_mask'],#         num_rows: 1000#     })# })

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

# создаем сборщик данныхdata_collator = DataCollatorWithPadding(tokenizer=tokenizer)

Метрики оценки

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

# импортируем метрику точностиaccuracy = evaluate.load("accuracy")# определяем функцию оценки для передачи в тренерdef compute_metrics(p):    predictions, labels = p    predictions = np.argmax(predictions, axis=1)    return {"accuracy": accuracy.compute(predictions=predictions, references=labels)}

Производительность ненастроенной модели

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

# определяем список примеровtext_list = ["It was good.", "Not a fan, don't recommed.", "Better than the first one.", "This is not worth watching even once.", "This one is a pass."]print("Прогнозы ненастроенной модели:")print("----------------------------")for text in text_list:    # токенизируем текст    inputs = tokenizer.encode(text, return_tensors="pt")    # вычисляем логиты    logits = model(inputs).logits    # преобразуем логиты в метку    predictions = torch.argmax(logits)    print(text + " - " + id2label[predictions.tolist()])# Вывод:# Прогнозы ненастроенной модели:# ----------------------------# It was good. - Negative# Not a fan, don't recommed. - Negative# Better than the first one. - Negative# This is not worth watching even once. - Negative# This one is a pass. - Negative

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

Настройка с помощью LoRA

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

peft_config = LoraConfig(task_type="SEQ_CLS", # классификация последовательностей                        r=4, # внутреннее ранговое значение обучаемой матрицы весов                        lora_alpha=32, # это похоже на коэффициент обучения                        lora_dropout=0.01, # вероятность исключения                        target_modules = ['q_lin']) # применяем LoRA только к слою запроса

Затем мы можем создать новую версию нашей модели, которую можно обучить с помощью PEFT. Обратите внимание, что масштаб обучаемых параметров уменьшился примерно в 100 раз.

model = получить_модель_пефт(model, peft_конфиг)model.print_trainable_parameters()# обучаемые параметры: 1,221,124 || все параметры: 67,584,004 || процент обучаемых: 1.8068239934408148

Далее мы определяем гиперпараметры для обучения модели.

# гиперпараметрыlr = 1e-3 # размер шага оптимизации batch_size = 4 # количество примеров, обрабатываемых за один шаг оптимизации num_epochs = 10 # количество проходов модели через обучающие данные# определяем аргументы обученияtraining_args = TrainingArguments(    output_dir= model_checkpoint + "-lora-text-classification",    learning_rate=lr,    per_device_train_batch_size=batch_size,     per_device_eval_batch_size=batch_size,    num_train_epochs=num_epochs,    weight_decay=0.01,    evaluation_strategy="epoch",    save_strategy="epoch",    load_best_model_at_end=True,)

Наконец, мы создаем объект trainer() и проводим тонкую настройку модели!

# создаем объект тренерatrainer = Trainer(    model=model, # наша модель peft    args=training_args, # гиперпараметры    train_dataset=tokenized_dataset["train"], # обучающие данные    eval_dataset=tokenized_dataset["validation"], # валидационные данные    tokenizer=tokenizer, # определяем токенизатор    data_collator=data_collator, # это будет динамически заполнять примеры в каждой партии до равной длины    compute_metrics=compute_metrics, # оценивает модель с помощью функции compute_metrics() из предыдущего кода)# обучаем модельtrainer.train()

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

Метрики обучения модели. Изображение от автора.

Производительность обученной модели

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

model.to('mps') # переводим в mps для Mac (можно также использовать 'cpu')print("Прогнозы обученной модели:")print("--------------------------")for text in text_list:    inputs = tokenizer.encode(text, return_tensors="pt").to("mps") # переводим в mps для Mac (можно также использовать 'cpu')    logits = model(inputs).logits    predictions = torch.max(logits,1).indices    print(text + " - " + id2label[predictions.tolist()[0]])# Вывод:# Прогнозы обученной модели:# ----------------------------# It was good. - Positive# Not a fan, don't recommed. - Negative# Better than the first one. - Positive# This is not worth watching even once. - Negative# This one is a pass. - Positive # this one is tricky

Тонко настроенная модель значительно улучшилась по сравнению с ее предыдущей случайной угадыванием и правильно классифицирует все примеры в вышеприведенном коде, кроме одного. Это соответствует метрике точности ~90%, которую мы видели во время обучения.

Ссылки: Репозиторий кода | Модель | Набор данных

Выводы

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

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

👉 Больше о LLM: Введение | OpenAI API | Hugging Face Transformers | Стратегии создания подсказок

Ресурсы

Связаться: Мой веб-сайт | Записать звонок | Спросить меня о чем угодно

Социальные сети: YouTube 🎥 | LinkedIn | Twitter

Поддержка: Купи мне кофе ☕️

Предприниматели в области данных

Сообщество для предпринимателей в сфере данных. 👉 Присоединяйтесь к Discord!

VoAGI.com

[1] Deeplearning.ai Файнтюнинг больших языковых моделей Краткий курс: https://www.deeplearning.ai/short-courses/finetuning-large-language-models/

[2] arXiv:2005.14165 [cs.CL] (Статья о GPT-3)

[3] arXiv:2303.18223 [cs.CL] (Обзор LLM)

[4] arXiv:2203.02155 [cs.CL] (Статья о InstructGPT)

[5] 🤗 PEFT: Эффективное настройка параметров моделей масштаба биллионов на низкоресурсном оборудовании: https://huggingface.co/blog/peft

[6] arXiv:2106.09685 [cs.CL] (Статья о LoRA)

[7] Исходные данные набора данных — Andrew L. Maas, Raymond E. Daly, Peter T. Pham, Dan Huang, Andrew Y. Ng и Christopher Potts. 2011. Обучение векторов слов для анализа настроений. В Proceedings of the 49th Annual Meeting of the Association for Computational Linguistics: Human Language Technologies, стр. 142–150, Портленд, Орегон, США. Association for Computational Linguistics.