فاز 3 · درس 5

توابع زیان در یادگیری عمیق: راهنمای جامع و پیاده‌سازی از صفر

نوع
ساختنی
زمان
حدود 75 دقیقه
زبان‌ها
Python
پیش‌نیاز
درس 03.04 (توابع فعال‌ساز)
یک ترازو با دو کفه که در یک طرف داده‌های پیش‌بینی‌شده و در طرف دیگر مقادیر واقعی وزن‌کشی می‌شوند و انحراف میان آن‌ها محاسبه می‌گردد
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. مفاهیم پایه
  4. میانگین مربعات خطا (MSE)
  5. تابع زیان آنتروپی متقاطع (Cross-Entropy Loss)
  6. چرا MSE برای دسته‌بندی شکست می‌خورد
  7. هموارسازی برچسب‌ها (Label Smoothing)
  8. تابع زیان کنتراستیو (Contrastive Loss)
  9. تابع زیان کانونی (Focal Loss)
  10. درخت تصمیم انتخاب تابع زیان
  11. چشم‌انداز زیان (Loss Landscape)
  12. پیاده‌سازی از صفر
  13. گام 1: MSE و گرادیان آن
  14. گام 2: آنتروپی متقاطع دودویی
  15. گام 3: آنتروپی متقاطع رده‌ای با سافت‌مکس
  16. گام 4: هموارسازی برچسب‌ها
  17. گام 5: تابع زیان کنتراستیو (نسخه‌ی ساده‌شده‌ی InfoNCE)
  18. گام 6: مقایسه‌ی MSE و آنتروپی متقاطع در مسئله‌ی دسته‌بندی
  19. استفاده از ابزارهای واقعی
  20. تحویل خروجی
  21. تمرین‌ها
  22. واژه‌نامه تخصصی
  23. برای مطالعه‌ی بیشتر

مدل‌های یادگیری عمیق چیزی جز تابعی ریاضی که برای کمینه‌سازی یک عدد تلاش می‌کند نیستند؛ این عدد همان مقدار تابع زیان (Loss Function) است. مدل هوشمند نیست، به درستی خروجی تجاری شما کاری ندارد و فقط به دنبال کاهش ریاضی این مقدار است. اگر در یک مسئله‌ی دسته‌بندی به‌جای آنتروپی متقاطع از MSE استفاده کنید، مدل با پیش‌بینی مداوم مقدار 0.5 کمترین زیان را کسب می‌کند درحالی‌که عملاً هیچ ارزشی تولید نکرده است.

درک سازوکار داخلی توابع زیان و مشتقات آن‌ها تفاوت یک مدل کاربردی را با سیستمی که به دور باطل افتاده مشخص می‌کند. در این درس به‌صورت تحلیلی و از صفر، توابع رگرسیون، دسته‌بندی‌های چندکلاسه و دودویی و زیان‌های مدرن خودنظارتی نظیر InfoNCE را پیاده‌سازی و رفتار گرادیان آن‌ها را موشکافی می‌کنیم.

پیاده‌سازی‌ها و کدهای این درس نیازمند سخت‌افزار سنگین یا سرویس‌های ابری خاصی نیستند و به‌سادگی روی CPU یا سیستم‌های شخصی با Python استاندارد و PyTorch اجرا می‌شوند.

شبکه‌ی شما پیش‌بینی می‌کند و داده‌ی واقعی (ground truth) چیز دیگری می‌گوید. مدل چقدر اشتباه کرده است؟ این عدد همان تابع زیان (loss) است. اگر تابع زیان را اشتباه انتخاب کنید، مدل کلاً برای هدفی اشتباه بهینه‌سازی می‌شود.

اهداف یادگیری

  • پیاده‌سازی MSE، آنتروپی متقاطع دودویی (Binary Cross-Entropy)، آنتروپی متقاطع رده‌ای (Categorical Cross-Entropy) و تابع زیان کنتراستیو (InfoNCE) به همراه گرادیان‌هایشان از صفر
  • توضیح علت شکست MSE در مسائل دسته‌بندی با نشان دادن حالت خرابی «پیش‌بینی 0.5 برای همه‌چیز»
  • اعمال هموارسازی برچسب‌ها (label smoothing) روی آنتروپی متقاطع و شرح چگونگی جلوگیری آن از پیش‌بینی‌های با اطمینان بیش‌ازحد (overconfident)
  • انتخاب تابع زیان درست برای مسائل رگرسیون، دسته‌بندی دودویی، دسته‌بندی چندکلاسه و یادگیری امبدینگ (embedding)

صورت مسئله

مدلی که در یک مسئله‌ی دسته‌بندی MSE را کمینه می‌کند، با اطمینان کامل برای همه‌چیز 0.5 پیش‌بینی خواهد کرد. این مدل تابع زیان را کمینه می‌کند، اما کاملاً بی‌فایده است.

