Как построить GPT-модель?

Гостевой
Как построить GPT-модель?
Capital

OpenAI представил мощные языковые модели генеративного предобученного трансформера (Generative Pre-trained Transformer, GPT), которые открыли новую страницу в обработке естественного языка (NLP). Интеграция GPT-моделей в виртуальных помощников и чат-ботов позволяет значительно расширить их возможности, что привело к резкому росту спроса на GPT-модели.

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

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

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

Что такое GPT-модель?

GPT расшифровывается как Generative Pre-trained Transformer (генеративный предобученный трансформер) — первая универсальная языковая модель в NLP. Предыдущие языковые модели разрабатывались для выполнения отдельных задач, например генерации текста, обобщения или классификации. GPT — первая универсальная языковая модель в истории обработки естественного языка, которая подходит для выполнения различных задач, связанных с NLP. Далее мы рассмотрим три компонента GPT, а именно — Генеративный, Предобученный и Трансформер, и разберёмся, что же они означают.

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

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

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

GPT может с высокой точностью выполнять различные задачи в области NLP в зависимости от больших наборов данных, которые использовались для обучения модели, и её архитектуры, включающей миллиарды параметров, благодаря которой модель может понимать логические связи внутри данных. GPT-модели, включая последнюю версию GPT-3, были предобучены на текстах из пяти больших наборов данных, включая Common Crawl и WebText2. Эти наборы состоят из почти триллиона слов, благодаря чему GPT-3 быстро и без каких-либо примеров данных справляется с задачами NLP.

Обзор GPT-моделей

Модели GPT — это продвинутые модели глубокого обучения, предназначенные для генерации текста, похожего на написанный человеком. Эти модели были разработаны OpenAI, который представил несколько версий: GPT-1, GPT-2, GPT-3 и последний GPT-4.

GPT-1, первый в серии вариант, был представлен в 2018 году на базе уникальной архитектуры Трансформер, что позволило значительно повысить возможности генерации языка. В создании модели использовалось 117 миллионов параметров, а для обучения разработчики взяли сочетание наборов данных из Common Crawl и BookCorpus. GPT-1 в рамках контекста могла генерировать беглый и связный язык. Однако у неё были определённые ограничения, включая склонность к повтору текста и проблемы с построением сложных диалогов и долгосрочными зависимостями.

Затем в 2019 году OpenAI представили GPT-2. Эта модель была намного больше, включала 1,5 миллиарда параметров и была обучена на ещё большем и разнообразном наборе данных. Её главным преимуществом была способность генерировать реалистичные текстовые последовательности и ответы, напоминающие человеческие. Однако в более длинных текстах GPT-2 с трудом придерживалась контекста и согласованности.

Появление GPT-3 в 2020 году стало огромным прорывом. GPT-3 включала целых 175 миллиардов параметров, была обучена на огромных наборах данных и могла помочь в решении самых разных задач. Модель могла генерировать тексты, писать коды, создавать иллюстрации и многое другое, благодаря чему она стала важным инструментом для многих приложений, включая чат-боты и переводчики. Однако GPT-3 не была идеальной и всё же допускала ошибки и неточности.

Вслед за GPT-3 OpenAI представляет обновлённую версию — GPT-3.5. А в марте 2023 выпускает GPT-4. GPT-4 — последняя и наиболее продвинутая из языковых моделей OpenAI, которая к тому же является мультимодальной. Она может генерировать более точные утверждения и обрабатывать входящие изображения, позволяя создавать подписи, классификации и анализы. GPT-4 также справляется с творческими заданиями, например может сочинять песни и сценарии. Она имеет две вариации, отличающиеся размером контекстного окна: gpt-4-8K и gpt-4-32K.

Способность GPT-4 понимать сложные промты и выполнять задачи, практически как человек, одновременно восхищает и пугает. Однако, как и в случае с любым мощным инструментом, высказываются обоснованные опасения по поводу потенциально неправильного использования и этических аспектов. При изучении возможностей и областей применения моделей GPT обязательно стоит учитывать эти факторы.

Какую выбрать GPT-модель

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

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

Однако если ваш проект предполагает более сложные действия, например глубокий анализ объёмного веб-контента, рекомендации материалов для чтения или написание историй, то более подходящим вариантом станет GPT-3. Она может обрабатывать и изучать миллиарды веб-страниц, предоставляя более подробный и сложный результат.

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

В отличие от GPT-3, GPT-1 и GPT-2 — более управляемые модели, которые можно эффективно обучать на меньших наборах данных. Эти версии больше подойдут для проектов с ограниченными наборами данных или для небольших задач.

Что касается GPT-4… Хотя конкретные возможности и требования этой модели пока ещё не известны, вполне возможно, что новая версия сможет обеспечить повышенную производительность и будет требовать ещё больших наборов данных и дополнительных вычислительных мощностей. При выборе подходящей модели для своего проекта всегда учитывайте сложность задачи, имеющиеся мощности и конкретные возможности каждой модели GPT.

