Intuisi attention

Mental model query, key, value, dan posisi attention pada transformer.

Daftar isi

Attention adalah mekanisme yang memungkinkan setiap posisi pada sekuens mengumpulkan informasi dari seluruh posisi lain secara langsung, dengan bobot yang dihitung dari kesesuaian antara posisi. Topik ini membangun mental model query, key, dan value, menghitung satu attention secara manual dengan torch, serta menempatkan mekanisme ini di dalam arsitektur transformer. Setelah topik ini, setiap komponen pada rumus attention dapat diikuti langkah demi langkah beserta alasan keberadaannya.

Mental model query, key, dan value#

Pencarian informasi memberi kerangka yang tepat untuk attention. Satu posisi membawa query, yaitu pertanyaan tentang informasi apa yang dibutuhkan, sementara setiap posisi lain menawarkan key sebagai label dan value sebagai isi. Pencarian kamus biasa mengembalikan tepat satu nilai, sedangkan attention melakukan pencarian lembut (soft lookup), yaitu mengambil semua value sekaligus dengan bobot yang proporsional terhadap kesesuaian antara query dan key.

Kerangka ini pula menjelaskan asal istilah multi-head. Satu pasang proyeksi memberi satu cara membaca kesesuaian antara posisi, sedangkan beberapa kepala memberi beberapa sudut pandang yang berjalan bersamaan pada posisi yang sama. Untuk membangun intuisinya, satu pasang query, key, dan value saja sudah cukup sebagai bahan hitung.

Ketiga besaran itu diproyeksikan dari embedding yang sama. Setiap posisi mengeluarkan vektor query, vektor key, dan vektor value melalui proyeksi linear yang bobotnya dipelajari, sehingga posisi yang sama dapat menggunakan “sudut pandang” yang berbeda untuk bertanya, untuk dilabeli, dan untuk menyumbang isi. Diagram berikut merangkum alur lengkapnya.

Setiap posisiembedding tokenproyeksiq, k, vvektor per posisiq · kSkor + softmaxbobot per barisbobot · vJumlah berbobotkeluaran per posisiSetiap baris bobot hasil softmax berjumlah 1, sehingga keluaran gabungan berbobot dari semua value
Alur attention dari embedding hingga jumlah berbobot

Rumus skor, softmax, dan jumlah berbobot#

Seluruh mekanisme di atas dirangkum dalam satu rumus. Untuk matriks query Q, key K, dan value V, attention dot-product berskala (scaled dot-product attention) dihitung sebagai

Attention(Q,K,V)=softmax ⁣(QK⊤dk)V \mathrm{Attention}(Q, K, V) = \mathrm{softmax}\!\left( \frac{QK^{\top}}{\sqrt{d_k}} \right) V

Setiap bagian rumus membawa satu peran. Hasil QK⊤ QK^{\top} adalah matriks skor berukuran panjang sekuens kali panjang sekuens, yang entrinya mengukur kesesuaian setiap pasangan query dan key. Pembagian dk \sqrt{d_k} menjaga skala skor agar softmax tidak menghasilkan bobot yang terlalu tajam, sedangkan softmax diterapkan pada setiap baris sehingga bobot setiap query berjumlah satu. Perkalian terakhir menjumlahkan value dengan bobot tersebut.

Langkah demi langkah yang sama dapat dihitung langsung dengan torch:

python attention-manual.py
import torch

torch.manual_seed(0)

panjang, d_k = 4, 3                      # panjang sekuens dan dimensi q/k
q = torch.randn(panjang, d_k)            # (4, 3) = (panjang_sekuens, d_k)
k = torch.randn(panjang, d_k)            # (4, 3) = (panjang_sekuens, d_k)
v = torch.randn(panjang, d_k)            # (4, 3) = (panjang_sekuens, d_v)

skor = q @ k.transpose(-2, -1) / d_k ** 0.5   # (4, 4) = (panjang_query, panjang_key)
bobot = torch.softmax(skor, dim=-1)           # softmax sepanjang dimensi terakhir
output = bobot @ v                            # (4, 4) @ (4, 3) -> (4, 3)

print("Bentuk skor  :", tuple(skor.shape))
print("Bobot baris 1:", [round(b, 3) for b in bobot[0].tolist()])
print("Jumlah bobot :", [round(b, 3) for b in bobot.sum(dim=-1).tolist()])
text Keluaran
Bentuk skor  : (4, 4)
Bobot baris 1: [0.392, 0.202, 0.096, 0.309]
Jumlah bobot : [1.0, 1.0, 1.0, 1.0]

Skor berbentuk (4, 4) karena setiap posisi query membandingkan dirinya dengan empat posisi key. Baris pertama bobot pada keluaran memperlihatkan pencarian lembut yang sesungguhnya, yaitu posisi pertama memberi bobot 0.392, 0.202, 0.096, dan 0.309 kepada keempat value, dan jumlah keempatnya tepat 1.0. Keluaran setiap posisi adalah gabungan berbobot dari seluruh value, bukan satu value yang dipilih.

Attention tidak mengetahui posisi#

Perhatikan bahwa skor dihitung murni dari isi vektor, tanpa satu pun informasi tentang letak langkah dalam sekuens. Konsekuensinya, attention bersifat setara permutasi (permutation equivariant), yaitu mengacak urutan input hanya mengacak urutan output dengan cara yang sama:

python attention-tanpa-posisi.py
import torch

torch.manual_seed(0)

d_k = 3
q = torch.randn(4, d_k)   # (4, 3) = (panjang_sekuens, d_k)
k = torch.randn(4, d_k)   # (4, 3) = (panjang_sekuens, d_k)
v = torch.randn(4, d_k)   # (4, 3) = (panjang_sekuens, d_v)