تابع زیان تنها چیزی است که مدل شما واقعاً بهینه‌سازی می‌کند؛ نه دقت (accuracy)، نه امتیاز F1 و نه هر معیاری که به مدیرتان گزارش می‌دهید. بهینه‌ساز گرادیان تابع زیان را می‌گیرد و وزن‌ها را طوری تنظیم می‌کند که این عدد کوچک‌تر شود. اگر تابع زیان آنچه را برایتان مهم است بازتاب ندهد، مدل ارزان‌ترین راه ریاضی را برای برآورده کردنش پیدا می‌کند؛ و این راه تقریباً هیچ‌وقت همان چیزی نیست که می‌خواستید.

یک مثال ملموس: مسئله‌ای برای دسته‌بندی دودویی دارید؛ دو کلاس با تقسیم متوازن 50/50. از MSE به عنوان تابع زیان استفاده می‌کنید. مدل برای تک‌تک ورودی‌ها مقدار 0.5 را پیش‌بینی می‌کند. میانگین MSE برابر با 0.25 می‌شود؛ کمترین مقدار ممکن بدون اینکه واقعاً چیزی یاد گرفته باشد. مدل هیچ توانایی تفکیکی ندارد، اما از نظر ریاضی تابع زیان شما را کمینه کرده است. حالا تابع زیان را به آنتروپی متقاطع تغییر دهید؛ همان مدل مجبور می‌شود پیش‌بینی‌ها را به سمت 0 یا 1 push کند، چون -log(0.5) = 0.693 زیان وحشتناکی است، در حالی که -log(0.99) = 0.01 به پیش‌بینی‌های درستِ با اطمینان بالا پاداش می‌دهد. تفاوت انتخاب تابع زیان، تفاوت میان مدلی است که یاد می‌گیرد با مدلی که معیار ارزیابی را دور می‌زند.

ماجرا از این هم بدتر می‌شود. در یادگیری خودنظارتی (self-supervised learning) اصلاً برچسبی ندارید. تابع زیان کنتراستیو (contrastive loss) تمام سیگنال یادگیری را تعریف می‌کند: چه چیزی مشابه به حساب بیاید، چه چیزی متفاوت باشد و مدل با چه شدتی آن‌ها را از هم دور کند. اگر تابع زیان کنتراستیو را اشتباه بچینید، امبدینگ‌های شما روی یک نقطه‌ی واحد فرو می‌پاشند (collapse)؛ یعنی هر ورودی به یک بردار یکسان نگاشت می‌شود. از نظر فنی زیان صفر است، ولی خروجی کاملاً بی‌ارزش خواهد بود.

مفاهیم پایه

میانگین مربعات خطا (MSE)

انتخاب پیش‌فرض برای مسائل رگرسیون. مجذور اختلاف بین پیش‌بینی و هدف را حساب کنید و روی تمام نمونه‌ها میانگین بگیرید.

MSE=1n∑(ypred−ytrue)2\text{MSE} = \frac{1}{n} \sum (y_{\text{pred}} - y_{\text{true}})^2

چرا به توان دو رساندن مهم است: خطاهای بزرگ را به صورت درجه‌دو جریمه می‌کند. خطای 2 هزینه‌ای 4 برابر خطای 1 دارد و خطای 10 هزینه‌ای 100 برابری. این ویژگی MSE را به داده‌های پرت حساس می‌کند؛ یک پیش‌بینی بسیار پرت و اشتباه کل تابع زیان را تحت‌الشعاع قرار می‌دهد.

اعداد در دنیای واقعی: اگر مدل شما قیمت مسکن را پیش‌بینی کند و روی بیشتر خانه‌ها 10,000 دلار خطا داشته باشد ولی روی یک عمارت 200,000 دلار اشتباه کند، MSE با تمام قوا تلاش می‌کند تا خطای آن یک عمارت را جبران کند و این کار احتمالاً به قیمت خراب شدن عملکرد مدل روی 99 خانه‌ی دیگر تمام می‌شود.

گرادیان MSE نسبت به پیش‌بینی برابر است با:

∂MSE∂ypred=2n(ypred−ytrue)\frac{\partial \text{MSE}}{\partial y_{\text{pred}}} = \frac{2}{n} (y_{\text{pred}} - y_{\text{true}})

این رابطه با خطا خطی است؛ خطاهای بزرگ‌تر گرادیان‌های بزرگ‌تری دریافت می‌کنند. این موضوع برای رگرسیون یک ویژگی مثبت است (خطاهای بزرگ نیاز به اصلاحات بزرگ دارند) و برای دسته‌بندی یک باگ (می‌خواهید پاسخ‌های غلطِ با اطمینان بالا را به‌شکل نمایی جریمه کنید، نه خطی).

تابع زیان آنتروپی متقاطع (Cross-Entropy Loss)

تابع زیان اصلی برای مسائل دسته‌بندی. ریشه در نظریه‌ی اطلاعات دارد و واگرایی بین توزیع احتمال پیش‌بینی‌شده و توزیع واقعی را اندازه می‌گیرد.

آنتروپی متقاطع دودویی (BCE):

BCE=−(ylog⁡(p)+(1−y)log⁡(1−p))\text{BCE} = -\left(y \log(p) + (1 - y) \log(1 - p)\right)