Предпосылки для построения модели GPT

Для построения модели GPT необходимы следующие инструменты и ресурсы:

  • Инфраструктура глубокого обучения, например TensorFlow или PyTorch, для развёртывания и обучения модели на больших объёмах данных.
  • Большой объём данных, например тексты из книг, статьи или сайты, для обучения модели конструкциям и структуре языка.
  • Высокопроизводительная вычислительная среда, включающая GPU и TPU, для ускорения процесса обучения.
  • Знание концепций глубокого обучения, таких как нейросети и обработка естественного языка, для разработки и развёртывания модели.
  • Инструменты для предварительной обработки и очистки данных, например Numpy, Pandas или NLTK, которые подготовят данные для ввода в модель.
  • Инструменты для оценки модели, такие как perplexity или BLEU scores, которые позволят измерить её эффективность и внести корректировки.
  • Библиотека NLP, такая как spaCy или NLTK, для разбивания, стемминга и выполнения других связанных с NLP задач.

Кроме того, для построения GPT-модели вы должны понимать следующие концепции глубокого обучения:

  • Нейросети: поскольку модели GPT основаны на нейронных сетях, вы должны чётко понимать механизм их работы и методы развёртывания в рамках глубокого обучения.
  • Обработка естественного языка (NLP): методы NLP широко используются для построения GPT-моделей, разбивания, стемминга и генерации текста. Поэтому необходимо иметь фундаментальное представление об этих методах и их использовании.
  • Трансформеры: GPT-модели работают на базе transformer-архитектуры, поэтому вам необходимо понимание её роли в обработке и генерации языка.
  • Механизмы внимания: для повышения эффективности GPT-модели необходимо понимание того, как работают механизмы внимания.
  • Предобучение: для повышения эффективности модели GPT при выполнении задач NLP необходимо применить концепцию предобучения.
  • Генеративные модели: знание основных концепций и методов генеративных моделей необходимо для понимания их применения при построении GPT-модели.
  • Моделирование языка: GPT-модели работают на основе больших объёмов текстовых данных. Поэтому необходимо чётко понимать принцип моделирования языка, чтобы применять его для обучения GPT-модели.
  • Оптимизация: для оптимизации GPT-модели во время обучения требуется понимание алгоритмов оптимизации, таких как стохастический градиентный спуск.

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

  • Python: самый распространённый язык программирования в сфере глубокого обучения и ИИ. Включает несколько библиотек, например TensorFlow, PyTorch и Numpy, которые используются для построения и обучения GPT-моделей.
  • R: популярный язык программирования для анализа данных и статистического моделирования, который включает несколько пакетов для глубокого обучения и ИИ.
  • Julia: высокоуровневый, высокопроизводительный язык программирования, который хорошо подходит для числовых и научных вычислений, включая глубокое обучение.

Как создать GPT-модель? Пошаговое руководство

В этом разделе с фрагментами кода будут показаны шаги, как создать модель GPT с нуля на базе библиотеки PyTorch и архитектуры transformer. Код разбит на несколько частей и последовательно выполняет следующие задачи:

  • Предварительная обработка данных: первая часть кода предварительно обрабатывает входные текстовые данные, преобразует их в список слов, шифрует каждое слово в уникальное целое число и с помощью метода скользящего окна генерирует последовательность фиксированной длины.
  • Конфигурация модели: эта часть кода определяет параметры конфигурации GPT-модели, включая количество слоёв трансформера, количество потоков внимания, размер скрытых слоёв и размер словаря.
  • Архитектура модели: эта часть кода задаёт архитектуру GPT-модели на базе модулей PyTorch. Модель состоит из embedding слоя, за которым идёт стек слоёв трансформеров, и линейного слоя, который показывает распределение вероятности для следующего слова в последовательности в соответствии со словарём.
  • Цикл обучения: эта часть кода задаёт цикл обучения для GPT-модели. Здесь используется оптимизатор Adam для минимизации потери перекрёстной энтропии между прогнозируемыми и фактическими следующими словами последовательности. Модель обучается на пакетах данных, сгенерированных из предварительно обработанных текстовых данных.
  • Генерация текста: последняя часть кода показывает, как использовать обученную GPT-модель для генерации нового текста. Она задаёт контекст с помощью начальной последовательности и многократно генерирует новые слова путём выборки из распределения вероятностей, которое модель создаёт для следующего слова в последовательности. Сгенерированный текст декодируется обратно в слова и показывается на экране.

Для обучения модели, основанной на transformer-архитектуре, будет использован этот набор данных — https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt. Полный код можно загрузить отсюда.

Этапы построения GPT-модели

Импорт библиотек

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

import torch
import torch.nn as nn
from torch.nn import functional as F

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

