إنتقل إلى المحتوى الرئيسي

الفصل 1: آلية الانتباه

المقدمة​

آلية الانتباه هي الابتكار الأساسي الذي يجعل المحولات ممكنة. إنها تسمح للنماذج بالتركيز ديناميكيًا على الأجزاء ذات الصلة من المدخلات عند إنتاج كل مخرج، متجاوزةً قيود متجهات السياق ذات الحجم الثابت.

الفكرة وراء الانتباه​

تشبيه بشري​

فكر في كيفية قراءة جملة معقدة:

"لم يعبر الحيوان الشارع لأن هو كان متعبًا جدًا."

لفهم ما يشير إليه "هو"، تقوم عقليًا بالانتباه إلى "الحيوان" بدلاً من "الشارع". هذا هو بالضبط ما يفعله الانتباه حسابيًا.

الفكرة الأساسية​

يحسب الانتباه مجموعًا مرجحًا للقيم، حيث يتم تحديد الأوزان بناءً على مدى صلة كل قيمة باستعلام معين:

Attention(Q,K,V)=softmax(QKTdk)V\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V

الاستعلام، المفتاح، القيمة: الإسقاطات الثلاثة​

فهم Q، K، V​

المكونالدورالتشبيه
الاستعلام (Q)ما نبحث عنهاستعلام بحث
المفتاح (K)ما يقدمه كل موضعإدخالات الفهرس
القيمة (V)المحتوى الفعليالوثائق

التعريف الرياضي​

بالنظر إلى إدخال X∈Rn×dX \in \mathbb{R}^{n \times d} (تسلسل من n رموز، كل منها بأبعاد d):

Q=XWQ,K=XWK,V=XWVQ = XW_Q, \quad K = XW_K, \quad V = XW_V

حيث:

  • WQ∈Rd×dkW_Q \in \mathbb{R}^{d \times d_k}
  • WK∈Rd×dkW_K \in \mathbb{R}^{d \times d_k}
  • WV∈Rd×dvW_V \in \mathbb{R}^{d \times d_v}

التنفيذ​

import torch
import torch.nn as nn
import math

class AttentionProjections(nn.Module):
def __init__(self, d_model, d_k, d_v):
super().__init__()
self.W_Q = nn.Linear(d_model, d_k, bias=False)
self.W_K = nn.Linear(d_model, d_k, bias=False)
self.W_V = nn.Linear(d_model, d_v, bias=False)

def forward(self, x):
"""
x: (batch, seq_len, d_model)
Returns Q, K, V projections
"""
Q = self.W_Q(x) # (batch, seq_len, d_k)
K = self.W_K(x) # (batch, seq_len, d_k)
V = self.W_V(x) # (batch, seq_len, d_v)
return Q, K, V

الانتباه بالنقطة المضاعفة المحسوبة​

الحساب خطوة بخطوة​

الخطوة 1: حساب درجات الانتباه

scores=QKT\text{scores} = QK^T

هذا يعطينا مصفوفة حيث تمثل المدخلة (i,j)(i, j) مقدار ما يجب أن ينتبه الموضع ii إلى الموضع jj.

# Q: (batch, seq_len, d_k)
# K: (batch, seq_len, d_k)
scores = torch.matmul(Q, K.transpose(-2, -1))
# scores: (batch, seq_len, seq_len)

الخطوة 2: مقياس الدرجات

scaled_scores=QKTdk\text{scaled\_scores} = \frac{QK^T}{\sqrt{d_k}}
لماذا نقوم بالمقياس؟

بدون المقياس، بالنسبة لـ dkd_k الكبيرة، يمكن أن تنمو النواتج النقطية بشكل كبير في الحجم، مما يدفع softmax إلى مناطق ذات تدرجات صغيرة جدًا. المقياس بواسطة dk\sqrt{d_k} يحافظ على استقرار التباين.

d_k = Q.size(-1)
scaled_scores = scores / math.sqrt(d_k)

الخطوة 3: تطبيق Softmax

attention_weights=softmax(scaled_scores)\text{attention\_weights} = \text{softmax}(\text{scaled\_scores})
attention_weights = torch.softmax(scaled_scores, dim=-1)
# Each row sums to 1

الخطوة 4: مجموع مرجح للقيم

output=attention_weights⋅V\text{output} = \text{attention\_weights} \cdot V
output = torch.matmul(attention_weights, V)
# output: (batch, seq_len, d_v)

التنفيذ الكامل​

def scaled_dot_product_attention(Q, K, V, mask=None):
"""
Compute scaled dot-product attention.

Args:
Q: Query tensor (batch, seq_len, d_k)
K: Key tensor (batch, seq_len, d_k)
V: Value tensor (batch, seq_len, d_v)
mask: Optional mask tensor

Returns:
output: Attention output (batch, seq_len, d_v)
attention_weights: Attention weights (batch, seq_len, seq_len)
"""
d_k = Q.size(-1)

# Step 1 & 2: Compute scaled scores
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)

# Optional: Apply mask (for causal attention)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))