که در آن yy برچسب واقعی (0 یا 1) و pp احتمال پیش‌بینی‌شده است.

چرا −log⁡(p)-\log(p) کار می‌کند: وقتی برچسب واقعی 1 است و شما p=0.99p = 0.99 پیش‌بینی می‌کنید، زیان −log⁡(0.99)=0.01-\log(0.99) = 0.01 است. وقتی p=0.01p = 0.01 پیش‌بینی می‌کنید، زیان −log⁡(0.01)=4.6-\log(0.01) = 4.6 می‌شود. همین اختلاف 460 برابری علت کارآمدی آنتروپی متقاطع است. این تابع پیش‌بینی‌های غلطِ با اطمینان بالا را بی‌رحمانه مجازات می‌کند، در حالی که پیش‌بینی‌های درستِ با اطمینان بالا را تقریباً جریمه نمی‌کند.

گرادیان هم همین واقعیت را نشان می‌دهد:

∂BCE∂p=−yp+1−y1−p\frac{\partial \text{BCE}}{\partial p} = -\frac{y}{p} + \frac{1-y}{1-p}

وقتی y=1y = 1 و pp نزدیک به صفر است، گرادیان −1/p-1/p است که به منفی بی‌نهایت میل می‌کند. مدل سیگنال شدیدی برای اصلاح اشتباهش دریافت می‌کند. وقتی pp نزدیک به 1 است، گرادیان بسیار کوچک می‌شود؛ پیش‌بینی از قبل درست است و چیزی برای اصلاح وجود ندارد.

آنتروپی متقاطع رده‌ای (Categorical Cross-Entropy):

برای دسته‌بندی چندکلاسه با تارگت‌های وان-هات (one-hot encoded).

CCE=−∑iyilog⁡(pi)\text{CCE} = -\sum_i y_i \log(p_i)

فقط کلاس واقعی در زیان سهم دارد (چون همه‌ی yiy_iهای دیگر صفر هستند). اگر 10 کلاس داشته باشیم و به کلاس درست احتمال 0.1 داده شود (حدس تصادفی)، زیان −log⁡(0.1)=2.3-\log(0.1) = 2.3 خواهد بود. اگر به کلاس درست احتمال 0.9 داده شود، زیان −log⁡(0.9)=0.105-\log(0.9) = 0.105 می‌شود. مدل یاد می‌گیرد جرم احتمال را روی پاسخ درست متمرکز کند.

چرا MSE برای دسته‌بندی شکست می‌خورد

آنتروپی متقاطع در دسته‌بندیMSE در دسته‌بندیگرادیان MSEنزدیک اشباعتخت می‌شودگرادیان CEنزدیک پاسخ غلطمنفجر می‌شودپیش‌بینی 0.5 برای کلاس 1MSE = 0.25پیش‌بینی 0.9 برای کلاس 1MSE = 0.01پیش‌بینی 0.1 برای کلاس 1MSE = 0.81پیش‌بینی 0.5 برای کلاس 1CE = 0.693پیش‌بینی 0.9 برای کلاس 1CE = 0.105پیش‌بینی 0.1 برای کلاس 1CE = 2.303تصحیح کندتصحیح سریع

وقتی پیش‌بینی‌ها نزدیک به 0 یا 1 باشند، گرادیان‌های MSE تخت و ناچیز می‌شوند (به دلیل اشباع سیگموئید). گرادیان‌های آنتروپی متقاطع این افت را جبران می‌کنند؛ عبارت −log⁡-\log نواحی تخت سیگموئید را خنثی می‌کند و دقیقاً در جاهایی که بیشترین نیاز را داریم، گرادیان‌های قدرتمندی پدید می‌آورد.

هموارسازی برچسب‌ها (Label Smoothing)

برچسب‌های استاندارد وان-هات می‌گویند: «این داده 100% متعلق به کلاس 3 و 0% متعلق به بقیه‌ی کلاس‌هاست.» این ادعای بسیار قاطعی است. هموارسازی برچسب‌ها این لبه‌های تیز را تعدیل می‌کند:

smooth_label=(1−α)⋅one_hot+αnum_classes\text{smooth\_label} = (1 - \alpha) \cdot \text{one\_hot} + \frac{\alpha}{\text{num\_classes}}

به عنوان مثال با α=0.1\alpha = 0.1 و 10 کلاس: به جای [..., 0 ,1 ,0 ,0]، تارگت تبدیل می‌شود به [..., 0.01 ,0.91 ,0.01 ,0.01]. مدل به جای 1.0، مقدار 0.91 را هدف می‌گیرد.

چرا این روش جواب می‌دهد: مدلی که تلاش می‌کند از خروجی سافت‌مکس (Softmax) دقیقاً مقدار 1.0 بسازد، باید لاجیت‌ها (Logits) را به سمت بی‌نهایت push کند. این موضوع باعث اطمینان بیش‌ازحد، افت تعمیم‌پذیری و شکنندگی مدل در برابر جابه‌جایی توزیع داده‌ها می‌شود. هموارسازی برچسب‌ها سقف تارگت را روی 0.9 می‌گذارد (با α=0.1\alpha = 0.1) و لاجیت‌ها را در محدوده‌ای معقول نگه می‌دارد. مدل‌های خانواده‌ی GPT و اکثر مدل‌های مدرن از هموارسازی برچسب‌ها یا معادل آن بهره می‌برند.