Определение гиперпараметров

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

# hyperparameters
batch_size = 16 # how many independent sequences will we process in parallel?
block_size = 32 # what is the maximum context length for predictions?
max_iters = 5000
eval_interval = 100
learning_rate = 1e-3
device = 'cuda' if torch.cuda.is_available() else 'cpu'
eval_iters = 200
n_embd = 64
n_head = 4
n_layer = 4
dropout = 0.0

В данном сегменте кода задаются следующие гиперпараметры:

  • batch_size: этот параметр определяет количество независимых последовательностей, которые во время обучения будут обрабатываться параллельно. Больший объём данных может ускорить обучение, но для этого потребуется больше памяти.
  • block_size: задаёт максимальную длину контекста для прогнозирования. GPT-модель генерирует прогнозы на базе контекста, который она получает в качестве входных данных, а этот параметр задаёт максимальную длину этого контекста.
  • max_iters: задаёт максимальное количество повторений обучения GPT-модели.
  • eval_interval: задаёт количество повторов обучения, после которых будет оцениваться эффективность модели.
  • learning_rate: определяет скорость обучения оптимизатора.
  • device: задаёт устройство (CPU или GPU), на котором будет обучаться GPT-модель.
  • eval_iters: задаёт количество повторов обучения, после которых эффективность модели оценивается и сохраняется.
  • n_embd: задаёт количество embedding-измерений для модели GPT. Слой embedding переводит последовательность входных данных в многомерное пространство, и этот параметр определяет размер этого пространства.
  • n_head: задаёт количество потоков внимания в многопоточном слое внимания GPT-модели. Механизм внимания позволяет модели фокусироваться на конкретных элементах последовательности входных данных.
  • n_layer: задаёт количество слоёв в GPT-модели.
  • dropout: задаёт вероятность прореживания для GPT-модели. Прореживание — это метод регуляризации, который в процессе обучения случайным образом удаляет некоторые узлы нейросети, чтобы предотвратить чрезмерное обучение.

Чтение входных данных

torch.manual_seed(1337)

# wget https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt
with open('input.txt', 'r', encoding='utf-8') as f:
text = f.read()

В этом фрагменте кода с помощью команды torch.manual_seed() разработчик вручную задаёт начальное значение для генератора случайных чисел PyTorch. Это необходимо для обеспечения воспроизводимости результатов GPT-модели. Аргумент torch.manual_seed() представляет собой произвольное число (в данном случае 1337), которое служит начальным значением для генератора случайных чисел. Фиксированное начальное значение позволяет гарантировать, что при каждом запуске кода генерируется одна и та же последовательность случайных чисел. Это гарантирует, что GPT-модель обучается и тестируется на одних и тех же данных.

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

Выявление уникальных символов в тексте

chars = sorted(list(set(text)))
vocab_size = len(chars)

В этом фрагменте кода создаётся словарь для GPT-модели.

Сначала с помощью функции set() и конструктора list() создаётся отсортированный список уникальных символов, присутствующих в текстовых данных. Функция set() возвращает набор уникальных элементов текста, а конструктор list() преобразует этот набор в список. Функция sorted() сортирует список по алфавиту, создавая отсортированный список уникальных символов из текста.

Далее с помощью функции len() мы получаем длину списка символов. Так задаётся количество уникальных символов в тексте и размер словаря для GPT-модели.

Размер словаря — это важный гиперпараметр, который определяет мощность модели GPT. Чем больше словарь, тем более выразительной может быть модель, однако одновременно увеличивается сложность модели и время обучения. Как правило, размер словаря выбирается в зависимости от размера входного текста и характера решаемой задачи.

Сразу после создания словаря можно кодировать символы в текстовых данных в виде целых чисел и передать их в GPT-модель для генерации прогнозов.

Составление схемы соответствия

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

create a mapping from characters to integers
stoi = { ch:i for i,ch in enumerate(chars) }
itos = { i:ch for i,ch in enumerate(chars) }
encode = lambda s: [stoi[c] for c in s] # encoder: take a string, output a list of integers
decode = lambda l: ''.join([itos[i] for i in l]) # decoder: take a list of integers, output a string

print(encode("hii there"))
print(decode(encode("hii there")))

Этот фрагмент кода создаёт схему соответствия символов и целых чисел и наоборот (преобразует целое число в символ) для набора символов. Словарь stoi содержит сопоставление между каждым из символов и уникальными целыми числами, а itos, наоборот, связывает каждое целое число с соответствующим символом. Функция encode принимает строку в качестве входных данных и возвращает список целых чисел, где каждое целое число соответствует индексу символа в наборе символов. Функция decode принимает список целых чисел, находит соответствующие символы в словаре itos и возвращает исходную строку. Затем код проверяет функции кодирования и декодирования путём кодирования строки «hii there» с последующим декодированием полученного списка целых чисел обратно в строку.

