در سیستمهای بینایی ماشین نوین، صرفاً خرد کردن تصویر به تکهها و تبدیل آنها به بردار کمکی به درک محتوا نمیکند؛ این بردارها در ابتدا هیچ دیدی نسبت به ارتباط فضایی و معنایی با یکدیگر ندارند. برای اینکه یک مدل چندحالته تفاوت بین پسزمینه و سوژهی اصلی را تشخیص دهد، به سازوکاری نیاز است که اطلاعات تکهها را با یکدیگر ترکیب کرده و رابطهی بین آنها را کشف کند.
انکودر ترنسفورمر بینایی (Vision Transformer Encoder) دقیقاً همان بستری است که قلب تپندهی مدلهای پیشرفتهای چون CLIP، SigLIP و DINOv2 را شکل میدهد. در این درس یاد میگیرید که چگونه با در کنار هم قرار دادن لایههای خوداتنشن چندسر، شبکهی پیشخور با انبساط 4 برابری و نرمالسازی Pre-LN، یک انکودر ViT-Base کامل را از صفر تا صد پیادهسازی و ارزیابی کنید.
کدها و محاسبات این معماری در مقیاس پایه بهگونهای طراحی شدهاند که بدون نیاز به GPUهای گرانقیمت یا سرویسهای ابری خارجی، مستقیماً روی پردازندهی سیستم شخصی یا تراشههای Apple Silicon قابل اجرا و آزمایش باشند.
تکهها بهتنهایی چیزی را «نمیبینند». یک ترنسفورمر (Transformer) دوازدهلایه با معماری Pre-LN و 12 سرِ اتنشن، توالیِ توکنهای وصلهای را به توالیِ توکنهای زمینهمند (Contextual) تبدیل میکند؛ توکن CLS نیز در آخرین وضعیت پنهان خود، ویژگیهای کل تصویر را یکجا جمع (Pool) میکند. این درس حکم موتورخانهی تمام مدلهای بینایی-زبانی مدرن را دارد.
اهداف یادگیری
- پیادهسازی یک بلوک ترنسفورمر با معماری Pre-LN، خوداتنشن چندسر (Multi-Head Self-Attention) و زیرلایهی پیشخور (Feed-Forward).
- چیدن 12 بلوک با 12 سر روی هم برای ساخت انکودر ViT-Base.
- اتصال فرانتاند تکهها از درس 58 به این انکودر و اجرای یک گذر رو به جلو (Forward pass).
- راستیآزمایی اینکه توکن CLS اطلاعات را از تکتک تکهها جمعآوری میکند.
صورت مسئله
امبدینگ تکهها توالیای شامل 197 توکن تولید میکند که هر کدام برداری بدون کوچکترین آگاهی از بقیهی تکهها است. در تصویر یک گربه، لازم است تکتک تکهها بدانند کدام بخشها مربوط به سبیل، کدام بخشها پسزمینه و کدام بخشها حاوی چشم هستند. ترنسفورمر همان سازوکاری است که این آگاهی را لایه به لایه، با هر لایهی اتنشن میسازد. بدون آن، فرانتاند تکهها صرفاً یک توکنایزر هوشمندانه اما فاقد درک است.
دستورالعمل استاندارد شامل عمق 12 بلوک، عرض 12 سر اتنشن، چینش Pre-LayerNorm، تابع فعالسازی GELU و ضریب انبساط 4 برابری در لایهی پیشخور است. این فرمول، ستون فقرات مدلهای CLIP ViT-L، SigLIP، DINOv2، خانوادهی Qwen-VL، InternVL و تمامی انکودرهای تصویر با وزنهای باز (Open-weight) در سالهای 2025 تا 2026 محسوب میشود. این ساختار آنقدر تثبیتشده است که در مطالعهی هر کدام از این مقالهها میتوانید چنین معماری بلوکی را پیشفرض بگیرید، مگر اینکه خلاف آن صراحتاً ذکر شده باشد.
مفاهیم پایه
مقایسهی Pre-LN و Post-LN
ترنسفورمر اولیه، لایهی LayerNorm را بعد از اتصال باقیمانده (Residual) قرار میداد. معماری Pre-LN (قرار گرفتن LayerNorm پیش از هر زیرلایه) نسخهای است که همهی مدلهای مدرن بینایی-زبانی به کار میبرند؛ چون بدون نیاز به ترفندهای گرمکردن نرخ یادگیری (LR warm-up)، آموزش پایداری ارائه میدهد. تفاوت در کد گذر رو به جلو تنها در حد یک خط است، اما رفتار جریان گرادیان در عمقهای بالاتر از 12 اصلاً قابل مقایسه نیست.
خوداتنشن چندسر
هر سر اتنشن، بردار توکن را به سهگانهی (query, key, value) اختصاصی خود با بعد head_dim = hidden / num_heads تصویر میکند. با داشتن hidden = 768 و heads = 12، بعد هر سر برابر با dim = 64 میشود. این 12 سر به شکل موازی اتنشن را اعمال میکنند، سپس خروجیهای آنها دوباره به بعد 768 به هم متصل (Concat) شده و از یک لایهی تصویر کردنِ خروجی عبور میکنند. مزیت چندسر بودن در این است که یک سر میتواند «اتنشن روی چشم گربه» را یاد بگیرد، در حالی که سر دیگر بدون هیچ تداخلی، «اتنشن روی گرادیان رنگ پسزمینه» را یاد میگیرد.
دلیل انبساط 4 برابری لایهی پیشخور
شبکهی پیشخور (FFN) مسیر hidden -> 4 * hidden -> hidden را با فعالساز GELU در میانهی آن طی میکند. ضریب 4 یک مقدار تجربی است که از سال 2017 در تمام ترنسفورمرهای متن و تصویر ثابت مانده است. مقادیر کوچکتر (مثل 2 برابر) دچار کمبرازش (Underfitting) میشوند و مقادیر بزرگتر (مثل 8 برابر) با بودجهی دادهی ثابت، دچار بیشبرازش (Overfitting) خواهند شد. شبکهی پرسپترون چندلایه (MLP) جایی است که مدل بیشترین بخش از فکتهای آموختهشدهی خود را ذخیره میکند و بخش عریضتر میانی، محل قرارگیری همین اطلاعات است.
| جزء سازنده | تعداد پارامتر در مقیاس ViT-Base |
|---|---|
| تصویر کردن qkv در هر بلوک | 3 * 768 * 768 = 1.77M |
| تصویر کردن خروجی در هر بلوک | 768 * 768 = 590K |
| لایهی FFN در هر بلوک (انبساط 4 برابری) | 2 * 768 * 4 * 768 = 4.72M |
| لایهی LayerNorm در هر بلوک | 4 * 768 = 3K |
| مجموع در هر بلوک | حدود 7.1M |
| 12 بلوک | حدود 85M |
| به اضافهی فرانتاند | در مجموع حدود 86M |
مدل ViT-Base یک انکودر 86 میلیون پارامتری است. این ابعاد با استانداردهای سال 2026 کوچک شمرده میشود (مدل SigLIP-So400M دارای 400 میلیون و ViT مدل Qwen-VL دارای 675 میلیون پارامتر است)، اما معماری آنها به جز در پهنا و عمق، کاملاً یکسان است.
استفاده از ماسک علّی یا بدون ماسک؟
ترنسفورمرهای بینایی فقط-انکودر (Encoder-only) و دوطرفه (Bidirectional) هستند: توکن i میتواند به توکن j برای هر جفتی توجه کند؛ بنابراین نیازی به ماسک نیست. خوداتنشن متن در دیکودر درس 61 از ماسک علّی استفاده خواهد کرد (البته کراساتنشن آن روی تکههای تصویر بدون ماسک است)، ولی درون انکودر تصویر، اتنشن به شکل کاملاً متصل عمل میکند.
توکن CLS چه چیزی یاد میگیرد؟
توکن CLS در ابتدا یک پارامتر قابلیادگیری است، هیچ محتوای اولیهای از جنس تکههای تصویر ندارد و اطلاعات را در طول لایهها از طریق اتنشن جمع میکند. در لایهی آخر، سطر مربوط به CLS به یک بردار خلاصه از کل تصویر تبدیل میشود؛ سرهای پاییندستی این بردار واحد را برای لاجیتهای کلاسی، امبدینگهای کنتراستیو یا کلیدهای کراساتنشن دیکودر متنی تصویر میکنند.
پیادهسازی از صفر
فایل code/main.py این بخشها را پیادهسازی میکند:
- کلاس
MultiHeadSelfAttentionهمراه با لایههای تصویر کردنqkvو خروجی، محاسبات ریاضی اتنشن ضرب نقطهای مقیاسشده (Scaled dot-product attention) و بررسیهای انطباق ابعاد (Shape assertions). - کلاس
FeedForward، همان پرسپترون چندلایهی GELU با انبساط 4 برابری. - کلاس
Block، یک بلوک Pre-LN که زیرلایههای اتنشن و پیشخور را به همراه اتصالات باقیمانده ترکیب میکند. - کلاس
ViT، دستهای از 12 بلوک روی هم به همراه لایهی نهایی LayerNorm. - کلاس
VisionEncoder، کهVisionFrontEndاز درس 58 را به بلوکهایViTمتصل میکند و متدforward()را با ارائهی توالی زمینهمند و بردار تجمیعشدهی CLS در اختیار میگذارد. - یک نمونهی نمایشی که تصویر ساختگی 224x224 را وارد کل انکودر میکند و ابعاد ورودی، ابعاد خروجی، تعداد پارامترها و نُرم (Norm) توکن CLS را یکلایه-در-میان چاپ میکند.
برای اجرا:
python3 code/main.py
خروجی: تصویر نمونه به یک تنسور با ابعاد (1, 197, 768) رمزگذاری میشود. نُرم توکن CLS با پیشروی در لایهها افزایش مییابد و سپس در LayerNorm نهایی تثبیت میشود. مجموع پارامترها نیز حدود 86 میلیون گزارش میشود.
استفاده از ابزارهای واقعی
انکودری که اینجا ساختید، به جز تغییرات جزئی در پهنا و عمق، دقیقاً همان مجموعهبلوکهایی است که در دل تمام مدلهای بینایی-زبانی متنباز سالهای 2025 تا 2026 قرار دارد. تفاوتها فقط در این بخشهاست:
- پهنا و عمق. در مدل ViT-Large مقادیر
hidden=1024, depth=24, heads=16است؛ در مدل SigLIP So400M این مقادیرhidden=1152, depth=27, heads=16هستند. بلوک اصلی همان است. - سر تجمیعکننده (Pooling head). تجمیع با CLS (این درس) در برابر تجمیع میانگین (Average pooling در SigLIP) یا تجمیع بر پایهی اتنشن (در مدلهای بینایی-زبانی جدیدتر).
- مدیریت موقعیت (Position handling). توابع سینوسی ثابت (درس 58) در برابر موقعیت یکبعدی یادگرفتنی، ALiBi یا RoPE دوبعدی. محاسبات پایهی بلوک تغییری نمیکند.
- توکنهای رجیستر. مدل DINOv2 تعداد 4 توکن رجیستر یادگرفتنی به ابتدای دنباله اضافه میکند که پیادهسازی آن فقط به یک خط کد نیاز دارد.
این معماریِ بلوکی، پایهی اصلی کار است. درسهای بعدی (60 تا 63) روی همین فونداسیون بنا میشوند.
تستها
فایل code/test_main.py موارد زیر را پوشش میدهد:
- یک تکبلوک، ابعاد را حفظ میکند و نسبت به اندازهی دستهی ورودی پایدار است.
- مجموع امتیازهای اتنشن در امتداد محور کلیدها برابر با یک است (راستیآزمایی سافتمکس).
- مسیرهای باقیمانده به درستی متصل هستند (ورودیِ صفر همچنان از طریق توکن CLS خروجی غیرصفر تولید میکند).
- گذر رو به جلو در 4 لایهی چیدهشده روی هم، ابعاد درستی میدهد.
- گرادیانها از خروجی CLS به لایهی تصویر کردنِ تکهها جریان پیدا میکنند.
برای اجرای تستها:
python3 -m unittest code/test_main.py
تمرینها
-
توکنهای رجیستر (4 بردار یادگرفتنی که بعد از CLS درج میشوند) را اضافه کرده و کد را دوباره اجرا کنید. میزان یکدستی نقشهی اتنشن را از طریق آنتروپی توزیع سافتمکس در آخرین لایه مقایسه کنید.
-
جای Pre-LN و Post-LN را عوض کنید و یک مدل را برای یک دور (Epoch) روی یک دستهبند اشکال ساختگی آموزش دهید. بررسی کنید کدامیک بدون گرمکردن نرخ یادگیری (LR warm-up)، آموزش پایدارتری دارد.
-
ماسک علّی را در قالب ورودی
attn_maskپیادهسازی کنید تا بتوان از همین بلوک به عنوان بلوک دیکودر هم استفاده کرد. ابعاد ماسک باید مثلثیِ پایینی و به شکل(seq, seq)باشد. -
یک گذر رو به جلو را با استفاده از
torch.profilerدر اندازههای دستهی 1، 8 و 64 پروفایل کنید. خواهید دید که لایهی MLP بیشترین زمان اجرا را به خود اختصاص میدهد، نه لایهی اتنشن. -
لایههای تصویر کردن q-k-v را در یکی از سرهای اتنشن با یک تطبیقدهنده LoRA کمرتبه جایگزین کنید؛ بقیهی بخشها را فریز (Freeze) کرده و مطمئن شوید گرادیان فقط در مسیرهای مورد نظر جریان مییابد.
اصطلاحات کلیدی
| اصطلاح | تعریف |
|---|---|
| Pre-LN | اعمال LayerNorm قبل از هر زیرلایه، به جای اعمال آن بعد از زیرلایه |
| خوداتنشن (Self-attention) | توجه هر توکن به تمام توکنهای دیگر در همان توالی |
| چندسر (Multi-head) | تقسیم بعد وضعیت پنهان بین H سرِ اتنشنِ مستقل از هم |
| انبساط FFN | عریضتر شدن لایهی پیشخور تا 4 * hidden قبل از جمعشدن دوباره |
| تجمیع با CLS (CLS pooling) | استفاده از وضعیت پنهان نهاییِ نخستین توکن به عنوان خلاصهی کل تصویر |
برای مطالعهی بیشتر
- مقالهی An Image is Worth 16x16 Words (معرفی ViT، سال 2021) برای ساختار پایهی انکودر.
- مقالهی DINOv2 (سال 2023) برای آشنایی با توکنهای رجیستر و تابع زیان پیشآموزش خودنظارتی (Self-supervised).
- مقالهی SigLIP (سال 2023) برای مشاهدهی روش تجمیع میانگین و تابع زیان کنتراستیو بر پایهی سیگموئید که در درس 62 استفاده میشود.