تابع زیان کنتراستیو (Contrastive Loss)

نه برچسبی در کار است، نه کلاسی؛ فقط جفت‌هایی از ورودی‌ها هستند و یک پرسش: آیا این‌ها مشابه‌اند یا متفاوت؟

تابع زیان کنتراستیو به سبک SimCLR (معروف به NT-Xent / InfoNCE):

یک تصویر را بردارید. دو نسخه‌ی تغییریافته (augmented) از آن بسازید (برش، چرخش، تغییر رنگ). این دو یک «جفت مثبت» (positive pair) می‌سازند و باید امبدینگ‌های مشابهی داشته باشند. هر تصویر دیگری در این بچ، یک «جفت منفی» (negative pair) شکل می‌دهد و باید امبدینگ‌های متفاوتی داشته باشد.

L=−log⁡exp⁡(sim(zi,zj)/τ)∑kexp⁡(sim(zi,zk)/τ)L = -\log \frac{\exp(\text{sim}(z_i, z_j) / \tau)}{\sum_k \exp(\text{sim}(z_i, z_k) / \tau)}

که در آن sim()\text{sim}() شباهت کسینوسی (Cosine Similarity) است، ziz_i و zjz_j جفت مثبت هستند، مجموع روی تمام نمونه‌های منفی بسته شده، و τ\tau (دما یا temperature) تیزی توزیع را تنظیم می‌کند. دمای پایین‌تر یعنی منفی‌های سخت‌تر و تفکیک تهاجمی‌تر.

اعداد در دنیای واقعی: اندازه‌ی بچ 256 یعنی 255 نمونه‌ی منفی به ازای هر جفت مثبت. مقدار دما τ=0.07\tau = 0.07 است (پیش‌فرض SimCLR). فرم این زیان شبیه سافت‌مکس روی مقادیر شباهت است؛ می‌خواهد شباهت جفت مثبت بالاترین مقدار را در بین تمام 256 حالت داشته باشد.

زیان سه‌گانه (Triplet Loss):

سه ورودی می‌گیرد: لنگر (anchor)، مثبت (هم‌کلاس) و منفی (از کلاسی دیگر).

L=max⁡(0,d(anchor,positive)−d(anchor,negative)+margin)L = \max\left(0, d(\text{anchor}, \text{positive}) - d(\text{anchor}, \text{negative}) + \text{margin}\right)

حاشیه یا مارجین (معمولاً بین 0.2 تا 1.0) حداقل فاصله‌ی لازم بین فواصل مثبت و منفی را اجباری می‌کند. اگر نمونه‌ی منفی به اندازه‌ی کافی دور باشد، مقدار زیان صفر می‌شود؛ در نتیجه نه گرادیانی داریم و نه به‌روزرسانی وزنی. این سازوکار فرایند آموزش را بهینه می‌کند، اما به استخراج هوشمندانه‌ی سه‌گانه‌ها (triplet mining) وابسته است (یعنی پیدا کردن نمونه‌های منفی سختی که به لنگر نزدیک‌اند).

تابع زیان کانونی (Focal Loss)

برای مجموعه‌داده‌های با عدم‌توازن کلاس‌ها. آنتروپی متقاطع استاندارد با تمام نمونه‌هایی که درست دسته‌بندی شده‌اند رفتاری یکسان دارد. تابع زیان کانونی، وزن نمونه‌های ساده را پایین می‌آورد:

FL=−α(1−pt)γlog⁡(pt)\text{FL} = -\alpha (1 - p_t)^\gamma \log(p_t)

که در آن ptp_t احتمال پیش‌بینی‌شده برای کلاس واقعی است و γ\gamma شدت تمرکز را مشخص می‌کند. با γ=0\gamma = 0، فرمول به همان آنتروپی متقاطع استاندارد تبدیل می‌شود. با γ=2\gamma = 2 (مقدار پیش‌فرض):

  • نمونه‌ی ساده (pt=0.9p_t = 0.9): وزن =(0.1)2=0.01= (0.1)^2 = 0.01 می‌گیرد؛ عملاً نادیده گرفته می‌شود.
  • نمونه‌ی سخت (pt=0.1p_t = 0.1): وزن =(0.9)2=0.81= (0.9)^2 = 0.81 می‌گیرد؛ سیگنال گرادیان را کامل دریافت می‌کند.

زیان کانونی توسط Lin و همکارانش برای تشخیص اشیا معرفی شد؛ جایی که 99% از نواحی کاندید، پس‌زمینه هستند (منفی‌های ساده). بدون این تابع زیان، مدل در دریایی از نمونه‌های ساده‌ی پس‌زمینه غرق می‌شود و هیچ‌وقت تشخیص اشیا را یاد نمی‌گیرد. با این زیان، مدل ظرفیت یادگیری‌اش را روی نمونه‌های مبهم و سختی می‌گذارد که واقعاً سرنوشت‌سازند.