Шифрование входных данных

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

let's now encode the entire text dataset and store it into a torch.Tensor
import torch # we use PyTorch: https://pytorch.org
data = torch.tensor(encode(text), dtype=torch.long)
print(data.shape, data.dtype)
print(data[:1000]) # the 1000 characters we looked at earier will to the GPT look like this

Этот код импортирует библиотеку PyTorch и создаёт тензор с именем data. Тензор заполняется закодированными текстовыми данными, которые получаются после применения функции encode для текстовой переменной. Параметру dtype присвоено значение torch.long, которое гарантирует, что элементы тензора являются целыми числами. Код возвращает размер и тип данных в тензоре данных. Атрибут shape сообщает размер тензора в каждом измерении, а атрибут dtype сообщает тип данных элементов тензора. Эта информация необходима для проверки правильности создания тензора и его совместимости с моделью GPT. Затем код возвращает первую 1000 элементов тензора данных, которые представляют собой закодированные текстовые данные. Это необходимо для проверки корректной работы процесса кодирования и правильности загрузки данных в тензор.

Разделение данных на наборы для обучения и проверки

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

# Let's now split up the data into train and validation sets
n = int(0.9*len(data)) # first 90% will be train, rest val
train_data = data[:n]
val_data = data[n:]
block_size = 8
train_data[:block_size+1]
x = train_data[:block_size]
y = train_data[1:block_size+1]
for t in range(block_size):
context = x[:t+1]
target = y[t]
print(f"when input is {context} the target: {target}")

Этот код разбивает закодированные текстовые данные на наборы для обучения и проверки. Первым 90% данных присваивается переменная train_data, а оставшимся 10% присваивается переменная val_data. Код задаёт значение 8 для переменной block_size, которая определяет размер входной последовательности, которую GPT-модель будет обрабатывать за раз. Затем код выбирает часть данных для обучения длиной block_size+1 элемент и присваивает ей значение train_data. Первым элементам block_size train_data присваивается переменная x, а следующим элементам block_size train_data, начиная со второго, присваивается переменная y. Другими словами, y смещается на одну позицию относительно x. Затем код перебирает элементы block_size x и y и возвращает входной контекст и цель для каждой позиции во входной последовательности. Каждому повтору цикла контекстной переменной присваивается значение первых t+1 элементов x, где t принадлежит диапазону от 0 до block_size-1. Целевой переменной присваивается значение t-го элемента y. Затем цикл возвращает сообщение, которое содержит текущий входной контекст и цель.

Генерирование пакетов входных и целевых данных для обучения GPT

torch.manual_seed(1337)
batch_size = 4 # how many independent sequences will we process in parallel?
block_size = 8 # what is the maximum context length for predictions?

def get_batch(split):
# generate a small batch of data of inputs x and targets y
data = train_data if split == 'train' else val_data
ix = torch.randint(len(data) - block_size, (batch_size,))
x = torch.stack([data[i:i+block_size] for i in ix])
y = torch.stack([data[i+1:i+block_size+1] for i in ix])
return x, y

xb, yb = get_batch('train')
print('inputs:')
print(xb.shape)
print(xb)
print('targets:')
print(yb.shape)
print(yb)

print('----')

for b in range(batch_size): # batch dimension
for t in range(block_size): # time dimension
context = xb[b, :t+1]
target = yb[b,t]
print(f"when input is {context.tolist()} the target: {target}")

Этот код задаёт начальное значение для генератора случайных чисел для PyTorch равным 1337, что гарантирует детерминированность и воспроизводимость генерации случайных чисел. Это необходимо для обучения GPT-модели и получения сопоставимых результатов. Код задаёт переменные batch_size и block_size. Переменная batch_size определяет, сколько независимых последовательностей будет обрабатываться параллельно в каждом пакете, а block_size определяет максимальную длину контекста для прогнозов. Затем код задаёт функцию get_batch, которая генерирует небольшой пакет данных из входных данных x и целевых данных y для заданного разделения (либо train, либо val). Сначала на основе разделения входных данных функция выбирает соответствующий набор данных (train_data или val_data). Затем она с помощью torch.randint() случайным образом выбирает начальные значения batch_size для x. Это гарантирует, что каждое начальное значение находится как минимум на расстоянии block_size от конца набора данных, и позволяет избежать выхода за границы набора. Затем код создаёт тензоры x и y, для чего выбираются элементы block_size, начиная с каждого начального значения, и при этом y смещается на одну позицию вправо относительно x. Функция возвращает тензоры x и y в виде кортежа. Для генерации пакета обучающих данных код вызывает функцию get_batch() с аргументом ‘train’. Затем он возвращает форму и содержимое тензоров x и y. После чего перебирает каждый элемент в пакете (измерение batch_size) и каждую позицию во входной последовательности (измерение block_size) и возвращает входной контекст последовательности и целевой объект для каждой позиции. Контекстной переменной присваивается значение первых t+1 элементов xb[b,:], где t принадлежит диапазону от 0 до block_size-1. Целевой переменной присваивается значение t-го элемента yb[b,:]. Затем цикл возвращает сообщение, содержащее текущий входной контекст и цель.

