Skip to content

Лекція 12 · Механізм уваги та трансформери ​

Згорткова мережа з минулої лекції зв'язує сусідні пікселі. Сьогодні розберемо архітектуру, у якій будь-які два елементи послідовності зв'язані напряму, за один крок — і саме на ній працюють сучасні мовні моделі.

Зміст лекції ​

Лекція будує трансформер з одного механізму, додаючи по одній деталі за раз.

  1. Чому послідовності важкі. Рекурентна мережа забуває початок; згорткова бачить лише окіл.
  2. Увага як зважене середнє. Запити, ключі, значення і формула scaled dot-product attention.
  3. Самоувага. Наскрізний числовий приклад, масштабування, причинна маска, кілька голів.
  4. Позиційне кодування. Чому увага не знає порядку слів і як це виправляють.
  5. Блок трансформера і повна архітектура. Залишкові зв'язки, нормалізація, енкодер і декодер.
  6. Мовні моделі. Навчання передбаченням наступного слова, авторегресивна генерація, обмеження.

Перші два блоки пояснюють, звідки взявся механізм; третій і четвертий доводять його до робочого стану; п'ятий збирає архітектуру, шостий показує, що з неї виросло.

Чого ви навчитеся ​

Після цієї лекції ви зможете:

  • пояснити, чому шлях між далекими елементами послідовності в рекурентній мережі коштує O(T) кроків, а в самоуваги — один;
  • записати формулу scaled dot-product attention і розшифрувати всі позначення;
  • порахувати ваги уваги вручну для короткої послідовності;
  • пояснити, навіщо оцінки діляться на dk, і що буде без цього;
  • побудувати причинну маску і сказати, де саме вона застосовується;
  • пояснити, навіщо потрібні кілька голів уваги;
  • показати, чому самоувага не розрізняє порядок токенів, і як його додає позиційне кодування;
  • описати блок трансформера і назвати, що робить кожен його підблок;
  • розрізнити режими енкодера, декодера та енкодера-декодера і сказати, для чого кожен;
  • реалізувати самоувагу з нуля на PyTorch і візуалізувати матрицю ваг.

Задача: на вході послідовність, на виході теж ​

Досі один об'єкт давав один прогноз. Тепер вхід є послідовністю змінної довжини, і задачі бувають двох типів.

Розмітка (tagging) вимагає прогнозу для кожного елемента: частина мови для кожного слова. Прогнози не є незалежними — щоб визначити частину мови, треба дивитися на сусідів.

Перетворення послідовності в послідовність (sequence-to-sequence) дає послідовність іншої довжини: переклад, стислий переказ, підпис до зображення.

Обидві задачі розв'язує та сама конструкція: перетворити кожен елемент на контекстуалізоване подання (contextualized representation) — вектор, який ураховує решту послідовності, — і поставити зверху лінійну модель.

"[A Transformer] iteratively transforms a sequence of non-contextualized representations into a sequence of contextualized representations. Mixes information between sequence elements via self-attention." — EPFL CS-433, лекція 09a

Джерело: EPFL CS-433 Machine Learning

Рекурентна мережа забуває початок послідовності ​

Класичний спосіб обробити послідовність — рекурентна мережа: стан оновлюється по одному елементу, ht=tanh⁡(Wht−1+xt). Щоб інформація з першого елемента дійшла до останнього, вона проходить через W стільки разів, скільки кроків між ними — і кожне множення її трохи гасить.

Виміряймо це прямо. На рисунку — норма градієнта виходу останнього кроку за кожним входом, для послідовності з 60 елементів. У рекурентної мережі градієнт спадає монотонно з відстанню і на першому елементі приблизно на тринадцять порядків менший, ніж на останньому. Для самоуваги залежності від відстані немає взагалі: вага першого токена така сама за природою, як вага сусіднього.

Гейтові варіанти, LSTM і GRU, послаблюють це згасання спеціальними вентилями пам'яті, але не усувають самої причини — послідовної обробки.

gradient decay in a recurrent network versus self attention

Три способи зв'язати елементи послідовності ​

Порівняймо архітектури за одним критерієм: скільки кроків потрібно, щоб інформація з одного елемента дійшла до іншого.

Згортка зв'язує лише окіл шириною k: у звичайної згортки з кроком 1 рецептивне поле росте лінійно, тож потрібні ∼T/k шарів, а оцінка logk⁡T у таблиці нижче стосується розширеної (dilated) або ієрархічної згортки. Рекурентна мережа передає інформацію послідовно, тому шлях дорівнює відстані. Самоувага з'єднує кожен елемент із кожним напряму: шлях завжди один крок, і всі пари обчислюються одночасно.

comparison of CNN RNN and self attention connectivity

Джерело: Dive into Deep Learning · Self-Attention and Positional Encoding

Складність трьох типів шарів ​