درخت تصمیم انتخاب تابع زیان

بلهبله، جریمه داده‌های پرتخیر، مقاوم به داده‌های پرتدودوییچندکلاسهنامتوازناطمینان کاذب؟داده‌های جفتیسه‌تایی‌ها موجودندخودنظارتی با دسته‌های بزرگمسئله شما چیست؟رگرسیون؟دسته‌بندی؟یادگیری امبدینگ؟حساس به داده‌های پرت؟استفاده از MSEاستفاده از MAE / Huberاستفاده از Binary CEاستفاده از Categorical CEاستفاده از Focal Lossافزودن هموارسازی برچسب‌هااستفاده از Contrastive Lossاستفاده از Triplet Lossاستفاده از InfoNCE

چشم‌انداز زیان (Loss Landscape)

شکل سطح تابع زیان (loss)مناسب‌ترین برایمناسب‌ترین برایمناسب‌ترین برایMSEسهمی هموارتک مینیممبهینه‌سازی آسانآنتروپی متقاطعشیب تند نزدیک پاسخ‌های غلطتخت نزدیک پاسخ‌های درستگرادیان‌های قوی در صورت نیازکنتراستیومینیمم‌های محلی متعددوابسته به ترکیب دستهدما تیزی را کنترل می‌کندرگرسیوندسته‌بندییادگیری بازنمایی
تابع زیان (loss) آنتروپی متقاطعاحتمال را بکشید
CROSS-ENTROPY LOSSCross-entropy charges −log of the probability the model put on the correct answer. Right and confident costs almost nothing; wrong and confident costs a fortune. That asymmetry is what pushes the model to be calibrated, not just correct.
آنتروپی متقاطع، مقدار log- از احتمالی که مدل به پاسخ صحیح داده است را جریمه می‌کند. پاسخ درست همراه با اطمینان بالا تقریباً هزینه‌ای ندارد؛ اما پاسخ اشتباه همراه با اطمینان بالا، جریمه‌ی سنگینی در پی دارد. همین عدم تقارن است که مدل را push می‌کند تا علاوه بر درست بودن، کالیبره نیز باشد.

پیاده‌سازی از صفر

گام 1: MSE و گرادیان آن

def mse(predictions, targets):
    n = len(predictions)
    total = 0.0
    for p, t in zip(predictions, targets):
        total += (p - t) ** 2
    return total / n

def mse_gradient(predictions, targets):
    n = len(predictions)
    grads = []
    for p, t in zip(predictions, targets):
        grads.append(2.0 * (p - t) / n)
    return grads

گام 2: آنتروپی متقاطع دودویی

مسئله‌ی log(0) کاملاً واقعی است. اگر مدل برای یک نمونه‌ی مثبت دقیقاً 0 پیش‌بینی کند، log⁡(0)=−∞\log(0) = -\infty رخ می‌دهد. با برش مقادیر (clipping) جلوی این خطا را می‌گیریم.

import math

def binary_cross_entropy(predictions, targets, eps=1e-15):
    n = len(predictions)
    total = 0.0
    for p, t in zip(predictions, targets):
        p_clipped = max(eps, min(1 - eps, p))
        total += -(t * math.log(p_clipped) + (1 - t) * math.log(1 - p_clipped))
    return total / n

def bce_gradient(predictions, targets, eps=1e-15):
    grads = []
    for p, t in zip(predictions, targets):
        p_clipped = max(eps, min(1 - eps, p))
        grads.append(-(t / p_clipped) + (1 - t) / (1 - p_clipped))
    return grads

گام 3: آنتروپی متقاطع رده‌ای با سافت‌مکس

سافت‌مکس لاجیت‌های خام را به احتمالات تبدیل می‌کند؛ سپس آنتروپی متقاطع را در برابر تارگت‌های وان-هات حساب می‌کنیم.

def softmax(logits):
    max_val = max(logits)
    exps = [math.exp(x - max_val) for x in logits]
    total = sum(exps)
    return [e / total for e in exps]

def categorical_cross_entropy(logits, target_index, eps=1e-15):
    probs = softmax(logits)
    p = max(eps, probs[target_index])
    return -math.log(p)

def cce_gradient(logits, target_index):
    probs = softmax(logits)
    grads = list(probs)
    grads[target_index] -= 1.0
    return grads

گرادیان ترکیب سافت‌مکس و آنتروپی متقاطع به شکلی زیبا ساده می‌شود: این گرادیان برای کلاس واقعی برابر است با (احتمال پیش‌بینی‌شده منهای 1) و برای سایر کلاس‌ها فقط (احتمال پیش‌بینی‌شده). این ساده‌سازی تمیز اتفاقی نیست؛ دقیقاً به همین دلیل است که همیشه سافت‌مکس و آنتروپی متقاطع را با هم جفت می‌کنند.

