Her Şeyi Değiştiren Makale
2017'de Vaswani ve ekibi "Attention Is All You Need" başlıklı makaleyi yayımladı ve oyunun kuralları tamamen değişti. O dönemde NLP görevlerinde RNN ve LSTM gibi sıralı modeller kullanılıyordu. Çalışıyorlardı ama temel bir sorunları vardı: token'ları teker teker işliyorlardı. Bu da uzun menzilli bağımlılıkları yakalamayı zorlaştırıyor ve eğitimi paralel hale getirmeyi neredeyse imkânsız kılıyordu.
Transformer bu iki sorunu tek hamlede çözdü: tekrarlama mekanizmasını (recurrence) tamamen kaldırıp yerine attention koydu. Diziyi adım adım işlemek yerine tüm pozisyonlara aynı anda bakıyor ve her token çifti arasında ilgi puanları hesaplıyor. Bu büyük bir fark yaratıyor — eğitimde devasa bir paralellik sağlıyor ve modelin dizideki mesafeden bağımsız olarak bağımlılıkları yakalamasına olanak tanıyor.
Bugün neredeyse tüm son teknoloji dil modelleri Transformer üzerine kurulu. BERT, GPT-4, LLaMA — hepsi. Modern yapay zekâ alanında çalışıyorsan bu mimariyi anlamak şart. Bu yazıda her bir temel bileşeni inceleyecek, matematiksel sezgiyi oluşturacak ve her şeyi çalışan bir PyTorch implementasyonuyla birleştireceğiz.
Self-Attention: İşin Sihirli Kısmı
Self-attention (ya da resmi adıyla scaled dot-product attention), bir dizideki her token'ın diğer tüm token'lara "bakmasını" sağlayan mekanizma. Şöyle çalışıyor: her giriş token'ı için model üç vektör hesaplıyor — Query (Q), Key (K) ve Value (V). Bunları giriş embedding'ini üç ayrı öğrenilmiş ağırlık matrisiyle (W_Q, W_K, W_V) çarparak elde ediyorsun.
İki pozisyon arasındaki attention skoru, birinin query vektörü ile diğerinin key vektörünün iç çarpımı. Formül şu: Attention(Q, K, V) = softmax(Q K^T / sqrt(d_k)) V. sqrt(d_k)'ya bölme önemli — yoksa iç çarpımlar çok büyüyüp softmax'i gradyanların neredeyse sıfırlandığı bölgelere itiyor.
Bunu şöyle düşünebilirsin: her query "bu dizide benim için en önemli token'lar hangileri?" diye soruyor. Key'ler yanıtı veriyor, attention ağırlıkları da her value'nun o pozisyondaki çıktıya ne kadar katkı yapacağını belirliyor. Bunu güçlü kılan şey tamamen dinamik olması — model bağlama göre girdinin farklı kısımlarına odaklanıyor. Sabit convolution pencereleri veya RNN'lerin adım adım işlemesinden çok daha esnek.
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
class ScaledDotProductAttention(nn.Module):
"""Ölçeklendirilmiş iç çarpım dikkat mekanizması."""
def __init__(self, dropout: float = 0.1):
super().__init__()
self.dropout = nn.Dropout(dropout)
def forward(
self,
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
mask: torch.Tensor | None = None,
) -> tuple[torch.Tensor, torch.Tensor]:
d_k = query.size(-1)
# Dikkat puanlarını hesapla: (batch, heads, seq_len, seq_len)
scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)
# Maske uygula (örn. nedensel / dolgu maskeleri)
if mask is not None:
scores = scores.masked_fill(mask == 0, float("-inf"))
# Softmax ile normalize et ve dropout uygula
attn_weights = F.softmax(scores, dim=-1)
attn_weights = self.dropout(attn_weights)
# Değerlerin ağırlıklı toplamı
output = torch.matmul(attn_weights, value)
return output, attn_weightsScaled dot-product attention — temel yapı taşı
Multi-Head Attention: Farklı İlişkileri Paralelde Öğrenmek
Tek bir attention head sadece bir tür ilişkiyi yakalayabiliyor. Bu oldukça kısıtlayıcı. Multi-head attention bunu çözmek için birden fazla attention işlemini paralelde çalıştırıyor, her birinin kendi öğrenilmiş projeksiyon matrisleri var. Orijinal Transformer 8 head kullanıyor ve her biri 512 boyutlu model uzayının 64 boyutlu bir diliminde çalışıyor.
Her head i için head_i = Attention(X W_Q_i, X W_K_i, X W_V_i) hesaplanıyor. Sonra tüm head çıktıları birleştirilip son bir lineer katmandan geçiriliyor: MultiHead(Q, K, V) = Concat(head_1, ..., head_h) W_O. Pratikte farklı head'ler doğal olarak uzmanlaşıyor — bazıları sözdizimsel kalıpları öğreniyor, bazıları anlamsal benzerliği yakalıyor, diğerleri pozisyonel ilişkilere odaklanıyor. Oldukça zarif bir tasarım.
class MultiHeadAttention(nn.Module):
"""Yapılandırılabilir sayıda başa sahip çok başlıklı dikkat."""
def __init__(self, d_model: int = 512, n_heads: int = 8, dropout: float = 0.1):
super().__init__()
assert d_model % n_heads == 0, "d_model, n_heads'e bölünebilir olmalıdır"
self.d_model = d_model
self.n_heads = n_heads
self.d_k = d_model // n_heads
# Q, K, V ve çıktı için doğrusal projeksiyonlar
self.w_q = nn.Linear(d_model, d_model)
self.w_k = nn.Linear(d_model, d_model)
self.w_v = nn.Linear(d_model, d_model)
self.w_o = nn.Linear(d_model, d_model)
self.attention = ScaledDotProductAttention(dropout)
def forward(
self,
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
mask: torch.Tensor | None = None,
) -> torch.Tensor:
batch_size = query.size(0)
# Projekte et ve yeniden şekillendir
q = self.w_q(query).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
k = self.w_k(key).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
v = self.w_v(value).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
# Tüm başlarda paralel olarak dikkat uygula
attn_output, _ = self.attention(q, k, v, mask)
# Başları birleştir ve projekte et
attn_output = (
attn_output.transpose(1, 2)
.contiguous()
.view(batch_size, -1, self.d_model)
)
return self.w_o(attn_output)Multi-head attention — modelin farklı örüntüleri öğrenmesini sağlıyor
Positional Encoding: Sırasız Bir Modele Sıra Öğretmek
Transformer'larla ilgili önemli bir detay var: tüm pozisyonları paralel işledikleri için kendi başlarına hiçbir sıra kavramları yok. 1. pozisyondaki "kedi" ile 50. pozisyondaki "kedi" modele aynı görünüyor. Bu yüzden pozisyon bilgisini açıkça eklememiz gerekiyor. Orijinal makale bunu sinüzoidal encoding ile yapıyor — her pozisyon farklı frekanslarda sinüs ve kosinüs değerlerinden oluşan bir vektör alıyor. Formül: PE(pos, 2i) = sin(pos / 10000^(2i/d_model)) ve PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model)).
Buradaki zekice kısım şu: sabit bir offset'teki encoding, mevcut pozisyondaki encoding'in lineer bir fonksiyonu olarak ifade edilebiliyor. Yani model sadece mutlak değil, göreceli pozisyona göre de dikkat etmeyi öğrenebiliyor. Pratikte birçok modern Transformer öğrenilmiş positional embedding'lere ya da RoPE gibi daha gelişmiş yöntemlere geçti. Ama sinüzoidal yaklaşım hâlâ harika bir başlangıç noktası ve şaşırtıcı derecede iyi çalışıyor.
class PositionalEncoding(nn.Module):
"""'Attention Is All You Need' makalesinden sinüzoidal pozisyonel kodlama."""
def __init__(self, d_model: int = 512, max_len: int = 5000, dropout: float = 0.1):
super().__init__()
self.dropout = nn.Dropout(dropout)
# Pozisyonel kodlama matrisini oluştur
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(
torch.arange(0, d_model, 2, dtype=torch.float)
* (-math.log(10000.0) / d_model)
)
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0) # (1, max_len, d_model)
self.register_buffer("pe", pe)
def forward(self, x: torch.Tensor) -> torch.Tensor:
# x şekli: (batch, seq_len, d_model)
x = x + self.pe[:, : x.size(1)]
return self.dropout(x)Sinüzoidal positional encoding — modele sıra duygusu veriyor
Feed-Forward Ağlar ve Residual Bağlantılar
Her Transformer katmanında, her pozisyona bağımsız olarak uygulanan bir feed-forward ağ (FFN) var. İki lineer dönüşümün arasına bir non-linearity sıkıştırılmış durumda: FFN(x) = max(0, x W_1 + b_1) W_2 + b_2. Orijinalde iç boyut 2048 — model boyutunun dört katı. Bu genişle-ve-daralt deseni modelin daha zengin bir uzayda özellik çıkarması yapıp sonra geri dönmesini sağlıyor.
Hem attention hem de feed-forward alt katmanları residual bağlantılarla sarılı: çıktı LayerNorm(x + SubLayer(x)) şeklinde hesaplanıyor. Neden önemli? Residual bağlantılar backpropagation sırasında gradyanların doğrudan akmasını sağlıyor. Bu, derin ağları vanishing gradient sorunu yaşamadan eğitmek için şart. Layer normalization da aktivasyonları özellik boyutunda normalize ederek eğitimi kararlı tutuyor.
Modern Transformer'larda sıkça Pre-LayerNorm görürsün — normalizasyonu alt katmandan sonra değil önce uyguluyor. Çok derin modellerde eğitim kararlılığına ciddi katkı sağladığı ortaya çıktı. Residual bağlantılar, layer normalization ve feed-forward genişlemesi bir araya gelince her katman, attention'ın oluşturduğu temsilleri kademeli olarak iyileştirme kapasitesi kazanıyor.
class TransformerEncoderLayer(nn.Module):
"""Pre-layer normalizasyonlu tek Transformer kodlayıcı katmanı."""
def __init__(
self,
d_model: int = 512,
n_heads: int = 8,
d_ff: int = 2048,
dropout: float = 0.1,
):
super().__init__()
self.self_attn = MultiHeadAttention(d_model, n_heads, dropout)
self.feed_forward = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(d_ff, d_model),
nn.Dropout(dropout),
)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x: torch.Tensor, mask: torch.Tensor | None = None) -> torch.Tensor:
# Pre-norm öz-dikkat ve artık bağlantı
normed = self.norm1(x)
x = x + self.dropout(self.self_attn(normed, normed, normed, mask))
# Pre-norm ileri beslemeli ve artık bağlantı
normed = self.norm2(x)
x = x + self.feed_forward(normed)
return x
class TransformerEncoder(nn.Module):
"""Transformer kodlayıcı katmanları yığını."""
def __init__(
self,
vocab_size: int,
d_model: int = 512,
n_heads: int = 8,
n_layers: int = 6,
d_ff: int = 2048,
max_len: int = 5000,
dropout: float = 0.1,
):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
self.pos_encoding = PositionalEncoding(d_model, max_len, dropout)
self.layers = nn.ModuleList(
[TransformerEncoderLayer(d_model, n_heads, d_ff, dropout) for _ in range(n_layers)]
)
self.norm = nn.LayerNorm(d_model)
self.d_model = d_model
def forward(self, src: torch.Tensor, mask: torch.Tensor | None = None) -> torch.Tensor:
x = self.embedding(src) * math.sqrt(self.d_model)
x = self.pos_encoding(x)
for layer in self.layers:
x = layer(x, mask)
return self.norm(x)Encoder katmanı ve tam encoder yığını — bütün resim
Tam Forward Pass: Her Şey Nasıl Birleşiyor?
Veri bir Transformer encoder'dan geçerken neler oluyor, adım adım bakalım. Önce her token yoğun bir embedding vektörüne dönüşüyor. Sonra sıra bilgisi için positional encoding ekleniyor. Ortaya çıkan temsiller özdeş katmanlardan oluşan bir yığından geçiyor — her katmanda multi-head self-attention, ardından feed-forward ağ, her adımda residual bağlantılar ve layer normalization var.
Encoder-decoder mimarilerde (orijinal makaledeki makine çevirisi modeli gibi) decoder'a ek olarak encoder çıktısına dikkat eden bir cross-attention alt katmanı ekleniyor. Decoder ayrıca gelecekteki token'lara bakmayı engelleyen masked self-attention kullanıyor — autoregressive üretim için bu şart. GPT gibi modern decoder-only modeller sadece bu masked self-attention varyantını kullanıyor ve bu bile inanılmaz sonuçlar üretmeye yetiyor.
Info
Ölçeği somutlaştıralım: orijinal Transformer'da 6 encoder katmanı, 6 decoder katmanı, 8 attention head ve 512 model boyutu vardı — toplamda yaklaşık 65 milyon parametre. GPT-3 aynı temel mimariyi 96 katman, 96 head ve 12288 model boyutuna taşıyarak 175 milyar parametreye ulaştı. Aynı plan, bambaşka bir ölçek.
BERT'ten GPT'ye, Bugünkü LLM'lere
Transformer iki büyük paradigma doğurdu. BERT sadece encoder yığınını çift yönlü attention ile kullanıyor ve masked language modeling ile eğitiliyor. Anlama görevlerinde çok başarılı — sınıflandırma, varlık tanıma, soru yanıtlama. GPT tam tersini yapıyor: sadece decoder, causal (soldan sağa) attention ile bir sonraki token'ı tahmin ediyor. Üretim görevlerinde parlıyor ve yeterince büyütünce kimsenin açıkça öğretmediği beceriler sergilemeye başlıyor.
LLM devrimini asıl başlatan şey önemli bir keşifti: Transformer'lar çok iyi ölçekleniyor. OpenAI'daki Kaplan ve ekibi, model performansının model boyutu, veri seti boyutu ve hesaplama bütçesiyle tahmin edilebilir bir güç yasası olarak arttığını gösterdi. Bu öngörülebilirlik araştırmacılara daha büyük modellere güvenle yatırım yapma cesaretini verdi — ve sonuç karşılığını fazlasıyla ödedi.
Modern yenilikler orijinal tasarımı iyileştirdi ama onu işler kılan temel prensipler aynı kaldı. RoPE göreceli pozisyon bilgisini doğrudan attention hesaplamasına kodluyor ve daha iyi uzunluk genellemesi sağlıyor. GQA birden fazla query head arasında key-value head'leri paylaşarak inference sırasında bellek bant genişliğini azaltıyor. FlashAttention, attention hesaplamasını IO-aware hale getirerek GPU kullanımını dramatik biçimde artırıyor. SwiGLU aktivasyonları feed-forward katmanlardaki ReLU'nun yerini alarak performansı yükseltiyor. Ama dikkat çekici olan şu: temel yapı taşları — attention, residual bağlantılar, layer normalization, feed-forward ağlar — 2017'den bu yana değişmedi.
"Transformer, giriş ve çıktısının temsillerini hesaplamak için tamamen self-attention'a dayanan, sıra hizalı RNN veya convolution kullanmayan ilk dönüşüm modelidir." — Vaswani ve ark., Attention Is All You Need (2017)
Bu Yazıdan Çıkarılacaklar
- Self-attention bir dizideki tüm pozisyonlar arasında ilgi puanları hesaplayarak RNN'lerin sıralı işlemesini tamamen paralel hesaplamayla değiştiriyor.
- Multi-head attention modelin farklı ilişki türlerini — sözdizimi, anlam, pozisyon — eş zamanlı olarak yakalamasını sağlıyor.
- Positional encoding şart çünkü attention mekanizmasının kendi başına sıra kavramı yok. Onsuz model "köpek adamı ısırdı" ile "adam köpeği ısırdı" arasındaki farkı anlayamaz.
- Residual bağlantılar ve layer normalization, düzinelerce (hatta yüzlerce) katmanı eğitimin çökmesi olmadan üst üste yığmayı mümkün kılıyor.
- Mimari tahmin edilebilir şekilde ölçekleniyor — 65 milyon parametrede çalışan aynı tasarım yüz milyarlara kadar uzanarak bugünün en güçlü yapay zekâ sistemlerini çalıştırıyor.
- RoPE, GQA, FlashAttention ve SwiGLU gibi modern iyileştirmeler işleri daha hızlı ve daha iyi yapıyor ama temel mimari 2017'den bu yana kayda değer ölçüde kararlı.
Transformer tüm bir alanı gerçekten yeniden şekillendiren nadir yeniliklerden biri. Bileşenlerini — scaled dot-product attention, positional encoding, residual feed-forward yapısı — anladığında herhangi bir modern dil modeliyle çalışabilecek temele sahip olursun. BERT'i sınıflandırma için fine-tune etmek, RAG pipeline'ları kurmak ya da sıfırdan model eğitmek istersen, bu yapay zekâdaki en değerli mimari bilgi.