# Step 3: Softmax
attention_weights = torch.softmax(scores, dim=-1)

# Step 4: Weighted sum
output = torch.matmul(attention_weights, V)

return output, attention_weights

تصور الانتباه​

مصفوفة الانتباه​

بالنسبة لجملة مثل "The cat sat":

The cat sat
The [ 0.7 0.2 0.1 ]
cat [ 0.3 0.5 0.2 ]
sat [ 0.2 0.4 0.4 ]

كل صف يظهر مقدار ما ينتبه ذلك الموضع إلى جميع المواضع الأخرى.

كود للتصور​

import matplotlib.pyplot as plt
import seaborn as sns

def visualize_attention(attention_weights, tokens):
"""
Visualize attention weights as a heatmap.
"""
plt.figure(figsize=(10, 8))
sns.heatmap(
attention_weights.detach().numpy(),
xticklabels=tokens,
yticklabels=tokens,
cmap='Blues',
annot=True,
fmt='.2f'
)
plt.xlabel('Keys')
plt.ylabel('Queries')
plt.title('Attention Weights')
plt.show()

الانتباه السببي (المقنع)​

بالنسبة لنماذج اللغة التوليدية، يجب أن ينتبه كل موضع فقط إلى المواضع السابقة.

القناع السببي​

def create_causal_mask(seq_len):
"""
Create a causal mask for autoregressive attention.
Position i can only attend to positions <= i.
"""
mask = torch.tril(torch.ones(seq_len, seq_len))
return mask

# Example for seq_len=4:
# [[1, 0, 0, 0],
# [1, 1, 0, 0],
# [1, 1, 1, 0],
# [1, 1, 1, 1]]

لماذا القناع السببي؟​

أثناء التدريب، نريد من النموذج أن يتنبأ بالرمز التالي بناءً فقط على الرموز السابقة، وليس الرموز المستقبلية (التي لن تكون لديه في وقت الاستدلال).

def causal_attention(Q, K, V):
"""
Attention with causal masking.
"""
seq_len = Q.size(1)
mask = create_causal_mask(seq_len).to(Q.device)
return scaled_dot_product_attention(Q, K, V, mask)

تعقيد الانتباه​

تعقيد الوقت​

O(n2⋅d)O(n^2 \cdot d)

حيث:

  • nn = طول التسلسل
  • dd = أبعاد النموذج

هذا التعقيد التربيعي في طول التسلسل هو السبب في أن النماذج ذات السياق الطويل تمثل تحديًا.

تعقيد الذاكرة​

O(n2)O(n^2)

بالنسبة لمصفوفة الانتباه نفسها (تخزين جميع الدرجات الزوجية).

تقنيات التحسين​

تتناول عدة طرق تعقيد الانتباه التربيعي:

  1. Flash Attention: تنفيذ فعال في الذاكرة
  2. Linear Attention: تقريب الانتباه في O(n)
  3. Sparse Attention: الانتباه فقط إلى مجموعة فرعية من المواضع
  4. Sliding Window: الانتباه المحلي مع الرموز العالمية

وضع كل شيء معًا​

class Attention(nn.Module):
"""
Complete attention module.
"""
def __init__(self, d_model, d_k=None, d_v=None, causal=False):
super().__init__()
d_k = d_k or d_model
d_v = d_v or d_model

self.W_Q = nn.Linear(d_model, d_k, bias=False)
self.W_K = nn.Linear(d_model, d_k, bias=False)
self.W_V = nn.Linear(d_model, d_v, bias=False)
self.W_O = nn.Linear(d_v, d_model, bias=False)

self.causal = causal
self.d_k = d_k

def forward(self, x, mask=None):
batch_size, seq_len, _ = x.size()

# Project to Q, K, V
Q = self.W_Q(x)
K = self.W_K(x)
V = self.W_V(x)

# Compute attention
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)

# Apply causal mask if needed
if self.causal:
causal_mask = torch.tril(torch.ones(seq_len, seq_len, device=x.device))
scores = scores.masked_fill(causal_mask == 0, float('-inf'))

# Apply additional mask if provided
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))

# Softmax and weighted sum
attn_weights = torch.softmax(scores, dim=-1)
output = torch.matmul(attn_weights, V)

# Output projection
output = self.W_O(output)

return output, attn_weights

النقاط الرئيسية​

الملخص
  1. الانتباه يحسب المجموعات المرجحة بناءً على الصلة
  2. Q، K، V هي ثلاث إسقاطات للإدخال بأدوار مميزة
  3. المقياس بواسطة dk\sqrt{d_k} يثبت التدريب
  4. القناع السببي يمنع النظر إلى الرموز المستقبلية
  5. التعقيد هو O(n2)O(n^2) في طول التسلسل

نظرة مستقبلية​

في الفصل التالي، سنستكشف كيفية استخدام الانتباه داخل تسلسل واحد (الانتباه الذاتي) وكيفية دمج آليات الانتباه المتعددة (الانتباه متعدد الرؤوس).