def attention(q, k, v):
    skor = q @ k.transpose(-2, -1) / d_k ** 0.5   # (4, 4)
    bobot = torch.softmax(skor, dim=-1)           # (4, 4)
    return bobot @ v                              # (4, 3)

urutan = torch.tensor([2, 0, 3, 1])
output_asli = attention(q, k, v)                           # (4, 3)
output_urut = attention(q[urutan], k[urutan], v[urutan])   # (4, 3)

# hasil pada urutan baru sama dengan hasil lama yang diurut ulang
selisih = (output_urut - output_asli[urutan]).abs().max()
print("Selisih maksimum:", selisih.item())
text Keluaran
Selisih maksimum: 5.960464477539063e-08

Selisih tersebut berupa pecahan pada batas pembulatan float, sehingga kedua keluaran dianggap sama. Sifat ini muncul karena seluruh operasinya bekerja per pasangan vektor tanpa melihat letak baris maupun kolomnya. Dua posisi yang isinya ditukar hanya menukar baris skor yang sesuai, softmax tetap bekerja per baris seperti biasa, dan hasilnya ikut tertukar. Bagi attention, sekuens pada tahap ini hanyalah kumpulan vektor yang tidak berurutan.

Namun, urutan merupakan bagian dari makna sekuens, sebagaimana perbedaan “perpustakaan tutup sore ini” dengan susunan kata yang diacak. Karena attention tidak menyediakan informasi posisi, transformer menyuntikkannya secara eksplisit melalui positional encoding, yaitu pola vektor posisi yang dijumlahkan ke embedding sebelum masuk ke layer attention.

Posisi attention pada transformer#

Transformer menyusun layer attention ini menjadi blok yang berulang, dan setiap blok memasangkan attention dengan jaringan feed-forward sederhana, ditambah koneksi residual dan normalisasi. Attention di dalamnya lazim berbentuk self-attention, yaitu query, key, dan value diproyeksikan dari sekuens yang sama sehingga posisi-posisi dalam satu sekuens saling menimbang. Varian cross-attention menggunakan query dari satu sekuens dan key serta value dari sekuens lain, misalnya pada penerjemah.

Satu layer attention tunggal hanya menyediakan satu cara menimbang. Multi-head attention menjalankan beberapa attention secara paralel dengan proyeksi yang berbeda, lalu menggabungkan keluarannya, sehingga setiap kepala dapat menangkap pola kesesuaian yang berbeda. Implementasi bawaannya adalah nn.MultiheadAttention:

python mha-dasar.py
import torch

torch.manual_seed(0)

mha = torch.nn.MultiheadAttention(embed_dim=8, num_heads=2, batch_first=True)

# input: (batch, panjang_sekuens, embed_dim) = (2, 5, 8)
x = torch.randn(2, 5, 8)
output, bobot = mha(x, x, x)   # self-attention: q, k, v dari sumber yang sama

# output: (2, 5, 8) = (batch, panjang_sekuens, embed_dim)
# bobot : (2, 5, 5) = (batch, panjang_query, panjang_key)
print("Bentuk output:", tuple(output.shape))
print("Bentuk bobot :", tuple(bobot.shape))
print("Jumlah bobot :", bobot[0, 0].sum().item())
text Keluaran
Bentuk output: (2, 5, 8)
Bentuk bobot : (2, 5, 5)
Jumlah bobot : 1.0

Keluaran berbentuk sama persis dengan input, yaitu (2, 5, 8), sehingga blok attention mudah ditumpuk. Bobot yang dikembalikan berbentuk (batch, panjang_query, panjang_key), dan jumlah bobot setiap barisnya tetap 1.0 seperti pada perhitungan manual. Tanpa rekurensi, seluruh posisi dihitung sekaligus, dan sifat inilah yang membedakan transformer dari keluarga layer rekuren.

Kesalahan umum#

Kesalahan pertama adalah membuang pembagian dk \sqrt{d_k} . Tanpa penskalaan itu, dot-product antara vektor berdimensi besar cenderung bernilai ekstrem, softmax mendekati pemilihan satu pemenang, dan gradien yang mengalir melaluinya mengecil. Penskalaan menjaga bobot tetap halus dan pelatihan tetap stabil.

Kesalahan kedua adalah menerapkan softmax pada dimensi yang salah. Bobot dijumlahkan per baris query, yaitu sepanjang dimensi key pada matriks skor berbentuk (panjang_query, panjang_key), sehingga torch.softmax(skor, dim=-1) menjadi pilihan yang aman. Softmax pada dimensi lain menghasilkan bobot yang bersaing antara query dan kehilangan arti “jumlah satu per posisi”.

Kesalahan ketiga adalah menganggap bobot attention sebagai parameter yang dipelajari langsung. Bobot tidak tersimpan di mana pun, melainkan dihitung dari proyeksi query dan key setiap kali forward berjalan, sehingga yang dipelajari adalah proyeksinya, bukan bobotnya. Perilaku ini juga berarti bobot berubah mengikuti setiap input baru.

Ringkasan#

  • Setiap posisi memproyeksikan embeddingnya menjadi query, key, dan value, dan attention menimbang kesesuaian antara posisi melalui dot-product.
  • Softmax mengubah skor setiap baris menjadi bobot yang berjumlah satu, lalu keluaran dihitung sebagai jumlah berbobot dari value.
  • Penskalaan dk \sqrt{d_k} menjaga softmax tetap halus, dan tanpa positional encoding, attention tidak membedakan urutan posisi.
  • nn.MultiheadAttention menjalankan beberapa attention paralel, mengembalikan output sebesar input beserta matriks bobotnya.

Referensi#