گام 4: هموارسازی برچسب‌ها

def label_smoothed_cce(logits, target_index, num_classes, alpha=0.1, eps=1e-15):
    probs = softmax(logits)
    loss = 0.0
    for i in range(num_classes):
        if i == target_index:
            smooth_target = 1.0 - alpha + alpha / num_classes
        else:
            smooth_target = alpha / num_classes
        p = max(eps, probs[i])
        loss += -smooth_target * math.log(p)
    return loss

گام 5: تابع زیان کنتراستیو (نسخه‌ی ساده‌شده‌ی InfoNCE)

def cosine_similarity(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    norm_a = math.sqrt(sum(x * x for x in a))
    norm_b = math.sqrt(sum(x * x for x in b))
    if norm_a < 1e-10 or norm_b < 1e-10:
        return 0.0
    return dot / (norm_a * norm_b)

def contrastive_loss(anchor, positive, negatives, temperature=0.07):
    sim_pos = cosine_similarity(anchor, positive) / temperature
    sim_negs = [cosine_similarity(anchor, neg) / temperature for neg in negatives]

    max_sim = max(sim_pos, max(sim_negs)) if sim_negs else sim_pos
    exp_pos = math.exp(sim_pos - max_sim)
    exp_negs = [math.exp(s - max_sim) for s in sim_negs]
    total_exp = exp_pos + sum(exp_negs)

    return -math.log(max(1e-15, exp_pos / total_exp))

گام 6: مقایسه‌ی MSE و آنتروپی متقاطع در مسئله‌ی دسته‌بندی

همان شبکه‌ی درس 04 (مجموعه‌داده‌ی دایره) را با هر دو تابع زیان آموزش دهید. ببینید که آنتروپی متقاطع چطور سریع‌تر همگرا می‌شود.

import random

def sigmoid(x):
    x = max(-500, min(500, x))
    return 1.0 / (1.0 + math.exp(-x))

def make_circle_data(n=200, seed=42):
    random.seed(seed)
    data = []
    for _ in range(n):
        x = random.uniform(-2, 2)
        y = random.uniform(-2, 2)
        label = 1.0 if x * x + y * y < 1.5 else 0.0
        data.append(([x, y], label))
    return data


class LossComparisonNetwork:
    def __init__(self, loss_type="bce", hidden_size=8, lr=0.1):
        random.seed(0)
        self.loss_type = loss_type
        self.lr = lr
        self.hidden_size = hidden_size

        self.w1 = [[random.gauss(0, 0.5) for _ in range(2)] for _ in range(hidden_size)]
        self.b1 = [0.0] * hidden_size
        self.w2 = [random.gauss(0, 0.5) for _ in range(hidden_size)]
        self.b2 = 0.0

    def forward(self, x):
        self.x = x
        self.z1 = []
        self.h = []
        for i in range(self.hidden_size):
            z = self.w1[i][0] * x[0] + self.w1[i][1] * x[1] + self.b1[i]
            self.z1.append(z)
            self.h.append(max(0.0, z))

        self.z2 = sum(self.w2[i] * self.h[i] for i in range(self.hidden_size)) + self.b2
        self.out = sigmoid(self.z2)
        return self.out

    def backward(self, target):
        if self.loss_type == "mse":
            d_loss = 2.0 * (self.out - target)
        else:
            eps = 1e-15
            p = max(eps, min(1 - eps, self.out))
            d_loss = -(target / p) + (1 - target) / (1 - p)

        d_sigmoid = self.out * (1 - self.out)
        d_out = d_loss * d_sigmoid

        for i in range(self.hidden_size):
            d_relu = 1.0 if self.z1[i] > 0 else 0.0
            d_h = d_out * self.w2[i] * d_relu
            self.w2[i] -= self.lr * d_out * self.h[i]
            for j in range(2):
                self.w1[i][j] -= self.lr * d_h * self.x[j]
            self.b1[i] -= self.lr * d_h
        self.b2 -= self.lr * d_out

    def compute_loss(self, pred, target):
        if self.loss_type == "mse":
            return (pred - target) ** 2
        else:
            eps = 1e-15
            p = max(eps, min(1 - eps, pred))
            return -(target * math.log(p) + (1 - target) * math.log(1 - p))

    def train(self, data, epochs=200):
        losses = []
        for epoch in range(epochs):
            total_loss = 0.0
            correct = 0
            for x, y in data:
                pred = self.forward(x)
                self.backward(y)
                total_loss += self.compute_loss(pred, y)
                if (pred >= 0.5) == (y >= 0.5):
                    correct += 1
            avg_loss = total_loss / len(data)
            accuracy = correct / len(data) * 100
            losses.append((avg_loss, accuracy))
            if epoch % 50 == 0 or epoch == epochs - 1:
                print(f"    Epoch {epoch:3d}: loss={avg_loss:.4f}, accuracy={accuracy:.1f}%")
        return losses

استفاده از ابزارهای واقعی

فریم‌ورک PyTorch همه‌ی توابع زیان استاندارد را با پایداری عددی (numerical stability) درونی پیاده کرده است:

import torch
import torch.nn as nn
import torch.nn.functional as F

predictions = torch.tensor([0.9, 0.1, 0.7], requires_grad=True)
targets = torch.tensor([1.0, 0.0, 1.0])

mse_loss = F.mse_loss(predictions, targets)
bce_loss = F.binary_cross_entropy(predictions, targets)

logits = torch.randn(4, 10)
labels = torch.tensor([3, 7, 1, 9])
ce_loss = F.cross_entropy(logits, labels)
ce_smooth = F.cross_entropy(logits, labels, label_smoothing=0.1)

به جای ترکیب F.nll_loss با سافت‌مکس دستی، مستقیماً از F.cross_entropy استفاده کنید. این تابع log-softmax و درست‌نمایی منفی لگاریتمی (negative log-likelihood) را در یک عملیات پایدار از نظر عددی یکی می‌کند. اعمال جداگانه‌ی سافت‌مکس و سپس لگاریتم گرفتن از آن پایداری کمتری دارد؛ چون در تفریق اعداد نمایی بزرگ، دقت اعشاری افت می‌کند.

برای یادگیری کنتراستیو، بیشتر تیم‌ها سراغ پیاده‌سازی‌های سفارشی یا کتابخانه‌هایی مثل lightly یا pytorch-metric-learning می‌روند. حلقه‌ی اصلی همیشه ثابت است: محاسبه‌ی شباهت‌های دوبه‌دو، ساخت سافت‌مکس روی مثبت‌ها و منفی‌ها، و در نهایت پس‌انتشار (backpropagation).

تحویل خروجی

این درس دو خروجی عملی دارد:

  • outputs/prompt-loss-function-selector.md -- پرامپتی آماده برای انتخاب تابع زیان درست
  • outputs/prompt-loss-debugger.md -- پرامپتی تشخیصی برای وقت‌هایی که منحنی زیان رفتار غیرعادی دارد

تمرین‌ها

  1. تابع زیان هوبر (Huber loss یا همان smooth L1 loss) را پیاده‌سازی کنید که برای خطاهای کوچک شبیه MSE و برای خطاهای بزرگ شبیه MAE رفتار می‌کند. یک شبکه‌ی رگرسیون را برای پیش‌بینی y=sin⁡(x)y = \sin(x) آموزش دهید؛ در شرایطی که به 5% از تارگت‌های آموزشی نویز تصادفی (داده‌های پرت) اضافه شده است، عملکرد MSE را با Huber مقایسه کنید و خطای آزمون نهایی را بسنجید.

  2. تابع زیان کانونی را به حلقه‌ی آموزش دسته‌بندی دودویی اضافه کنید. یک مجموعه‌داده‌ی نامتوازن بسازید (90% کلاس 0 و 10% کلاس 1). بعد از 200 دور (epoch)، عملکرد BCE استاندارد را با زیان کانونی (γ=2\gamma = 2) روی بازخوانی (recall) کلاس اقلیت مقایسه کنید.

  3. زیان سه‌گانه را با استخراج منفی‌های نیمه‌سخت (semi-hard negative mining) پیاده‌سازی کنید. داده‌های امبدینگ دوبعدی برای 5 کلاس تولید کنید. برای هر لنگر، سخت‌ترین نمونه‌ی منفی را که هنوز از نمونه‌ی مثبت دورتر است (نیمه‌سخت) پیدا کنید. سرعت همگرایی این حالت را با انتخاب تصادفی سه‌گانه‌ها بسنجید.

  4. مقایسه‌ی MSE و آنتروپی متقاطع را تکرار کنید، اما این بار اندازه‌ی گرادیان‌ها را در هر لایه حین آموزش ثبت کنید. میانگین نُرم گرادیان را در هر دور رسم کنید. بررسی کنید که آیا آنتروپی متقاطع در دورهای اولیه (زمانی که مدل بیشترین تردید را دارد) واقعاً گرادیان‌های بزرگ‌تری تولید می‌کند یا نه.

  5. تابع زیان واگرایی کولبک-لایبلر (KL divergence) را پیاده‌سازی کنید و ببینید آیا کمینه‌سازی KL(true∥predicted)\mathrm{KL}(\text{true} \Vert \text{predicted}) وقتی توزیع واقعی وان-هات است، گرادیان‌هایی دقیقاً یکسان با آنتروپی متقاطع می‌دهد یا خیر. سپس تارگت‌های نرم را تست کنید (مانند تقطیر دانش یا knowledge distillation)، جایی که توزیع «واقعی» از خروجی سافت‌مکس مدل آموزگار (teacher) می‌آید.

واژه‌نامه تخصصی

اصطلاح در گفت‌وگوهای روزمره معنای دقیق فنی
Loss function «میزان اشتباه مدل» تابعی مشتق‌پذیر که پیش‌بینی‌ها و تارگت‌ها را به یک عدد اسکالر نگاشت می‌کند تا بهینه‌ساز آن را کمینه کند
MSE «میانگین مجذور خطاها» میانگین مجذور اختلافات بین پیش‌بینی‌ها و تارگت‌ها؛ خطاهای بزرگ را به شکل درجه‌دو جریمه می‌کند
Cross-entropy «زیان دسته‌بندی» واگرایی بین توزیع احتمال پیش‌بینی‌شده و توزیع واقعی را با رابطه‌ی −log⁡(p)-\log(p) اندازه می‌گیرد
Binary cross-entropy «BCE» آنتروپی متقاطع برای دو کلاس: −(ylog⁡(p)+(1−y)log⁡(1−p))-(y \log(p) + (1-y) \log(1-p))
Label smoothing «نرم کردن تارگت‌ها» جایگزین کردن تارگت‌های صلب 0 و 1 با مقادیر ملایم‌تر (مثلاً 0.1 و 0.9) برای جلوگیری از اطمینان کاذب و تقویت تعمیم‌پذیری
Contrastive loss «نزدیک کردن مشابه‌ها و راندن متضادها» تابعی که بازنمایی‌ها را با نزدیک کردن جفت‌های مشابه و دور کردن جفت‌های ناهمسان در فضای امبدینگ یاد می‌گیرد
InfoNCE «تابع زیان CLIP و SimCLR» آنتروپی متقاطع با مقیاس دما روی نمرات شباهت نرمال‌شده؛ یادگیری کنتراستیو را شبیه مسئله‌ی دسته‌بندی می‌بیند
Focal loss «حلال داده‌های نامتوازن» آنتروپی متقاطع وزن‌دار با ضریب (1−pt)γ(1-p_t)^\gamma برای کم کردن سهم نمونه‌های ساده و فوکوس روی نمونه‌های دشوار
Triplet loss «لنگر-مثبت-منفی» لنگر را در فضای امبدینگ، دست‌کم به اندازه‌ی یک مارجین مشخص، به نمونه‌ی مثبت نزدیک‌تر از منفی می‌کند
Temperature «پیچ تنظیم تیزی» مقسوم‌علیهی اسکالر روی لاجیت‌ها یا شباهت‌ها که قله‌دار بودن توزیع نهایی را تعیین می‌کند؛ عدد کمتر یعنی توزیع تیزتر

برای مطالعه‌ی بیشتر

  • مقاله‌ی Lin و همکاران با عنوان «Focal Loss for Dense Object Detection» (سال 2017) -- معرفی تابع زیان کانونی برای مدیریت عدم‌توازن شدید کلاس‌ها در آشکارسازی اشیا (RetinaNet)
  • مقاله‌ی Chen و همکاران با عنوان «A Simple Framework for Contrastive Learning of Visual Representations» (مدل SimCLR، سال 2020) -- تعریف پایپ‌لاین مدرن یادگیری کنتراستیو با تابع زیان NT-Xent
  • مقاله‌ی Szegedy و همکاران با عنوان «Rethinking the Inception Architecture» (سال 2016) -- معرفی هموارسازی برچسب‌ها به عنوان تکنیک منظم‌سازی (regularization) که امروزه در اکثر مدل‌های بزرگ فراگیر شده است
  • مقاله‌ی Hinton و همکاران با عنوان «Distilling the Knowledge in a Neural Network» (سال 2015) -- تقطیر دانش با استفاده از تارگت‌های نرم و واگرایی کولبک-لایبلر، که پایه‌گذار فشرده‌سازی مدل‌ها شد

سؤالات متداول

چرا استفاده از میانگین مربعات خطا (MSE) برای دسته‌بندی مناسب نیست؟

در دسته‌بندی، پیش‌بینی‌های اشتباه با اطمینان بالا به دلیل اشباع لایه‌های فعال‌ساز مثل سیگموئید، گرادیان‌های بسیار تختی تولید می‌کنند. علاوه بر این، مدل می‌تواند با پیش‌بینی یکنواخت احتمال 0.5 زیان MSE را به حداقل برساند بدون آنکه یادگیری واقعی رخ دهد.

هموارسازی برچسب‌ها (Label Smoothing) چه مشکلی را حل می‌کند؟

تارگت‌های سفت‌وسخت وان-هات باعث می‌شوند لاجیت‌های لایه‌ی سافت‌مکس به بی‌نهایت میل کنند و مدل بیش‌از‌حد مطمئن (overconfident) شود. با جایگزینی مقادیر ملایم‌تر، لاجیت‌ها کنترل شده و تعمیم‌پذیری بهبود می‌یابد.

چرا در PyTorch باید از F.cross_entropy به جای ترکیب softmax و nll_loss استفاده کرد؟

تابع F.cross_entropy عملیات log-softmax و negative log-likelihood را در قالب یک فرمول پایدار از نظر محاسبات اعشاری ترکیب می‌کند و مانع از سرریز یا خطای گردکردن اعداد در تفریق‌های نمایی بزرگ می‌شود.

ما رو توی گوگل بیشتر ببین

با افزودن نکست ادمین به «منابع ترجیحی» گوگل، درس‌ها و مطالب ما را در بخش اخبار برتر جستجو بیشتر می‌بینید.