Те саме порівняння в числах — таблиця з оригінальної статті про трансформер, у позначеннях курсу: T — довжина послідовності, D — розмірність подання, k — ширина згортки.

Тип шаруСкладність шаруПослідовних операційНайдовший шлях між елементами
СамоувагаO(T2⋅D)O(1)O(1)
РекурентнийO(T⋅D2)O(T)O(T)
ЗгортковийO(k⋅T⋅D2)O(1)O(logk⁡T)

Читати треба два останні стовпці разом. Самоувага платить квадратом за довжину, але виграє двічі: між будь-якими двома токенами один крок, тож нічого не забувається, і немає послідовної залежності, тож усе рахується паралельно на GPU. Рекурентна мережа дешевша за один шар, але змушена робити T кроків підряд.

Джерело: Vaswani A. та ін., Attention Is All You Need, NIPS 2017, таблиця 1

Ціна квадратичної складності на практиці ​

Перевірмо обидві сторони на практиці. Ліворуч — виміряний час одного проходу на цьому комп'ютері. Попри квадратичну складність, самоувага виявляється швидшою за рекурентну GRU на всіх довжинах: при T=2048 це 7,7 мс проти 37,8 мс. Причина в тому, що всі T2 скалярних добутків — це одне множення матриць, а GRU робить 2048 кроків один за одним.

Праворуч — те, за що доводиться платити. Матриця ваг уваги має розмір T×T: при 2048 токенах це 4,2 мільйона чисел, тобто 16 МБ на одну голову в одному шарі. При 32 тисячах токенів це вже 4 ГБ на одну голову, і саме тому скорочення квадратичної залежності — окремий напрям досліджень.

measured cost of self attention versus recurrence and quadratic growth

Увага — це зважене середнє з навченими вагами ​

Механізм влаштований простіше, ніж здається. Кожен вихідний вектор — це зважене середнє всіх вхідних:

zi=∑j=1Tpi,jvj,∑j=1Tpi,j=1

Ваги pi,j невід'ємні й сумуються в одиницю, тобто кожен рядок матриці P — це розподіл імовірностей: скільки уваги вихід i приділяє входу j.

Уся суть у тому, звідки беруться ваги. Вони не є параметрами моделі й не фіксовані — вони обчислюються з самих даних для кожної конкретної послідовності. Тому одне й те саме слово в різних реченнях дістане різний контекст.

attention computes a weighted sum of values

Джерело: Dive into Deep Learning · Attention Scoring Functions

Запит, ключ і значення ​

Ваги беруться з подібності двох векторів, і саме тут з'являється термінологія, запозичена з баз даних.

Для кожного токена обчислюють три вектори, множачи його подання на три навчувані матриці. Запит (query) qi — це те, що шукає токен i. Ключ (key) kj — це те, що пропонує токен j. Значення (value) vj — інформація, яку з токена j візьмуть, якщо він виявиться потрібним.

Вага pi,j тим більша, чим краще запит i збігається з ключем j. Ключ і значення розділені навмисно: те, за чим токен знаходять, і те, що з нього беруть, — різні речі.

queries keys and values are produced from the input embeddings

Джерело: J. Alammar · The Illustrated Transformer

Формула scaled dot-product attention ​

Подібність вимірюють скалярним добутком, ділять на корінь із розмірності й пропускають через softmax:

pi,j=exp(qikj⊤/dk)∑t=1Texp(qikt⊤/dk)
ПозначенняЗначення
Tдовжина послідовності в токенах
Dрозмірність подання токена, у статті позначена dmodel
qi,kj∈Rdkзапит вихідного токена i і ключ вхідного токена j
qikj⊤сира оцінка подібності (score)
dkмасштабування, що тримає дисперсію оцінок близько одиниці
pi,jвага уваги; softmax береться по кожному рядку окремо

Матричний запис компактніший і саме він реалізований у бібліотеках:

Attention(Q,K,V)=softmax(QK⊤dk)V

Крок за кроком на двох токенах ​

Розгляньмо, як це виглядає на реченні «Thinking Machines», коли ми обчислюємо подання першого слова.

Для обох слів будують запити, ключі та значення. Оцінки — це скалярні добутки запиту першого слова з обома ключами: 112 із власним ключем і 96 із ключем другого слова. Обидві оцінки діляться на 8 (це dk при dk=64) і дають 14 та 12. Softmax перетворює їх на ваги 0,88 і 0,12.

Останній крок: кожне значення множиться на свою вагу, і результати додаються. Подання слова «Thinking» на 88 % складається з нього самого і на 12 % — із сусіда.

step by step attention score division and softmax for two tokens

Джерело: J. Alammar · The Illustrated Transformer

Матричний запис: три множення замість циклу ​

Робити це по одному токену незручно й повільно. На практиці всю послідовність складають у матрицю X∈RT×D, де рядок — це токен, і отримують усі запити, ключі та значення трьома множеннями матриць:

