برای سالها شبکههای عصبی کانولوشنی (CNN) انتخاب بیچونوچرا در بینایی ماشین بودند؛ چون ساختار لایههای کانولوشن فرضهای اولیهای مثل محلیبودن پیکسلها را رعایت میکرد. با معرفی ترنسفورمرهای بینایی (Vision Transformers یا ViT)، این قاعده تغییر کرد: شکستن تصویر به چندین تکه، فرض کردن آنها مثل کلمات یک متن و سپردن کل فرایند به مکانیزم خوداتنشن استاندارد. امروزه ستون فقرات مدلهای مدرن چندوجهی مانند CLIP و بخشبندی تصاویر بر پایهی همین معماری شکل گرفته است.
در این درس ساختار داخلی ViT را کالبدشکافی میکنیم و تکتک اجزای آن را — از Patch Embedding تا توکن کلاس و بلوکهای اینکودر با Pre-LN — از صفر در PyTorch میسازیم. سپس به دلایل تغییر معادلات با روشهای DeiT و MAE میپردازیم و در نهایت نحوهی استفاده از مدلهای آماده را در کتابخانهی استاندارد timm بررسی میکنیم.
برای اجرای کدهای پیادهسازی پایهی این درس، نیازی به کارت گرافیکهای ابری گرانقیمت یا منابع سنگین پردازشی ندارید؛ مدل پیادهسازیشده در متن بهینهسازی شده و بهراحتی روی پردازندهی معمولی (CPU) یا شتابدهندههای سیستم شخصی قابل اجرا و تست است. اگر برای دانلود بستههای پایتونی نظیر timm با کندی یا اختلال مواجه شدید، میتوانید از میرورهای داخلی مخزن PyPI استفاده کنید.
تصویر را به تکهها برش بزنید، با هر تکه مثل یک کلمه رفتار کنید و یک ترنسفورمر استاندارد روی آن اجرا کنید. دیگر به عقب نگاه نکنید.
اهداف یادگیری
- پیادهسازی امبدینگ تکهها (patch embedding)، امبدینگ مکانی یادگرفتنی، توکن کلاس (class token) و بلوکهای اینکودر ترنسفورمر از صفر برای ساخت یک ViT مینیمال
- توضیح اینکه چرا تصور میشد ViT به دادههای پیشآموزش بسیار حجیم نیاز دارد، تا اینکه DeiT و MAE خلاف آن را ثابت کردند
- مقایسهی ViT، Swin و ConvNeXt از نظر پیشینهای معماری (بدون پیشین، اتنشن پنجرهای محلی، ستون فقرات کانولوشنی)
- فاینتیونینگ یک ViT از پیش آموزشدیده روی یک مجموعهدادهی کوچک با استفاده از
timmو دستورالعمل استاندارد پروب خطی / فاینتیونینگ
صورت مسئله
برای یک دهه، کانولوشن مترادف بینایی ماشین بود. شبکههای عصبی کانولوشنی (CNN) سوگیریهای استقرایی (inductive biases) قدرتمندی داشتند — مثل محلی بودن (locality) و یکسانی تبدیل (translation equivariance) — که هیچکس فکر نمیکرد بتوان آنها را کنار گذاشت. سپس دسویتسکی و همکاران (2020) نشان دادند که یک ترنسفورمر خالص روی تکههای فلتشدهی تصویر، بدون هیچ ساختار کانولوشنی، در مقیاس بزرگ میتواند با بهترین CNNها رقابت کند یا حتی آنها را شکست دهد.
نکتهی کلیدی همین «در مقیاس بزرگ» بود. ViT روی ImageNet-1k به ResNet باخت. اما ViT از پیش آموزشدیده روی ImageNet-21k یا JFT-300M که سپس روی ImageNet-1k فاینتیون شد، آن را شکست داد. نتیجه این بود که ترنسفورمرها فاقد پیشینهای مفیدند، ولی اگر دادهی کافی داشته باشند میتوانند آنها را یاد بگیرند. پژوهشهای بعدی (DeiT، MAE، DINO) نشان دادند که با دستورالعملهای آموزشی درست — بزرگافزایی قوی، پیشآموزش خودنظارتی، تقطیر (distillation) — مدلهای ViT روی دادههای کوچک هم بهخوبی آموزش میبینند.
تا سال 2026، مدلهای CNN خالص هنوز روی دستگاههای لبه رقابتی هستند (ConvNeXt قویترین آنهاست)، اما ترنسفورمرها بر همهی حوزههای دیگر مسلط شدهاند: بخشبندی (Mask2Former، SegFormer)، تشخیص اشیا (DETR، RT-DETR)، چندوجهی (CLIP، SigLIP) و ویدیو (VideoMAE، VJEPA). ساختار بلوک ViT همان معماری پایهای است که باید بشناسید.
مفاهیم پایه
پایپلاین
هفت مرحله: تکهها -> توکنها -> اتنشن -> دستهبند. هر نوع مشتقشدهای (DeiT، Swin، ConvNeXt، پیشآموزش MAE) یک یا دو مرحله از این هفت مرحله را تغییر میدهد و به بقیه دست نمیزند.
امبدینگ تکهها
راز اصلی در اولین کانولوشن نهفته است. اندازهی کرنل 16، گام (stride) 16؛ بنابراین یک تصویر 224x224 به یک شبکهی 14x14 از تکههای 16x16 تبدیل میشود که هر کدام به یک امبدینگ 768بُعدی تصویر میشوند. همین یک لایهی کانولوشن هم تصویر را تکهتکه میکند و هم تصویر کردن خطی را انجام میدهد.
Input: (3, 224, 224)
Conv (3 -> 768, k=16, s=16, no padding):
Output: (768, 14, 14)
Flatten spatial: (196, 768)
196 تکه = 196 توکن. بُعد ویژگی هر توکن برابر است با 768 (در ViT-B)، 1024 (در ViT-L) یا 1280 (در ViT-H).
توکن کلاس
یک بردار یادگرفتنی منفرد که به ابتدای توالی اضافه میشود:
tokens = [CLS; patch_1; patch_2; ...; patch_196] shape (197, 768)
پس از N بلوک ترنسفورمر، خروجی [CLS] همان بازنمایی سراسری تصویر است. سر دستهبندی (classification head) فقط همین یک بردار را میخواند.
امبدینگ مکانی
ترنسفورمرها درک درونی از موقعیت مکانی ندارند. بنابراین یک بردار یادگرفتنی به هر توکن اضافه میکنیم:
tokens = tokens + learned_pos_embedding (also shape (197, 768))
این امبدینگ یکی از پارامترهای مدل است؛ آموزش مبتنی بر گرادیان آن را با ساختار دوبعدی تصویر تطبیق میدهد. جایگزینهای سینوسی دوبعدی هم وجود دارند اما در عمل بهندرت استفاده میشوند.
بلوک اینکودر ترنسفورمر
استاندارد: خوداتنشن چندسر (multi-head self-attention)، MLP، اتصالات باقیمانده (residual connections) و پیشنرمالسازی لایه (pre-LayerNorm).
x = x + MSA(LN(x))
x = x + MLP(LN(x))
MLP is two-layer with GELU: Linear(d -> 4d) -> GELU -> Linear(4d -> d)
مدل ViT-B/16 تعداد 12 عدد از این بلوکها را روی هم میچیند که هر کدام 12 سر اتنشن دارند و در مجموع به 86M پارامتر میرسند.
چرا pre-LN؟
ترنسفورمرهای اولیه از پسنرمالسازی (x = LN(x + sublayer(x))) استفاده میکردند و آموزش آنها فراتر از 6 تا 8 لایه بدون مرحلهی گرمکردن (warmup) با مشکل روبهرو میشد. پیشنرمالسازی (x = x + sublayer(LN(x))) شبکههای عمیقتر را بدون نیاز به warmup بهطور پایدار آموزش میدهد. همهی مدلهای ViT و تمامی مدلهای زبانی بزرگ مدرن از pre-LN استفاده میکنند.
موازنهی اندازهی تکهها
- تکههای 16x16 -> 196 توکن، حالت استاندارد.
- تکههای 32x32 -> 49 توکن، سریعتر اما با تفکیکپذیری پایینتر.
- تکههای 8x8 -> 784 توکن، جزئیات دقیقتر اما هزینهی اتنشن مقیاسپذیری بسیار ضعیفی پیدا میکند.
تکههای بزرگتر = توکنهای کمتر = سرعت بالاتر اما جزئیات مکانی کمتر. SwinV2 از تکههای 4x4 در پنجرههای سلسلهمراتبی استفاده میکند.
دستورالعمل DeiT برای آموزش ViT روی ImageNet-1k
مدل اولیهی ViT برای شکست دادن CNNها به JFT-300M نیاز داشت. DeiT (توورون و همکاران، 2020) با چهار تغییر موفق شد ViT-B را تنها با ImageNet-1k به دقت top-1 معادل 81.8% برساند:
- بزرگافزایی سنگین: RandAugment، Mixup، CutMix و Random Erasing.
- عمق تصادفی (حذف تصادفی کل بلوکها در طول آموزش).
- بزرگافزایی تکراری (نمونهبرداری 3 باره از یک تصویر در هر بچ).
- تقطیر از یک شبکهی معلم CNN (اختیاری، که دقت را بالاتر میبرد).
تمام دستورالعملهای مدرن آموزش ViT ریشه در DeiT دارند.
مقایسهی Swin و ConvNeXt
- Swin (لیو و همکاران، 2021) — اتنشن مبتنی بر پنجره. هر بلوک فقط درون یک پنجرهی محلی اتنشن را اعمال میکند؛ بلوکهای یکدرمیان پنجره را جابهجا میکنند تا اطلاعات بین پنجرهها ترکیب شود. این ایده، پیشین محلیبودن شبیه به CNN را در عین حفظ عملگر اتنشن بازمیگرداند.
- ConvNeXt (لیو و همکاران، 2022) — یک CNN بازطراحیشده که با انتخابهای معماری Swin مطابقت دارد (کانولوشنهای تفکیکپذیر در عمق، LayerNorm، GELU، گلوگاه معکوس). این مدل نشان داد که تفاوت اصلی در «اتنشن در برابر کانولوشن» نیست، بلکه در «دستورالعمل آموزش مدرن + جزئیات معماری» است.
در سال 2026، هر دو مدل ConvNeXt-V2 و Swin-V2 در سطح محیط عملیاتی هستند؛ انتخاب درست به پشتهی استنتاج شما (ConvNeXt برای دستگاههای لبه بهتر کامپایل میشود) و مجموعهدادهی پیشآموزش بستگی دارد.
پیشآموزش با MAE
خودرمزگذار پوشانده (Masked Autoencoder - He et al., 2022): بهصورت تصادفی 75% از تکهها را ماسک کنید، اینکودر را طوری آموزش دهید که فقط 25% تکههای قابلمشاهده را پردازش کند، و یک دیکودر کوچک را برای بازسازی تکههای ماسکشده از خروجی اینکودر آموزش دهید. پس از پیشآموزش، دیکودر را دور بیندازید و اینکودر را فاینتیون کنید.
مدل MAE آموزش ViT را روی ImageNet-1k بهتنهایی ممکن میکند، به بهترین نتایج روز (SOTA) میرسد و در حال حاضر دستورالعمل پیشفرض خودنظارتی است.
پیادهسازی از صفر
مرحلهی 1: امبدینگ تکهها
import torch
import torch.nn as nn
class PatchEmbedding(nn.Module):
def __init__(self, in_channels=3, patch_size=16, dim=192, image_size=64):
super().__init__()
assert image_size % patch_size == 0
self.proj = nn.Conv2d(in_channels, dim, kernel_size=patch_size, stride=patch_size)
num_patches = (image_size // patch_size) ** 2
self.num_patches = num_patches
def forward(self, x):
x = self.proj(x)
return x.flatten(2).transpose(1, 2)
یک کانولوشن، یک فلت کردن، یک ترانهاده. کل فرایند تبدیل تصویر به توکنها همین است.
مرحلهی 2: بلوک ترنسفورمر
پیشنرمالسازی، خوداتنشن چندسر، MLP با فعالساز GELU و اتصالات باقیمانده.
class Block(nn.Module):
def __init__(self, dim, num_heads, mlp_ratio=4, dropout=0.0):
super().__init__()
self.ln1 = nn.LayerNorm(dim)
self.attn = nn.MultiheadAttention(dim, num_heads, dropout=dropout, batch_first=True)
self.ln2 = nn.LayerNorm(dim)
self.mlp = nn.Sequential(
nn.Linear(dim, dim * mlp_ratio),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(dim * mlp_ratio, dim),
nn.Dropout(dropout),
)
def forward(self, x):
a, _ = self.attn(self.ln1(x), self.ln1(x), self.ln1(x), need_weights=False)
x = x + a
x = x + self.mlp(self.ln2(x))
return x
کلاس nn.MultiheadAttention تقسیم به سرها، ضرب داخلی مقیاسشده (scaled dot-product) و تصویر کردن خروجی را انجام میدهد. مقدار batch_first=True تنظیم شده تا ابعاد بهصورت (N, seq, dim) باشند.
مرحلهی 3: مدل ViT
class ViT(nn.Module):
def __init__(self, image_size=64, patch_size=16, in_channels=3,
num_classes=10, dim=192, depth=6, num_heads=3, mlp_ratio=4):
super().__init__()
self.patch = PatchEmbedding(in_channels, patch_size, dim, image_size)
num_patches = self.patch.num_patches
self.cls_token = nn.Parameter(torch.zeros(1, 1, dim))
self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, dim))
self.blocks = nn.ModuleList([
Block(dim, num_heads, mlp_ratio) for _ in range(depth)
])
self.ln = nn.LayerNorm(dim)
self.head = nn.Linear(dim, num_classes)
nn.init.trunc_normal_(self.pos_embed, std=0.02)
nn.init.trunc_normal_(self.cls_token, std=0.02)
def forward(self, x):
x = self.patch(x)
cls = self.cls_token.expand(x.size(0), -1, -1)
x = torch.cat([cls, x], dim=1)
x = x + self.pos_embed
for blk in self.blocks:
x = blk(x)
x = self.ln(x[:, 0])
return self.head(x)
vit = ViT(image_size=64, patch_size=16, num_classes=10, dim=192, depth=6, num_heads=3)
x = torch.randn(2, 3, 64, 64)
print(f"output: {vit(x).shape}")
print(f"params: {sum(p.numel() for p in vit.parameters()):,}")
حدود 2.8M پارامتر — یک ViT کوچک و سبک که روی CPU هم بهراحتی اجرا میشود. مدل واقعی ViT-B دارای 86M پارامتر است؛ همان تعریف کلاس را با dim=768, depth=12, num_heads=12 اجرا کنید.
مرحلهی 4: بررسی صحت — استنتاج روی یک تصویر
logits = vit(torch.randn(1, 3, 64, 64))
print(f"logits: {logits}")
print(f"probs: {logits.softmax(-1)}")
باید بدون خطا اجرا شود. مجموع احتمالات باید برابر با 1 باشد.
استفاده از ابزارهای واقعی
کتابخانهی timm تمام انواع ViT را همراه با وزنهای از پیش آموزشدیده روی ImageNet عرضه میکند. فقط با یک خط:
import timm
model = timm.create_model("vit_base_patch16_224", pretrained=True, num_classes=10)
کتابخانهی timm در سال 2026 انتخاب پیشفرض محیطهای عملیاتی برای ترنسفورمرهای بینایی است. این کتابخانه از مدلهای ViT، DeiT، Swin، Swin-V2، ConvNeXt، ConvNeXt-V2، MaxViT، MViT، EfficientFormer و دهها مدل دیگر تحت یک API یکپارچه پشتیبانی میکند.
برای کارهای چندوجهی (تصویر + متن)، کتابخانهی transformers مدلهای CLIP، SigLIP، BLIP-2 و LLaVA را ارائه میدهد. اینکودر تصویر در همهی این مدلها یکی از انواع مشتقشدهی ViT است.
خروجی این درس
این درس این موارد را تولید میکند:
outputs/prompt-vit-vs-cnn-picker.md— یک پرامپت که بر اساس حجم مجموعهداده، توان پردازشی و پشتهی استنتاج، از میان ViT، ConvNeXt یا Swin بهترین گزینه را انتخاب میکند.outputs/skill-vit-patch-and-pos-embed-inspector.md— مهارتی که بررسی میکند ابعاد امبدینگ تکهها و امبدینگ مکانی یک ViT با طول توالی مورد انتظار مدل همخوانی داشته باشد تا جلوی رایجترین باگهای پورت کردن گرفته شود.
تمرینها
- (ساده) ابعاد تمام تنسورهای میانی را در یک گذر رو به جلو در مدل ViT کوچک پیادهشده در بالا چاپ کنید. این موارد را تأیید کنید: ورودی
(N, 3, 64, 64)-> تکهها(N, 16, 192)-> با توکن کلاس(N, 17, 192)-> ورودی دستهبند(N, 192)-> خروجی(N, num_classes). - (متوسط) یک مدل ViT-S/16 از پیش آموزشدیده از
timmرا روی مجموعهدادهی synthetic-CIFAR از درس 4 فاینتیون کنید. نتایج را با فاینتیونینگ ResNet-18 روی همان دادهها مقایسه کنید. زمان آموزش و دقت نهایی را گزارش دهید. - (سخت) پیشآموزش MAE را برای مدل ViT کوچک پیاده کنید: 75% تکهها را ماسک کنید، اینکودر به همراه یک دیکودر کوچک را برای بازسازی تکههای ماسکشده آموزش دهید. دقت پروب خطی را روی دادههای ساختگی، قبل و بعد از پیشآموزش ارزیابی و مقایسه کنید.
اصطلاحات کلیدی
| اصطلاح | در گفتوگوها | معنای دقیق فنی |
|---|---|---|
| امبدینگ تکهها (Patch embedding) | «اولین کانولوشن» | کانولوشنی با اندازهی کرنل = گام = اندازهی تکه؛ تصویر را به شبکهای از امبدینگهای توکن تبدیل میکند |
| توکن کلاس (Class token) | «توکن [CLS]» | یک بردار یادگرفتنی که به ابتدای توالی توکنها اضافه میشود؛ خروجی نهایی آن بازنمایی سراسری تصویر است |
| امبدینگ مکانی (Positional embedding) | «موقعیت یادگرفتنی» | برداری یادگرفتنی که به تکتک توکنها اضافه میشود تا ترنسفورمر بداند هر تکه از کجای تصویر آمده است |
| پیشنرمالسازی (Pre-LN) | «LayerNorm قبل از زیرلایه» | نسخهی پایدارتر ترنسفورمر: x + sublayer(LN(x)) بهجای LN(x + sublayer(x)) |
| اتنشن چندسر (Multi-head attention) | «اتنشن موازی» | اتنشن استاندارد ترنسفورمر که به چند زیرفضای مستقل تقسیم شده و سپس به هم متصل میشوند |
| ViT-B/16 | «مدل Base با تکهی 16» | اندازهی استاندارد: dim=768، depth=12، heads=12، patch_size=16، ابعاد تصویر 224؛ حدود 86M پارامتر |
| DeiT | «مدل ViT بهینه از نظر داده» | مدل ViT که صرفاً روی ImageNet-1k با بزرگافزایی سنگین آموزش دیده؛ ثابت کرد مجموعهدادههای عظیم برای پیشآموزش اجباری نیستند |
| MAE | «خودرمزگذار پوشانده» | پیشآموزش خودنظارتی: ماسک کردن 75% از تکهها و بازسازی آنها؛ دستورالعمل غالب برای پیشآموزش ViT |
برای مطالعهی بیشتر
- یک تصویر معادل 16x16 کلمه است (Dosovitskiy et al., 2020) — مقالهی اصلی معرفی ViT
- مدل DeiT: ترنسفورمرهای کارآمد تصویر از نظر داده (Touvron et al., 2020) — روش آموزش ViT صرفاً با استفاده از ImageNet-1k
- خودرمزگذارهای پوشانده یادگیرندههای مقیاسپذیر بینایی هستند (He et al., 2022) — پیشآموزش MAE
- مستندات timm — مرجع تمام ترنسفورمرهای بینایی که در محیط عملیاتی استفاده خواهید کرد
