ژرفا — خودآموز یادگیری عمیق و مهندسی هوش مصنوعی (متوسط)

فصل ۷ از ۸

پیشرفت ترم
۰٪

ترم ۲ · آموزشِ درست: تعمیم و آزمایشِ منظم

آزمایشِ منظم: یک تغییر در هر بار

فصل ۷پیش‌نمایش رایگان

در این فصل چه یاد می‌گیری#

شش فصل، ده‌ها عدد. حالا سؤالی که همهٔ آن‌ها را به چالش می‌کشد: از کجا می‌دانی تغییری که دادی واقعاً بهتر کرد، و نه اینکه فقط شانس آورد؟

در این فصل یک جدولِ آزمایش می‌سازی، همان جدول را با پنج 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 بازه‌های هم‌پوشان نشانهٔ اینکه تفاوت معنادار نیست

تمرین‌ها

اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.

در فصل بعد#

فصلِ آخرِ ترم: همهٔ این‌ها را روی یک مسئلهٔ واقعاً سخت به‌کار می‌بری — همان دادهٔ برچسب‌نویزیِ فصلِ ۲ — و پنج آزمایشِ ثبت‌شده انجام می‌دهی. و این‌بار، برخلافِ جدولِ بالا، تفاوت‌ها واقعی درمی‌آیند.

به آخر این فصل رسیدی!

اگر ساختی و جواب داد، این دکمه مال توست.