Q=XWQ,K=XWK,V=XWV

Матриці WQ,WK∈RD×dk і WV∈RD×dv — це і є всі параметри шару уваги; вони навчаються зворотним поширенням разом із рештою мережі.

Слово «само-» в назві самоувага (self-attention) означає рівно те, що всі три проєкції зроблено з одного й того самого X: послідовність дивиться сама на себе.

queries keys and values computed as matrix products

Джерело: J. Alammar · The Illustrated Transformer

Увесь шар самоуваги одним виразом ​

Підставивши проєкції у формулу уваги, отримуємо весь шар як один рядок:

Z=softmax(XWQWK⊤X⊤dk)XWV

Розміри легко перевірити: QK⊤ має розмір T×T — це матриця «кожен з кожним», а після множення на V виходить T×dv, тобто знову по вектору на токен. Шар уваги перетворює послідовність на послідовність тієї самої довжини.

Звідси ж видно і вартість: матриця T×T — це і є та квадратична залежність, про яку йшлося вище.

full attention formula as softmax of QK over root dk times V

Джерело: J. Alammar · The Illustrated Transformer

Наскрізний приклад: «кіт п'є молоко» ​

Порахуймо все на трьох токенах із dk=2. Візьмімо для наочності готові проєкції:

Q=K=(100111),V=(100231)

Ліва панель — оцінки QK⊤/2. Найбільша, 1,41, стоїть у токена «молоко» на самого себе: його вектор має найбільшу норму. Середня панель — ті самі оцінки після softmax по рядках: «кіт» розподілив увагу як 0,40 / 0,20 / 0,40, «молоко» — як 0,25 / 0,25 / 0,50.

Права панель — результат Z=PV. Токен «кіт» вийшов вектором (1,60; 0,80): це вже не його власне значення (1; 0), а суміш значень усіх трьох токенів із вагами його уваги. Саме це й означає слово «контекстуалізоване подання».

attention scores weights and output for a three token sentence
Увага на трьох токенах, число за числом
Той самий приклад, що на рисунку: три токени, dk = 2. Оберіть токен і посуньте його вектори — оцінки, ваги після softmax і вихід перерахуються тут-таки за формулою розділу.
оцінки QKᵀ / √d
кітп'ємолоко
кіт0,710,000,71
п'є0,000,710,71
молоко0,710,711,41
ваги після softmax по рядках
кітп'ємолоко
кіт0,400,200,40
п'є0,200,400,40
молоко0,250,250,50
вихід Z = P · V
1-ша2-гаентропія
кіт1,600,801,52
п'є1,401,201,52
молоко1,761,001,50

За замовчуванням тут числа з рисунка: найбільша оцінка 1,41 стоїть у «молока» на самого себе, «кіт» розподіляє увагу як 0,40 / 0,20 / 0,40, а його вихід дорівнює (1,60; 0,80) — уже не власне значення (1; 0), а суміш усіх трьох. Це і є контекстуалізоване подання, порахувати яке можна на папері за хвилину. Три речі варто побачити руками. Перше: оберіть «молоко» і збільште його першу координату до 3 — оцінка на самого себе злетить, softmax стягне на нього майже всю увагу (0,99), а ентропія рядка впаде з 1,50 біта до 0,08 при максимально можливих 1,58. Друге: вимкніть ділення на √dk і подивіться на ваги «кота»: 0,40 / 0,20 / 0,40 перетворюються на 0,42 / 0,16 / 0,42. Оцінки виросли всього в 1,41 раза, а розподіл уже помітно гостріший — і саме це псує навчання, коли dk дорівнює не 2, а 64. Третє: змініть лише значення V, не чіпаючи запитів і ключів, — ваги не зрушать зовсім, а вихід зміниться. Увага вирішує, звідки брати, а не що саме брати.

Код: самоувага з нуля на PyTorch ​

Уся формула — чотири рядки. Маска знадобиться в наступних розділах.

Вивід код запущено, вивід не редагувався
text
ваги уваги:
 tensor([[0.4000, 0.2000, 0.4000],
        [0.2000, 0.4000, 0.4000],
        [0.2500, 0.2500, 0.5000]])
вихід:
 tensor([[1.6000, 0.8000],
        [1.4000, 1.2000],
        [1.7600, 1.0000]])

На що дивитися: ваги уваги й вихід збігаються з ручним розрахунком: «кіт» розподілив увагу як 0,4 / 0,2 / 0,4 і отримав вектор (1,6; 0,8) — суміш значень усіх трьох токенів.

У робочому коді використовують torch.nn.functional.scaled_dot_product_attention — та сама операція, але з оптимізованою реалізацією і без явного зберігання матриці T×T.

Джерело: PyTorch · scaled_dot_product_attention

Ділення на корінь із розмірності рятує softmax ​

