- PVSM.RU - https://www.pvsm.ru -

В этом посте мы реализуем с нуля GPT всего в 60 строках numpy [1]. Затем мы загрузим в нашу реализацию опубликованные OpenAI веса обученной модели GPT-2 и сгенерируем текст.
Примечание:
GPT расшифровывается как Generative Pre-trained Transformer. Этот тип архитектуры нейросети основан на трансформере [5]. Статья How GPT3 Works [6] Джея Аламмара — прекрасное высокоуровневое введение в GPT, которое вкратце можно изложить так:
Большие языковые модели (Large Language Model, LLM) наподобие GPT-3 [7] компании OpenAI, LaMDA [8] компании Google и Command XLarge [9] компании Cohere по своему строению являются всего лишь GPT. Особенными их делает то, что они 1) очень большие (миллиарды параметров) и 2) обучены на множестве данных (сотни гигабайтов текста).
По сути, GPT генерирует текст на основании промпта (запроса). Даже при этом очень простом API (на входе текст, на выходе текст), хорошо обученная GPT способна выполнять потрясающие задачи, например, писать за вас электронные письма [10], составить резюме книги [11], давать идеи подписей к постам в соцсетях [12], объяснить чёрные дыры пятилетнему ребёнку [13], писать код на SQL [14] и даже составить завещание [15].
Это было краткое описание GPT и их возможностей. А теперь давайте углубимся в подробности.
Сигнатура функции GPT выглядит примерно так:
def gpt(inputs: list[int]) -> list[list[float]]:
# вводы имеют форму [n_seq]
# выводы имеют форму [n_seq, n_vocab]
output = # бип-бип, магия нейронной сети
return output
Вводом является последовательность целых чисел, представляющих собой токены некоторого текста:
# целые числа обозначают токены в нашем тексте, например:
# текст = "not all heroes wear capes":
# токены = "not" "all" "heroes" "wear" "capes"
inputs = [1, 0, 2, 4, 6]
Мы определяем целочисленное значение токена на основании вокабулярия токенизатора:
# индекс токена в вокабулярии обозначает целочисленный идентификатор этого токена
# например, целочисленный идентификатор для "heroes" будет равен 2, так как vocab[2] = "heroes"
vocab = ["all", "not", "heroes", "the", "wear", ".", "capes"]
# имитация токенизатора, выполняющая токенизацию по пробелу
tokenizer = WhitespaceTokenizer(vocab)
# метод encode() выполняет преобразование str -> list[int]
ids = tokenizer.encode("not all heroes wear") # ids = [1, 0, 2, 4]
# мы видим реальные токены при помощи сопоставления вокабулярия
tokens = [tokenizer.vocab[i] for i in ids] # tokens = ["not", "all", "heroes", "wear"]
# метод decode() выполняет обратное преобразование list[int] -> str
text = tokenizer.decode(ids) # text = "not all heroes wear"
Вкратце:
На практике используются более сложные методы токенизации, чем простое разбиение по пробелам, например, Byte-Pair Encoding [16] или WordPiece [17], но принцип остаётся тем же:
vocab, сопоставляющий токены строк с целочисленными индексамиencode, выполняющий преобразование str -> list[int]decode, выполняющий преобразование list[int] -> str
Выводом является 2D-массив, в котором output[i][j] — это прогнозируемая вероятность модели того, что токен в vocab[j] является следующим токеном inputs[i+1]. Например:
vocab = ["all", "not", "heroes", "the", "wear", ".", "capes"]
inputs = [1, 0, 2, 4] # "not" "all" "heroes" "wear"
output = gpt(inputs)
# ["all", "not", "heroes", "the", "wear", ".", "capes"]
# output[0] = [0.75 0.1 0.0 0.15 0.0 0.0 0.0 ]
# на основе одного "not" модель с наибольшей вероятностью прогнозирует слово "all"
# ["all", "not", "heroes", "the", "wear", ".", "capes"]
# output[1] = [0.0 0.0 0.8 0.1 0.0 0.0 0.1 ]
# на основе последовательности ["not", "all"] модель с наибольшей вероятностью прогнозирует слово "heroes"
# ["all", "not", "heroes", "the", "wear", ".", "capes"]
# output[-1] = [0.0 0.0 0.0 0.1 0.0 0.05 0.85 ]
# на основе полной последовательности ["not", "all", "heroes", "wear"] модель с наибольшей вероятностью прогнозирует слово "capes"
Чтобы получить прогноз следующего токена для всей последовательности, мы просто берём токен с наибольшей вероятностью в output[-1]:
vocab = ["all", "not", "heroes", "the", "wear", ".", "capes"]
inputs = [1, 0, 2, 4] # "not" "all" "heroes" "wear"
output = gpt(inputs)
next_token_id = np.argmax(output[-1]) # next_token_id = 6
next_token = vocab[next_token_id] # next_token = "capes"
Взятие токена с наибольшей вероятностью в качестве окончательного прогноза часто называют greedy decoding [18] (жадным декодированием) или greedy sampling (жадным сэмплированием).
Задача прогнозирования следующего логичного слова в тексте называется языковым моделированием. Поэтому можно назвать GPT языковой моделью.
Генерировать одно слово — это, конечно, здорово, но как насчёт генерации целых предложений, абзацев и так далее?
Мы можем генерировать законченные предложения, итеративно запрашивая у модели прогноз следующего токена. На каждой итерации мы добавляем спрогнозированный токен к вводу:
def generate(inputs, n_tokens_to_generate):
for _ in range(n_tokens_to_generate): # цикл авторегрессивного декодирования
output = gpt(inputs) # прямой проход модели
next_id = np.argmax(output[-1]) # жадное сэмплирование
inputs = np.append(out, [next_id]) # добавление прогноза к вводу
return list(inputs[len(inputs) - n_tokens_to_generate :]) # возвращаем только сгенерированные id
input_ids = [1, 0] # "not" "all"
output_ids = generate(input_ids, 3) # output_ids = [2, 4, 6]
output_tokens = [vocab[i] for i in output_ids] # "heroes" "wear" "capes"
Этот процесс прогнозирования будущего значения (регрессия) и добавление его обратно во ввод («авто») и стал причиной того, что модель GPT иногда называют авторегрессивной.
Мы можем добавить генерированию стохастичности (случайности), выполняя сэмплирование не жадным образом, а из распределения вероятностей:
inputs = [1, 0, 2, 4] # "not" "all" "heroes" "wear"
output = gpt(inputs)
np.random.choice(np.arange(vocab_size), p=output[-1]) # capes
np.random.choice(np.arange(vocab_size), p=output[-1]) # hats
np.random.choice(np.arange(vocab_size), p=output[-1]) # capes
np.random.choice(np.arange(vocab_size), p=output[-1]) # capes
np.random.choice(np.arange(vocab_size), p=output[-1]) # pants
Это не только позволяет нам генерировать разные предложения по одному вводу, но и повышает качество выводов по сравнению с жадным декодированием.
Также часто используются техники наподобие top-k [19], top-p [20] и temperature [21] для изменения распределения вероятностей перед сэмплированием из него. Это ещё больше увеличивает качество генераций и добавляет гиперпараметры, с которыми можно экспериментировать для получения разного поведения генераций (например, повышение «температуры» увеличивает рискованность модели, делая её более «творческой»).
Если вы хотите почитать анализ других техник сэмплирования для управления генерациями языковых моделей, рекомендую Controllable Neural Text Generation [22] Лиллиан Венг.
Мы обучаем GPT как любую другую нейросеть — при помощи градиентного спуска [23] с учётом некоей функции потерь. В случае GPT мы берём потерю перекрёстной энтропии [24] для задачи языкового моделирования:
def lm_loss(inputs: list[int], params) -> float:
# метки y - это просто input, сдвинутый на 1 влево
#
# inputs = [not, all, heros, wear, capes]
# x = [not, all, heroes, wear]
# y = [all, heroes, wear, capes]
#
# разумеется, у нас нет метки для inputs[-1], поэтому мы исключаем её из x
#
# поэтому для N вводов у нас будет N - 1 примеров пар для языкового моделирования
x, y = inputs[:-1], inputs[1:]
# прямой проход
# все распределения вероятностей спрогнозированных следующих токенов на каждой позиции
output = gpt(x, params)
# потеря перекрёстной энтропии
# мы берём среднее по всем N-1 примерам
loss = np.mean(-np.log(output[y]))
return loss
def train(texts: list[list[str]], params) -> float:
for text in texts:
inputs = tokenizer.encode(text)
loss = lm_loss(inputs, params)
gradients = compute_gradients_via_backpropagation(loss, params)
params = gradient_descent_update_step(gradients, params)
return params
Для понятности мы добавили аргумент params ко вводу gpt. При каждой итерации цикла обучения мы выполняем этап градиентного спуска для обновления параметров модели, делая нашу модель всё лучше и лучше в моделировании языка с каждым новым фрагментом текста, который она видит. Это крайне упрощённая структура обучения, однако она демонстрирует процесс в целом.
Обратите внимание, что мы не используем явным образом размеченные данные. Создавать пары input/label можно и просто из сырого текста. Это называется self-supervised learning [25] (самообучением).
Это означает, что мы можем очень просто масштабировать объём данных обучения, всего лишь показывая модели как можно большее количество сырых текстов. Например, GPT-3 была обучена на 300 миллиардах токенов текста из Интернета и книг:

Таблица 2.2 из статьи о GPT-3
Чтобы учиться на всех этих данных, нам нужна модель существенно бОльших размеров, поэтому GPT-3 имеет 175 миллиардов параметров, а вычислительные затраты на её обучение приблизительно составляют $1-10 миллионов [26]. [Однако после статей про InstructGPT [27] и Chinchilla [28] мы осознали, что на самом деле необязательно обучать столь огромные модели. Оптимально обученная и подстроенная на основе инструкций GPT с 1,3 миллиардами параметров может превзойти GPT-3 с 175 миллиардами параметров.]
Этот этап самообучения называется pre-training (предварительным обучением), поскольку мы можем повторно использовать веса «предварительно обученных» моделей для дальнейшего обучения модели для следующих задач, например, для определения токсичности твита.
Обучение модели на углублённых задачах называется fine-tuning (подстройкой), поскольку веса модели уже были предварительно обучены пониманию языка и всего лишь подстраиваются под конкретную задачу.
Стратегия «предварительное обучение на общей задаче + подстройка на конкретной задаче» называется трансферным обучением [29].
В принципе, исходная GPT [30] просто использовала преимущества предварительного обучения модели трансформера для трансферного обучения, аналогично BERT [31].
И только после появления научных статей о GPT-2 [32] and GPT-3 [33] мы осознали, что предварительно обученная модель GPT сама по себе способна выполнять любую задачу просто после создания промпта и выполнения авторегрессивного языкового моделирования, без необходимости подстройки. Это называется in-context learning (обучением в контексте), поскольку для выполнения задачи модель использует только контекст промпта. Обучение в контексте может быть без примеров (zero shot), с одним (one shot) или несколькими (few shot) примерами:

Иллюстрация 2.1 из научной статьи про GPT-3
Разумеется, можно использовать GPT в качестве чат-бота [34], а не заставлять её выполнять «задачи». История беседы передаётся в модель в качестве промпта, возможно, с каким-то предварительным описанием, например «Ты чат-бот, веди себя хорошо». Если изменить промпт, можно даже придать чат-боту черты личности [35].
Разобравшись со всем этим, можно, наконец, перейти к реализации!
Клонируйте репозиторий для этого туториала:
git clone https://github.com/jaymody/picoGPT
cd picoGPT
Теперь давайте установим зависимости:
pip install -r requirements.txt
Учтите, что если вы работаете на Macbook с M1, то прежде чем выполнять pip install, нужно будет заменить в requirements.txt tensorflow на tensorflow-macos. Этот код тестировался на Python 3.9.10.
Краткое описание каждого из файлов:
encoder.py содержит код BPE Tokenizer компании OpenAI, взятый напрямую из её репозитория gpt-2 [36].utils.py содержит код для скачивания и загрузки весов модели GPT-2, токенизатора и гиперпараметров.gpt2.py содержит саму модель GPT и код генерации, которые можно запускать как скрипт на Python.gpt2_pico.py — это то же самое, что и gpt2.py, но с меньшим количеством строк. Зачем? А почему бы и нет.
Мы заново реализуем gpt2.py с нуля, так что удалим его и воссоздадим как пустой файл:
rm gpt2.py
touch gpt2.py
Для начала вставим в gpt2.py следующий код:
import numpy as np
def gpt2(inputs, wte, wpe, blocks, ln_f, n_head):
pass # TODO: реализовать это
def generate(inputs, params, n_head, n_tokens_to_generate):
from tqdm import tqdm
for _ in tqdm(range(n_tokens_to_generate), "generating"): # цикл авторегрессивного декодирования
logits = gpt2(inputs, **params, n_head=n_head) # прямой проход модели
next_id = np.argmax(logits[-1]) # жадное сэмплирование
inputs = np.append(inputs, [next_id]) # добавляем прогноз к вводу
return list(inputs[len(inputs) - n_tokens_to_generate :]) # возвращаем только сгенерированные id
def main(prompt: str, n_tokens_to_generate: int = 40, model_size: str = "124M", models_dir: str = "models"):
from utils import load_encoder_hparams_and_params
# загружаем encoder, hparams, и params из опубликованных open-ai файлов gpt-2
encoder, hparams, params = load_encoder_hparams_and_params(model_size, models_dir)
# кодируем строку ввода при помощи BPE tokenizer
input_ids = encoder.encode(prompt)
# убеждаемся, что не вышли за пределы максимальной длины последовательности нашей модели
assert len(input_ids) + n_tokens_to_generate < hparams["n_ctx"]
# генерируем id вывода
output_ids = generate(input_ids, params, hparams["n_head"], n_tokens_to_generate)
# декодируем id обратно в строку
output_text = encoder.decode(output_ids)
return output_text
if __name__ == "__main__":
import fire
fire.Fire(main)
Опишем каждую из четырёх частей:
gpt2 — это сам код GPT, который мы должны реализовать. Можно заметить, что наряду с inputs сигнатура функции содержит дополнительные параметры:
wte, wpe, blocks и ln_f — параметры модели.n_head — гиперпараметр, который нужно передавать во время прямого прохода.generate — это алгоритм авторегрессивного декодирования, который мы видели ранее. Для простоты мы пользуемся жадным сэмплированием. tqdm [37] — это шкала прогресса, помогающая визуализировать процесс декодирования, один за другим генерирующего токены.main выполняет следующие задачи:
encoder), веса модели (params) и гиперпараметры (hparams)
fire.Fire(main) [38] просто превращает наш файл в приложение CLI, чтобы мы могли запускать наш код следующим образом: python gpt2.py "здесь какой-то промпт"
Давайте присмотримся к encoder, hparams и params. Выполним в ноутбуке или интерактивной сессии Python следующее:
from utils import load_encoder_hparams_and_params
encoder, hparams, params = load_encoder_hparams_and_params("124M", "models")
При этом в models/124M скачаются необходимые файлы модели и токенизатора [39], а в наш код загрузятся encoder, hparams и params [40].
encoder — это BPE tokenizer, используемый GPT-2:
>>> ids = encoder.encode("Not all heroes wear capes.")
>>> ids
[3673, 477, 10281, 5806, 1451, 274, 13]
>>> encoder.decode(ids)
"Not all heroes wear capes."
При помощи вокабулярия токенизатора (хранящегося в encoder.decoder) мы можем узнать, как выглядят реальные токены:
>>> [encoder.decoder[i] for i in ids]
['Not', 'Ġall', 'Ġheroes', 'Ġwear', 'Ġcap', 'es', '.']
Обратите внимание, что иногда токены — это слова (например, Not), иногда это слова, но с пробелом в начале (например, Ġall (Ġ обозначает пробел [41]), иногда это часть слова (например, слово capes разделено на Ġcap и es), а иногда это знаки препинания (например, .).
BPE удобен тем, что может кодировать любую произвольную строку. Если он встречает то, чего нет в вокабулярии, то просто разбивает это на подстроки, которые понимает:
>>> [encoder.decoder[i] for i in encoder.encode("zjqfl")]
['z', 'j', 'q', 'fl']
Также мы можем проверить размер вокабулярия:
>>> len(encoder.decoder)
50257
Вокабулярий, а также слияния байтовых пар, определяющие способ разбиения строк, получаются обучением токенизатора. Когда мы загружаем токенизатор, то загружаем уже обученный вокабулярий и слияния байтовых пар из каких-то файлов, которые были скачаны вместе с файлами модели, когда мы выполнили load_encoder_hparams_and_params. См. models/124M/encoder.json (вокабулярий) и models/124M/vocab.bpe (слияния байтовых пар).
hparams — это словарь, содержащий гиперпараметры модели:
>>> hparams
{
"n_vocab": 50257, # количество токенов в вокабулярии
"n_ctx": 1024, # максимально возможная длина последовательности ввода
"n_embd": 768, # размерность эмбеддингов (определяет "ширину" сети)
"n_head": 12, # количество голов внимания (n_embd должно делиться на n_head)
"n_layer": 12 # количество слоёв (определяет "глубину" сети)
}
Мы будем использовать эти символы в комментариях к коду, чтобы показать внутреннюю структуру. Также мы будем использовать n_seq для обозначения длины последовательности ввода (например, n_seq = len(inputs)).
params — это вложенный json-словарь, содержащий обученные веса модели. Узлы листьев json — это массивы NumPy. Если мы выведем params, заменив массивы на их shape, то получим следующее:
>>> import numpy as np
>>> def shape_tree(d):
>>> if isinstance(d, np.ndarray):
>>> return list(d.shape)
>>> elif isinstance(d, list):
>>> return [shape_tree(v) for v in d]
>>> elif isinstance(d, dict):
>>> return {k: shape_tree(v) for k, v in d.items()}
>>> else:
>>> ValueError("uh oh")
>>>
>>> print(shape_tree(params))
{
"wpe": [1024, 768],
"wte": [50257, 768],
"ln_f": {"b": [768], "g": [768]},
"blocks": [
{
"attn": {
"c_attn": {"b": [2304], "w": [768, 2304]},
"c_proj": {"b": [768], "w": [768, 768]},
},
"ln_1": {"b": [768], "g": [768]},
"ln_2": {"b": [768], "g": [768]},
"mlp": {
"c_fc": {"b": [3072], "w": [768, 3072]},
"c_proj": {"b": [768], "w": [3072, 768]},
},
},
... # повторяем для n_layers
]
}
Всё это загружается из исходного чекпоинта tensorflow компании OpenAI:
>>> import tensorflow as tf
>>> tf_ckpt_path = tf.train.latest_checkpoint("models/124M")
>>> for name, _ in tf.train.list_variables(tf_ckpt_path):
>>> arr = tf.train.load_variable(tf_ckpt_path, name).squeeze()
>>> print(f"{name}: {arr.shape}")
model/h0/attn/c_attn/b: (2304,)
model/h0/attn/c_attn/w: (768, 2304)
model/h0/attn/c_proj/b: (768,)
model/h0/attn/c_proj/w: (768, 768)
model/h0/ln_1/b: (768,)
model/h0/ln_1/g: (768,)
model/h0/ln_2/b: (768,)
model/h0/ln_2/g: (768,)
model/h0/mlp/c_fc/b: (3072,)
model/h0/mlp/c_fc/w: (768, 3072)
model/h0/mlp/c_proj/b: (768,)
model/h0/mlp/c_proj/w: (3072, 768)
model/h1/attn/c_attn/b: (2304,)
model/h1/attn/c_attn/w: (768, 2304)
...
model/h9/mlp/c_proj/b: (768,)
model/h9/mlp/c_proj/w: (3072, 768)
model/ln_f/b: (768,)
model/ln_f/g: (768,)
model/wpe: (1024, 768)
model/wte: (50257, 768)
Показанный ниже код [42] преобразует представленные выше переменные tensorflow в словарь params.
Для справки вот shape params, где числа заменены на hparams, которые они означают:
{
"wpe": [n_ctx, n_embd],
"wte": [n_vocab, n_embd],
"ln_f": {"b": [n_embd], "g": [n_embd]},
"blocks": [
{
"attn": {
"c_attn": {"b": [3*n_embd], "w": [n_embd, 3*n_embd]},
"c_proj": {"b": [n_embd], "w": [n_embd, n_embd]},
},
"ln_1": {"b": [n_embd], "g": [n_embd]},
"ln_2": {"b": [n_embd], "g": [n_embd]},
"mlp": {
"c_fc": {"b": [4*n_embd], "w": [n_embd, 4*n_embd]},
"c_proj": {"b": [n_embd], "w": [4*n_embd, n_embd]},
},
},
... # повторяем для n_layers
]
}
Вероятно, вы захотите возвращаться к этому словарю, чтобы проверять shape весов в процессе реализации нашей GPT. Для согласованности имена переменных в нашем коде будут соответствовать ключам этого словаря.
Прежде чем мы приступим к самой архитектуре GPT, давайте реализуем часть самых базовых слоёв сети, не специфичных конкретно для моделей GPT.
Для нелинейности (функция активации) выбора GPT-2 используется GELU (Gaussian Error Linear Units) [43], альтернатива ReLU:

Рисунок 1 из научной статьи о GELU
Она аппроксимируется следующей функцией:
def gelu(x):
return 0.5 * x * (1 + np.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * x**3)))
Как и ReLU, GELU обрабатывает ввод поэлементно:
>>> gelu(np.array([[1, 2], [-2, 0.5]]))
array([[ 0.84119, 1.9546 ],
[-0.0454 , 0.34571]])
Использование GELU в моделях-трансформерах популяризировала BERT [31], и я думаю, её продолжат применять ещё долго.
Старый добрый softmax [44]:
def softmax(x):
exp_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
return exp_x / np.sum(exp_x, axis=-1, keepdims=True)
Для числовой стабильности мы используем трюк с max(x) [45].
Softmax применяется для преобразования множества вещественных чисел (от до
) в вероятности (от 0 до 1, где сумма всех чисел равна 1). Мы применяем
softmax для последней оси ввода.
>>> x = softmax(np.array([[2, 100], [-5, 0]]))
>>> x
array([[0.00034, 0.99966],
[0.26894, 0.73106]])
>>> x.sum(axis=-1)
array([1., 1.])
Нормализация слоёв [46] стандартизирует значения так, чтобы они имели среднее значение 0 и дисперсию 1:
где — среднее
,
— дисперсия
, а
и
— изучаемые параметры.
def layer_norm(x, g, b, eps: float = 1e-5):
mean = np.mean(x, axis=-1, keepdims=True)
variance = np.var(x, axis=-1, keepdims=True)
x = (x - mean) / np.sqrt(variance + eps) # нормализуем x, чтобы иметь mean=0 и var=1 по последней оси
return g * x + b # масштабируем и смещаем с параметрами gamma/beta
Нормализация слоёв гарантирует, что вводы для каждого слоя будут находиться в согласованном интервале, что должно ускорить и стабилизировать процесс обучения. Как и при Batch Normalization [47], нормализированный вывод затем масштабируется и смещается при помощи двух изучаемых векторов gamma и beta. Небольшой член epsilon в делителе используется, чтобы избежать ошибку деления на ноль.
По разным причинам [48] в трансформере используется не batch norm, а норма слоёв. Различия между разными методиками нормализации описаны в этом замечательном посте [49].
Мы применяем нормализацию слоёв для последней оси ввода.
>>> x = np.array([[2, 2, 3], [-5, 0, 1]])
>>> x = layer_norm(x, g=np.ones(x.shape[-1]), b=np.zeros(x.shape[-1]))
>>> x
array([[-0.70709, -0.70709, 1.41418],
[-1.397 , 0.508 , 0.889 ]])
>>> x.var(axis=-1)
array([0.99996, 1. ]) # учитываем тонкости работы с плавающей запятой
>>> x.mean(axis=-1)
array([-0., -0.])
Стандартное матричное умножение + перекос:
def linear(x, w, b): # [m, in], [in, out], [out] -> [m, out]
return x @ w + b
Линейные слои часто называют проекциями (поскольку они проецируют из одного векторного пространства в другое).
>>> x = np.random.normal(size=(64, 784)) # input dim = 784, batch/sequence dim = 64
>>> w = np.random.normal(size=(784, 10)) # output dim = 10
>>> b = np.random.normal(size=(10,))
>>> x.shape # shape до линейного проецирования
(64, 784)
>>> linear(x, w, b).shape # shape после линейного проецирования
(64, 10)
Продолжение следует.
Автор:
PatientZero
Источник [50]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/382855
Ссылки в тексте:
[1] 60 строках numpy: https://github.com/jaymody/picoGPT/blob/29e78cc52b58ed2c1c483ffea2eb46ff6bdec785/gpt2_pico.py#L3-L58
[2] как обычно: http://www.incompleteideas.net/IncIdeas/BitterLesson.html
[3] аппаратную лотерею: https://hardwarelottery.github.io
[4] github.com/jaymody/picoGPT: https://github.com/jaymody/picoGPT
[5] трансформере: https://arxiv.org/pdf/1706.03762.pdf
[6] How GPT3 Works: https://jalammar.github.io/how-gpt3-works-visualizations-animations/
[7] GPT-3: https://en.wikipedia.org/wiki/GPT-3
[8] LaMDA: https://blog.google/technology/ai/lamda/
[9] Command XLarge: https://docs.cohere.ai/docs/command-beta
[10] писать за вас электронные письма: https://machinelearningknowledge.ai/ezoimgfmt/b2611031.smushcdn.com/2611031/wp-content/uploads/2022/12/ChatGPT-Demo-of-Drafting-an-Email.png?lossy=0&strip=1&webp=1&ezimgfmt=ng:webp/ngcb1
[11] составить резюме книги: https://machinelearningknowledge.ai/ezoimgfmt/b2611031.smushcdn.com/2611031/wp-content/uploads/2022/12/ChatGPT-Example-Book-Summarization.png?lossy=0&strip=1&webp=1&ezimgfmt=ng:webp/ngcb1
[12] давать идеи подписей к постам в соцсетях: https://khrisdigital.com/wp-content/uploads/2022/12/image-1.png
[13] объяснить чёрные дыры пятилетнему ребёнку: https://machinelearningknowledge.ai/ezoimgfmt/b2611031.smushcdn.com/2611031/wp-content/uploads/2022/12/ChatGPT-Examples-Explaining-Black-Holes.png?lossy=0&strip=1&webp=1&ezimgfmt=ng:webp/ngcb1
[14] писать код на SQL: https://machinelearningknowledge.ai/ezoimgfmt/b2611031.smushcdn.com/2611031/wp-content/uploads/2022/12/ChatGPT-Demo-of-Writing-SQL-Queries.png?lossy=0&strip=1&webp=1&ezimgfmt=ng:webp/ngcb1
[15] даже составить завещание: https://machinelearningknowledge.ai/ezoimgfmt/b2611031.smushcdn.com/2611031/wp-content/uploads/2022/12/Chat-GPT-Example-Writing-a-Will.png?lossy=0&strip=1&webp=1&ezimgfmt=ng:webp/ngcb1
[16] Byte-Pair Encoding: https://huggingface.co/course/chapter6/5?fw=pt
[17] WordPiece: https://huggingface.co/course/chapter6/6?fw=pt
[18] greedy decoding: https://docs.cohere.ai/docs/controlling-generation-with-top-k-top-p#1-pick-the-top-token-greedy-decoding
[19] top-k: https://docs.cohere.ai/docs/controlling-generation-with-top-k-top-p#2-pick-from-amongst-the-top-tokens-top-k
[20] top-p: https://docs.cohere.ai/docs/controlling-generation-with-top-k-top-p#3-pick-from-amongst-the-top-tokens-whose-probabilities-add-up-to-15-top-p
[21] temperature: https://docs.cohere.ai/docs/temperature
[22] Controllable Neural Text Generation: https://lilianweng.github.io/posts/2021-01-02-controllable-text-generation/
[23] градиентного спуска: https://en.wikipedia.org/wiki/Gradient_descent
[24] потерю перекрёстной энтропии: https://www.youtube.com/watch?v=ErfnhcEV1O8
[25] self-supervised learning: https://en.wikipedia.org/wiki/Self-supervised_learning
[26] $1-10 миллионов: https://twitter.com/eturner303/status/1266264358771757057
[27] InstructGPT: https://arxiv.org/pdf/2210.11416.pdf
[28] Chinchilla: https://arxiv.org/pdf/2203.15556.pdf
[29] трансферным обучением: https://en.wikipedia.org/wiki/Transfer_learning
[30] GPT: https://s3-us-west-2.amazonaws.com/openai-assets/research-covers/language-unsupervised/language_understanding_paper.pdf
[31] BERT: https://arxiv.org/pdf/1810.04805.pdf
[32] GPT-2: https://d4mucfpksywv.cloudfront.net/better-language-models/language_models_are_unsupervised_multitask_learners.pdf
[33] GPT-3: https://arxiv.org/abs/2005.14165
[34] GPT в качестве чат-бота: https://openai.com/blog/chatgpt/
[35] чат-боту черты личности: https://imgur.com/a/AbDFcgk
[36] репозитория gpt-2: https://github.com/openai/gpt-2/blob/master/src/encoder.py
[37] tqdm: https://www.google.com/search?q=tqdm
[38] fire.Fire(main): https://github.com/google/python-fire
[39] скачаются необходимые файлы модели и токенизатора: https://github.com/jaymody/picoGPT/blob/a750c145ba4d09d5764806a6c78c71ffaff88e64/utils.py#L13-L40
[40] загрузятся encoder, hparams и params: https://github.com/jaymody/picoGPT/blob/a750c145ba4d09d5764806a6c78c71ffaff88e64/utils.py#L68-L82
[41] Ġ обозначает пробел: https://github.com/karpathy/minGPT/blob/37baab71b9abea1b76ab957409a1cc2fbfba8a26/mingpt/bpe.py#L22-L33
[42] Показанный ниже код: https://github.com/jaymody/picoGPT/blob/29e78cc52b58ed2c1c483ffea2eb46ff6bdec785/utils.py#L43-L65
[43] GELU (Gaussian Error Linear Units): https://arxiv.org/pdf/1606.08415.pdf
[44] softmax: https://en.wikipedia.org/wiki/Softmax_function
[45] трюк с max(x): https://jaykmody.com/blog/stable-softmax/
[46] Нормализация слоёв: https://arxiv.org/pdf/1607.06450.pdf
[47] Batch Normalization: https://arxiv.org/pdf/1502.03167.pdf
[48] разным причинам: https://stats.stackexchange.com/questions/474440/why-do-transformers-use-layer-norm-instead-of-batch-norm
[49] этом замечательном посте: https://tungmphung.com/deep-learning-normalization-methods/
[50] Источник: https://habr.com/ru/post/716902/?utm_source=habrahabr&utm_medium=rss&utm_campaign=716902
Нажмите здесь для печати.