Глава 1 из 14 30 мин
Считаем буквы
Самая простая языковая модель на свете — таблица: какая буква за какой идёт в 41 миллионе букв детских историй. Соберём её и дадим ей что-нибудь написать.
В этой главе
- превратить текст в числа — ровно такие, какие видит модель
- посчитать пары букв и получить из счёта условные вероятности
- вытянуть случайную букву из распределения с помощью кубика с перевесом — и заставить таблицу писать
В прошлой главе мы договорились, чем занимается языковая модель: смотрит на текст и раздаёт вероятности всем возможным продолжениям. Росток делает это с помощью 17 миллионов чисел и трансформера. А мы зайдём с другого конца — с самой простой модели, которая ещё заслуживает этого названия. В ней нет нейронов, нет обучения и нет хитрой математики. Она только считает.
Идея такая. Учитывать всё, что было раньше, трудно, поэтому схитрим и посмотрим только на последнюю букву. Допустим, текст пока заканчивается на «t». Что дальше? Чутьё у вас уже есть: часто «h» (the, that, then), иногда пробел, иногда «o». Такому правилу вас никто не учил — вы просто много читали. Компьютер тоже умеет много читать, а вести счёт у него получается куда лучше нашего.
Буквы превращаются в числа
Компьютер не знает, что такое буква. Он знает числа. Поэтому первым делом любая языковая модель — от нашей таблички до GPT — договаривается об алфавите и выдаёт каждому символу номер.
В первых главах курса алфавит Ростка состоит из 97 символов. Это 95 печатных знаков обычной английской клавиатуры (пробел, знаки препинания, цифры, заглавные и строчные латинские буквы), перевод строки и одна особая метка под номером 0. Мы будем рисовать её так: ◆. Она стоит на границе текстов — перед первой буквой каждой истории и после последней. Благодаря ей модель узнаёт, как начинаются истории, и может сказать «на этом всё».
Напечатайте что-нибудь в поле и посмотрите, во что текст превращается на входе модели.
Почему буквы, а не целые слова?
Можно было бы дать собственный номер каждому слову, и тогда «cat» был бы для модели одним символом, а не тремя. Но список всех слов огромен и никогда не полон: каждому имени, опечатке и новому слову нужна своя строчка, а слово, которого нет в списке, модель вообще не сможет прочитать. С буквами наоборот: 97 символов покрывают любой английский текст, зато модели приходится больше трудиться, чтобы разглядеть в них слова. Взрослый Росток пользуется компромиссом — частыми кусочками текста, токенами; им посвящена глава 6. А для первых глав буквы идеальны: таблицы получаются достаточно маленькими, чтобы рассмотреть их целиком.
Заметим две вещи. Во-первых, «a» и «A» — разные символы, 67 и 35, и для модели они различаются ровно так же, как «a» и «?». Ничто не подсказывает ей, что одна буква — заглавная форма другой; если эта связь важна, модель должна найти её в данных сама. Во-вторых, сами номера ничего не значат. Из того, что у «a» номер 67, не следует, что она «больше» «A». Это просто имена, как номера кресел в театре: их можно было раздать в любом другом порядке.
Кто за кем ходит
Начнём с совсем маленького примера — со слова banana. Поставим по краям метки, ◆banana◆, и выпишем все пары соседей: ◆b, ba, an, na, an, na, a◆. Семь пар. Теперь сгруппируем их по первой букве:
| после… | шла… |
|---|---|
| ◆ | b — один раз |
| b | a — один раз |
| a | n — два раза, ◆ — один |
| n | a — два раза |
Вот и всё: эта табличка — полноценная языковая модель слова banana. Она знает, что слово начинается с «b», что после «n» всегда идёт «a», а после «a» — либо «n», либо конец.
Теперь сделаем то же самое, только всерьёз. Мы взяли 41,3 миллиона букв из историй и диалогов, на которых учится Росток, — всего 39 184 текста — и посчитали все пары соседей. Таблица, где у каждого из 97 символов есть строка и столбец, состоит из 97 × 97 = 9 409 клеток. Вот она. Строка — предыдущая буква, столбец — следующая; чем ярче клетка, тем вероятнее пара.
Посидите над картой минуту — в ней полно маленьких историй про английский язык.
- После q почти всегда идёт u. Из 13 061 буквы «q» за 13 003 следовала «u» — 99,6%. Строка «q» почти пуста: одна яркая клетка и несколько бледных точек.
- Самая частая пара — «e␣», то есть «e» на конце слова: 1 312 444 раза, 3,2% всех пар. Следом идут «he» и «␣t». Узнаёте «the»?
- У знаков препинания свои привычки. После запятой в 96,5% случаев стоит пробел. После заглавной T в 82,3% случаев идёт «h»: The, They, There.
- Четверть всех историй начинается с заглавной O. В строке ◆ у буквы «O» 25,4%: «Once upon a time…», «One day…».
- Большая часть клеток пуста. 7 051 пара из 9 409 не встретилась ни разу — три четверти таблицы. Даже среди строчных букв и пробела не бывает 136 пар из 729: «jj», «qa», «fq»… Нет и двух пробелов подряд: корпус перед обучением почистили, и двойные пробелы пропали.
Сам подсчёт устроен до неприличия просто. Вот он на чистом Python, на тексте, который можно проверить глазами. Нажмите «Запустить» — а потом замените текст своим.
zip(text, text[1:]) — изящный приём: он идёт одновременно по тексту и по его копии, сдвинутой на одну букву, так что на каждом шаге мы получаем букву вместе с её правым соседом.
От счёта к вероятностям
Счёт — ещё не модель. Пара «th» встретилась 829 367 раз. Это много? Зависит от того, сколько у неё было шансов, то есть сколько всего было букв «t». А их было 2 702 151. Значит, примерно за 31 «t» из каждых ста шла «h». Такая доля — это и есть вероятность: как часто что-то случилось по сравнению с тем, сколько раз могло случиться.
Нас интересует вероятность следующей буквы $b$ при условии, что предыдущая была $a$. Записывают её как $p(b \mid a)$, а по нашей таблице она считается элементарно:
$$p(b \mid a) = \frac{\text{count}(a, b)}{\text{count}(a)}$$Сверху — сколько раз встретилась пара $a b$, снизу — сколько раз встретилась $a$ вообще (с чем угодно после неё). Для нашей «t»: $p(\text{h} \mid \text{t}) = 829\,367 / 2\,702\,151 \approx 0{,}307$. Для banana: $p(\text{n} \mid \text{a}) = 2/3$, а $p(\blacklozenge \mid \text{a}) = 1/3$.
Такая вероятность называется условной: мы спрашиваем не о том, насколько вероятна «h» вообще, а о том, насколько она вероятна при условии, что только что была «t». Разница огромная. Среди всех 41,3 миллиона пар на «th» приходится всего 2,0%. Но стоит нам узнать, что перед нами «t», — шанс увидеть «h» подскакивает до 30,7%. Знание о том, что было раньше, — ровно то, ради чего языковая модель существует.
Биграммная модель — это просто таблица. Строка — предыдущая буква, числа в строке — вероятности следующей, и каждая строка в сумме даёт единицу.
(«Биграмма» значит «две буквы»: модель смотрит только на пары. Таблицы по трём буквам называют триграммами, а в общем случае — n-граммами.)
Есть одна загвоздка. Ноль в таблице означает «невозможно». Но так ли невозможна пара «jj»? Слово «hajj» существует — просто не в детских сказках. Модель, которая клянётся, что чего-то не бывает, окажется в большой беде, как только это всё-таки случится. Поэтому настоящая таблица Ростка перед делением добавляет единицу в каждую клетку:
$$p(b \mid a) = \frac{\text{count}(a, b) + 1}{\text{count}(a) + 97}$$Это называется сглаживанием Лапласа (или «плюс один»). Для частых пар оно ничего не меняет: 829 368 / 2 702 248 — всё те же 30,7%. Зато ни одна пара больше не запрещена наглухо. Почему это так важно, станет ясно в следующей главе: там ноль превратится в бесконечность.
После «q» буква «u» встретилась 13 003 раза, сама «q» — 13 061 раз, а всего в корпусе 41,3 миллиона букв. Чему равна p(u | q)?
Условную вероятность делят на число шансов — сколько раз встретилась «q», а не на размер всего корпуса. И это не ровно единица: 25 раз после «q» стояло двоеточие — в диалогах героев по имени Eriq, Tariq и Tyriq («Tariq: So…»), — и ещё несколько раз другие символы.
Кубик с перевесом
Таблица говорит, насколько вероятна каждая следующая буква. Но чтобы она что-нибудь написала, букву надо выбрать. Можно всегда брать самую вероятную — но тогда после «t» всегда будет «h», после «h» всегда «e», после «e» всегда пробел… и выйдет бесконечное «the the the the». Нам нужно выбирать случайно, но честно: после «t» брать «h» три раза из десяти, пробел — два с половиной раза из десяти, и так далее.
Это кубик с перевесом: у каждой грани свой вес. После «h» у такого кубика 42 грани (столько разных символов вообще встречалось после «h»), и одна грань «e» занимает больше половины. Но как компьютеру бросить такой кубик? Он умеет только одно — выдавать случайное число $u$, равномерно распределённое между 0 и 1.
Хитрость в том, чтобы уложить вероятности друг за другом на отрезке от 0 до 1. Первая буква получает кусок от 0 до $p_1$, вторая — от $p_1$ до $p_1 + p_2$, и так далее: границы кусков — это накопленные суммы вероятностей. Теперь роняем на отрезок стрелку $u$. Она попадает в каждый кусок с шансом, в точности равным его длине, то есть вероятности буквы. Кубик брошен.
Бросьте несколько раз вручную, а потом нажмите «×10 000». Столбики подползут вплотную к чёрточкам: при большом числе бросков частота исхода приближается к его вероятности. Это закон больших чисел — и заодно причина, по которой частоты, посчитанные по 41 миллиону букв, вообще можно считать вероятностями.
На Python весь кубик умещается в несколько строк. В стандартной библиотеке есть и готовый вариант — random.choices, который проходит по тем же накопленным суммам. Эта ячейка пользуется парами, посчитанными в предыдущей: все ячейки страницы живут в одном сеансе Python, как в блокноте Jupyter.
Буква за буквой
Теперь у нас есть всё, чтобы писать. Начинаем с метки ◆ и бросаем кубик строки ◆ — получаем первую букву. Потом бросаем кубик строки этой буквы — и так далее. Каждая новая буква выбирает кубик для следующего броска. Когда выпадает ◆, история окончена.
Получается лепет — но любопытный лепет. Один из наших запусков начался так: «Frthendd, aced. tre sedsck.. oale, ateyinet thee prerootosal s thu fear frime». У «слов» правдоподобная длина, и между ними пробелы. После точки — пробел и часто заглавная буква. Кавычки открываются и закрываются, мелькают «the» и «he». Смысла, конечно, никакого: модель помнит одну-единственную букву и не знает, где она — посреди слова или в конце фразы. Увидев «h», она понятия не имеет, пришла эта «h» из «th» или из «sh».
И всё же остановитесь на секунду. Никто не объяснял таблице, где ставить пробелы, что предложение начинается с заглавной буквы и что «e» — самая частая буква. Всё это нашлось в подсчёте само.
Вся таблица в numpy
Словари хороши для одной фразы, а для 97 × 97 чисел нужна настоящая таблица — массив numpy. Ячейка ниже загружает те самые числа, что вы видели на карте (22 КБ JSON), превращает их в вероятности с единицей Лапласа и пишет несколько сотен букв.
Вся нормировка — строчка с P. Выражение (N + 1).sum(1, keepdims=True) складывает каждую строку и оставляет результат столбцом из 97 чисел; деление таблицы 97 × 97 на такой столбец делит каждую строку на её собственную сумму (в numpy это называется broadcasting, «растягивание»). А rng.choice(97, p=…) — наш кубик с перевесом: внутри он проходит по тем же накопленным суммам.
А вот функция, которая построила настоящую таблицу Ростка, — из кода курса, файл snapshots.py. Она читает 41,3 миллиона букв, одной строкой считает все пары через np.add.at (для каждой позиции прибавляет единицу в клетку «эта буква, следующая буква»), нормирует с единицей Лапласа и сохраняет результат в файл модели, который умеет запускать браузер.
Две строчки в ней пока выглядят загадочно. Таблица хранится в виде логарифмов вероятностей, np.log(probs), а в конце печатается какая-то «loss» — 2,3650 «ната на букву». И то и другое — тема следующей главы: это число и есть ответ на вопрос «насколько модель хороша».
Чего не видно с одной буквы
Наша таблица честная и быстрая, но у неё жёсткий предел: она помнит одну букву. Напрашивается очевидное — считать контексты подлиннее. Будем смотреть на две предыдущие буквы — и таблице понадобится строка на каждую пару букв: 97 × 97 строк по 97 столбцов, 912 673 клетки. Три буквы контекста — уже 88,5 миллиона клеток, больше, чем 41,3 миллиона букв, которые мы посчитали. Восемь букв — около $7{,}6 \cdot 10^{17}$ клеток.
И дело не только в памяти. Почти все такие клетки останутся пустыми: большинство сочетаний из восьми букв не встречается ни в одном корпусе, даже огромном, а про последовательность, которую таблица не видела в точности, ей нечего сказать. Нам нужна модель, которая умеет обобщать: видела «the cat sat» и «the dog sat» — и подозревает, что «the fox sat» тоже годится. Счётом такого не добиться. Нейросети — могут, и именно к ним ведут ближайшие главы.
Почему нельзя просто посчитать таблицу с восемью буквами контекста — так же, как мы посчитали с одной?
Сам подсчёт как раз быстрый: окон столько же, сколько букв, и на каждое уходит одно прибавление. Память — проблема настоящая, но даже бесконечная память не спасёт: при $7{,}6 \cdot 10^{17}$ клетках и всего лишь миллиардах букв текста почти все клетки останутся пустыми. Нужна модель, которая переносит выученное с похожих контекстов, а не ищет точное совпадение.
Остался открытым ещё один вопрос. Мы посмотрели на лепет и сказали: «вроде похоже на английский». Это впечатление, а не измерение. Как выразить качество модели числом — чтобы сравнивать две модели или видеть, как модель становится лучше во время обучения? Об этом следующая глава.
Росток сейчас
Росток впервые заговорил — пока лепетом. Сейчас он — таблица из этой главы, 9 409 вероятностей, и работает прямо у вас в браузере. Каждую букву он выбирает, бросая новый кубик с перевесом. Начало можно задать любое, но важна только его последняя буква: «Once upon a time» и «I like» стартуют с одного и того же кубика — кубика «e». Попробуйте заготовку «Q»: в девяти случаях из десяти следом придёт «u». В следующей главе мы точно измерим, насколько эта модель плоха, — и получим число, которое обучение будет толкать вниз.