Вычисление средних потерь в наборах данных для обучения и проверки с помощью предобученной модели

@torch.no_grad()
def estimate_loss():
out = {}
model.eval()
for split in ['train', 'val']:
losses = torch.zeros(eval_iters)
for k in range(eval_iters):
X, Y = get_batch(split)
logits, loss = model(X, Y)
losses[k] = loss.item()
out[split] = losses.mean()
model.train()
return out

Этот код задаёт функцию estimate_loss(), которая с помощью предобученной модели вычисляет средние потери в наборах данных для обучения и проверки. Для отключения вычисления градиента во время оценки в коде используется декоратор @torch.no_grad(), а также model.eval(), который переводит модель в режим оценки. Затем код повторяет наборы данных для обучения и проверки eval_iters раз, с помощью предобученной модели вычисляет логиты и потери для каждой партии и записывает их. Наконец, код возвращает средние потери для двух наборов данных и с помощью model.train() возвращает модель в режим обучения. Эта функция полезна для мониторинга эффективности модели в ходе обучения и определения момента завершения обучения.

Определение одного из потоков механизма самовнимания в модели-трансформере

class Head(nn.Module):
""" one head of self-attention """

def __init__(self, head_size):
super().__init__()
self.key = nn.Linear(n_embd, head_size, bias=False)
self.query = nn.Linear(n_embd, head_size, bias=False)
self.value = nn.Linear(n_embd, head_size, bias=False)
self.register_buffer('tril', torch.tril(torch.ones(block_size, block_size)))

self.dropout = nn.Dropout(dropout)

def forward(self, x):
B,T,C = x.shape
k = self.key(x) # (B,T,C)
q = self.query(x) # (B,T,C)
# compute attention scores ("affinities")
wei = q @ k.transpose(-2,-1) * C**-0.5 # (B, T, C) @ (B, C, T) -> (B, T, T)
wei = wei.masked_fill(self.tril[:T, :T] == 0, float('-inf')) # (B, T, T)
wei = F.softmax(wei, dim=-1) # (B, T, T)
wei = self.dropout(wei)
# perform the weighted aggregation of the values
v = self.value(x) # (B,T,C)
out = wei @ v # (B, T, T) @ (B, T, C) -> (B, T, C)
return out

Этот код задаёт модуль Head, который представляет собой один из потоков механизма самовнимания в GPT-модели. Метод __init__ создаёт три линейных слоя (key, query и value), которые используются для проецирования входного тензора x в пространство меньшей размерности, что помогает эффективно вычислять показатели внимания. Прямой метод принимает в качестве входных данных тензор x shape (batch_size, sequence_length, embedding_size) и с помощью механизма скалярного произведения внимания вычисляет показатели самовнимания. Показатели внимания вычисляются с помощью скалярного произведения проекций query и key и нормализации результата на квадратный корень из размера embedding. Затем на полученные показатели внимания накладывается треугольная матрица, предотвращающая обращение к будущим токенам. Далее показатели внимания нормализуются с помощью функции softmax, умножаются на проекцию value и, наконец, агрегируются для получения выходного тензора shape (batch_size, sequence_length, embedding_size). Перед окончательной агрегацией к показателям внимания применяется слой dropout.

Внедрение механизма многопоточного внимания

class MultiHeadAttention(nn.Module):
""" multiple heads of self-attention in parallel """

def __init__(self, num_heads, head_size):
super().__init__()
self.heads = nn.ModuleList([Head(head_size) for _ in range(num_heads)])
self.proj = nn.Linear(n_embd, n_embd)
self.dropout = nn.Dropout(dropout)

def forward(self, x):
out = torch.cat([h(x) for h in self.heads], dim=-1)
out = self.dropout(self.proj(out))
return out

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

Добавление модуля FeedFoward

class FeedFoward(nn.Module):
""" a simple linear layer followed by a non-linearity """

def __init__(self, n_embd):
super().__init__()
self.net = nn.Sequential(
nn.Linear(n_embd, 4 * n_embd),
nn.ReLU(),
nn.Linear(4 * n_embd, n_embd),
nn.Dropout(dropout),
)

def forward(self, x):
return self.net(x)

class Block(nn.Module):
""" Transformer block: communication followed by computation """

def __init__(self, n_embd, n_head):
# n_embd: embedding dimension, n_head: the number of heads we'd like
super().__init__()
head_size = n_embd // n_head
self.sa = MultiHeadAttention(n_head, head_size)
self.ffwd = FeedFoward(n_embd)
self.ln1 = nn.LayerNorm(n_embd)
self.ln2 = nn.LayerNorm(n_embd)