Ділення на dk виглядає технічною дрібницею, але саме воно робить навчання стійким. Скалярний добуток двох випадкових векторів довжини dk має дисперсію порядку dk, тож при dk=64 оцінки розкидані на десятки одиниць — а softmax від таких чисел майже сходинка.

На рисунку — розподіл ваг уваги одного запиту по 24 ключах при випадковій ініціалізації, dk=64. Без ділення вся увага зосереджена на одному ключі: максимальна вага 0,78, ентропія розподілу 0,54. Із діленням максимум падає до 0,23, а ентропія зростає до 2,56 при максимально можливих 3,18.

Наслідок практичний. У зоні насичення softmax градієнти майже нульові, тому модель, що стартувала з гострого розподілу, довго не може з нього вийти. Масштабування дає на старті майже рівномірну увагу — і швидшу збіжність.

attention weight distribution with and without the square root scaling

"For large values of dk, the dot products grow large in magnitude, pushing the softmax into regions with extremely small gradients." — Vaswani та ін., 2017, розділ 3.2.1

Джерело: Attention Is All You Need

Ваги уваги в навченій моделі справді осмислені ​

Дотепер ми дивилися на ваги при випадковій ініціалізації. Ось як вони виглядають у навченому трансформері.

Речення: «The animal didn't cross the street because it was too tired». Слово «it» — займенник, і щоб зрозуміти його, модель має вирішити, до чого він належить: до тварини чи до вулиці. На рисунку показано, куди дивиться подання слова «it» у п'ятому шарі енкодера: найбільша вага припадає на «The animal».

Ніхто не позначав цю відповідність у даних. Модель навчалася лише передбачати слова, і потрібний зв'язок утворився сам, бо він допомагає передбачати.

attention weights of the word it point to the animal in a trained model

Джерело: J. Alammar · The Illustrated Transformer

Причинна маска забороняє дивитися в майбутнє ​

Коли модель учиться передбачати наступне слово, вона не має права бачити відповідь. Тому в декодері увагу обмежують: токен i може дивитися лише на токени 1,…,i.

Технічно це робиться додаванням маски до softmax:

