الفصل 3: الترميز الموضعي والتضمينات
مقدمة
على عكس الشبكات العصبية المتكررة (RNNs) التي تعالج التسلسلات رمزًا برمز، تعالج المحولات جميع المواقع في وقت واحد. هذا يخلق تحديًا أساسيًا: كيف يعرف النموذج ترتيب الرموز؟ يشرح هذا الفصل الترميز الموضعي وعملية تضمين الرموز الأوسع.
مشكلة الموضع
لماذا الموضع مهم
اعتبر هذه الجمل:
- "الكلب عض الرجل"
- "الرجل عض الكلب"
نفس الرموز، معاني مختلفة تمامًا. الموضع ضروري لفهم اللغة.
المحولات وثبات التبديل
بدون معلومات موضعية، يكون الانتباه الذاتي ثابت التبديل:
حيث هو أي تبديل. يعامل النموذج "الكلب عض الرجل" و"الرجل عض الكلب" بشكل متطابق!
# توضيح: درجات الانتباه مستقلة عن الموضع
import torch
def attention_scores(Q, K):
return torch.softmax(Q @ K.T, dim=-1)
# التسلسل الأصلي
x1 = torch.tensor([[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]])
# التسلسل المبدل (تبديل المواضع 0 و 1)
x2 = torch.tensor([[0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])
# نفس نمط الانتباه (فقط مبدل)!
print(attention_scores(x1, x1))
print(attention_scores(x2, x2))
تضمينات الرموز
من الرموز إلى المتجهات
قبل إضافة المعلومات الموضعية، نقوم أولاً بتحويل الرموز إلى متجهات كثيفة:
التنفيذ
import torch
import torch.nn as nn
class TokenEmbedding(nn.Module):
"""
تحويل معرفات الرموز إلى متجهات كثيفة.
"""
def __init__(self, vocab_size, d_model):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
self.d_model = d_model
def forward(self, token_ids):
"""
token_ids: (batch, seq_len) من معرفات الرموز الصحيحة
Returns: (batch, seq_len, d_model) التضمينات
"""
# مقياس التضمينات (من الورقة الأصلية للمحول)
return self.embedding(token_ids) * (self.d_model ** 0.5)
# مثال
vocab_size = 50000
d_model = 512
embedding_layer = TokenEmbedding(vocab_size, d_model)
tokens = torch.tensor([[101, 7592, 2088, 102]]) # [CLS] Hello World [SEP]
embeddings = embedding_layer(tokens)
print(f"Shape: {embeddings.shape}") # (1, 4, 512)
الترميز الموضعي الجيبي
النهج الأصلي
استخدمت الورقة الأصلية للمحول دوال جيبية ثابتة:
حيث:
- = الموضع في التسلسل
- = مؤشر البعد
- = بعد النموذج
الفكرة
كل بعد يتذبذب بتردد مختلف:
- الأبعاد المنخفضة: تذبذب بطيء (التقاط المواضع بعيدة المدى)
- الأبعاد العالية: تذبذب سريع (التقاط المواضع الدقيقة)
التنفيذ
import numpy as np
def get_sinusoidal_encoding(max_len, d_model):
"""
توليد الترميزات الموضعية الجيبية.
Args:
max_len: الطول الأقصى للتسلسل
d_model: بعد النموذج
Returns:
مصفوفة الترميز الموضعي (max_len, d_model)
"""
position = np.arange(max_len)[:, np.newaxis]
div_term = np.exp(np.arange(0, d_model, 2) * -(np.log(10000.0) / d_model))
pe = np.zeros((max_len, d_model))
pe[:, 0::2] = np.sin(position * div_term) # الفهارس الزوجية
pe[:, 1::2] = np.cos(position * div_term) # الفهارس الفردية
return pe
# التصور
import matplotlib.pyplot as plt
pe = get_sinusoidal_encoding(100, 512)
plt.figure(figsize=(12, 4))
plt.imshow(pe.T, aspect='auto', cmap='RdBu')
plt.xlabel('Position')
plt.ylabel('Dimension')
plt.colorbar(label='Value')
plt.title('Sinusoidal Positional Encoding')
plt.show()
خصائص الترميز الجيبي
- فريد: لكل موضع ترميز فريد
- محدود: القيم دائمًا في [-1, 1]
- المواضع النسبية: العلاقة الخطية تمكن من تعلم المواضع النسبية
- التعميم: يمكن التعميم على تسلسلات أطول مما شوهد في التدريب
تنفيذ PyTorch
class SinusoidalPositionalEncoding(nn.Module):
"""
الترميز الموضعي الجيبي من 'Attention Is All You Need'.
"""
def __init__(self, d_model, max_len=5000, dropout=0.1):
super().__init__()
self.dropout = nn.Dropout(p=dropout)
# إنشاء مصفوفة الترميز الموضعي
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).float() * (-np.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) # إضافة بعد الدفعة
# تسجيل كذاكرة مؤقتة (ليس معلمة، ولكن محفوظة مع النموذج)
self.register_buffer('pe', pe)
def forward(self, x):
"""
x: (batch, seq_len, d_model)
"""
x = x + self.pe[:, :x.size(1)]
return self.dropout(x)
التضمينات الموضعية المتعلمة
نهج بديل
تستخدم العديد من نماذج اللغة الكبيرة الحديثة (LLMs) تضمينات موضعية متعلمة:
class LearnedPositionalEmbedding(nn.Module):
"""
التضمينات الموضعية المتعلمة (مثل المستخدمة في GPT-2).
"""
def __init__(self, max_len, d_model):
super().__init__()
self.position_embedding = nn.Embedding(max_len, d_model)
def forward(self, x):
"""
x: (batch, seq_len, d_model)
"""
positions = torch.arange(x.size(1), device=x.device)
return x + self.position_embedding(positions)
المقارنة
| الجانب | جيبي | متعلم |
|---|---|---|
| المعلمات | لا يوجد | max_len × d_model |
| التعميم | أفضل | محدود بـ max_len |
| الأداء | قابل للمقارنة | قابل للمقارنة |
| المرونة | ثابت | يتكيف مع البيانات |
تضمين الموضع الدوار (RoPE)
ابتكار حديث
RoPE، المستخدم في LLaMA وMistral والعديد من النماذج الحديثة، يرمز الموضع من خلال الدوران:
الفوائد الرئيسية
- المواضع النسبية: يلتقط بشكل طبيعي معلومات الموضع النسبي
- التعميم: معالجة أفضل للتسلسلات الأطول
- الكفاءة: يطبق مباشرة على Q، K بدون معلمات إضافية
تنفيذ مبسط
def rotary_embedding(x, seq_len, d_model):
"""
تنفيذ RoPE مبسط.
x: (batch, seq_len, d_model)
"""
# توليد زوايا الدوران
position = torch.arange(seq_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2) * -(np.log(10000.0) / d_model))
angles = position * div_term
# تطبيق الدوران على أزواج الأبعاد
x_rotated = x.clone()
x_rotated[..., 0::2] = x[..., 0::2] * torch.cos(angles) - x[..., 1::2] * torch.sin(angles)
x_rotated[..., 1::2] = x[..., 0::2] * torch.sin(angles) + x[..., 1::2] * torch.cos(angles)
return x_rotated
ALiBi: الانتباه مع التحيزات الخطية
نهج آخر
ALiBi (الانتباه مع التحيزات الخطية) يضيف عقوبة خطية بناءً على المسافة:
حيث هو ميل خاص بالرأس.
الفوائد
- لا توجد معلمات إضافية
- تعميم ممتاز للطول
- تنفيذ بسيط
طبقة التضمين الكاملة
جمع كل شيء معًا
class TransformerEmbedding(nn.Module):
"""
طبقة التضمين الكاملة: رمز + موضع.
"""
def __init__(self, vocab_size, d_model, max_len=5000,
pos_encoding='sinusoidal', dropout=0.1):
super().__init__()
self.token_embedding = nn.Embedding(vocab_size, d_model)
self.d_model = d_model
self.dropout = nn.Dropout(dropout)
if pos_encoding == 'sinusoidal':
self.pos_encoding = SinusoidalPositionalEncoding(d_model, max_len)
elif pos_encoding == 'learned':
self.pos_encoding = LearnedPositionalEmbedding(max_len, d_model)
else:
raise ValueError(f"Unknown pos_encoding: {pos_encoding}")
def forward(self, token_ids):
"""
token_ids: (batch, seq_len)
Returns: (batch, seq_len, d_model)
"""
# تضمينات الرموز (مقاسة)
x = self.token_embedding(token_ids) * (self.d_model ** 0.5)
# إضافة الترميز الموضعي
x = self.pos_encoding(x)
return self.dropout(x)
# الاستخدام
embedding = TransformerEmbedding(
vocab_size=50000,
d_model=512,
max_len=2048,
pos_encoding='sinusoidal'
)
tokens = torch.randint(0, 50000, (32, 100)) # دفعة من 32، طول التسلسل 100
embedded = embedding(tokens)
print(f"Output shape: {embedded.shape}") # (32, 100, 512)
الصورة الكاملة
إليك كيف تتدفق الرموز عبر التضمينات إلى الانتباه:
معرفات الرموز: [101, 7592, 2088, 102]
↓
تضمين الرموز: البحث في جدول التضمين
↓
[0.23, -0.45, ...] [0.89, 0.12, ...] [0.45, -0.23, ...] [0.67, 0.34, ...]
↓
إضافة الترميز الموضعي
↓
[0.23+PE_0, -0.45+PE_0, ...] [0.89+PE_1, 0.12+PE_1, ...] ...
↓
الانتباه متعدد الرؤوس
↓
الإخراج مع السياق الواعي للموضع
النقاط الرئيسية
- معلومات الموضع ضرورية - المحولات ثابتة التبديل بخلاف ذلك
- تضمينات الرموز تحول الرموز المنفصلة إلى متجهات مستمرة
- الترميز الجيبي يستخدم أنماط تردد ثابتة (المحول الأصلي)
- التضمينات المتعلمة تتكيف مع البيانات ولكن لها طول أقصى ثابت
- RoPE يرمز المواضع النسبية من خلال الدوران (النماذج الحديثة)
- ALiBi يستخدم تحيزات الانتباه القائمة على المسافة
نظرة مستقبلية
الآن بعد أن فهمنا البنية الكاملة للمحول - الانتباه، الانتباه متعدد الرؤوس، والترميز الموضعي - نحن مستعدون للانتقال إلى الوحدة 3، حيث سنستكشف كيفية تدريب هذه النماذج الضخمة.