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

الفصل 2: الانتباه الذاتي والانتباه متعدد الرؤوس

المقدمة​

بناءً على فهمنا لآلية الانتباه، يستكشف هذا الفصل كيفية تطبيق الانتباه عندما ينتبه تسلسل إلى نفسه (الانتباه الذاتي) ولماذا يؤدي استخدام رؤوس متعددة للانتباه بشكل متوازٍ إلى تحسين قدرة النموذج بشكل كبير.

الانتباه الذاتي: جوهر المحولات​

ما هو الانتباه الذاتي؟​

في الانتباه الذاتي، ينتبه التسلسل إلى نفسه. يمكن لكل موضع في المدخلات أن ينتبه إلى كل موضع آخر في نفس المدخلات.

المدخلات: "القط جلس على السجادة"

الانتباه الذاتي: كل كلمة تنتبه إلى جميع الكلمات
├─ "ال" تنتبه إلى ["ال", "قط", "جلس", "على", "ال", "سجادة"]
├─ "قط" تنتبه إلى ["ال", "قط", "جلس", "على", "ال", "سجادة"]
├─ "جلس" تنتبه إلى ["ال", "قط", "جلس", "على", "ال", "سجادة"]
└─ ... وهكذا

لماذا الانتباه الذاتي؟​

يمكن الانتباه الذاتي من:

  1. الاعتماد على المدى الطويل: اتصالات مباشرة بين أي موضعين
  2. المعالجة المتوازية: يتم حساب جميع المواضع في وقت واحد
  3. السياق الديناميكي: أوزان مختلفة لمدخلات مختلفة

الصيغة الرياضية​

بالنسبة للانتباه الذاتي، تأتي Q و K و V جميعها من نفس المدخلات:

SelfAttention(X)=softmax(XWQ(XWK)Tdk)XWV\text{SelfAttention}(X) = \text{softmax}\left(\frac{XW_Q(XW_K)^T}{\sqrt{d_k}}\right)XW_V

التنفيذ​

import torch
import torch.nn as nn
import math

class SelfAttention(nn.Module):
"""
طبقة الانتباه الذاتي حيث تأتي Q و K و V جميعها من نفس المدخلات.
"""
def __init__(self, d_model, d_k=None):
super().__init__()
d_k = d_k 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_k, bias=False)
self.d_k = d_k

def forward(self, x, mask=None):
"""
x: موتر المدخلات (batch, seq_len, d_model)
mask: قناع الانتباه الاختياري
"""
# جميع الإسقاطات من نفس المدخلات
Q = self.W_Q(x)
K = self.W_K(x)
V = self.W_V(x)

# حساب درجات الانتباه
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)

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

attention_weights = torch.softmax(scores, dim=-1)
output = torch.matmul(attention_weights, V)

return output, attention_weights

مقارنة أنواع الانتباه​

الانتباه الذاتي مقابل الانتباه المتقاطع​

النوعمصدر Qمصدر K, Vحالة الاستخدام
الانتباه الذاتيالمدخلات Xالمدخلات Xالمشفر، المفكك
الانتباه المتقاطعالمفككالمشفرنماذج المشفر-المفكك

مثال على الانتباه المتقاطع​

class CrossAttention(nn.Module):
"""
الانتباه المتقاطع حيث تأتي Q من مصدر واحد، و K و V من مصدر آخر.
يستخدم في هياكل المشفر-المفكك.
"""
def __init__(self, d_model, d_k=None):
super().__init__()
d_k = d_k 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_k, bias=False)
self.d_k = d_k

def forward(self, query_input, key_value_input, mask=None):
"""
query_input: مصدر Q (مثل حالة المفكك)
key_value_input: مصدر K, V (مثل مخرجات المشفر)
"""
Q = self.W_Q(query_input)
K = self.W_K(key_value_input)
V = self.W_V(key_value_input)

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

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

attention_weights = torch.softmax(scores, dim=-1)
output = torch.matmul(attention_weights, V)

return output, attention_weights

الانتباه متعدد الرؤوس​

الدافع​

يمكن لرأس انتباه واحد فقط التقاط نوع واحد من العلاقات. يقوم الانتباه متعدد الرؤوس بتشغيل عمليات الانتباه المتعددة بشكل متوازٍ، مما يسمح للنموذج بالانتباه بشكل مشترك إلى المعلومات من فضاءات تمثيل مختلفة.

الفكرة​

يمكن للرؤوس المختلفة تعلم أنماط مختلفة:

  • الرأس 1: العلاقات النحوية (الموضوع-الفعل)
  • الرأس 2: العلاقات الموضعية (الكلمات القريبة)
  • الرأس 3: العلاقات الدلالية (المرادفات)
  • الرأس 4: الإحالة (الضمائر إلى الأسماء)

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

MultiHead(Q,K,V)=Concat(head1,...,headh)WO\text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, ..., \text{head}_h)W^O

حيث كل رأس هو:

headi=Attention(QWiQ,KWiK,VWiV)\text{head}_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V)

أبعاد المعلمات​

بالنسبة لـ hh رؤوس مع بُعد النموذج dmodeld_{model}:

  • كل رأس له بُعد dk=dv=dmodel/hd_k = d_v = d_{model} / h
  • تبقى المعلمات الإجمالية تقريبًا كما هي في الانتباه ذو الرأس الواحد
# مثال: d_model = 512, h = 8
d_model = 512
num_heads = 8
d_k = d_model // num_heads # 64 لكل رأس

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

class MultiHeadAttention(nn.Module):
"""
آلية الانتباه متعدد الرؤوس.
"""
def __init__(self, d_model, num_heads, dropout=0.1):
super().__init__()
assert d_model % num_heads == 0, "يجب أن يكون d_model قابلاً للقسمة على num_heads"