def forward(self, x):
x = x + self.sa(self.ln1(x))
x = x + self.ffwd(self.ln2(x))
return x

Обучение модели и генерирование текста

class BigramLanguageModel(nn.Module):

def __init__(self):
super().__init__()
# each token directly reads off the logits for the next token from a lookup table
self.token_embedding_table = nn.Embedding(vocab_size, n_embd)
self.position_embedding_table = nn.Embedding(block_size, n_embd)
self.blocks = nn.Sequential(*[Block(n_embd, n_head=n_head) for _ in range(n_layer)])
self.ln_f = nn.LayerNorm(n_embd) # final layer norm
self.lm_head = nn.Linear(n_embd, vocab_size)

def forward(self, idx, targets=None):
B, T = idx.shape

# idx and targets are both (B,T) tensor of integers
tok_emb = self.token_embedding_table(idx) # (B,T,C)
pos_emb = self.position_embedding_table(torch.arange(T, device=device)) # (T,C)
x = tok_emb + pos_emb # (B,T,C)
x = self.blocks(x) # (B,T,C)
x = self.ln_f(x) # (B,T,C)
logits = self.lm_head(x) # (B,T,vocab_size)

if targets is None:
loss = None
else:
B, T, C = logits.shape
logits = logits.view(B*T, C)
targets = targets.view(B*T)
loss = F.cross_entropy(logits, targets)

return logits, loss

def generate(self, idx, max_new_tokens):
# idx is (B, T) array of indices in the current context
for _ in range(max_new_tokens):
# crop idx to the last block_size tokens
idx_cond = idx[:, -block_size:]
# get the predictions
logits, loss = self(idx_cond)
# focus only on the last time step
logits = logits[:, -1, :] # becomes (B, C)
# apply softmax to get probabilities
probs = F.softmax(logits, dim=-1) # (B, C)
# sample from the distribution
idx_next = torch.multinomial(probs, num_samples=1) # (B, 1)
# append sampled index to the running sequence
idx = torch.cat((idx, idx_next), dim=1) # (B, T+1)
return idx

model = BigramLanguageModel()
m = model.to(device)
# print the number of parameters in the model
print(sum(p.numel() for p in m.parameters())/1e6, 'M parameters')

# create a PyTorch optimizer
optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)

for iter in range(max_iters):

# every once in a while evaluate the loss on train and val sets
if iter % eval_interval == 0 or iter == max_iters - 1:
losses = estimate_loss()
print(f"step {iter}: train loss {losses['train']:.4f}, val loss {losses['val']:.4f}")

# sample a batch of data
xb, yb = get_batch('train')

# evaluate the loss
logits, loss = model(xb, yb)
optimizer.zero_grad(set_to_none=True)
loss.backward()
optimizer.step()

# generate from the model
context = torch.zeros((1, 1), dtype=torch.long, device=device)
print(decode(m.generate(context, max_new_tokens=2000)[0].tolist()))

С помощью PyTorch этот код задаёт модель языка биграммы, который используется для обучения GPT-модели.

Класс BigramLanguageModel определяется как подкласс nn.Module и включает несколько слоёв, которые используются для построения модели. Метод __init__ задаёт модель с помощью слоя embedding для токенов и отдельным слоем embedding для положения токенов. Кроме того, модель включает последовательность блоков-трансформеров, которые задаются функцией Block, а также последний уровень norm и линейный уровень для вывода логитов следующего токена. Прямой метод принимает входные последовательности и цели, вычисляет вложения, применяет блоки-трансформеры и возвращает логиты следующего токена вместе с потерями в случае, если указаны цели.

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

Основная часть кода отвечает за создание экземпляра класса BigramLanguageModel и его перемещение на указанное устройство. Затем создаётся оптимизатор PyTorch AdamW и начинается цикл обучения. В каждом цикле обучения с помощью функции get_batch из набора для обучения отбирается пакет данных. Затем модель оценивается на основе этого пакета данных, вычисляются потери и с помощью loss.backward() градиенты распространяются в обратном направлении. Наконец, для обновления параметров модели вызывается метод оптимизатора step().

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

Как обучать существующую GPT-модель на своих данных?

В предыдущем разделе было рассказано о том, как создать GPT-модель с нуля. Теперь подробно рассмотрим процесс улучшения уже существующей модели на базе ваших уникальных данных. Этот процесс известен как «тонкая настройка», которая уточняет базовую, или «фундаментальную», модель для конкретных задач или наборов данных. OpenAI предлагает использовать ряд базовых моделей, ярким примером которых является GPT-NeoX. Если вы хотите научиться тонко настраивать GPT-NeoX на основе своих данных, то инструкция ниже точно вам поможет.

