Глава 2 из 17 35 мин
Мера удивления
Как сравнить два продолжателя текста? Научимся измерять качество предсказаний и разберём, что означают функция потерь, биты и перплексия.
В этой главе
- измерить, насколько модель удивлена каждой буквой настоящего текста
- понять, почему вероятности не перемножают, а складывают их логарифмы, и что такое биты и наты
- посчитать функцию потерь (кросс-энтропию) и перплексию — и узнать, ниже какого предела она не опустится
Наша таблица уже пишет что-то вроде «Frthendd, aced. tre sedsck». Теперь хочется понять, стала ли следующая версия лучше. Одного впечатления «похоже на английский» недостаточно: для обучения нужна оценка, которую программа сможет вычислять после каждого шага. В этой главе выберем такую оценку и разберём, что именно она измеряет.
Возьмём текст, который модель не использовала при обучении, и будем открывать его по одному символу. Перед каждым открытием спросим модель, какую вероятность она дала настоящему продолжению. Чем выше эти вероятности в среднем, тем лучше она предсказывает выбранный текст. Для краткости будем говорить, что модель меньше удивляется. Это математическая метафора: мы измеряем качество предсказания, а не чувства модели и не достоверность её ответов.
Удивлённый Росток
Попробуем пока без формул. Ниже — Росток в его нынешнем виде, буквенная таблица из прошлой главы. Напишите что угодно по-английски. Каждая буква окрашена по тому, насколько она удивила таблицу: бледная — её ждали, тёмная — как снег на голову. Стрелка показывает среднее по всему тексту. Нажмите на любую букву, чтобы увидеть, чего таблица ждала вместо неё.
Поиграйте с заготовками. Обычная фраза из сказки даёт в среднем около 2,3; самая тёмная буква в ней — заглавная L в «Lily»: имена предсказывать трудно. Буква «u» после «q» почти ничего не стоит. Панграмма про лису заметно труднее: в ней редкие «q», «x» и «z». Абракадабра уходит к 6,4 — далеко за отметку «наугад»: таблица тут не просто не уверена, она уверенно ошибается. КАПСЛОК ещё хуже, 7,8: в детских сказках заглавная почти никогда не идёт за заглавной. Русский текст латиницей где-то посередине, около 3,5: буквы настоящие, а вот пары для английского непривычные.
Цвета уже многое рассказывают. Теперь превратим их в математику.
Вероятность всего текста
Самый естественный способ оценить модель на тексте — спросить, какую вероятность она даёт всему тексту. Для нашей таблицы это легко посчитать. Шанс, что история начнётся с «O», равен $p(\text{O} \mid \blacklozenge) = 0{,}253$. Шанс, что следом придёт «n», если была «O», — $0{,}730$. Буквы идут одна за другой, поэтому вероятности перемножаются:
$$\begin{aligned} p(\text{текст}) &= p(x_1 \mid \blacklozenge) \cdot p(x_2 \mid x_1) \cdots \\ &= \prod_{t=1}^{N} p(x_t \mid x_{t-1}), \qquad x_0 = \blacklozenge \end{aligned}$$(Большая $\prod$ значит «перемножить всё от $t = 1$ до $N$» — так же, как $\sum$ значит «сложить всё».) Это число называется правдоподобием текста при данной модели. Если сравнить две модели на одном и том же настоящем тексте, лучше та, что даёт ему большую вероятность: она этот текст предвидела.
Посчитаем для «Once upon a time». Шестнадцать вероятностей: 0,253, 0,730, 0,023, 0,156, 0,313, 0,008 (это «u» из «upon»: слова после пробела редко начинаются с u)… и их произведение — около $8{,}0 \cdot 10^{-17}$. Для фразы из шестнадцати букв это уже очень маленькое число. Для 45 букв «Once upon a time, there was a dog named Spot.» — $8{,}0 \cdot 10^{-47}$. А для целой истории?
У чисел в компьютере ограничен диапазон. Наименьшее положительное число формата float64 — примерно $5 \cdot 10^{-324}$; достаточно малый результат округлится до нуля. В данном примере это происходит на 324-м символе. В формате float32 диапазон уже, и здесь произведение обнуляется на 45-м символе. Современные модели используют разные форматы вычислений, но проблема та же: по обнулённым произведениям уже нельзя сравнить вероятности длинных текстов.
Логарифм спешит на помощь
Выход даёт инструмент из школьной программы — логарифм. Его самое полезное свойство: он превращает умножение в сложение.
$$\log(a \cdot b) = \log a + \log b$$Значит, вместо крошечного произведения можно хранить сумму логарифмов вероятностей:
$$\log p(\text{текст}) = \sum_{t=1}^{N} \log p(x_t \mid x_{t-1})$$Слагаемые — обычные умеренные числа, и сумма тоже. Для «Once upon a time» это −37,1 вместо $8{,}0 \cdot 10^{-17}$; для всей истории из 706 букв — около −1618 (само произведение было бы около $3 \cdot 10^{-703}$), а −1618 помещается в любой компьютер. И ничего не теряется: логарифм — возрастающая функция, у большей вероятности всегда больший логарифм, так что сравнение моделей по сумме логарифмов даёт тот же вердикт, что и по вероятностям. Это розовая линия в виджете выше: она спокойно идёт вниз и после того, как оба произведения упёрлись в дно.
Остался косметический шаг. Вероятности не больше единицы, так что их логарифмы — ноль или отрицательные числа. Говорить об отрицательных неудобно, поэтому поменяем знак и дадим результату имя. Удивление от события с вероятностью $p$ — это
$$s = -\log p$$Оно ведёт себя ровно так, как положено удивлению. Событие, в котором мы были уверены ($p = 1$), удивляет на ноль. Чем событие менее вероятно, тем удивление больше, а то, что мы считали невозможным ($p \to 0$), удивляет бесконечно. И удивление от двух событий подряд — сумма их удивлений: логарифм превратил произведение в сумму. (В теории информации ту же величину называют собственной информацией.)
Биты и наты
Какой логарифм брать? Годится любой — они отличаются лишь постоянным множителем, — но по традиции берут один из двух, и у каждого своя единица удивления.
- По основанию 2 удивление измеряется в битах. Один бит — удивление от того, что честная монетка упала орлом: $-\log_2 \tfrac12 = 1$. Три орла подряд, вероятность $\tfrac18$, — 3 бита.
- С натуральным логарифмом $\ln$ (основание $e \approx 2{,}718$) единица называется нат. PyTorch и почти всё машинное обучение пользуются натуральным логарифмом, поэтому функцию потерь почти всегда указывают в натах.
Переводить просто: $1\ \text{нат} = 1/\ln 2 \approx 1{,}443$ бита, а $1\ \text{бит} \approx 0{,}693$ ната. Виджет ниже помогает почувствовать цену неожиданности: ведите точку по кривой и смотрите, сколько орлов подряд удивили бы вас так же.
Несколько ориентиров, которые стоит запомнить. Угадать один из 97 символов совсем наугад стоит $\log_2 97 \approx 6{,}6$ бита, или $\ln 97 \approx 4{,}575$ ната, — это отметка «наугад» на счётчике. У «x» после «e» вероятность 0,87%, это 6,8 бита — даже больше, чем стоит догадка наугад. А «u» после «q» — 98,8% в сглаженной таблице Ростка — стоит всего 0,017 бита.
Среднее удивление — это и есть функция потерь
Теперь у нас есть всё. Сумма удивлений растёт вместе с длиной текста, а тексты бывают разной длины, поэтому делим на число букв. Среднее удивление на букву — та самая оценка, которую мы искали:
$$L = \frac{1}{N} \sum_{t=1}^{N} -\ln p(x_t \mid x_{t-1})$$Это наша функция потерь (loss): число, которое обучение будет стараться уменьшить. Конкретно здесь используется средний отрицательный логарифм вероятности наблюдаемых символов. Его также называют эмпирической кросс-энтропией: он оценивает, насколько прогноз модели расходится с данными. Другие задачи могут требовать других функций потерь. Связь с энтропией разберём в конце главы.
У нашей биграммы, измеренной на 4,9 миллиона букв текста, которого она никогда не видела (его заранее отложили в сторону), функция потерь равна 2,364 ната на букву, или 3,41 бита. Вот тот же расчёт в нескольких строчках Python — на одной фразе:
Последняя строка — наш старый враг: 400 букв средней вероятности перемножаются в точный ноль. А вот как функцию потерь настоящей таблицы считает код курса — последние две строчки bigram() из snapshots.py, которые мы в прошлой главе оставили без объяснений:
probs[ids[:-1], ids[1:]] выбирает вероятность фактического следующего символа на каждой позиции; -np.log превращает её в удивление, а .mean() берёт среднее. Сумма этих удивлений — NLL, negative log-likelihood, отрицательный логарифм правдоподобия; наш loss — NLL, поделённый на число предсказаний. На обучающем тексте таблица получает 2,3650 ната на символ, на отложенном — 2,364. Близость значений говорит, что в этом опыте она примерно одинаково предсказывает оба набора. Это не общее доказательство того, что маленькая модель не может переобучиться: многое зависит и от объёма данных, и от их сходства.
Потери в битах — это ещё и размер архива
У потерь в битах есть практический смысл. Арифметический кодер может использовать вероятности модели, чтобы записать текст с длиной, близкой к суммарному удивлению на нём. Если средние потери равны 3,41 бита на символ, длинный текст из такого же источника можно кодировать примерно с этой затратой вместо 8 бит на ASCII-символ. Но это оценка размера закодированного текста, а не всего архива: получателю также нужна модель, и её хранение имеет свою цену. Для короткого текста эта цена может перевесить экономию.
Перплексия: сколько вариантов на самом деле
Наты трудно сопоставить с привычными величинами. Поэтому часто показывают $e^L$ — перплексию. Если модель каждый раз равномерно выбирает из $k$ вариантов, её loss равен $\ln k$, а перплексия — ровно $k$. Для неравномерных прогнозов это эквивалентное число равновероятных вариантов с теми же средними потерями. Это не буквальное количество токенов, которое модель оставила на выбор: вероятности могут быть распределены по всему словарю.
Угадывание наугад из 97 символов: перплексия 97. Наша биграмма: $e^{2{,}364} \approx 10{,}6$. Знание одной предыдущей буквы сужает выбор с 97 символов примерно до десяти с половиной. Для 9 409 чисел — очень неплохо.
Функция потерь — это среднее удивление: минус логарифм вероятности, которую модель дала тому, что пришло на самом деле, усреднённый по настоящему тексту. Чем меньше, тем лучше, а $e^{\text{loss}}$ — из скольких вариантов модель по сути выбирает.
Лестница моделей
Число что-то значит только в сравнении. Вот три модели, которые легко построить; каждая знает чуть больше предыдущей:
- Наугад: не знает ничего и даёт каждому из 97 символов $1/97$. Её потери — $\ln 97 \approx 4{,}575$ на любом тексте.
- Частоты букв (униграмма): знает, какие буквы часты («e» — часто, «z» — редко), но никогда не смотрит назад. Это наша таблица, свёрнутая в одну строку.
- Биграмма: наша таблица, память на одну букву.
Все три измерены на одном и том же отложенном тексте в 4,9 миллиона букв — вместе с двумя моделями из будущих глав:
| модель | потери, наты/букву | биты/букву | перплексия |
|---|---|---|---|
| наугад из 97 символов | 4,575 | 6,60 | 97 |
| частоты букв (униграмма) | 3,084 | 4,45 | 21,9 |
| биграмма (эта модель) | 2,364 | 3,41 | 10,6 |
| нейросеть, 3 буквы контекста (глава 4) | 1,479 | 2,13 | 4,4 |
| нейросеть, 8 букв контекста (глава 5) | 1,103 | 1,59 | 3,0 |
Каждая ступенька вниз — модель, которая поняла про английский ещё немного больше. Проверьте свой текст сразу на всех — и позовите большого, настоящего Ростка. Он читает текст не по буквам, а токенами — кусочками примерно по четыре буквы. Чтобы поставить его на ту же шкалу, мы складываем его удивление по всему тексту и делим на число букв. (На отложенном тексте на один токен приходится 4,085 буквы, так что потери $L$ на токен — это примерно $L / 4{,}085$ на букву.)
Посмотрите, как перемещаются тёмные пятна. Модель «наугад» удивлена всем одинаково. Униграмма удивляется редким буквам, где бы они ни стояли. Биграмма прощает редкие буквы в привычной компании («u» после «q»), зато спотыкается на первой букве каждого слова. А большая модель, которая помнит сотни токенов, в идеале удивляется только тому, чего не предскажет никто: имени, выбору слова, повороту сюжета. Судите сами, насколько к этому приблизился Росток.
На одном и том же тексте у модели A потери 2,0 ната на букву, у модели B — 2,5. Какая лучше и что это значит в «вариантах выбора»?
Потери — это удивление, а хорошая модель удивляется меньше. Полната на букву — большая разница: на тексте в 1000 букв это 500 натов, то есть B даёт настоящему тексту в $e^{500}$ раз меньшую вероятность, чем A.
Сколько удивления не избежать
Можно ли получить нулевые потери? На запомненном конечном наборе модель может приблизиться к нулю, но на новых текстах остаётся неопределённость: после «She picked up the» подходят десятки продолжений. Предел ожидаемых потерь задаётся распределением источника и доступным контекстом. Чтобы разобраться в этом пределе, сначала посмотрим на более простой источник — монетку или кубик.
У этой заложенной доли есть имя — энтропия. Это среднее удивление идеальной модели, которая точно знает настоящие вероятности $p$:
$$H(p) = -\sum_i p_i \log p_i$$У честной монетки энтропия 1 бит. У честного шестигранного кубика — $\log_2 6 \approx 2{,}58$ бита. У монетки, погнутой так, что орёл выпадает в 90% случаев, — всего 0,47 бита: она редко удивляет. У достоверного события — ноль. Энтропия измеряет неопределённость самого источника — неважно, кто и как его угадывает.
А теперь самый важный поворот. Пусть мир бросает кубик с вероятностями $p$, а модель верит в другие вероятности $q$. Тогда её среднее удивление —
$$H(p, q) = -\sum_i p_i \log q_i \ \ge\ H(p),$$и равно $H(p)$ оно только при $q = p$. Это и есть кросс-энтропия — ровно то, что считает наша функция потерь, где в роли мира выступает настоящий текст. Разница $H(p, q) - H(p)$ — плата за неверные представления. Поиграйте: настройте кубик мира, потом кубик модели и следите за числами.
В нашей таблице 97 маленьких кубиков, и энтропии у них очень разные. После «q» энтропия всего 0,16 бита — угадывать нечего. После пробела — 4,55 бита: новое слово может начаться почти с чего угодно. В самом начале истории — 4,0 бита. Ячейка ниже считает это для любой строки.
У английского языка нет одной известной константы, которую можно подставить в любой опыт. Оценка зависит от текстов, алфавита и длины доступного контекста. В работе 1951 года Клод Шеннон исследовал угадывание букв по предшествующему тексту и оценивал энтропию литературного английского примерно в 0,6–1,3 бита на букву. С нашими 3,41 бита это можно сравнить лишь как ориентир: у нас другой набор текстов и символов. Для честного сравнения моделей будем использовать один отложенный корпус.
В PyTorch вся формула — одна функция, F.cross_entropy. Она принимает «логиты» и правильные ответы. С логитами и softmax мы познакомимся в следующей главе, а пока достаточно того, что логиты нашей таблицы — просто логарифмы её вероятностей, ровно то, что bigram() сохранила в файл модели.
2,3586 — то же среднее удивление, что напечатала запускаемая ячейка выше (там оно округлено до 2,359), только теперь его посчитала функция, которая будет обучать все модели начиная с главы 4.
Модель дала какой-то букве вероятность ровно 0, а потом эта буква встретилась в тексте. Что станет с функцией потерь?
Одно бесконечное слагаемое делает бесконечным всё среднее. Сглаживание защищает нашу таблицу от нулевых вероятностей. Softmax при конечных логитах тоже даёт строго положительные вероятности в точной математике, но в вычислениях маленькое число может округлиться до нуля. Поэтому библиотечная кросс-энтропия работает с логитами устойчивым способом, не вычисляя сначала крошечные вероятности и затем их логарифмы.
Росток сейчас
Росток — та же буквенная таблица, что и в прошлой главе, но теперь мы умеем её измерить: 2,364 ната удивления на букву на тексте, которого она никогда не видела, — как будто каждую букву она выбирает из десяти с половиной вариантов. Это число и надо побить. В следующей главе мы выбросим подсчёт: начнём с таблицы случайных чисел и будем учить её, спускаясь с горы функции потерь. И вы увидите, как она придёт ровно к тому числу, которое подсчёт даёт на обучающем тексте, — 2,365. А пока проверьте, насколько Ростка удивит ваша собственная фраза: