در این فصل چه یاد میگیری#
شش فصل، دهها عدد. حالا سؤالی که همهٔ آنها را به چالش میکشد: از کجا میدانی تغییری که دادی واقعاً بهتر کرد، و نه اینکه فقط شانس آورد؟
در این فصل یک جدولِ آزمایش میسازی، همان جدول را با پنج seed تکرار میکنی، و نتیجهای میگیری که برای بیشترِ آزمایشهای تا اینجا ناخوشایند است: تفاوتهایی که «واضح» بهنظر میرسیدند، از نوسانِ تصادف کوچکترند.

آخر این فصل میتوانی:
- جدولِ آزمایش بسازی و یک تغییر در هر بار بدهی
- تفاوتِ واقعی را از نوسانِ تصادف جدا کنی
ablationرا بهکار ببری- بگویی کِی حق داری بگویی «این بهتر است»
قبل از شروع#
از فصل ۶: seed و اینکه بدونش نتیجه تکرار نمیشود.
از فصلهای ۲ تا ۵: همهٔ اهرمهایی که سنجیدیم.
📓 نوتبوک: نوتبوک این فصل را در Colab باز کن — همهٔ کدهای این فصل آماده و بهترتیب داخلش هست.
۱. جدولِ آزمایش: چهار پیکربندی#
import torch
import torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader
loss_fn = nn.CrossEntropyLoss()
@torch.no_grad()
def accuracy(model, X, y):
model.eval()
return float((model(X).argmax(1) == y).float().mean())
def build(dropout=0.0):
layers = [nn.Linear(64, 128), nn.ReLU()]
if dropout:
layers.append(nn.Dropout(dropout))
return nn.Sequential(*layers, nn.Linear(128, 10))
def run(seed=0, dropout=0.0, sched=False, epochs=30, subset=200):
torch.manual_seed(seed)
model = build(dropout)
loader = DataLoader(TensorDataset(Xtr[:subset], ytr[:subset]), batch_size=32, shuffle=True)
opt = torch.optim.Adam(model.parameters(), lr=0.01)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=epochs) if sched else None
for _ in range(epochs):
model.train()
for xb, yb in loader:
opt.zero_grad()
loss_fn(model(xb), yb).backward()
opt.step()
if scheduler:
scheduler.step()
return accuracy(model, Xva, yva)
EXPERIMENTS = [
("پایه", dict()),
("+ scheduler", dict(sched=True)),
("+ dropout 0.3", dict(dropout=0.3)),
("+ هر دو", dict(sched=True, dropout=0.3)),
]
for name, options in EXPERIMENTS:
print(f"{name:>14}: {run(seed=0, **options):.4f}")
پایه: 0.9499
+ scheduler: 0.9443
+ dropout 0.3: 0.9499
+ هر دو: 0.9443
نگاهِ اول: «scheduler بدتر میکند، dropout بیاثر است.» و اگر همینجا بایستی، همین را در گزارشت مینویسی.
سه قاعده در همین جدولِ کوچک رعایت شده و هر سه لازماند:
- یک تغییر در هر بار. ردیفِ دوم فقط
schedulerرا عوض میکند، نه چیزِ دیگری. اگر دو چیز را با هم عوض کنی، نمیدانی کدامش اثر داشت. - همان
seedبرای همه. وگرنه بخشی از تفاوت از تصادفِ وزنهای اولیه است. - همان تابعِ اجرا.
runیکی است و فقطparameterهایش عوض میشوند — کدِ کپیشده جایی است که تفاوتِ ناخواسته پنهان میشود.
۲. حالا همان جدول، با پنج seed#
for name, options in EXPERIMENTS:
values = [run(seed=s, **options) for s in (0, 1, 2, 3, 4)]
print(f"{name:>14}: میانگین={sum(values) / len(values):.4f} | "
f"بازه={min(values):.4f}..{max(values):.4f}")
پایه: میانگین=0.9454 | بازه=0.9415..0.9499
+ scheduler: میانگین=0.9443 | بازه=0.9415..0.9471
+ dropout 0.3: میانگین=0.9421 | بازه=0.9331..0.9499
+ هر دو: میانگین=0.9404 | بازه=0.9304..0.9471
و اینجا کلِ جدولِ بخشِ ۱ فرو میریزد.
به بازهها نگاه کن: هر چهار بازه با هم همپوشانی دارند. میانگینها بینِ ۰٫۹۴۰۴ و ۰٫۹۴۵۴ فشردهاند — یعنی نیم درصد — در حالی که پهنترین بازهٔ داخلیِ یک ردیف (dropout، از ۰٫۹۳۳۱ تا ۰٫۹۴۹۹) بیش از سه برابرِ آن است.
نتیجهٔ صادقانه: هیچکدام از این سه تغییر، روی این مسئله، قابلِ تشخیص از تصادف نیست.
📏 اندازه بگیر: این مهمترین جملهٔ کلِ ترم است: تفاوتی که از نوسانِ بینِ
seedها کوچکتر باشد، تفاوت نیست. و توجه کن که جدولِ بخشِ ۱ — با یکseed— دقیقاً برعکس را میگفت. یک اجرا، هر چقدر هم دقیق، فقط یک نمونه است. حداقل سهseed، و برای تصمیمِ مهم پنج تا.
🌱 ریشهاش کجاست: آنچه اینجا «بازه» مینامیم، شکلِ خامِ چیزی است که در آمار پراکندگی نام دارد — و ریشه، ترم ۶ فصل ۶ — میانگین، میانه، پراکندگی دقیقاً همین را میسازد، با همین پیام: میانگین بدونِ پراکندگی گمراهکننده است. ما اینجا از سادهترین معیارِ پراکندگی (کمینه و بیشینه) استفاده کردیم چون با پنج عدد بیشترش معنا ندارد؛ در ترمِ ۵ فصلِ ۶ نسخهٔ جدیترش را میسازیم که با نمونهٔ بزرگتر کار میکند.
۳. چه چیزی جای «بهتر شد» را میگیرد#
| بهجای این ننویس | این را بنویس |
|---|---|
«dropout بهتر کرد» |
«۰٫۹۴۲ در برابرِ ۰٫۹۴۵، با بازهٔ همپوشان روی ۵ seed — تفاوتی دیده نشد» |
| «دقت ۹۵ درصد» | «۹۴٫۵ درصد میانگین، بینِ ۹۴٫۲ و ۹۵٫۰ روی ۵ seed» |
| «مدلِ بزرگتر بهتر است» | «برای این اندازهٔ داده، تا ۲۵۶ نورون بهتر شد؛ بالاتر نسنجیدیم» |
«scheduler کمک نمیکند» |
«روی این مسئله و این بودجه، نه — روی ۳۰ epoch و دادهٔ تمیز» |
آن ستونِ راست طولانیتر است و همین درست است. هر ادعای کوتاه، شرطی را حذف کرده که ممکن است تمامِ ماجرا باشد.
۴. ablation: از بالا به پایین، نه از پایین به بالا#
جدولِ بخشِ ۱ افزودنی بود: از پایه شروع کرد و چیز اضافه کرد. راهِ دیگر برعکس است — از بهترین پیکربندی شروع کن و هر بار یک چیز را بردار.
| روش | چه سؤالی جواب میدهد | کِی بهتر است |
|---|---|---|
| افزودنی | «اگر این را اضافه کنم چه میشود؟» | وقتی از صفر شروع میکنی |
ablation |
«اگر این را بردارم چه از دست میدهم؟» | وقتی سامانهای داری که کار میکند |
و این دو همیشه یک جواب نمیدهند — چون اجزا با هم برهمکنش دارند. چیزی که بهتنهایی بیاثر است میتواند در کنارِ چیزِ دیگری حیاتی باشد، و برعکس.
ablation استانداردِ گزارشنویسیِ پژوهش است، دقیقاً به این دلیل: نشان میدهد هر جزءِ سامانهات واقعاً چه سهمی دارد — و کدام جزء را میشود بیضرر حذف کرد.
۵. دفترچهٔ آزمایش#
هر آزمایش باید اینها را ثبت کند، وگرنه سه هفته بعد بیفایده است:
record = {
"name": "dropout-0.3",
"changed": "dropout: 0.0 -> 0.3", # فقط یک چیز
"seeds": [0, 1, 2, 3, 4],
"val_acc": [0.9499, 0.9443, 0.9387, 0.9331, 0.9443], # به ترتیبِ همان seedها
"mean": 0.9421,
"range": [0.9331, 0.9499],
"config": {"lr": 0.01, "epochs": 30, "batch": 32, "subset": 200},
"data_fingerprint": "…",
"conclusion": "قابل تشخیص از نوسان نیست",
}
فیلدِ changed را جدی بگیر. اگر نتوانستی در یک سطر بنویسی چه چیزی عوض شده، بیش از یک چیز عوض کردهای — و آزمایشت قابلِ تفسیر نیست.
و conclusion را همان لحظه بنویس، نه بعداً. سه هفته بعد فقط عددها را میبینی و یادت نمیآید که آنوقت به چه نتیجهای رسیده بودی و چرا.
⚠️ مواظب باش: هرچه پیکربندیِ بیشتری روی مجموعهٔ اعتبارسنجی امتحان کنی، آن عدد خوشبینانهتر میشود — حتی اگر همهچیز را درست انجام داده باشی. با بیست آزمایش، برنده تا حدی «برندهٔ شانس» است. راهِ درست: تصمیمها را با اعتبارسنجی بگیر، و عددِ نهایی را یک بار روی مجموعهٔ آزمون گزارش کن — همان مجموعهای که از فصلِ ۱ دستنخورده کنار گذاشتیم و فصلِ بعد یک بار بازش میکنیم.
۶. «وقتی کار کرد، اول شک کن»#
قاعدهٔ سومِ این دوره، و اینجا معنای عملیاش:
| نشانه | اول این را چک کن |
|---|---|
| دقت ناگهان خیلی بالا رفت | نشتی — آیا دادهٔ آزمون به آموزش راه پیدا کرده؟ |
| مدل از انتظارِ منطقی بهتر است | برچسبها — آیا ویژگیای داری که خودِ جواب را لو میدهد؟ |
| تغییرِ کوچک، اثرِ بزرگ | seed — با سه seed دیگر تکرار کن |
| نتیجه با گزارشهای دیگر نمیخواند | معیار — آیا همان چیزی را میسنجی که آنها میسنجند؟ |
✅ چک کن: سادهترین آزمونِ نشتی: برچسبها را تصادفی بُر بزن و دوباره آموزش بده. اگر دقتِ اعتبارسنجی همچنان بهطورِ معناداری بالاتر از حدسِ تصادفی است، جایی نشتی داری — چون با برچسبِ تصادفی هیچ الگوی واقعیای برای یادگرفتن نمانده. این آزمون سه خط کد است و ارزشش را بارها ثابت کرده.
🔧 اگر کار نکرد: آزمایشهایت تکرارپذیر نیستند — فصلِ ۶. همهٔ ردیفها یکی درمیآیند — احتمالاً
parameterرا واقعاً به تابع ندادهای؛ یکی از ردیفها را عمداً خراب کن و ببین عدد عوض میشود. اجرای جدول خیلی طول میکشد — تعدادِepochرا برای مرحلهٔ کاوش کم کن و فقط برای نامزدهای نهایی کامل اجرا کن.
🤖 از دستیارت بپرس: جدولِ بخشِ ۲ را به دستیارت بده و بپرس: «از این جدول چه نتیجهای میشود گرفت؟» اگر گفت
schedulerبدتر است یاdropoutبهتر، جوابش غلط است — بازهها همپوشانی دارند. این تمرینِ خوبی است برای اینکه ببینی دستیارها هم مثلِ آدمها به تفاوتِ کوچک معنا میدهند و چرا خواندنِ درستِ عدمقطعیت، مهارتی است که خودت باید داشته باشی.
واژههای تازهٔ این فصل#
| کلمه | تلفظ به حروف فارسی | یعنی چه |
|---|---|---|
| ablation | ابلیشن | برداشتنِ یک جزء برای سنجشِ سهمش |
| variance | واریانس | نوسانِ نتیجه بینِ اجراهای مشابه |
| controlled experiment | کنترلد اکسپریمنت | فقط یک چیز عوض میشود |
| overlapping ranges | بازههای همپوشان | نشانهٔ اینکه تفاوت معنادار نیست |
تمرینها
اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.
در فصل بعد#
فصلِ آخرِ ترم: همهٔ اینها را روی یک مسئلهٔ واقعاً سخت بهکار میبری — همان دادهٔ برچسبنویزیِ فصلِ ۲ — و پنج آزمایشِ ثبتشده انجام میدهی. و اینبار، برخلافِ جدولِ بالا، تفاوتها واقعی درمیآیند.
به آخر این فصل رسیدی!
اگر ساختی و جواب داد، این دکمه مال توست.