Полный код для GPT-NeoX можно загрузить отсюда.

Начальные требования

Прежде чем использовать модель GPT-NeoX, требуется настроить среду и установить зависимости.

Как настроить хост

Для начала убедитесь, что в вашей среде имеется Python 3.8 и подходящая версия PyTorch 1.8 и выше. Имейте в виду, что для GPT-NeoX требуются определённые библиотеки, которые могут быть несовместимы с Python 3.10 и более поздними версиями. Python 3.9, вроде, работает корректно, однако наша кодовая база разработана и протестирована в основном на Python 3.8.

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

pip install -r requirements/requirements.txt
python ./megatron/fused_kernels/setup.py install # optional if not using fused kernels

Используемая база кода основана на DeeperSpeed — пользовательской версии библиотеки DeepSpeed. DeeperSpeed представляет собой специализированный форк библиотеки DeepSpeed от Microsoft, адаптированный к потребностям проекта GPT-NeoX. Он поставляется с дополнительными изменениями, разработанными EleutherAI специально для GPT-NeoX. Также рекомендуется использовать инструмент для изоляции среды, например Anaconda, или виртуальную машину. Это поможет не нарушить работу других репозиториев, зависящих от DeepSpeed.

Flash Attention

Чтобы использовать Flash-Attention, сначала необходимо установить дополнительные зависимости, указанные в ./requirements/requirements-flashattention.txt. Затем в настройках измените тип внимания на необходимый (см. в настройках). Такая модификация может значительно повысить эффективность по сравнению со стандартным вниманием, особенно с учётом архитектуры некоторых GPU, таких как Ampere GPU (например, A100s). Дополнительную информацию можно найти в репозитории.

Контейнерная установка

Если вы предпочитаете контейнерное выполнение, для запуска NeoX можно использовать Dockerfile. Для этого сначала из корневого каталога репозитория создайте образ с именем gpt-neox, используя команду:

docker build -t gpt-neox -f Dockerfile ..

Кроме того, готовый образ можно получить в leogao2/gpt-neox на Docker Hub.

После этого на основе созданного образа можно запускать контейнерную установку. Например, нижеприведённая команда присоединяет каталог клонированного репозитория (gpt-next) к /gpt-neox в контейнере и использует nvidia-docker для открытия доступа к контейнеру четырём GPU (под номерами 0-3).

Применение

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

Вы получаете доступ к трём основным функциям:

  1. train.py — для обучения и точной настройки моделей.
  2. evaluate.py — используйте эту функцию для оценки обученной модели с помощью инструмента оценки языковой модели.
  3. generate.py — эта функция предназначена для выборки текста из обученной модели.

Эти функции запускаются с помощью следующей команды:

./deepy.py [script.py] [./path/to/config_1.yml] [./path/to/config_2.yml] ... [./path/to/config_n.yml]

Например, чтобы сгенерировать текст без ограничений с помощью модели GPT-NeoX-20B, используйте:

./deepy.py generate.py ./configs/20B.yml

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

./deepy.py generate.py ./configs/20B.yml -i prompt.txt -o sample_outputs.txt

Для воспроизведения наших показателей оценки в таких задачах, как Trivia QA и PIGA, используйте:

./deepy.py evaluate.py ./configs/20B.yml --eval_tasks triviaqa piqa

Настройки

Работа GPT-NeoX регулируется параметрами в файле настроек YAML, который необходимо загрузить в программу запуска deepy.py. В папке configs для примера было размещено несколько файлов .yaml, включая один для GPT-NeoX-20B, и примеры настроек для моделей других размеров.

Как правило, эти файлы включают всё необходимое, но не обязательно являются оптимальными. В зависимости от настроек конкретного GPU вам может потребоваться настроить такие параметры, как pipe-parallel-size, model-parallel-size для параллелизма, train_micro_batch_size_per_gpu или gradient-accumulation-steps для корректировки размера пакета, или zero_optimization dict для распараллеливания оптимизатора.

Подробное руководство по доступным функциям и их настройке вы найдёте в инструкции к настройке README. Подробная информация обо всех возможных аргументах находится в configs/neox_arguments.md.

Подготовка данных

Подготовьте текстовые данные в формате, совместимом с моделью GPT-NeoX. Как правило, для этого требуется токенизация с помощью токенизатора, совместимого с GPT-NeoX.

Чтобы обучать модель на персонализированных данных, необходимо отформатировать набор данных в виде большого файла jsonl, где каждый элемент словаря представлен в виде отдельного документа. Текст документа должен располагаться под одним ключом JSON, а именно — “text”. Модель проигнорирует любые дополнительные данные в других полях.

Далее убедитесь, что вы загрузили словарь токенизатора GPT2 и объединили файлы. Это можно сделать по следующим ссылкам:

Словарь: https://s3.amazonaws.com/models.huggingface.co/bert/gpt2-vocab.json