self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads

# الإسقاطات الخطية لـ Q, K, V (جميع الرؤوس مجتمعة)
self.W_Q = nn.Linear(d_model, d_model, bias=False)
self.W_K = nn.Linear(d_model, d_model, bias=False)
self.W_V = nn.Linear(d_model, d_model, bias=False)

# الإسقاط النهائي
self.W_O = nn.Linear(d_model, d_model, bias=False)

self.dropout = nn.Dropout(dropout)

def split_heads(self, x, batch_size):
"""
تقسيم البُعد الأخير إلى (num_heads, d_k).
تحويل إلى الشكل (batch, num_heads, seq_len, d_k)
"""
x = x.view(batch_size, -1, self.num_heads, self.d_k)
return x.transpose(1, 2)

def forward(self, query, key, value, mask=None):
batch_size = query.size(0)

# الإسقاطات الخطية
Q = self.W_Q(query)
K = self.W_K(key)
V = self.W_V(value)

# تقسيم إلى رؤوس
Q = self.split_heads(Q, batch_size) # (batch, heads, seq, d_k)
K = self.split_heads(K, batch_size)
V = self.split_heads(V, batch_size)

# الانتباه بالنقاط المتدرجة لجميع الرؤوس بشكل متوازٍ
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)

if mask is not None:
# توسيع القناع لأبعاد الرؤوس
if mask.dim() == 3:
mask = mask.unsqueeze(1)
scores = scores.masked_fill(mask == 0, float('-inf'))

attention_weights = torch.softmax(scores, dim=-1)
attention_weights = self.dropout(attention_weights)

# تطبيق الانتباه على القيم
context = torch.matmul(attention_weights, V)

# دمج الرؤوس
context = context.transpose(1, 2).contiguous()
context = context.view(batch_size, -1, self.d_model)

# الإسقاط الخطي النهائي
output = self.W_O(context)

return output, attention_weights

استخدام الانتباه متعدد الرؤوس​

# مثال على الاستخدام
d_model = 512
num_heads = 8
seq_len = 100
batch_size = 32

mha = MultiHeadAttention(d_model, num_heads)

# المدخلات (نفسها للانتباه الذاتي)
x = torch.randn(batch_size, seq_len, d_model)

# الانتباه الذاتي
output, attn_weights = mha(x, x, x)
print(f"شكل المخرجات: {output.shape}") # (32, 100, 512)
print(f"شكل الانتباه: {attn_weights.shape}") # (32, 8, 100, 100)

فوائد التوازي​

لماذا تتدرب المحولات بسرعة​

على عكس RNNs، يمكن حساب جميع المواضع في المحول في وقت واحد:

# RNN: تسلسلي (بطيء)
for t in range(seq_len):
h_t = rnn_cell(x[t], h_{t-1})

# المحول: متوازي (سريع)
output = multi_head_attention(x, x, x) # جميع المواضع دفعة واحدة

كفاءة وحدة معالجة الرسومات​

الانتباه متعدد الرؤوس مناسب بشكل خاص لوحدة معالجة الرسومات:

# جميع الرؤوس محسوبة في عملية ضرب مصفوفة مجمعة واحدة
# الشكل: (batch, heads, seq, d_k) @ (batch, heads, d_k, seq)
# → (batch, heads, seq, seq)
scores = torch.matmul(Q, K.transpose(-2, -1)) # متوازي عبر جميع الرؤوس!

تصور الرؤوس المتعددة​

def visualize_multi_head_attention(attention_weights, tokens, num_heads_to_show=4):
"""
تصور أنماط الانتباه من رؤوس متعددة.
attention_weights: (batch, heads, seq, seq)
"""
import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, num_heads_to_show, figsize=(4*num_heads_to_show, 4))

for head_idx in range(num_heads_to_show):
ax = axes[head_idx]
attn = attention_weights[0, head_idx].detach().numpy() # العنصر الأول في الدفعة

im = ax.imshow(attn, cmap='Blues')
ax.set_xticks(range(len(tokens)))
ax.set_yticks(range(len(tokens)))
ax.set_xticklabels(tokens, rotation=45, ha='right')
ax.set_yticklabels(tokens)
ax.set_title(f'الرأس {head_idx + 1}')

plt.tight_layout()
plt.show()

أنماط الانتباه في الممارسة​

أظهرت الأبحاث أن الرؤوس المختلفة تتعلم أنماطًا متخصصة:

نوع النمطما يلتقطهمثال
قطرينفس الموضع / الهويةالرمز ينتبه إلى نفسه
الرمز السابقالعلاقات التسلسليةأنماط نموذج اللغة
الرمز الأولالتجميع العالميرمز CLS في BERT
نحويهيكل القواعدتوافق الموضوع-الفعل
دلاليالعلاقات المعنويةالمرادفات، الأضداد

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

ملخص
  1. الانتباه الذاتي يسمح لكل موضع بالانتباه إلى جميع المواضع في نفس التسلسل
  2. الانتباه المتقاطع يمكن من الانتباه إلى تسلسل مختلف (المشفر-المفكك)
  3. الانتباه متعدد الرؤوس يشغل عمليات الانتباه المتعددة بشكل متوازٍ
  4. الرؤوس المختلفة تتعلم أنواعًا مختلفة من العلاقات
  5. التوازي يجعل المحولات فعالة على وحدات معالجة الرسومات

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

في الفصل التالي، سنستكشف كيف تتعامل المحولات مع المعلومات الموضعية من خلال الترميز الموضعي، وكيف يتم تمثيل الرموز من خلال التضمينات.