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

فصل ۱ از ۸

پیشرفت ترم
۰٪

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

`overfitting` را با چشم ببین

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

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

ترمِ ۱ با یک نتیجهٔ ناخوشایند تمام شد: شبکه از مدلِ خطیِ سرنخ بدتر درآمد، و در جدولِ آخر دیدی که از 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 و توقفِ زودهنگام. و آن‌ها را روی داده‌ای امتحان می‌کنیم که واقعاً به آن‌ها نیاز دارد — داده‌ای با برچسبِ نویزی، همان چیزی که دادهٔ واقعی همیشه دارد.

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

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