P=softmax(QK⊤dk+M),Mij={−∞,j>i0,j≤i

Мінус нескінченність, а не нуль: exp⁡(−∞)=0, тож заборонені позиції дістають рівно нульову вагу, а решта ваг перенормовуються й далі сумуються в одиницю. Порівняйте четвертий рядок обох матриць: без маски це 0,24 / 0,16 / 0,04 / 0,06 / 0,05 / 0,01 / 0,10 / 0,35, з маскою — 0,48 / 0,32 / 0,09 / 0,11 і чотири нулі.

attention matrix with and without the causal mask

Кілька голів дивляться на різні речі ​

Одна матриця ваг задає один патерн уваги, а в реченні водночас важливі кілька різних зв'язків: узгодження підмета з присудком, до чого належить займенник, який час дієслова.

Розв'язок прямий: обчислити кілька незалежних механізмів уваги паралельно. Кожна голова (head) h має власні матриці WQ,h,WK,h,WV,h і будує власну матрицю ваг. Це прямий аналог кількох вихідних каналів згортки з минулої лекції: різні фільтри шукають різні речі в тому самому вході.

two attention heads have separate query key and value matrices

Джерело: J. Alammar · The Illustrated Transformer

Голови склеюються і повертаються в розмірність D ​

Голови працюють паралельно, кожна дає свій вихід Zh розміру T×dv. Далі їх склеюють по стовпцях і повертають у вихідну розмірність ще одним множенням:

Z=[Z1,…,ZH]WO,WO∈RHdv×D

Розмірність голови беруть як dk=dv=D/H, щоб загальний обсяг обчислень не зріс: у базовій моделі статті D=512 і H=8, тож dk=64. Вісім голів по 64 коштують стільки ж, скільки одна на 512, але дають вісім різних поглядів замість одного.

multi head attention splits concatenates and projects back

Джерело: J. Alammar · The Illustrated Transformer

Код: багатоголова увага з бібліотеки ​

Писати руками це не доводиться: у PyTorch багатоголова увага — готовий шар. Прапорець average_attn_weights=False потрібен, щоб отримати ваги кожної голови окремо, а не їхнє середнє.

Вивід код запущено, вивід не редагувався
text
вихід: (1, 10, 64) · ваги уваги: (1, 8, 10, 10)
сума першого рядка ваг: 1.0000

На що дивитися: вихід має ту саму форму, що й вхід, ваги — окрема матриця 10 × 10 для кожної з 8 голів, і кожен рядок ваг у сумі дає одиницю.

Форма ваг читається так: одна послідовність, 8 голів, і для кожної матриця 10 × 10 «кожен токен на кожного». Сума першого рядка дорівнює одиниці — це той самий softmax по рядках. Вихід має ту саму форму, що й вхід, тому такі блоки можна ставити один на одного.

Джерело: PyTorch · nn.MultiheadAttention

Самоувага не розрізняє порядок слів ​

У механізму є принципова вада. Переставмо два слова місцями: усі скалярні добутки збережуться, softmax береться по рядках — і вихід просто переставиться так само, як вхід. Формально для будь-якої матриці перестановки R виконується SA(RX)=R⋅SA(X).

На рисунку — ваги уваги для п'ятислівного речення до і після перестановки третього й четвертого слова. Матриця праворуч є точною перестановкою матриці ліворуч: ті самі числа, переставлені рядки й стовпці. Перевірка на векторах виходу дає розбіжність 1,2⋅10−7 — чиста похибка округлення.

Для мови це неприйнятно: «вона любить котів більше за собак» і «вона любить собак більше за котів» складаються з однакових слів, але означають протилежне.

attention weights are permuted with the input and nothing else

Позиційне кодування додає моделі відчуття порядку ​

Оскільки сам механізм порядку не бачить, інформацію про позицію додають прямо у вхідні вектори. Найпростіший варіант — навчувана матриця позицій. Оригінальна стаття натомість використала фіксовані синусоїди різної частоти:

PE(pos,2i)=sin(pos100002i/D),PE(pos,2i+1)=cos(pos100002i/D)

Ліва панель — уся матриця кодування для 64 позицій і 64 координат. Верхні координати змінюються швидко, нижні повільно: разом вони кодують позицію так само, як розряди двійкового числа.

Права панель пояснює, навіщо саме синусоїди. Схожість кодувань двох позицій плавно спадає з відстанню: для самої позиції вона 0,5, для сусідньої 0,483, а через двадцять позицій уже 0,295. Модель отримує не просто мітку «це позиція 20», а й придатну для узагальнення міру відстані. Автори зазначають, що навчувані й синусоїдні кодування дали майже однакові результати, але синусоїди дозволяють працювати з довшими послідовностями, ніж бачила модель.

sinusoidal positional encoding matrix and similarity between positions
Чим позиції відрізняються одна від одної
Матриця рахується просто тут, за формулою вище. Кожен рядок — вектор однієї позиції, кожен стовпець — своя частота: ліві стовпці міняються швидко, праві майже сталі.
кодування: рядок — позиція, стовпець — вимір
схожість кодування позиції 10 з усіма іншими
позиція в послідовності+1−1
1,00сама з собою
0,97із сусідньою
0,66через десять позицій
64чисел на позицію
≈ 0розбіжність після перестановки двох токенів
6 × 8токенів і вимірів у перевірці
нікодування додано до вкладень

Пік схожості стоїть рівно на обраній позиції й спадає з відстанню — саме тому модель може вивести «наскільки далеко» один токен від іншого, хоча сама увага порядку не бачить. Посуньте позицію: форма кривої їде за нею, не змінюючись, бо скалярний добуток двох кодувань залежить тільки від різниці позицій. Зменште D до восьми — частот стає замало, і далекі позиції починають виглядати схожими; це і є причина, чому розмірність кодування беруть такою самою, як розмірність вкладення.

Друга частина вставки перевіряє те, заради чого кодування й вигадали. Кнопка «сама увага» рахує самоувагу на шести токенах, потім міняє місцями третій і четвертий і порівнює результат із переставленим виходом. Різниця дорівнює нулю з точністю машинної арифметики: увага справді не бачить порядку, і це властивість архітектури, а не наслідок навчання. У лекції той самий дослід на PyTorch дає 1,2·10⁻⁷ — розбіжність більша лише тому, що там float32, а тут подвійна точність. Тепер натисніть «увага + позиційне кодування». Розбіжність перестає бути нулем — на цих числах 0,076. Причина в тому, що кодування прив'язане до позиції, а не до токена: переставивши слова, ми лишили кодування на місці, і входи справді змінилися. Саме це й потрібно — так модель дізнається, що «вона любить котів більше за собак» не те саме, що «вона любить собак більше за котів».

Позиційне кодування просто додається до вкладень ​

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

X=вкладення токенів⏟T×D+вкладення позицій⏟T×D

Може здатися дивним, що дві різні речі складаються в один вектор. Але розмірність D велика — 512 у базовій моделі, — і мережа має досить місця, щоб рознести обидва сигнали по різних напрямках простору.

positional encoding vectors are added to the token embeddings

Джерело: J. Alammar · The Illustrated Transformer

Блок трансформера: увага, MLP і два обходи ​

Тепер зберімо блок. Він складається з двох підблоків, кожен обгорнутий залишковим зв'язком і нормалізацією:

X←X+SA(LN(X)),X←X+MLP(LN(X))

Залишкові зв'язки — ті самі, що в ResNet із минулої лекції; без них глибокий стек блоків не навчається. Пошарова нормалізація (layer normalization) стандартизує кожен вектор токена по його координатах — на відміну від пакетної, вона не залежить від інших зразків у батчі, що зручно для послідовностей різної довжини.

Сучасні моделі ставлять нормалізацію на початок залишкової гілки (pre-LN), як у формулі вище; в оригінальній статті 2017 року вона стояла після додавання: LN(X+Sublayer(X)).

encoder block with self attention feed forward and add and normalize steps

Джерело: J. Alammar · The Illustrated Transformer

Увага змішує між токенами, MLP — усередині токена ​

Два підблоки роблять принципово різні речі, і це варто запам'ятати як одне речення.

ПідблокЩо змішуєФормулаПараметри
Самоувагаінформацію між токенамиsoftmax(QK⊤/dk)VWQ,WK,WV,WO
MLPінформацію всередині кожного токенаφ(xtW1)W2W1,W2 і зсуви

MLP застосовується до кожного токена незалежно й однаково — це та сама маленька мережа, прогнана по всіх позиціях. Внутрішня розмірність зазвичай учетверо більша за D: у базовій моделі 2048 при D=512. Саме в цих шарах зосереджена більшість параметрів моделі.

Отже, увага збирає контекст, а MLP його обробляє. Стек із L таких блоків повторює цю пару L разів.

Повна архітектура трансформера ​

Ось усе разом. Ліва половина — енкодер: вкладення, позиційне кодування і n блоків «багатоголова увага → додати й нормалізувати → позиційний FFN → додати й нормалізувати».

Права половина — декодер. У ньому три підблоки замість двох. Перший — маскована багатоголова увага, щоб не підглядати в майбутнє. Другий — увага енкодер-декодер: запити беруться з декодера, а ключі та значення — з виходу енкодера, тобто декодер «читає» закодований вхід. Третій — той самий FFN.

full transformer architecture with encoder and decoder stacks

Джерело: Dive into Deep Learning · The Transformer Architecture

Стек блоків і потік даних у перекладі ​

Та сама архітектура з іншого боку. Речення «Je suis étudiant» проходить крізь шість енкодерів; вихід останнього подається в усі шість декодерів, і ті породжують переклад по слову. Важлива деталь: блоки не діляться вагами — однакова будова, різні параметри, тож кожен наступний працює з дедалі абстрактнішими поданнями, як шари згорткової мережі.

stack of six encoders and six decoders in the original transformer

Джерело: J. Alammar · The Illustrated Transformer

Три способи вживати трансформер ​

Із тих самих блоків збирають три різні моделі, і вибір визначається задачею.

РежимЗадачіЯк працюєМаскування
Лише енкодеррозмітка, класифікація текстуусі токени обробляються одночасно, вихід фіксованого розмірубез маски: кожен бачить усіх
Лише декодергенерація тексту, ChatGPTавторегресивно: породити токен, дописати до входу, повторитипричинна маска
Енкодер-декодерпереклад, стислий переказспершу закодувати весь вхід, потім декодувати токен за токеноммаска лише в декодері

Оригінальний трансформер був енкодером-декодером, бо його будували для перекладу; сучасні великі мовні моделі — переважно чисті декодери.

Чотири підходи до однієї задачі класифікації текстів ​

Курс збудував трансформер із нуля — і на цьому зазвичай зупиняються. Але справжня робота починається з чужої попередньо навченої моделі, тож порівняймо чесно. Задача: чотири категорії 20 Newsgroups, 2341 навчальний і 1558 тестових документів, звичайний процесор.

Вивід код запущено, вивід не редагувався
text
мішок слів: 0.9134

На що дивитися: проста лінійна модель на мішку слів правильно класифікує 91,34 % тестових повідомлень — з цим числом порівнюватимемо трансформери в таблиці нижче.

ПідхідПараметрівТочністьЧас навчання
мішок слів + логістична регресія20 тис.0,91340,5 с
наш трансформер із нуля1,5 млн0,670135 с
донавчений bert-tiny4,4 млн0,864613 с
донавчений distilbert67 млн0,9134284 с

Читайте таблицю знизу вгору. Донавчений distilbert із шістдесяти семи мільйонів параметрів після п'яти хвилин навчання дає рівно ту саму точність, що мішок слів за півсекунди. Трансформер, навчений із нуля на цих же двох тисячах документів, програє обом на двадцять пунктів.

Висновок не «трансформери не потрібні», а «модель обирають під обсяг даних і природу задачі». Категорії тут різняться самою лексикою: слова «orbit», «hockey» і «graphics» майже не перетинаються, а мішок слів саме лексику й міряє. Трансформер виграє там, де вирішує порядок і контекст — заперечення, іронія, довгі залежності, — і там, де документів не дві тисячі, а мільйони. На цій задачі його перевагу просто нема на чому показати, і чесніше це визнати, ніж підганяти висновок під моду.

Токен — це не завжди слово ​

Перш ніж мережа щось побачить, текст треба перетворити на числа. Це робить токенізатор: він ріже рядок на шматки зі свого фіксованого словника — часто це частини слів. Наприклад, «Transformers are awesome!» перетворюється на сім токенів, кожен зі своїм номером у словнику.

Далі номер токена замінюється рядком матриці вкладень Wemb∈RNvocab×D — і саме ця матриця навчається разом із мережею, тоді як сам токенізатор лишається фіксованим.

Той самий підхід працює і поза текстом. Зображення ріжуть на клаптики 16 × 16 пікселів, розгортають кожен у вектор довжини 768 і множать на спільну матрицю вкладень — виходить послідовність, з якою трансформер працює так само, як зі словами. Звідси й гасло «токенізуй усе»: одна архітектура для тексту, зображень і звуку.

tokens are turned into embedding vectors of fixed size

Джерело: J. Alammar · The Illustrated Transformer

Мовна модель учиться передбачати наступний токен ​

Найдивовижніше в сучасних мовних моделях — те, наскільки простим є їхнє навчання. Це звичайна багатокласова класифікація, де кількість класів дорівнює розміру словника.

Візьмімо рядок «The sun is shining, the weather is …». Кожному токену приписуємо мітку — токен, що йде за ним. Модель пропускає рядок крізь стек блоків із причинною маскою, бере контекстуалізоване подання кожної позиції, множить на матрицю розміру D×Nvocab і застосовує softmax. Втрата — та сама перехресна ентропія з лекції 05.

Маска тут не деталь реалізації, а умова коректності: без неї модель, передбачаючи слово i, бачила б його саме серед входів і завдання стало б тривіальним. З маскою одне речення довжиною T дає одразу T навчальних прикладів, і жоден із них не потребує людської розмітки. Саме тому такі моделі вдається навчати на текстах інтернет-масштабу.

Генерація: передбачити, дописати, повторити ​

Навчену модель запускають у зворотний бік. Подання останнього токена множиться на матрицю виходу, softmax перетворює логіти на розподіл над словником, з нього беруть наступний токен — і дописують його до входу:

xt+1∼softmax(f(x1,…,xt))

Далі все повторюється з подовженою послідовністю. Такий режим називають авторегресивним (autoregressive), і саме він дозволяє видавати відповіді довільної довжини: модель зупиняється тоді, коли сама породжує спеціальний токен кінця.

Вибір із розподілу, а не завжди найімовірнішого токена, — це те, що робить відповіді різними за однакового запиту.

linear layer and softmax turn the decoder output into a word

Джерело: J. Alammar · The Illustrated Transformer

Без кешу кожен новий токен перераховує всю відповідь заново ​

Цикл із попереднього розділу описано правильно, але рахувати так не можна. Дописавши токен, ми подаємо на вхід усю послідовність — і модель заново обчислює ключі та значення для всіх попередніх токенів, хоча вони не змінилися. Вартість генерації n токенів виходить O(n3) замість O(n2).

Ліки називаються KV-кеш: ключі та значення кожного шару зберігають і дописують по одному вектору за крок.

Вивід код запущено, вивід не редагувався
text
модель: 4 блоки по 128 вимірів, параметрів 1 445 876

На що дивитися: модель не навчена — нижче нас цікавить лише обсяг обчислень, а не якість тексту; понад третину з 1 445 876 параметрів займає таблиця позицій на 4096 місць.

Вивід код запущено, вивід не редагувався
text
64 нові токени з кешем і без кешу: ті самі
токенів  без кешу, с  з кешем, с   виграш   кеш, МБ
    128         0.24        0.09     2.6×      0.56
    512         1.93        0.61     3.2×      2.13
   1024         6.67        1.49     4.5×      4.23

На що дивитися: спершу перевірка, що кеш не змінює жодного токена; далі дивіться на стовпець «виграш»: він зростає з довжиною відповіді, бо без кешу кожен крок заново перераховує всю послідовність.

На нашій моделі з чотирьох блоків по 128 вимірів пришвидшення зростає разом із довжиною: ×2,6 на 128 нових токенах, ×3,2 на 512 і ×4,5 на 1024. Це не константа, а саме те, чого й слід чекати: без кешу вартість росте кубічно, з кешем — квадратично.

Плата — пам'ять. Кеш займає 2⋅L⋅n⋅D чисел: у нашій моделі це 4,2 мегабайта на тисячу токенів, а в мовній моделі з 32 шарів по 4096 вимірів — 4,3 гігабайта на вісім тисяч токенів у half-точності. Ось звідки береться ціна довгого контексту: не з обчислень, а з пам'яті під кеш.

Код: маленький трансформер, який учиться відтворювати послідовність ​

Зберімо все в працюючу модель. Задача навмисно проста: на вході вісім випадкових цифр, роздільник, і ті самі вісім цифр; модель має відтворити другу половину. Розв'язати її можна лише навчившись дивитися на потрібну позицію в першій половині — тобто саме увагою.

Вивід код запущено, вивід не редагувався
text
параметрів: 27659
крок  20: втрата 1.3729, точність 82.9 %
крок  90: втрата 0.0000, точність 100.0 %

На що дивитися: за 20 кроків точність сягає 82,9 %, а до 90-го втрата падає до нуля й модель відтворює 100 % цифр у нових послідовностях.

Навчання займає секунди.

Що саме вивчила єдина голова уваги ​

Тепер найцікавіше — подивитися, як модель це зробила. Ліворуч крива навчання: за перші два десятки кроків точність стрибає з випадкових 10 % до 82,9 %, до дев'яностого кроку виходить на 100 %.

Праворуч — матриця ваг уваги на конкретній послідовності. Вище діагоналі матриця порожня — працює причинна маска. І чітко видно рівну похилу смугу: кожна позиція другої половини дивиться рівно на одну позицію першої, зсунуту так, щоб узяти саме ту цифру, яку треба вивести наступною. Позиція 12, наприклад, дивиться на позицію 4.

Мережа не отримувала жодних вказівок про структуру задачі. Вона знайшла потрібний патерн уваги сама — і це той самий тип механізму, який у великих моделях відповідає за копіювання імен, дат і цитат із контексту.

attention pattern learned by a one layer transformer on a copy task

Числа оригінальної статті ​

Щоб мати відчуття масштабу, ось конфігурації з роботи «Attention Is All You Need» — вона вийшла на конференції NIPS 2017 (сучасну назву NeurIPS цій конференції дали пізніше).

ГіперпараметрBaseBig
Блоків у енкодері та декодері66
D (у статті dmodel)5121024
Голів H816
dk=dv6464
Внутрішня розмірність MLP20484096
Dropout0,10,3
Параметрів65 млн213 млн
BLEU, WMT 2014 англійська → німецька27,328,4

Навчання йшло на восьми GPU NVIDIA P100: base — 100 тисяч кроків приблизно за 12 годин, big — 300 тисяч кроків за три з половиною доби. За мірками 2017 року це був недорогий експеримент, і саме доступність відтворення пришвидшила поширення архітектури.

Джерело: Vaswani A. та ін., Attention Is All You Need, NIPS 2017

Обмеження трансформерів ​

Квадратична вартість за довжиною. Матриця T×T обмежує довжину контексту і за часом, і за пам'яттю. Обхідні способи — розріджена увага, наближення низького рангу, оптимізовані реалізації — є окремим і активним напрямом досліджень.

Потрібно дуже багато даних. Трансформер не має вбудованих припущень про структуру входу: ні локальності згортки, ні порядку рекурентної мережі. Свободу доводиться оплачувати даними, тому на малих вибірках згорткова мережа часто виграє.

Порядок доводиться повідомляти окремо. Позиційне кодування — це латка, і поводиться вона по-різному на послідовностях, довших за ті, що були під час навчання.

Впевнені хибні відповіді. Модель оптимізує правдоподібність продовження, а не істинність. Звідси вигадані факти й слабка багатокрокова логіка.

Ваги уваги — не пояснення. Висока вага не доводить причинного впливу: це підказка для аналізу, а не інтерпретація.

Підсумок лекції ​

Увага — це зважене середнє, ваги якого обчислюються з даних. Запит шукає, ключ відповідає, значення переноситься; softmax по рядку робить із оцінок розподіл.

Формула вміщується в один рядок: softmax(QK⊤/dk)V, де ділення на dk рятує від насичення softmax: у нашому досліді воно знизило максимальну вагу з 0,78 до 0,23.

Головна перевага — прямий шлях. Між будь-якими двома токенами один крок, тому нічого не забувається; у рекурентної мережі градієнт за першим токеном виявився меншим на тринадцять порядків. Ціна — матриця T×T.

Самоувага не бачить порядку — це доводиться перестановкою, і виправляється додаванням позиційного кодування до вкладень.

Блок трансформера робить дві різні речі: увага змішує інформацію між токенами, MLP — усередині кожного токена; залишкові зв'язки й нормалізація дозволяють ставити такі блоки десятками.

Мовна модель — це класифікатор наступного токена з причинною маскою, і вся генерація зводиться до повторення одного кроку.

Джерела й додаткові матеріали ​

Курси

  • EPFL CS-433 — лекція 09a про трансформери
  • UBC CPSC 340 — лекція 34: від LSTM до уваги
  • Stanford CS231n — лекція про увагу й трансформери

Стаття, з якої все почалося

Пояснення й підручники

Код і документація

Матеріали курсу. Схеми із зовнішніх джерел належать їхнім авторам — посилання під кожною ілюстрацією.