مکانیزم توجه (Attention Mechanism) یکی از مهمترین نوآوریهای دهه اخیر در یادگیری عمیق است که توانسته انقلابی در حوزه پردازش زبان طبیعی و بینایی ماشین ایجاد کند. این مقاله به بررسی جامع مفاهیم نظری، ریاضیات پشت مکانیزم توجه و پیادهسازی عملی آن در PyTorch میپردازد. در این راهنما، انواع مختلف مکانیزم توجه شامل Self-Attention، Scaled Dot-Product Attention و Multi-Head Attention را با جزئیات کامل و کدهای قابل اجرا بررسی خواهیم کرد.
۱. مقدمه و پیشینه
۱.۱ تاریخچه و پیدایش مکانیزم توجه
مکانیزم توجه برای اولین بار در سال ۲۰۱۵ توسط Bahdanau و همکاران در مقاله “Neural Machine Translation by Jointly Learning to Align and Translate” معرفی شد. این مکانیزم برای حل محدودیتهای مدلهای Sequence-to-Sequence مبتنی بر شبکههای عصبی بازگشتی (RNN) طراحی شد.
پیش از معرفی مکانیزم توجه، مدلهای ترجمه ماشینی بر پایه معماری Encoder-Decoder ساخته میشدند که در آن تمام اطلاعات جمله ورودی در یک بردار زمینه (Context Vector) با طول ثابت فشرده میشد. این رویکرد برای جملات بلند و پیچیده با مشکل جدی گلوگاه بردار زمینه (Context Vector Bottleneck) مواجه بود.
۱.۲ انقلاب Transformer و “Attention Is All You Need”
نقطه عطف اصلی در سال ۲۰۱۷ با انتشار مقاله تاریخساز “Attention Is All You Need” توسط Vaswani و همکاران در Google رخ داد. این مقاله معماری Transformer را معرفی کرد که بهطور کامل بر مکانیزم Self-Attention متکی بود و هیچ لایه بازگشتی یا پیچشی نداشت.
معماری Transformer و مکانیزمهای توجه آن پایهگذار مدلهای زبانی بزرگ مدرن مانند BERT، GPT و T5 شدند که امروزه در کاربردهایی مانند ChatGPT استفاده میشوند.
۲. مفاهیم پایه و ضرورت مکانیزم توجه
۲.۱ مشکل اساسی: محدودیت حافظه ثابت
در معماریهای کلاسیک Encoder-Decoder، انکودر باید تمام اطلاعات یک دنباله ورودی را در یک بردار حالت پنهان نهایی (Final Hidden State) با ابعاد ثابت خلاصه کند. این رویکرد دارای معایب عمدهای است:
- از دست رفتن اطلاعات: برای جملات طولانی، اطلاعات ابتدای جمله بهتدریج در حافظه محو میشود
- عدم مقیاسپذیری: عملکرد مدل با افزایش طول جمله بهشدت کاهش مییابد
- عدم توانایی در مدلسازی وابستگیهای بلندمدت: ارتباط بین کلمات دور از هم در جمله بهدرستی مدلسازی نمیشود
۲.۲ راهحل: توجه انتخابی به اطلاعات مرتبط
مکانیزم توجه به مدل این امکان را میدهد که در هر گام از پردازش، بهصورت دینامیک به قسمتهای مختلف ورودی وزنهای متفاوتی اختصاص دهد. این رویکرد الهامگرفته از نحوه عملکرد توجه انسانی است که بهطور انتخابی روی جزئیات مهم تمرکز میکند و جزئیات کماهمیتتر را نادیده میگیرد.
۲.۳ تعریف ریاضی کلی
بهطور کلی، مکانیزم توجه یک تابع است که یک Query (پرسوجو) را به مجموعهای از جفتهای Key-Value (کلید-مقدار) نگاشت میکند:
Attention(Q, K, V) = Output
که در آن:
- Query (Q): بردار سؤال یا جستجو که مشخص میکند به دنبال چه اطلاعاتی هستیم
- Key (K): بردارهای کلیدی که نشان میدهند هر عنصر چه اطلاعاتی ارائه میدهد
- Value (V): بردارهای مقدار که حاوی اطلاعات واقعی هستند
- Output: میانگین وزندار از بردارهای Value
۳. ریاضیات مکانیزم توجه
۳.۱ Scaled Dot-Product Attention
سادهترین و پرکاربردترین نوع مکانیزم توجه، Scaled Dot-Product Attention است که در معماری Transformer استفاده میشود. فرمول ریاضی آن بهصورت زیر است:
Attention(Q, K, V) = softmax(QK^T / √d_k) V
این فرمول در چهار گام اساسی محاسبه میشود:
گام ۱: محاسبه امتیازات توجه (Attention Scores)
ابتدا حاصلضرب داخلی (Dot Product) بین Query و Key محاسبه میشود:
Scores = QK^T
این عملیات شباهت بین Query و هر Key را اندازهگیری میکند. هرچه دو بردار مشابهتر باشند، حاصلضرب داخلی آنها بزرگتر است.
گام ۲: مقیاسبندی (Scaling)
امتیازات به دست آمده را بر جذر بعد کلیدها (√d_k) تقسیم میکنیم:
Scaled_Scores = Scores / √d_k
چرا مقیاسبندی ضروری است؟
برای ابعاد بزرگ d_k، حاصلضرب داخلی میتواند مقادیر بسیار بزرگی داشته باشد. فرض کنید عناصر Q و K از توزیع نرمال استاندارد N(0,1) نمونهبرداری شدهاند. در این صورت:
q · k = Σ(q_i × k_i) ~ N(0, d_k)
یعنی واریانس حاصلضرب برابر d_k است. مقادیر بزرگ باعث میشوند تابع softmax به نواحی با گرادیان بسیار کوچک (Vanishing Gradient) برود. با تقسیم بر √d_k، واریانس را به ۱ کاهش میدهیم و پایداری عددی را تضمین میکنیم.
گام ۳: اعمال Softmax
تابع softmax برای تبدیل امتیازات به وزنهای احتمالاتی اعمال میشود:
Attention_Weights = softmax(Scaled_Scores)
تابع softmax تضمین میکند که:
- تمام وزنها مثبت هستند
- مجموع وزنها برابر ۱ است
- وزنهای بزرگتر به امتیازات بالاتر اختصاص مییابند
گام ۴: محاسبه خروجی وزندار
در نهایت، خروجی با ضرب وزنها در Value به دست میآید:
Output = Attention_Weights × V
۳.۲ ماسکگذاری (Masking)
در برخی کاربردها نیاز است از توجه به موقعیتهای خاص جلوگیری شود. دو نوع ماسک رایج عبارتند از:
Padding Mask
برای نادیده گرفتن توکنهای پر کننده (Padding) در دنبالهها:
mask = (input != pad_token) # True برای توکنهای واقعی
scores = scores.masked_fill(mask == 0, float('-inf'))
Look-Ahead Mask (Causal Mask)
برای جلوگیری از دیدن توکنهای آینده در وظایف تولید متن:
mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1).bool()
scores = scores.masked_fill(mask, float('-inf'))
۴. انواع مکانیزم توجه
۴.۱ Self-Attention
در Self-Attention، Query، Key و Value همگی از یک منبع واحد (ورودی) مشتق میشوند. این به مدل امکان میدهد روابط بین عناصر مختلف یک دنباله را مدلسازی کند.
کاربردها:
- درک زمینه در جملات
- تشخیص ارجاعات ضمیری
- مدلسازی وابستگیهای دوربرد
۴.۲ Cross-Attention
در Cross-Attention، Query از یک منبع و Key-Value از منبع دیگر میآیند. این معمولاً در Decoder برای توجه به خروجی Encoder استفاده میشود.
کاربردها:
- ترجمه ماشینی (توجه جمله منبع به هدف)
- توصیف تصویر (توجه متن به تصویر)
- سؤال و جواب (توجه سؤال به متن)
۴.۳ Multi-Head Attention
Multi-Head Attention به مدل امکان میدهد همزمان به اطلاعات مختلف از فضاهای نمایش متفاوت توجه کند. بهجای یک عملیات توجه، h عملیات توجه موازی انجام میشود:
MultiHead(Q, K, V) = Concat(head_1, ..., head_h) W^O
که head_i = Attention(QW_i^Q, KW_i^K, VW_i^V)
مزایا:
- یادگیری الگوهای متنوعتر
- افزایش ظرفیت مدل
- بهبود کیفیت نمایش
۵. پیادهسازی عملی در PyTorch
در این بخش، پیادهسازی کامل و عملی انواع مکانیزم توجه را ارائه میدهیم.
۵.۱ Scaled Dot-Product Attention
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
class ScaledDotProductAttention(nn.Module):
"""
پیادهسازی Scaled Dot-Product Attention
Args:
dropout: نرخ dropout برای منظمسازی
"""
def __init__(self, dropout=0.1):
super(ScaledDotProductAttention, self).__init__()
self.dropout = nn.Dropout(dropout)
def forward(self, query, key, value, mask=None):
"""
Args:
query: (batch_size, num_queries, d_k)
key: (batch_size, num_keys, d_k)
value: (batch_size, num_keys, d_v)
mask: (batch_size, num_queries, num_keys) یا None
Returns:
output: (batch_size, num_queries, d_v)
attention_weights: (batch_size, num_queries, num_keys)
"""
d_k = query.size(-1)
# گام 1: محاسبه امتیازات - QK^T
scores = torch.matmul(query, key.transpose(-2, -1))
# گام 2: مقیاسبندی - تقسیم بر √d_k
scores = scores / math.sqrt(d_k)
# گام 3: اعمال ماسک (در صورت نیاز)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
# گام 4: اعمال Softmax
attention_weights = F.softmax(scores, dim=-1)
# اعمال Dropout
attention_weights = self.dropout(attention_weights)
# گام 5: محاسبه خروجی وزندار
output = torch.matmul(attention_weights, value)
return output, attention_weights
# مثال استفاده
batch_size = 2
seq_len = 5
d_k = 64
d_v = 64
# تولید دادههای نمونه
query = torch.randn(batch_size, seq_len, d_k)
key = torch.randn(batch_size, seq_len, d_k)
value = torch.randn(batch_size, seq_len, d_v)
# ایجاد مدل
attention = ScaledDotProductAttention(dropout=0.1)
# اجرای Forward Pass
output, weights = attention(query, key, value)
print(f"شکل خروجی: {output.shape}") # (2, 5, 64)
print(f"شکل وزنهای توجه: {weights.shape}") # (2, 5, 5)
۵.۲ Self-Attention Layer
class SelfAttention(nn.Module):
"""
پیادهسازی کامل لایه Self-Attention
"""
def __init__(self, embed_dim, dropout=0.1):
super(SelfAttention, self).__init__()
self.embed_dim = embed_dim
# لایههای خطی برای تبدیل Q, K, V
self.query_proj = nn.Linear(embed_dim, embed_dim)
self.key_proj = nn.Linear(embed_dim, embed_dim)
self.value_proj = nn.Linear(embed_dim, embed_dim)
# لایه توجه
self.attention = ScaledDotProductAttention(dropout)
# لایه خروجی
self.output_proj = nn.Linear(embed_dim, embed_dim)
def forward(self, x, mask=None):
"""
Args:
x: (batch_size, seq_len, embed_dim)
mask: ماسک اختیاری
Returns:
output: (batch_size, seq_len, embed_dim)
attention_weights: وزنهای توجه
"""
# تولید Query, Key, Value از ورودی
query = self.query_proj(x)
key = self.key_proj(x)
value = self.value_proj(x)
# اعمال توجه
attn_output, attention_weights = self.attention(query, key, value, mask)
# پروجکشن نهایی
output = self.output_proj(attn_output)
return output, attention_weights
# مثال استفاده
embed_dim = 128
seq_len = 10
batch_size = 4
x = torch.randn(batch_size, seq_len, embed_dim)
self_attn = SelfAttention(embed_dim)
output, weights = self_attn(x)
print(f"خروجی مدل ترجمه: {output.shape}")
۶.۲ طبقهبندی متن با Self-Attention
class TextClassifierWithAttention(nn.Module):
"""
مدل طبقهبندی متن با استفاده از Self-Attention
"""
def __init__(self, vocab_size, embed_dim, num_heads, num_classes,
max_len=512, dropout=0.1):
super(TextClassifierWithAttention, self).__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.pos_encoding = self._create_positional_encoding(max_len, embed_dim)
# لایههای Self-Attention
self.attention1 = MultiHeadAttention(embed_dim, num_heads, dropout)
self.attention2 = MultiHeadAttention(embed_dim, num_heads, dropout)
self.norm1 = nn.LayerNorm(embed_dim)
self.norm2 = nn.LayerNorm(embed_dim)
# لایههای طبقهبندی
self.classifier = nn.Sequential(
nn.Linear(embed_dim, embed_dim // 2),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(embed_dim // 2, num_classes)
)
self.dropout = nn.Dropout(dropout)
def _create_positional_encoding(self, max_len, embed_dim):
pe = torch.zeros(max_len, embed_dim)
position = torch.arange(0, max_len).unsqueeze(1).float()
div_term = torch.exp(torch.arange(0, embed_dim, 2).float() *
-(math.log(10000.0) / embed_dim))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
return pe.unsqueeze(0)
def forward(self, x, mask=None):
"""
Args:
x: (batch_size, seq_len) - ایندکسهای توکن
mask: ماسک padding
Returns:
logits: (batch_size, num_classes)
"""
# Embedding
x = self.embedding(x)
seq_len = x.size(1)
x = x + self.pos_encoding[:, :seq_len, :].to(x.device)
x = self.dropout(x)
# لایه اول Self-Attention
attn_out1, _ = self.attention1(x, x, x, mask)
x = self.norm1(x + attn_out1)
# لایه دوم Self-Attention
attn_out2, _ = self.attention2(x, x, x, mask)
x = self.norm2(x + attn_out2)
# Global Average Pooling
if mask is not None:
mask_expanded = mask.unsqueeze(-1).float()
x = (x * mask_expanded).sum(dim=1) / mask_expanded.sum(dim=1)
else:
x = x.mean(dim=1)
# طبقهبندی
logits = self.classifier(x)
return logits
# مثال استفاده برای طبقهبندی احساسات
vocab_size = 15000
embed_dim = 256
num_heads = 8
num_classes = 3 # مثبت، منفی، خنثی
batch_size = 8
seq_len = 128
# دادههای نمونه
texts = torch.randint(0, vocab_size, (batch_size, seq_len))
labels = torch.randint(0, num_classes, (batch_size,))
# ایجاد مدل
classifier = TextClassifierWithAttention(vocab_size, embed_dim,
num_heads, num_classes)
# Forward pass
logits = classifier(texts)
print(f"خروجی طبقهبندی: {logits.shape}") # (8, 3)
# محاسبه دقت
predictions = logits.argmax(dim=-1)
accuracy = (predictions == labels).float().mean()
print(f"دقت: {accuracy.item():.2%}")
۶.۳ Vision Transformer – کاربرد در بینایی ماشین
class PatchEmbedding(nn.Module):
"""
تبدیل تصویر به Patch Embeddings
"""
def __init__(self, img_size=224, patch_size=16, in_channels=3, embed_dim=768):
super(PatchEmbedding, self).__init__()
self.img_size = img_size
self.patch_size = patch_size
self.num_patches = (img_size // patch_size) ** 2
# Convolution برای استخراج patches
self.projection = nn.Conv2d(in_channels, embed_dim,
kernel_size=patch_size, stride=patch_size)
def forward(self, x):
"""
Args:
x: (batch_size, channels, height, width)
Returns:
(batch_size, num_patches, embed_dim)
"""
x = self.projection(x) # (batch, embed_dim, h/p, w/p)
x = x.flatten(2) # (batch, embed_dim, num_patches)
x = x.transpose(1, 2) # (batch, num_patches, embed_dim)
return x
class VisionTransformer(nn.Module):
"""
Vision Transformer برای طبقهبندی تصویر
"""
def __init__(self, img_size=224, patch_size=16, in_channels=3,
num_classes=1000, embed_dim=768, num_heads=12,
num_layers=12, ff_dim=3072, dropout=0.1):
super(VisionTransformer, self).__init__()
# Patch Embedding
self.patch_embed = PatchEmbedding(img_size, patch_size,
in_channels, embed_dim)
# Class Token
self.cls_token = nn.Parameter(torch.randn(1, 1, embed_dim))
# Positional Embedding
num_patches = self.patch_embed.num_patches
self.pos_embed = nn.Parameter(torch.randn(1, num_patches + 1, embed_dim))
# Transformer Encoder Blocks
self.encoder_blocks = nn.ModuleList([
TransformerEncoderBlock(embed_dim, num_heads, ff_dim, dropout)
for _ in range(num_layers)
])
# Classification Head
self.norm = nn.LayerNorm(embed_dim)
self.head = nn.Linear(embed_dim, num_classes)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
"""
Args:
x: (batch_size, channels, height, width)
Returns:
logits: (batch_size, num_classes)
"""
batch_size = x.size(0)
# تبدیل به patches
x = self.patch_embed(x) # (batch, num_patches, embed_dim)
# اضافه کردن class token
cls_tokens = self.cls_token.expand(batch_size, -1, -1)
x = torch.cat([cls_tokens, x], dim=1)
# اضافه کردن positional embedding
x = x + self.pos_embed
x = self.dropout(x)
# عبور از encoder blocks
for block in self.encoder_blocks:
x = block(x)
# استفاده از class token برای طبقهبندی
x = self.norm(x[:, 0])
logits = self.head(x)
return logits
# مثال استفاده
img_size = 224
patch_size = 16
num_classes = 10 # مثلاً CIFAR-10
batch_size = 4
# تصاویر نمونه
images = torch.randn(batch_size, 3, img_size, img_size)
# ایجاد مدل (نسخه کوچک)
vit = VisionTransformer(
img_size=img_size,
patch_size=patch_size,
num_classes=num_classes,
embed_dim=384,
num_heads=6,
num_layers=6,
ff_dim=1536
)
# پیشبینی
logits = vit(images)
print(f"خروجی Vision Transformer: {logits.shape}") # (4, 10)
۷. بهینهسازی و نکات پیشرفته
۷.۱ بهینهسازی حافظه با Gradient Checkpointing
برای مدلهای بزرگ، میتوان از Gradient Checkpointing استفاده کرد:
from torch.utils.checkpoint import checkpoint
class EfficientTransformerBlock(nn.Module):
def __init__(self, embed_dim, num_heads, ff_dim, use_checkpoint=True):
super(EfficientTransformerBlock, self).__init__()
self.use_checkpoint = use_checkpoint
self.attention = MultiHeadAttention(embed_dim, num_heads)
self.ff_network = nn.Sequential(
nn.Linear(embed_dim, ff_dim),
nn.GELU(),
nn.Linear(ff_dim, embed_dim)
)
self.norm1 = nn.LayerNorm(embed_dim)
self.norm2 = nn.LayerNorm(embed_dim)
def _forward(self, x):
attn_out, _ = self.attention(x, x, x)
x = self.norm1(x + attn_out)
ff_out = self.ff_network(x)
x = self.norm2(x + ff_out)
return x
def forward(self, x):
if self.use_checkpoint and self.training:
return checkpoint(self._forward, x)
return self._forward(x)
۷.۲ Flash Attention برای سرعت بالاتر
استفاده از پیادهسازی بهینه Flash Attention:
# نصب: pip install flash-attn
try:
from flash_attn import flash_attn_func
FLASH_AVAILABLE = True
except ImportError:
FLASH_AVAILABLE = False
class OptimizedAttention(nn.Module):
def __init__(self, embed_dim, num_heads, use_flash=True):
super(OptimizedAttention, self).__init__()
self.embed_dim = embed_dim
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads
self.use_flash = use_flash and FLASH_AVAILABLE
self.qkv_proj = nn.Linear(embed_dim, 3 * embed_dim)
self.out_proj = nn.Linear(embed_dim, embed_dim)
def forward(self, x):
batch_size, seq_len, _ = x.shape
# پروجکت Q, K, V همزمان
qkv = self.qkv_proj(x)
qkv = qkv.reshape(batch_size, seq_len, 3, self.num_heads, self.head_dim)
qkv = qkv.permute(2, 0, 3, 1, 4) # (3, batch, heads, seq, head_dim)
q, k, v = qkv[0], qkv[1], qkv[2]
if self.use_flash and self.training:
# استفاده از Flash Attention
out = flash_attn_func(q, k, v, causal=False)
else:
# Attention استاندارد
scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim)
attn = F.softmax(scores, dim=-1)
out = torch.matmul(attn, v)
# ترکیب سرها
out = out.transpose(1, 2).reshape(batch_size, seq_len, self.embed_dim)
out = self.out_proj(out)
return out
۷.۳ Relative Position Encoding
پیادهسازی Relative Position Encoding برای عملکرد بهتر:
class RelativePositionAttention(nn.Module):
"""
Self-Attention با Relative Position Encoding
"""
def __init__(self, embed_dim, num_heads, max_relative_position=128):
super(RelativePositionAttention, self).__init__()
self.embed_dim = embed_dim
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads
self.max_relative_position = max_relative_position
self.qkv_proj = nn.Linear(embed_dim, 3 * embed_dim)
self.out_proj = nn.Linear(embed_dim, embed_dim)
# Relative Position Embeddings
self.relative_positions_embeddings = nn.Embedding(
2 * max_relative_position + 1, self.head_dim
)
def _get_relative_positions(self, seq_len):
"""محاسبه ماتریس موقعیتهای نسبی"""
range_vec = torch.arange(seq_len)
distance_mat = range_vec[None, :] - range_vec[:, None]
distance_mat_clipped = torch.clamp(
distance_mat, -self.max_relative_position, self.max_relative_position
)
final_mat = distance_mat_clipped + self.max_relative_position
return final_mat
def forward(self, x):
batch_size, seq_len, _ = x.shape
# Q, K, V projection
qkv = self.qkv_proj(x)
qkv = qkv.reshape(batch_size, seq_len, 3, self.num_heads, self.head_dim)
qkv = qkv.permute(2, 0, 3, 1, 4)
q, k, v = qkv[0], qkv[1], qkv[2]
# محاسبه attention scores
scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim)
# اضافه کردن relative position bias
relative_positions = self._get_relative_positions(seq_len).to(x.device)
relative_embeddings = self.relative_positions_embeddings(relative_positions)
# محاسبه relative position scores
relative_scores = torch.einsum('bhqd,qkd->bhqk', q, relative_embeddings)
scores = scores + relative_scores
# Softmax و محاسبه خروجی
attn = F.softmax(scores, dim=-1)
out = torch.matmul(attn, v)
# ترکیب سرها
out = out.transpose(1, 2).reshape(batch_size, seq_len, self.embed_dim)
out = self.out_proj(out)
return out
۷.۴ Mixed Precision Training
استفاده از Automatic Mixed Precision برای تسریع آموزش:
import torch
from torch.cuda.amp import autocast, GradScaler
def train_with_amp(model, train_loader, optimizer, device, num_epochs):
"""
آموزش با Mixed Precision
"""
scaler = GradScaler()
model.to(device)
for epoch in range(num_epochs):
model.train()
total_loss = 0
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
# Forward pass با autocast
with autocast():
output = model(data)
loss = F.cross_entropy(output, target)
# Backward pass با gradient scaling
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
total_loss += loss.item()
if batch_idx % 100 == 0:
print(f'Epoch: {epoch}, Batch: {batch_idx}, Loss: {loss.item():.4f}')
avg_loss = total_loss / len(train_loader)
print(f'Epoch {epoch} completed. Average Loss: {avg_loss:.4f}')
# مثال استفاده
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = VisionTransformer(num_classes=10)
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)
# فرض کنید train_loader آماده است
# train_with_amp(model, train_loader, optimizer, device, num_epochs=10)
۷.۵ تنظیمات Hyperparameter
جدول پیشنهادی hyperparameters برای کاربردهای مختلف:
# تنظیمات پیشنهادی
# مدل کوچک - برای منابع محدود
SMALL_CONFIG = {
'embed_dim': 256,
'num_heads': 4,
'num_layers': 4,
'ff_dim': 1024,
'dropout': 0.1,
'learning_rate': 1e-4,
'batch_size': 32
}
# مدل متوسط - برای کاربردهای عمومی
MEDIUM_CONFIG = {
'embed_dim': 512,
'num_heads': 8,
'num_layers': 6,
'ff_dim': 2048,
'dropout': 0.1,
'learning_rate': 5e-5,
'batch_size': 16
}
# مدل بزرگ - برای دقت بالا
LARGE_CONFIG = {
'embed_dim': 768,
'num_heads': 12,
'num_layers': 12,
'ff_dim': 3072,
'dropout': 0.1,
'learning_rate': 3e-5,
'batch_size': 8
}
def create_model_from_config(config, num_classes):
"""ساخت مدل بر اساس configuration"""
model = TextClassifierWithAttention(
vocab_size=30000,
embed_dim=config['embed_dim'],
num_heads=config['num_heads'],
num_classes=num_classes,
dropout=config['dropout']
)
optimizer = torch.optim.AdamW(
model.parameters(),
lr=config['learning_rate'],
weight_decay=0.01
)
return model, optimizer
# استفاده
model, optimizer = create_model_from_config(MEDIUM_CONFIG, num_classes=5)
print(f"تعداد کل پارامترها: {sum(p.numel() for p in model.parameters()):,}")
۸. ارزیابی و تست مدل
۸.۱ تابع ارزیابی کامل
def evaluate_model(model, test_loader, device, return_attention=False):
"""
ارزیابی جامع مدل
Args:
model: مدل آموزشدیده
test_loader: DataLoader دادههای تست
device: دستگاه (CPU/GPU)
return_attention: برگرداندن وزنهای توجه
Returns:
metrics: دیکشنری شامل معیارهای ارزیابی
"""
model.eval()
model.to(device)
total_loss = 0
correct = 0
total = 0
all_predictions = []
all_labels = []
attention_weights_list = []
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
loss = F.cross_entropy(output, target)
total_loss += loss.item()
pred = output.argmax(dim=1)
correct += (pred == target).sum().item()
total += target.size(0)
all_predictions.extend(pred.cpu().numpy())
all_labels.extend(target.cpu().numpy())
accuracy = 100. * correct / total
avg_loss = total_loss / len(test_loader)
# محاسبه معیارهای اضافی
from sklearn.metrics import precision_recall_fscore_support, confusion_matrix
precision, recall, f1, _ = precision_recall_fscore_support(
all_labels, all_predictions, average='weighted'
)
cm = confusion_matrix(all_labels, all_predictions)
metrics = {
'accuracy': accuracy,
'loss': avg_loss,
'precision': precision,
'recall': recall,
'f1_score': f1,
'confusion_matrix': cm
}
return metrics
def visualize_attention(model, text, tokenizer, device, layer_idx=0):
"""
تجسم وزنهای توجه
Args:
model: مدل آموزشدیده
text: متن ورودی
tokenizer: tokenizer برای تبدیل متن
device: دستگاه
layer_idx: اندیس لایه برای تجسم
"""
import matplotlib.pyplot as plt
import seaborn as sns
model.eval()
model.to(device)
# Tokenize
tokens = tokenizer.encode(text)
input_tensor = torch.tensor([tokens]).to(device)
# استخراج attention weights
with torch.no_grad():
# باید مدل را طوری تغییر دهیم که attention weights را برگرداند
# این بستگی به ساختار دقیق مدل دارد
pass
# تجسم با heatmap
plt.figure(figsize=(10, 8))
sns.heatmap(attention_weights[0, 0].cpu().numpy(),
xticklabels=tokens, yticklabels=tokens,
cmap='viridis', cbar=True)
plt.title(f'Attention Weights - Layer {layer_idx}')
plt.xlabel('Key Position')
plt.ylabel('Query Position')
plt.tight_layout()
plt.savefig('attention_visualization.png', dpi=300)
plt.show()
۸.۲ مثال کامل آموزش و ارزیابی
# اسکریپت کامل آموزش
def complete_training_pipeline():
"""
پایپلاین کامل آموزش و ارزیابی
"""
# تنظیمات
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"استفاده از دستگاه: {device}")
# ساخت مدل
model = TextClassifierWithAttention(
vocab_size=10000,
embed_dim=256,
num_heads=8,
num_classes=3
).to(device)
print(f"تعداد پارامترها: {sum(p.numel() for p in model.parameters()):,}")
# Optimizer و Scheduler
optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4, weight_decay=0.01)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=100)
# فرض: دادهها آماده شدهاند
# train_loader = ...
# val_loader = ...
best_val_accuracy = 0
patience = 5
patience_counter = 0
num_epochs = 50
for epoch in range(num_epochs):
# آموزش
model.train()
train_loss = 0
train_correct = 0
train_total = 0
# برای هر batch در train_loader:
# ... (کد آموزش)
print(f'Epoch {epoch+1}/{num_epochs}')
print(f'Train Loss: {train_loss:.4f}, Train Acc: {train_correct/train_total:.2%}')
# ارزیابی
val_metrics = evaluate_model(model, val_loader, device)
print(f"Val Accuracy: {val_metrics['accuracy']:.2f}%")
print(f"Val F1-Score: {val_metrics['f1_score']:.4f}")
# ذخیره بهترین مدل
if val_metrics['accuracy'] > best_val_accuracy:
best_val_accuracy = val_metrics['accuracy']
torch.save({
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'accuracy': best_val_accuracy,
}, 'best_model.pt')
patience_counter = 0
else:
patience_counter += 1
# Early Stopping
if patience_counter >= patience:
print(f"Early stopping در epoch {epoch+1}")
break
scheduler.step()
print(f"بهترین دقت validation: {best_val_accuracy:.2f}%")
# بارگذاری بهترین مدل
checkpoint = torch.load('best_model.pt')
model.load_state_dict(checkpoint['model_state_dict'])
return model
۹. مقایسه عملکرد و Benchmarking
۹.۱ مقایسه انواع Attention
import time
def benchmark_attention_mechanisms():
"""
مقایسه سرعت و حافظه انواع مختلف attention
"""
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
batch_size = 16
seq_len = 512
embed_dim = 512
num_heads = 8
x = torch.randn(batch_size, seq_len, embed_dim).to(device)
mechanisms = {
'Self-Attention': SelfAttention(embed_dim),
'Multi-Head Attention': MultiHeadAttention(embed_dim, num_heads),
'Relative Position Attention': RelativePositionAttention(embed_dim, num_heads)
}
results = {}
for name, mechanism in mechanisms.items():
mechanism = mechanism.to(device)
mechanism.eval()
# Warmup
with torch.no_grad():
for _ in range(10):
if 'Multi-Head' in name:
_ = mechanism(x, x, x)
else:
_ = mechanism(x)
# Timing
torch.cuda.synchronize() if torch.cuda.is_available() else None
start_time = time.time()
with torch.no_grad():
for _ in range(100):
if 'Multi-Head' in name:
output = mechanism(x, x, x)
else:
output = mechanism(x)
torch.cuda.synchronize() if torch.cuda.is_available() else None
end_time = time.time()
avg_time = (end_time - start_time) / 100
# حافظه
if torch.cuda.is_available():
memory = torch.cuda.max_memory_allocated() / 1024**2 # MB
torch.cuda.reset_peak_memory_stats()
else:
memory = 0
results[name] = {
'avg_time_ms': avg_time * 1000,
'memory_mb': memory,
'params': sum(p.numel() for p in mechanism.parameters())
}
# چاپ نتایج
print("\n" + "="*70)
print("مقایسه عملکرد مکانیزمهای Attention")
print("="*70)
print(f"{'مکانیزم':<30} {'زمان (ms)':<15} {'حافظه (MB)':<15} {'پارامترها':<15}")
print("-"*70)
for name, metrics in results.items():
print(f"{name:<30} {metrics['avg_time_ms']:<15.2f} "
f"{metrics['memory_mb']:<15.1f} {metrics['params']:<15,}")
print("="*70)
return results
# اجرای benchmark
# results = benchmark_attention_mechanisms()
۱۰. نکات عملی و Best Practices
۱۰.۱ مدیریت Overfitting
class AttentionWithRegularization(nn.Module):
"""
Attention با تکنیکهای منظمسازی
"""
def __init__(self, embed_dim, num_heads, dropout=0.1,
attention_dropout=0.1, use_layer_drop=False):
super(AttentionWithRegularization, self).__init__()
self.attention = MultiHeadAttention(embed_dim, num_heads, attention_dropout)
self.dropout = nn.Dropout(dropout)
self.use_layer_drop = use_layer_drop
self.layer_drop_prob = 0.1
def forward(self, x):
# Layer Dropout: تصادفی skip کردن لایه
if self.use_layer_drop and self.training:
if torch.rand(1).item() < self.layer_drop_prob:
return x
out, _ = self.attention(x, x, x)
out = self.dropout(out)
return x + out # Residual connection
# تکنیکهای منظمسازی دیگر:
# 1. Label Smoothing
class LabelSmoothingCrossEntropy(nn.Module):
def __init__(self, smoothing=0.1):
super().__init__()
self.smoothing = smoothing
def forward(self, pred, target):
n_class = pred.size(1)
one_hot = torch.zeros_like(pred).scatter(1, target.unsqueeze(1), 1)
one_hot = one_hot * (1 - self.smoothing) + self.smoothing / n_class
log_prob = F.log_softmax(pred, dim=1)
loss = -(one_hot * log_prob).sum(dim=1).mean()
return loss
# 2. Weight Decay با تنظیم خاص
def configure_optimizers(model, learning_rate, weight_decay):
"""
تنظیم optimizer با decay متفاوت برای پارامترهای مختلف
"""
# پارامترهایی که نباید decay شوند
no_decay = ['bias', 'LayerNorm.weight', 'norm']
optimizer_grouped_parameters = [
{
'params': [p for n, p in model.named_parameters()
if not any(nd in n for nd in no_decay)],
'weight_decay': weight_decay
},
{
'params': [p for n, p in model.named_parameters()
if any(nd in n for nd in no_decay)],
'weight_decay': 0.0
}
]
optimizer = torch.optim.AdamW(optimizer_grouped_parameters, lr=learning_rate)
return optimizer
۱۰.۲ نکات debugging
def debug_attention_mechanism(model, sample_input, device):
"""
تابع کمکی برای debug کردن attention
"""
model.eval()
model.to(device)
sample_input = sample_input.to(device)
print("="*50)
print("Debug Information")
print("="*50)
# بررسی شکل ورودی
print(f"Input shape: {sample_input.shape}")
# Hook برای capture کردن activations
activations = {}
def get_activation(name):
def hook(model, input, output):
activations[name] = output.detach()
return hook
# ثبت hooks
for name, module in model.named_modules():
if isinstance(module, MultiHeadAttention):
module.register_forward_hook(get_activation(name))
# Forward pass
with torch.no_grad():
output = model(sample_input)
print(f"Output shape: {output.shape}")
# بررسی activations
for name, activation in activations.items():
print(f"\n{name}:")
print(f" Shape: {activation.shape}")
print(f" Mean: {activation.mean().item():.4f}")
print(f" Std: {activation.std().item():.4f}")
print(f" Min: {activation.min().item():.4f}")
print(f" Max: {activation.max().item():.4f}")
# بررسی NaN یا Inf
if torch.isnan(activation).any():
print(f" ⚠️ WARNING: NaN detected!")
if torch.isinf(activation).any():
print(f" ⚠️ WARNING: Inf detected!")
print("="*50)
# بررسی gradient flow
def check_gradient_flow(model):
"""
بررسی جریان گرادیان در مدل
"""
import matplotlib.pyplot as plt
ave_grads = []
max_grads = []
layers = []
for n, p in model.named_parameters():
if p.requires_grad and p.grad is not None:
layers.append(n)
ave_grads.append(p.grad.abs().mean().item())
max_grads.append(p.grad.abs().max().item())
plt.figure(figsize=(12, 6))
plt.bar(np.arange(len(max_grads)), max_grads, alpha=0.5, label="Max Gradient")
plt.bar(np.arange(len(ave_grads)), ave_grads, alpha=0.5, label="Mean Gradient")
plt.hlines(0, 0, len(ave_grads)+1, linewidth=2, color="k")
plt.xticks(range(0, len(ave_grads), 1), layers, rotation="vertical")
plt.xlim(left=0, right=len(ave_grads))
plt.ylim(bottom=-0.001, top=0.02)
plt.xlabel("Layers")
plt.ylabel("Gradient")
plt.title("Gradient Flow")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.savefig('gradient_flow.png')
plt.show()
۱۱. کاربردهای پیشرفته
۱۱.۱ Cross-Modal Attention (تصویر-متن)
class CrossModalAttention(nn.Module):
"""
Attention برای ترکیب اطلاعات تصویر و متن
"""
def __init__(self, img_embed_dim, text_embed_dim, num_heads, dropout=0.1):
super(CrossModalAttention, self).__init__()
# پروجکشن به فضای مشترک
self.img_proj = nn.Linear(img_embed_dim, text_embed_dim)
# Cross-Attention: Query از متن، Key/Value از تصویر
self.cross_attention = MultiHeadAttention(
text_embed_dim, num_heads, dropout
)
self.norm = nn.LayerNorm(text_embed_dim)
def forward(self, text_features, img_features):
"""
Args:
text_features: (batch, text_len, text_embed_dim)
img_features: (batch, img_patches, img_embed_dim)
Returns:
fused_features: (batch, text_len, text_embed_dim)
"""
# پروجکت تصویر به فضای متن
img_proj = self.img_proj(img_features)
# Cross-Attention: متن به تصویر توجه میکند
attended_features, attn_weights = self.cross_attention(
query=text_features,
key=img_proj,
value=img_proj
)
# Residual + Norm
output = self.norm(text_features + attended_features)
return output, attn_weights
class ImageTextMatcher(nn.Module):
"""
مدل برای تطبیق تصویر-متن با Cross-Modal Attention
"""
def __init__(self, img_embed_dim=768, text_embed_dim=512, num_heads=8):
super(ImageTextMatcher, self).__init__()
# Image encoder (فرض: از ViT استفاده میکنیم)
self.img_encoder = PatchEmbedding(embed_dim=img_embed_dim)
# Text encoder (فرض: Embedding layer)
self.text_encoder = nn.Embedding(30000, text_embed_dim)
# Cross-Modal Attention
self.cross_modal_attn = CrossModalAttention(
img_embed_dim, text_embed_dim, num_heads
)
# Classification head
self.classifier = nn.Linear(text_embed_dim, 1)
def forward(self, images, texts):
"""
Args:
images: (batch, 3, H, W)
texts: (batch, seq_len)
Returns:
scores: (batch,) - امتیاز تطبیق
"""
# Encode تصویر و متن
img_features = self.img_encoder(images)
text_features = self.text_encoder(texts)
# Cross-Modal fusion
fused_features, _ = self.cross_modal_attn(text_features, img_features)
# Global pooling
pooled = fused_features.mean(dim=1)
# پیشبینی امتیاز تطبیق
scores = self.classifier(pooled).squeeze(-1)
return torch.sigmoid(scores)
۱۱.۲ Sparse Attention برای دنبالههای بلند
class SparseAttention(nn.Module):
"""
Sparse Attention برای کاهش پیچیدگی O(n²) به O(n√n)
"""
def __init__(self, embed_dim, num_heads, block_size=64, dropout=0.1):
super(SparseAttention, self).__init__()
self.embed_dim = embed_dim
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads
self.block_size = block_size
self.qkv_proj = nn.Linear(embed_dim, 3 * embed_dim)
self.out_proj = nn.Linear(embed_dim, embed_dim)
self.dropout = nn.Dropout(dropout)
def _create_sparse_mask(self, seq_len, device):
"""
ایجاد ماسک برای sparse attention pattern
"""
mask = torch.zeros(seq_len, seq_len, device=device)
# Local attention: هر توکن به block_size توکن اطراف خود توجه میکند
for i in range(seq_len):
start = max(0, i - self.block_size // 2)
end = min(seq_len, i + self.block_size // 2)
mask[i, start:end] = 1
# Strided attention: توجه به توکنهای در فواصل منظم
stride = self.block_size
for i in range(seq_len):
mask[i, ::stride] = 1
return mask
def forward(self, x):
batch_size, seq_len, _ = x.shape
# Q, K, V
qkv = self.qkv_proj(x)
qkv = qkv.reshape(batch_size, seq_len, 3, self.num_heads, self.head_dim)
qkv = qkv.permute(2, 0, 3, 1, 4)
q, k, v = qkv[0], qkv[1], qkv[2]
# محاسبه scores
scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim)
# اعمال sparse mask
sparse_mask = self._create_sparse_mask(seq_len, x.device)
scores = scores.masked_fill(sparse_mask.unsqueeze(0).unsqueeze(0) == 0, float('-inf'))
# Softmax و output
attn = F.softmax(scores, dim=-1)
attn = self.dropout(attn)
out = torch.matmul(attn, v)
# ترکیب سرها
out = out.transpose(1, 2).reshape(batch_size, seq_len, self.embed_dim)
out = self.out_proj(out)
return out
۱۲. نتیجهگیری
مکانیزم توجه به ابزاری ضروری در جعبهابزار هر محقق و مهندس یادگیری عمیق تبدیل شده است. درک عمیق این مکانیزم و توانایی پیادهسازی صحیح آن در PyTorch، پایهای محکم برای کار با مدلهای پیشرفتهتر فراهم میکند.
کدهای ارائهشده در این مقاله بهعنوان نقطه شروعی برای پروژههای شما عمل میکنند. توصیه میشود با تغییر hyperparameterها، آزمایش با معماریهای مختلف و اعمال تکنیکهای بهینهسازی، درک عملی خود را عمیقتر کنید.
یادگیری عمیق حوزهای پویا و در حال تحول است. مکانیزم توجه نه تنها امروز مهم است، بلکه پایهگذار نوآوریهای آینده نیز خواهد بود.
