در این فصل چه یاد میگیری#
ترمِ ۱ با یک نتیجهٔ ناخوشایند تمام شد: شبکه از مدلِ خطیِ سرنخ بدتر درآمد، و در جدولِ آخر دیدی که از epoch دهم به بعد خطای آموزش پایین میرفت و خطای آزمون بالا. این ترم کاملاً دربارهٔ همان جدول است.
در این فصل اول یاد میگیری آن پدیده را ببینی — با دو منحنی کنارِ هم — و بعد دو عاملی را که کنترلش میکنند اندازه میگیری: ظرفیتِ مدل و اندازهٔ داده.

آخر این فصل میتوانی:
- منحنیِ آموزش و اعتبارسنجی را کنارِ هم ثبت و تفسیر کنی
- نقطهٔ واگرایی را پیدا کنی
- اثرِ ظرفیتِ مدل را با عدد بسنجی
- بگویی چرا بیشتر داده بهترین درمانِ
overfittingاست
قبل از شروع#
از ترمِ ۱: حلقهٔ آموزش، CrossEntropyLoss، train/eval.
از سرنخ ترمِ ۳ فصل ۸: مفهومِ overfitting — اینجا با چشمِ خودت در یک شبکه میبینیاش.
دادهٔ این ترم: ۱۷۹۷ تصویرِ ۸×۸ از ارقامِ دستنویس که همراهِ scikit-learn میآید. بدونِ دانلود، و بهقدری سبک که هر آزمایشِ این ترم روی CPU در چند ثانیه تمام میشود.
📓 نوتبوک: نوتبوک این فصل را در Colab باز کن — همهٔ کدهای این فصل آماده و بهترتیب داخلش هست.
۱. سهتکه کردنِ داده، نه دوتکه#
سلولِ راهاندازی داده را به سه بخش تقسیم کرد، نه دو. این تفاوت با سرنخ عمدی است:
| بخش | برای چه | چند بار نگاهش میکنی |
|---|---|---|
train |
آموزشِ مدل | هر epoch |
validation |
تصمیمگیری: کِی بایستم، کدام مدل، کدام lr |
دهها بار |
test |
گزارشِ نهایی | یک بار، در آخر |
چرا سهتا؟ چون در این ترم دهها بار به عددِ اعتبارسنجی نگاه میکنی و بر اساسش تصمیم میگیری. هر تصمیمی که بر اساسِ یک مجموعه گرفته شود، آن مجموعه را کمی «سوخته» میکند — همان نشتیِ انتخابیِ سرنخ ترمِ ۳ فصلِ ۹. پس یک مجموعهٔ دستنخورده کنار میگذاریم که فقط یک بار، در پایان، دیده شود.
۲. ابزارِ ثابتِ این ترم#
import torch
import torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader
def make_model(hidden=256, dropout=0.0, seed=0):
torch.manual_seed(seed)
layers = [nn.Linear(64, hidden), nn.ReLU()]
if dropout:
layers.append(nn.Dropout(dropout))
layers += [nn.Linear(hidden, hidden), nn.ReLU()]
if dropout:
layers.append(nn.Dropout(dropout))
return nn.Sequential(*layers, nn.Linear(hidden, 10))
@torch.no_grad()
def accuracy(model, X, y):
model.eval()
return float((model(X).argmax(1) == y).float().mean())
def train(model, epochs=40, lr=0.01, weight_decay=0.0, subset=None, quiet=True):
Xt, yt = (Xtr[:subset], ytr[:subset]) if subset else (Xtr, ytr)
loader = DataLoader(TensorDataset(Xt, yt), batch_size=64, shuffle=True)
opt = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=weight_decay)
loss_fn = nn.CrossEntropyLoss()
history = []
for epoch in range(1, epochs + 1):
model.train()
for xb, yb in loader:
opt.zero_grad()
loss_fn(model(xb), yb).backward()
opt.step()
history.append((epoch, accuracy(model, Xt, yt), accuracy(model, Xva, yva)))
if not quiet and epoch in (1, 5, 10, 20, 40):
print(f"epoch {epoch:>2}: دقت آموزش = {history[-1][1]:.4f} | دقت اعتبارسنجی = {history[-1][2]:.4f}")
return history
history = train(make_model(256), epochs=40, subset=150, quiet=False)
best = max(history, key=lambda row: row[2])
print("بهترین دقت اعتبارسنجی:", round(best[2], 4), "در epoch", best[0])
print("فاصلهٔ نهایی:", round(history[-1][1] - history[-1][2], 4))
epoch 1: دقت آموزش = 0.4133 | دقت اعتبارسنجی = 0.3203
epoch 5: دقت آموزش = 0.9600 | دقت اعتبارسنجی = 0.8719
epoch 10: دقت آموزش = 0.9733 | دقت اعتبارسنجی = 0.8357
epoch 20: دقت آموزش = 1.0000 | دقت اعتبارسنجی = 0.8969
epoch 40: دقت آموزش = 1.0000 | دقت اعتبارسنجی = 0.9192
بهترین دقت اعتبارسنجی: 0.9192 در epoch 35
فاصلهٔ نهایی: 0.0808
دقتِ آموزش به ۱۰۰ درصد رسید و همانجا ماند. یعنی مدل هر ۱۵۰ نمونهٔ آموزش را کاملاً حفظ کرده. ولی روی دادهٔ ندیده ۹۲ درصد است — یک فاصلهٔ هشتدرصدی.
آن فاصله همان overfitting است، و اندازهاش همان عددی است که باید کنترلش کنی.
توجه کن که اینجا برخلافِ پروژهٔ ترمِ ۱، دقتِ اعتبارسنجی بالا میرود و بعد صاف میشود، نه اینکه سقوط کند. این هم یک حالتِ کاملاً عادی است: مدل داده را حفظ کرده ولی همچنان الگوی مفیدی هم یاد گرفته. فصلِ ۲ حالتی را نشان میدهد که واقعاً سقوط میکند.
📏 اندازه بگیر: این تابعِ
trainرا نگه دار — کلِ ترم با همین کار میکنیم. و به یک تصمیمِ طراحیاش دقت کن:historyرا برمیگرداند بهجای اینکه فقط چاپ کند. عددی که ذخیره نشده باشد، قابلِ مقایسه نیست — و در فصلِ ۷ میبینی که همین یک عادت، پایهٔ کارِ آزمایشیِ منظم است.
۳. عاملِ اول: ظرفیتِ مدل#
for hidden in [8, 64, 256]:
model = make_model(hidden)
h = train(model, epochs=40, subset=150)
params = sum(p.numel() for p in model.parameters())
print(f"hidden={hidden:>3} ({params:>6} پارامتر): "
f"آموزش={h[-1][1]:.4f} اعتبارسنجی={h[-1][2]:.4f} فاصله={h[-1][1] - h[-1][2]:.4f}")
hidden= 8 ( 682 پارامتر): آموزش=0.8800 اعتبارسنجی=0.7549 فاصله=0.1251
hidden= 64 ( 8970 پارامتر): آموزش=1.0000 اعتبارسنجی=0.8942 فاصله=0.1058
hidden=256 ( 85002 پارامتر): آموزش=1.0000 اعتبارسنجی=0.9192 فاصله=0.0808
و اینجا نتیجهای است که با داستانِ رایج نمیخواند: مدلِ بزرگتر هم دقتِ بهتری داد و هم فاصلهٔ کمتری.
دلیلش در ستونِ آموزش است. مدلِ ۸تایی اصلاً نتوانست دادهٔ آموزش را یاد بگیرد — ۸۸ درصد، نه ۱۰۰. مشکلش overfitting نیست، underfitting است: ظرفیتش برای این کار کم است.
| حالت | دقتِ آموزش | دقتِ اعتبارسنجی | درمان |
|---|---|---|---|
underfitting |
پایین | پایین | مدلِ بزرگتر، آموزشِ بیشتر |
overfitting |
بالا | خیلی پایینتر | داده بیشتر، regularization |
| متعادل | بالا | نزدیک به آموزش | همین را میخواهی |
پس «ظرفیتِ بیشتر یعنی overfitting بیشتر» یک قاعدهٔ کلی نیست. در عمل — و این چیزی است که پژوهشِ سالهای اخیر مدام تأییدش کرده — مدلهای بزرگتر اغلب هم بهتر برازش میکنند و هم بهتر تعمیم میدهند، به شرطی که آموزششان مهار شده باشد. فصلِ ۲ همان مهار را میسازد.
⚠️ مواظب باش: اولین کاری که با یک نتیجهٔ بد باید بکنی، تشخیصِ
underfittingازoverfittingاست — و تنها راهش نگاه کردن به هر دو عدد است. کسی که فقط دقتِ اعتبارسنجی را میبیند، نمیداند باید مدل را بزرگتر کند یا مهارش کند، و این دو درمان دقیقاً خلافِ هماند.
۴. عاملِ دوم: اندازهٔ داده#
for n in [150, 400, 1078]:
h = train(make_model(256), epochs=40, subset=n)
print(f"n={n:>4}: آموزش={h[-1][1]:.4f} اعتبارسنجی={h[-1][2]:.4f} فاصله={h[-1][1] - h[-1][2]:.4f}")
n= 150: آموزش=1.0000 اعتبارسنجی=0.9192 فاصله=0.0808
n= 400: آموزش=1.0000 اعتبارسنجی=0.9666 فاصله=0.0334
n=1078: آموزش=1.0000 اعتبارسنجی=0.9861 فاصله=0.0139
همان مدل، همان تعدادِ epoch، همان seed. فقط داده بیشتر — و فاصله از ۸ درصد به ۱٫۴ درصد رسید.
دلیلش شهودی است: با ۱۵۰ نمونه، حفظ کردن آسان است. با هزار نمونه، حفظ کردن سختتر از یادگرفتنِ الگو میشود — و مدل راهِ ارزانتر را میرود.
📏 اندازه بگیر: بیشتر داده تقریباً همیشه بهترین درمانِ
overfittingاست، و هیچ تکنیکی جایش را نمیگیرد. قبل از اینکه ساعتهاdropoutوweight decayتنظیم کنی، این سؤال را بپرس: میتوانم داده بیشتری جمع کنم؟ فصلِ ۵ راهی میدهد که وقتی جواب «نه» است، تا حدی جایش را بگیرد — ولی فقط تا حدی.
۵. سه رفتارِ منحنی، و معنایشان#
| شکلِ منحنی | معنی | کارِ بعدی |
|---|---|---|
| هر دو پایین، نزدیک هم | underfitting |
مدلِ بزرگتر، epoch بیشتر، lr بهتر |
| آموزش بالا، اعتبارسنجی صاف | overfitting ملایم |
قابلِ تحمل؛ اگر عدد کافی است، ادامه بده |
| آموزش بالا، اعتبارسنجی پایینرونده | overfitting جدی |
توقفِ زودهنگام، regularization، داده بیشتر |
| هر دو نوسانِ شدید | مشکلِ lr یا داده |
فصلِ ۸ ترمِ ۱، و فصلِ ۴ همین ترم |
✅ چک کن: قبل از هر تفسیری، مطمئن شو
model.eval()در تابعِ ارزیابی هست. بدونش عددِ اعتبارسنجی مصنوعاً بد میشود و توoverfittingی میبینی که وجود ندارد. درaccuracyبالا، آن دکوراتورِ@torch.no_grad()وmodel.eval()دقیقاً برای همیناند.
🔧 اگر کار نکرد: دقتِ اعتبارسنجی بالاتر از آموزش است — عجیب نیست اگر
Dropoutداشته باشی (چون در آموزش فعال است و کار را سختتر میکند)، ولی اگر نداری، احتمالاً دو مجموعه را جابهجا کردهای. دقت روی ۰٫۱ گیر کرده — با ده کلاس یعنی مدل دارد حدس میزند؛ سراغِ چکلیستِ فصلِ ۸ ترمِ ۱ برو.
🤖 از دستیارت بپرس: «چه چیزهایی جز اندازهٔ مدل روی
overfittingاثر میگذارند؟» فهرستش را بگیر و هر کدام را در همین چارچوب امتحان کن —epochبیشتر،batchکوچکتر،lrمتفاوت. جدولِ خودت را بساز، چون تنها چیزی که واقعاً یاد میگیری همان است که خودت اندازه گرفتهای.
واژههای تازهٔ این فصل#
| کلمه | تلفظ به حروف فارسی | یعنی چه |
|---|---|---|
| validation set | ولیدیشن ست | دادهٔ تصمیمگیری، جدا از آموزش و آزمون |
| capacity | کپسیتی | توانِ مدل در برازشِ الگوهای پیچیده |
| underfitting | آندرفیتینگ | مدل حتی دادهٔ آموزش را هم یاد نگرفته |
| generalization gap | گپِ جنرالیزیشن | فاصلهٔ دقتِ آموزش و اعتبارسنجی |
| learning curve | لرنینگ کِرو | نمودارِ دقت در طولِ آموزش |
تمرینها
اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.
در فصل بعد#
overfitting را دیدی و دو عاملش را سنجیدی. حالا سه ابزار برای مهارش: weight decay، dropout و توقفِ زودهنگام. و آنها را روی دادهای امتحان میکنیم که واقعاً به آنها نیاز دارد — دادهای با برچسبِ نویزی، همان چیزی که دادهٔ واقعی همیشه دارد.
به آخر این فصل رسیدی!
اگر ساختی و جواب داد، این دکمه مال توست.