## Механізми уваги та самоувага в нейронних мережах
Механізми уваги дозволяють нейронним мережам **зосереджуватися на конкретних частинах вхідних даних під час генерації кожної частини виходу**. Вони призначають різні ваги різним вхідним даним, допомагаючи моделі вирішити, які вхідні дані є найбільш релевантними для поставленого завдання. Це є критично важливим у таких завданнях, як машинний переклад, де розуміння контексту всього речення необхідне для точного перекладу.
Мета цього четвертого етапу дуже проста: **застосувати деякі механізми уваги**. Це будуть багато **повторюваних шарів**, які **захоплюватимуть зв'язок слова у словнику з його сусідами в поточному реченні, що використовується для навчання LLM**.\
У традиційних моделях послідовність до послідовності, що використовуються для перекладу мов, модель кодує вхідну послідовність у вектор контексту фіксованого розміру. Однак цей підхід має труднощі з довгими реченнями, оскільки вектор контексту фіксованого розміру може не захоплювати всю необхідну інформацію. Механізми уваги вирішують це обмеження, дозволяючи моделі враховувати всі вхідні токени під час генерації кожного вихідного токена.
Розглянемо переклад німецького речення "Kannst du mir helfen diesen Satz zu übersetzen" на англійську. Переклад слово за словом не дасть граматично правильного англійського речення через відмінності в граматичних структурах між мовами. Механізм уваги дозволяє моделі зосереджуватися на релевантних частинах вхідного речення під час генерації кожного слова вихідного речення, що призводить до більш точного та зв'язного перекладу.
Самоувага, або внутрішня увага, є механізмом, де увага застосовується в межах однієї послідовності для обчислення представлення цієї послідовності. Це дозволяє кожному токену в послідовності звертатися до всіх інших токенів, допомагаючи моделі захоплювати залежності між токенами незалежно від їх відстані в послідовності.
#### Ключові концепції
* **Токени**: Окремі елементи вхідної послідовності (наприклад, слова в реченні).
* **Векторні представлення**: Векторні представлення токенів, що захоплюють семантичну інформацію.
* **Ваги уваги**: Значення, які визначають важливість кожного токена відносно інших.
### Обчислення ваг уваги: покроковий приклад
Розглянемо речення **"Hello shiny sun!"** і представимо кожне слово з 3-вимірним векторним представленням:
* **Hello**: `[0.34, 0.22, 0.54]`
* **shiny**: `[0.53, 0.34, 0.98]`
* **sun**: `[0.29, 0.54, 0.93]`
Наша мета - обчислити **вектор контексту** для слова **"shiny"** за допомогою самоуваги.
#### Крок 1: Обчислення оцінок уваги
{% hint style="success" %}
Просто помножте кожне значення виміру запиту на відповідне значення кожного токена і додайте результати. Ви отримуєте 1 значення для кожної пари токенів.
Крім того, **функція softmax** використовується, оскільки вона підкреслює відмінності завдяки експоненціальній частині, що полегшує виявлення корисних значень.
{% endhint %}
Застосуйте **функцію softmax** до оцінок уваги, щоб перетворити їх на ваги уваги, які в сумі дають 1.
Просто візьміть кожну вагу уваги і помножте її на відповідні виміри токена, а потім додайте всі виміри, щоб отримати лише 1 вектор (вектор контексту) 
1.**Обчисліть оцінки уваги**: Використовуйте скалярний добуток між векторним представленням цільового слова та векторними представленнями всіх слів у послідовності.
На практиці механізми самоуваги використовують **навчальні ваги** для навчання найкращих представлень для запитів, ключів і значень. Це передбачає введення трьох матриць ваг:
Схоже на попередній приклад, але цього разу, замість використання значень вимірів токенів, ми використовуємо матрицю ключів токена (яка вже була обчислена за допомогою вимірів):. Отже, для кожного запиту `qi` та ключа `kj`:
Взявши приклад з [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01\_main-chapter-code/ch03.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01\_main-chapter-code/ch03.ipynb), ви можете перевірити цей клас, який реалізує функціональність самостійної уваги, про яку ми говорили:
Зверніть увагу, що замість ініціалізації матриць випадковими значеннями, використовується `nn.Linear`, щоб позначити всі ваги як параметри для навчання.
{% endhint %}
## Причинна Увага: Приховування Майбутніх Слів
Для LLM ми хочемо, щоб модель враховувала лише токени, які з'являються перед поточною позицією, щоб **прогнозувати наступний токен**. **Причинна увага**, також відома як **замаскована увага**, досягає цього, модифікуючи механізм уваги, щоб запобігти доступу до майбутніх токенів.
Щоб реалізувати причинну увагу, ми застосовуємо маску до оцінок уваги **перед операцією softmax**, щоб залишкові значення все ще складали 1. Ця маска встановлює оцінки уваги майбутніх токенів на негативну нескінченність, забезпечуючи, що після softmax їх ваги уваги дорівнюють нулю.
### Маскування Додаткових Ваг Уваги з Допомогою Dropout
Щоб **запобігти перенавчанню**, ми можемо застосувати **dropout** до ваг уваги після операції softmax. Dropout **випадковим чином обнуляє деякі з ваг уваги** під час навчання.
Code example from [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01\_main-chapter-code/ch03.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01\_main-chapter-code/ch03.ipynb):
self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1)) # New
def forward(self, x):
b, num_tokens, d_in = x.shape
# b is the num of batches
# num_tokens is the number of tokens per batch
# d_in is the dimensions er token
keys = self.W_key(x) # This generates the keys of the tokens
queries = self.W_query(x)
values = self.W_value(x)
attn_scores = queries @ keys.transpose(1, 2) # Moves the third dimension to the second one and the second one to the third one to be able to multiply
attn_scores.masked_fill_( # New, _ ops are in-place
self.mask.bool()[:num_tokens, :num_tokens], -torch.inf) # `:num_tokens` to account for cases where the number of tokens in the batch is smaller than the supported context_size
attn_weights = torch.softmax(
attn_scores / keys.shape[-1]**0.5, dim=-1
)
attn_weights = self.dropout(attn_weights)
context_vec = attn_weights @ values
return context_vec
torch.manual_seed(123)
context_length = batch.shape[1]
d_in = 3
d_out = 2
ca = CausalAttention(d_in, d_out, context_length, 0.0)
context_vecs = ca(batch)
print(context_vecs)
print("context_vecs.shape:", context_vecs.shape)
```
## Розширення одноголової уваги до багатоголової уваги
**Багатоголова увага** на практиці полягає в виконанні **декількох екземплярів** функції самостійної уваги, кожен з яких має **свої власні ваги**, тому розраховуються різні фінальні вектори.
### Приклад коду
Можливо, можна повторно використовувати попередній код і просто додати обгортку, яка запускає його кілька разів, але це більш оптимізована версія з [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01\_main-chapter-code/ch03.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01\_main-chapter-code/ch03.ipynb), яка обробляє всі голови одночасно (зменшуючи кількість витратних циклів). Як ви можете бачити в коді, розміри кожного токена діляться на різні розміри відповідно до кількості голів. Таким чином, якщо токен має 8 розмірів і ми хочемо використовувати 3 голови, розміри будуть поділені на 2 масиви по 4 розміри, і кожна голова використовуватиме один з них:
Для ще однієї компактної та ефективної реалізації ви можете використовувати клас [`torch.nn.MultiheadAttention`](https://pytorch.org/docs/stable/generated/torch.nn.MultiheadAttention.html) у PyTorch.
Хоча дозволити кожній голові обробляти всі вимірювання вбудовування може здаватися вигідним, оскільки кожна голова матиме доступ до всієї інформації, стандартною практикою є **розділення вимірів вбудовування між головами**. Цей підхід забезпечує баланс між обчислювальною ефективністю та продуктивністю моделі та заохочує кожну голову вивчати різноманітні представлення. Тому розподіл вимірів вбудовування зазвичай є кращим, ніж надання кожній голові можливості перевіряти всі виміри.