Объединить файлы: https://s3.amazonaws.com/models.huggingface.co/bert/gpt2-merges.txt

Теперь вы готовы к предварительной токенизации данных с помощью скрипта из tools/preprocess_data.py. Ниже вы найдёте объяснение необходимых аргументов для этого скрипта:

usage: preprocess_data.py [-h] --input INPUT [--jsonl-keys JSONL_KEYS [JSONL_KEYS ...]] [--num-docs NUM_DOCS] --tokenizer-type {HFGPT2Tokenizer,HFTokenizer,GPT2BPETokenizer,CharLevelTokenizer} [--vocab-file VOCAB_FILE] [--merge-file MERGE_FILE] [--append-eod] [--ftfy] --output-prefix OUTPUT_PREFIX
[--dataset-impl {lazy,cached,mmap}] [--workers WORKERS] [--log-interval LOG_INTERVAL]

optional arguments:
-h, --help show this help message and exit

input data:
--input INPUT Path to input jsonl files or lmd archive(s) - if using multiple archives, put them in a comma separated list
--jsonl-keys JSONL_KEYS [JSONL_KEYS ...]
space separate listed of keys to extract from jsonl. Defa
--num-docs NUM_DOCS Optional: Number of documents in the input data (if known) for an accurate progress bar.

tokenizer:
--tokenizer-type {HFGPT2Tokenizer,HFTokenizer,GPT2BPETokenizer,CharLevelTokenizer}
What type of tokenizer to use.
--vocab-file VOCAB_FILE
Path to the vocab file
--merge-file MERGE_FILE
Path to the BPE merge file (if necessary).
--append-eod Append an <eod> token to the end of a document.
--ftfy Use ftfy to clean text

output data:
--output-prefix OUTPUT_PREFIX
Path to binary output file without suffix
--dataset-impl {lazy,cached,mmap}
Dataset implementation to use. Default: mmap

runtime:
--workers WORKERS Number of worker processes to launch
--log-interval LOG_INTERVAL
Interval between progress updates

Например:

python tools/preprocess_data.py \
--input ./data/mydataset.jsonl.zst \
--output-prefix ./data/mydataset \
--vocab ./data/gpt2-vocab.json \
--merge-file gpt2-merges.txt \
--dataset-impl mmap \
--tokenizer-type GPT2BPETokenizer \
--append-eod

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

"data-path": "data/mydataset/mydataset",

Обучение и тонкая настройка

Начните обучение с помощью ‘deepy.py’ — оболочки для программы запуска DeepSpeed. Она параллельно выполняет скрипт на нескольких GPU или узлах.

Чтобы ею воспользоваться, выполните:

python ./deepy.py train.py [path/to/config1.yml] [path/to/config2.yml] ...

Вы можете указать любое количество файлов с настройками: при запуске скрипта они будут объединены.

При желании можно добавить префикс config, который является общим путём для всех файлов настроек.

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

python ./deepy.py train.py -d configs 125M.yml local_setup.yml

В данной инструкции сценарий ‘train.py’ выполняется на каждом узле сети, при этом на каждом GPU выполняется один случай сценария. Это означает, что каждый GPU на каждом узле будет отдельно запускать сценарий ‘train.py’. Рабочие узлы и количество GPU задаются в файле ‘/job/hostfile’ (см. документацию по параметрам) или могут быть просто включены в качестве аргумента ‘num_gpus’, если вы настраиваете один узел.

Для лучшей организации рекомендуется задать параметры модели в одном файле настроек (например, ‘configs/125M.yml’), а параметры пути к данным — в другом (например, ‘configs/local_setup.yml’), хотя это не обязательно.

Что необходимо иметь в виду при построении GPT-модели

Избегать предвзятости и токсичности

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

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

Бороться с «галлюцинациями»

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

Предотвращать утечку данных

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

Включать запросы и действия

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


GPT-модели стали важной вехой в истории развития ИИ, которая в свою очередь является частью более обширной тенденции LLM, которая будет развиваться в будущем. Кроме того, инновационное решение OpenAI предоставить доступ к API является частью бизнес-схемы «модель как услуга». Кроме того, языковые возможности GPT позволяют создавать инновационные продукты, ведь такие модели отлично справляются с задачами по обобщению текста, классификации и взаимодействию. Ожидается, что в будущем именно GPT-модели будут формировать интернет и направления использования технологий и программного обеспечения. Может показаться, что построение GPT-модели — сложная задача, но при использовании правильного подхода и инструментов затраченные усилия приносят достойные плоды, открывающие новые возможности для применения NLP.



Great! Next, complete checkout for full access to All-In-One Person
Welcome back! You've successfully signed in
You've successfully subscribed to All-In-One Person
Success! Your account is fully activated, you now have access to all content
Success! Your billing info has been updated
Your billing was not updated