Attention позволяет каждой позиции построить собственную взвешенную выборку из
контекста. Запрос говорит, что мы ищем; ключ — что может предложить позиция;
value — какой сигнал будет перенесён. Карта внимания не хранится в весах: она
вычисляется заново для каждого текста, и потому одна и та же голова на разных
фразах смотрит в разные стороны.
Местоимение ищет опору
В предложении «Робот положил стакан на стол, потому что он был свободен»
местоимение связано со столом, а не со стаканом. Между словами несколько
позиций, и фиксированного локального окна может не хватить: поменяйте
«свободен» на «полон» — и опора переедет на стакан, хотя расстояния останутся
теми же. Рекуррентная сеть должна пронести нужный факт через
скрытое состояние, шаг за шагом, теряя его в произведении матриц. Attention
поступает иначе: он строит прямое ребро от одной позиции к другой и делает это
заново для каждой фразы.
Ключевая мысль урока помещается в одну строку: внимание — это мягкий поиск
по словарю. Обычный словарь по точному ключу возвращает одно значение.
Внимание сравнивает запрос со всеми ключами, превращает степени совпадения в
веса и возвращает смесь значений. Жёсткий поиск не дифференцируем, мягкий —
дифференцируем, и потому его можно обучать обратным
распространением.
Три матрицы: запрос, ключ, значение
Пусть матрица представлений X∈Rn×d содержит по строке на
позицию. Три обучаемые проекции дают три разные роли:
Q=XWQ,K=XWK,V=XWV.
Строка qi — query позиции i, строка kj — key позиции j, строка vj
— переносимое value. Оценка совместимости и веса:
sij=dkqi⊤kj,aij=∑ℓ=1nexpsiℓexpsij,
а выход позиции — взвешенная сумма значений:
zi=j=1∑naijvj.
В матричной записи всё это одна формула:
Attn(Q,K,V)=softmax(dkQK⊤)V.
Отсюда сразу следует геометрическое ограничение одной головы:
zi∈conv{v1,…,vn},∥zi∥≤jmax∥vj∥.
Разделение на key и value кажется избыточным, но именно оно даёт свободу:
адрес и содержимое могут жить в разных подпространствах. Ключ отвечает на
вопрос «подхожу ли я под запрос», value — на вопрос «что я передам, если
подойду». В библиотеке шифр на корешке и текст внутри книги тоже не совпадают.
Рис. 76.1. Три шага одной головы: совместимость, конкуренция, перенос
Числовой пример, который стоит проделать руками. Scores (2,1,0) дают веса
(0,6652;0,2447;0,0900), а при значениях (1,4,−2) выход равен
z=1,4641. Он лежит внутри отрезка [−2,4] — выпуклой оболочки value.
Фиолетовая звезда показывает тот же расчёт с маской j≤2: веса
перенормируются в (0,7311;0,2689), выход становится 1,8068.
Бюджет внимания нельзя увеличить
Строка карты — распределение вероятностей. Это значит, что внимание —
игра с нулевой суммой: чтобы одна позиция получила больше, другие обязаны
получить меньше. Формально при изменении одного score
∂si∂ai=ai(1−ai),∂sj∂ai=−aiaj(i=j).
В матричной записи якобиан softmax равен
∂s∂a=diag(a)−aa⊤,
и он вырожден: вектор из единиц лежит в его ядре, потому что прибавление
константы ко всем scores ничего не меняет. Отрицательный знак вне диагонали —
это и есть конкуренция. Отсюда следует
неприятный практический вывод: вес зависит не только от своего score, но и от
того, кто ещё стоит в очереди. Возьмём опору со score 4 и конкурентов со
score 1. При одном конкуренте вес опоры равен 0,953; добавим ещё десять
одинаковых нерелевантных токенов — и он падает до 0,646, а при сорока — до
0,329. Score опоры не изменился ни разу; изменился знаменатель.
Чтобы говорить о «размытости» карты количественно, удобна энтропия строки
H(ai)=−j=1∑naijlnaij,0≤H(ai)≤lnn,
где нуль отвечает жёсткому выбору одной позиции, а lnn — полному отказу
выбирать.
Почему делим на корень размерности
Пусть компоненты q и k независимы, имеют нулевое среднее и единичную
дисперсию. Тогда для одной пары
E[qrkr]=0,Var(qrkr)=E[qr2kr2]=1,
а для скалярного произведения
Var(r=1∑dkqrkr)=dk,Var(dkq⊤k)=1.
Модельный эксперимент (seed 760, восемь ключей на запрос) подтверждает это с
точностью до третьего знака: измеренная дисперсия при dk=64 равна 64,2,
а отношение дисперсии к dk по всем размерностям от 4 до 512 лежит между
0,977 и 1,003. Последствия для softmax видны сразу. Без деления при
dk=512 средний максимальный вес равен 0,955, а энтропия карты падает до
0,107 при максимуме ln8=2,079: распределение почти one-hot. С делением
максимальный вес остаётся около 0,360 при любой размерности, а энтропия —
1,729.
Рис. 76.2. Что делает деление на корень размерности
Модельный эксперимент с фиксированным seed 760. Слева: дисперсия q⊤k
растёт ровно как dk. В центре: без деления softmax при большой размерности
схлопывается в одну позицию. Справа: суммарная чувствительность
∑jaj(1−aj) при dk=512 равна 0,065 без деления и 0,769 с
делением — разница почти в двенадцать раз. Схлопнувшийся softmax почти не
пропускает градиент.
Это не косметическая константа, а нормировка масштаба — родня приёмам
инициализации сетей: при росте числа слагаемых сумма обязана
оставаться управляемой. Заметьте и обратную сторону: если бы мы хотели
«острого» внимания, достаточно было бы поделить на меньшее число. Так и делают,
когда вводят температуру τ и пишут softmax(s/τ).
Это первая формулировка внимания как обучаемого выравнивания: декодер не
обязан помнить всё предложение, он каждый раз заново решает, куда посмотреть.
Карта внимания на реальном тексте
Абстракцию полезно приземлить. Возьмём корпус SMS Spam Collection: 5569
сообщений, из них 747 спамовых, то есть 13,4%. По самому корпусу
построим словарь из 1045 слов, встречающихся не реже десяти раз, посчитаем
матрицу совместной встречаемости в окне четырёх слов, превратим её в PPMI и
возьмём 32 первых сингулярных направления. Получатся честные embeddings,
выученные из этих же SMS — техника прямо из урока об
ортогональности и PCA. Ближайшие соседи выглядят осмысленно: к
слову free ближе всего nokia (0,912) и mobile (0,850), к слову
call — land (0,847) и claim (0,799).
Теперь возьмём реальное сообщение из корпуса, оставим девять различных слов
free nokia motorola on orange call mobileupd or optout и построим одну голову
без обучения: пусть WQ=WK=WV будут пропорциональны единичной матрице, то
есть ключ, запрос и значение — это сам embedding. Карта получается
содержательной. Запрос call отдаёт себе вес 0,616, а слову free — всего
0,036: в этом корпусе call живёт рядом с claim и land, а не рядом с
free. Энтропия строки равна 1,413 при равномерном максимуме
ln9=2,197 — карта заметно сфокусирована, но не вырождена.
Рис. 76.3. Полная и причинная карта на реальном SMS
Слева каждая позиция видит весь текст, справа включена causal mask. Первая
строка правой карты состоит из единственного веса 1,000: у первого токена
нет прошлого, и вся масса уходит на себя. У запроса call собственный вес
растёт с 0,616 до 0,793 — не потому, что он «стал важнее», а потому,
что конкуренты справа исчезли из знаменателя.
Маска меняет множество допустимого
В декодере языковой модели позиция i не должна видеть будущие токены. К
scores прибавляют матрицу
Mij={0,−∞,j≤i,j>i,A=softmax(dkQK⊤+M).
Эквивалентная запись — softmax по разрешённому множеству Ai:
aij=∑ℓ∈Aiexpsiℓexpsij1[j∈Ai].
После softmax запрещённые веса равны нулю точно, а не приблизительно:
e−∞=0. Padding mask убирает фиктивные позиции, дополняющие короткие
строки до общей длины батча. Смысл масок разный: causal защищает от утечки
будущего, padding — от искусственного содержимого, которого в тексте нет.
При реализации −∞ заменяют большим отрицательным числом. В float16
диапазон невелик, и слишком большая по модулю константа даёт NaN уже на
стадии вычитания максимума; библиотечные ядра учитывают это отдельно.
Позиция не входит в формулу сама
Посмотрите на определение внимания ещё раз: в нём нет ни одного индекса
позиции, кроме как через содержимое строк. Если переставить строки X
перестановкой P, то
Attn(PX)=PAttn(X),
то есть self-attention эквивариантен перестановке. Численно это проверяется
мгновенно: на нашей карте максимальное расхождение между переставленной картой
и картой переставленного входа равно 1,1⋅10−16 — машинный ноль. Без
позиционной информации модель различает множество токенов, но не их порядок,
и фразы «стакан на столе» и «стол на стакане» для неё одинаковы.
Классическое синусоидальное кодирование прибавляет к embeddings
Скалярное произведение двух таких векторов зависит в основном от разности
позиций: при d=64 оно равно 32,00 для нулевого сдвига, 30,92 для
сдвига в одну позицию и 22,41 для сдвига в восемь. Это и делает код
полезным: близкие позиции похожи, далёкие — нет.
Есть и точное свойство: сдвиг позиции действует на код линейно,
то есть каждая пара координат просто поворачивается на угол, зависящий только
от расстояния.
Относительные схемы (в том числе RoPE, поворачивающий query и key на угол,
пропорциональный позиции) выражают «на три позиции раньше» напрямую и потому
лучше переносятся на длины, которых не было в обучении. Но переносимость надо
измерять, а не постулировать: поведение за обученной длиной — отдельный
экспериментальный вопрос, родственный вопросу об экстраполяции из
урока о базисных признаках.
Несколько голов — несколько вопросов
Multi-head attention выполняет H независимых наборов проекций:
headh=softmax(dhQhKh⊤)Vh,dh=Hd,
а затем склеивает результаты и смешивает их выходной матрицей:
MHA(X)=Concat(head1,…,headH)WO.
Число параметров при этом не растёт: все головы вместе несут столько же весов,
сколько одна «широкая»,
H⋅3ddh+d2=3d2+d2=4d2.
Зачем это нужно, видно на паре построенных вручную голов. Голова A задана
позиционно: её score равен −3∣j−(i−1)∣, то есть «смотри на предыдущий токен».
Средний вес на предыдущий токен у неё 0,911. Голова B задана содержательно:
score равен скалярному произведению embeddings без диагонали, и её самая
сильная связь — пара mobileupd–motorola с весом 0,354. Две головы
задают контексту два разных вопроса и обе полезны.
Слева голова A: почти чистая поддиагональ, средний вес на предыдущий токен
0,911. Справа голова B: диагональ запрещена, и голова ищет ближайшего по
смыслу соседа; сильнейшая пара — mobileupd–motorola с весом 0,354.
Обе карты построены вручную, чтобы показать: специализация голов — это
разные вопросы, а не разные «умения».
В обученных моделях специализация действительно наблюдается, но она
статистическая, а не гарантированная. Головы дублируют друг друга, часть из них
можно удалить почти без потери качества, а «голова номер 3» в одном запуске и в
другом — разные объекты: если переставить головы и согласованно переставить
блоки WO, функция MHA не изменится вовсе.
Внимание до нейросетей: рассогласование по Соколову
Слово «внимание» пришло в машинное обучение из физиологии, и русская школа
занималась им задолго до 2017 года. Евгений Николаевич Соколов, изучая
ориентировочный рефлекс, предложил в конце 1950-х идею нервной модели
стимула: нервная система хранит след предъявлявшегося раздражителя и
сравнивает с ним каждый новый вход. Совпадение приводит к угасанию реакции,
рассогласование — к вспышке ориентировочной реакции, то есть к повороту
внимания на изменившийся элемент.
Структура рассуждения та же, что в формуле внимания: есть хранимая модель
(ключ), есть текущий вход (запрос), есть операция сравнения и есть следствие
сравнения — усиление или ослабление передачи сигнала. Соколовская схема
объясняла угасание реакции на повторяющийся звук и её мгновенное возвращение
при малейшем изменении тона; в терминах нашей формулы угасание — это падение
score, а вспышка — его рост. Позже Соколов развил идею векторного кодирования:
стимул представляется вектором активностей нейронов-детекторов, а близость
стимулов — углом между векторами. Это буквально та геометрия, в которой мы
считаем q⊤k.
Александр Романович Лурия в книге «Внимание и память» (1975) формулировал ту же
мысль с другой стороны: внимание есть фактор избирательности, без которого
всякий психический процесс тонет в потоке сигналов. Механизм внимания в сети
устроен так же прагматично: ёмкость представления ограничена, и её приходится
распределять.
Лаборатория внимания
Query, key, value и карта связей между словами
График шире экрана — листайте по горизонтали →
Загружается живая иллюстрация…
Выберите запрос и двигайте ползунок «прибавка к score ключа „стол“». Следите не
за поднятым столбиком, а за соседними: softmax перераспределяет всю массу, и
чужие веса падают, хотя их scores не менялись. Затем нажмите «подменить value
слова „стол“»: веса останутся теми же до последнего знака, а выход переедет —
это и есть разделение адреса и содержимого.
Переключите масштаб logits в режим «без деления» и увеличьте dk до 256:
распределение схлопнется в один столбик, максимальный вес подскочит, энтропия
упадёт почти до нуля. Верните деление на dk — картина восстановится
при любой размерности. Наконец включите causal mask и попробуйте связать
позицию с будущим: нулевой вес там — следствие запрета до softmax, а не
результат обучения. Кнопка «добавить 6 шумовых токенов» показывает разбавление:
вес опоры падает от одного роста знаменателя.
Обучаемое взвешивание: одна голова на реальном спаме
Проверим внимание в деле, на тех же SMS. Модель максимально простая: у нас есть
фиксированные embeddings ew из корпуса и обучаемый вектор-запрос u. Для
сообщения из токенов w1,…,wm считаем
sj=du⊤ewj,a=softmax(s),c=j=1∑majewj,
а затем обычный логистический классификатор:
p=σ(v⊤c+b),σ(t)=1+e−t1.
При u=0 формула вырождается в обычное усреднение:
aj=∑ℓ=1mexp0exp0=m1,c=m1j=1∑mewj.
Это внимание в чистом виде: вместо среднего по токенам, где
aj≡1/m, сеть сама решает, кого слушать. Градиент по u проходит через
softmax:
∂sj∂L=aj(gj−ℓ∑aℓgℓ),gj=(p−y)v⊤ewj,
и хорошо видно, что общий сдвиг всех gj ничего не меняет — обучается
только относительный порядок токенов.
На 1368 отложенных сообщениях среднее по токенам даёт точность 96,3%,
одна обученная голова — 96,6%; константный ответ «не спам» дал бы 87,0%.
Прибавка скромная, 0,3 процентных пункта, то есть 51 ошибка против 47:
на коротких SMS усреднение и так почти не мешает. Но на сообщениях средней
длины (от 10 до 25 токенов, их 586) разрыв заметнее: 94,5% против
95,2% — ровно там, где разбавление начинает вредить.
Рис. 76.5. Одна обученная голова на реальном SMS-корпусе
Слева — точность на отложенных сообщениях. Справа — какие слова обученный
запрос поднимает выше всех: tone (0,36), uk (0,33), co
(0,32), www (0,22), pobox (0,19). Ниже всех голова ставит
служебные слова: that (−1,49), and (−1,39), it (−1,37).
Никто не размечал «важные слова» — голова вывела их из задачи.
Стоит задержаться на этом списке. Он честно рассказывает, чем спам отличается
от переписки в этом корпусе: адресами, доменами, тарифами, словом txt.
И он же показывает границу метода: голова научилась не «понимать спам», а
находить лексические маркеры именно этой коллекции 2000-х годов — ровно та
опасность, о которой шла речь в
уроке про утечки и валидацию.
Квадратичная цена
Матрица QK⊤ имеет n2 элементов, поэтому время и память одной головы
растут как
T(n)=O(n2dk),M(n)=O(n2).
Для блока из H голов размерности dh=d/H это даёт
TMHA(n)=O(Hn2dh+nd2)=O(n2d+nd2),
и первое слагаемое обгоняет второе, как только n превосходит d.
Это не теоретическая страшилка, а измеримый факт. Прямой замер на одной машине
(numpy, float32, dk=64, медиана из пяти повторов) даёт наклон в
логарифмических координатах b≈1,9: удвоение длины контекста с 512 до
1024 увеличило время примерно вчетверо. Абсолютные миллисекунды у вас будут
свои, показатель степени — тот же. Память карты считается совсем точно: при
n=2048 во float32 это 16,0 МиБ на голову на слой, при n=8192 —
уже 256,0 МиБ.
Рис. 76.6. Измеренная и подсчитанная цена внимания
Слева: реальный замер, наклон b≈1,9 вдоль эталона n2 (небольшой
недобор объясняется тем, что при малых n доминируют накладные расходы).
Справа: число ячеек карты. При n=8192 окно ширины 128 дешевле полной карты
ровно в 64 раза — но и видит только 128 соседей.
Отсюда весь зоопарк «эффективных» вариантов: локальное окно, разреженные
схемы с выбранными рёбрами, линейное внимание, меняющее порядок умножений
через аппроксимацию ядра. Цена экономии всегда одна и та же — исчезнувшие
рёбра. Сравнивать такие схемы имеет смысл только на задачах, где дальний
контекст действительно нужен; на текстах с локальной статистикой окно выиграет
всегда и ничему не научит.
Стоимость авторегрессивной генерации складывается по шагам:
t=1∑nO(tdk)=O(n2dk)
с кэшем против ∑t=1nO(t2dk)=O(n3dk) без него.
Cross-attention и выравнивание перевода
В схеме encoder–decoder запросы приходят из декодера, а ключи и значения — из
энкодера:
Z=softmax(dkQdecKenc⊤)Venc.
Формула та же, разные только источники. На корпусах перевода такую карту
сравнивают с человеческим выравниванием слов — и сразу упираются в то, что
перевод не бывает один-к-одному: артикль не имеет русского соответствия, а
целая фраза перестраивается. Поэтому оценивать стоит не только BLEU, но и
конкретные явления: имена, числа, отрицания, дальние согласования. Карта
внимания здесь — инструмент поиска гипотезы об ошибке, а не приговор: итоговый
выход зависит ещё и от residual-пути, от MLP и от всех остальных слоёв
трансформера.
Карта — не объяснение
Соблазн велик: нарисовать красивую тепловую карту и объявить её объяснением
решения. Против этого есть и теоретические, и экспериментальные возражения.
Высокий вес говорит ровно одно: откуда в этой голове и на этом слое
смешивался value. Он не доказывает, что токен важен для итогового ответа —
хотя бы потому, что соседняя голова может нести ту же информацию, а
residual-путь проносит представление мимо внимания вообще.
Проверка делается вмешательством. Возьмите сто реальных текстов, выберите
голову и токен с максимальным весом. Замаскируйте его, потом токен со средним
весом, потом случайный, и сравните изменение нужного logit. Если маскирование
максимального веса не влияет сильнее прочих, карта на этом наборе ничего не
объясняет. У маскирования, впрочем, есть свой изъян: получается строка,
которой не бывает в данных. Мягче работает activation patching — подмена value
одного токена значением из контрольного примера при сохранении остального
контекста.
Мини-исследование: задача адресации
Синтетическая задача проверяет механизм лучше любой heatmap. Генерируйте строки
вида A\:7 B\:2 C\:9 ?B и требуйте вернуть значение запрошенного ключа. Меняйте
число пар от 2 до 64, порядок и расстояние до запроса. Здесь известен
правильный паттерн внимания: запрос обязан найти соответствующий ключ и
перенести именно его value.
Обучите одну голову и проведите тройку вмешательств. Переставьте пары — ответ
обязан сохраниться (эквивариантность). Замените ключ при прежнем value — адрес
должен смениться. Замените value при прежнем ключе — вес должен остаться, а
выход измениться. Эта тройка проверяет функциональное разделение Q, K и V
строже, чем любая картинка.
Затем добавьте два одинаковых ключа с разными значениями и правило «брать
последний». Без позиционной информации задача неразрешима в принципе: вход, с
точностью до перестановки, один и тот же. С относительным кодированием модель
может предпочесть ближайшее прошлое. Измерьте точность по длинам, превышающим
обучающие, — и вы получите честную кривую переноса вместо общих слов.
Адресация не равна пониманию
Соберём урок в одну картину. Внимание — динамическая адресация контекста:
запрос сравнивается с ключами, softmax превращает совместимость в
конкурирующие веса, и выход собирается из values, оставаясь в их выпуклой
оболочке. Деление на dk удерживает масштаб logits, а с ним и
градиенты. Маска задаёт множество допустимых связей. Позиционное кодирование
возвращает порядок, которого в формуле нет. Головы задают несколько вопросов
параллельно. Всё это стоит n2 и упирается в длину контекста.
И главное ограничение — методологическое. Карта показывает, откуда смешано
содержимое, а не почему модель ответила именно так. Рисунок внимания хорош,
когда отвечает на конкретный вопрос и проверяется вмешательством; во всех
остальных случаях это цветной орнамент. Следующий шаг — увидеть, как этот
механизм встраивается в блок с residual и нормализацией и превращается в
трансформер.