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

فصل ۴ از ۹

پیشرفت ترم
۰٪

ترم ۱ · مکانیزمِ یادگیری: PyTorch از پایه

اولین شبکه: از یک خط تا یک لایه

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

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

فصلِ قبل دو پارامترِ w و b را دستی نگه داشتی. برای یک خط قابلِ تحمل است؛ برای شبکه‌ای با هزاران پارامتر نه. در این فصل nn.Module را می‌بینی — ظرفی که پارامترها و محاسبه را با هم نگه می‌دارد — و اولین شبکهٔ چندلایه‌ات را می‌سازی.

و یک چیزِ آرامش‌بخش کشف می‌کنی: nn.Linear هیچ جادویی ندارد و دقیقاً همان فصلِ قبل است. خودت با دست ثابتش می‌کنی.

یک جعبهٔ برشِ آموزشی که داخلش چند ردیف اتصالِ ساده دیده می‌شود

آخر این فصل می‌توانی:

  • یک nn.Module بنویسی و forwardش را پر کنی
  • بگویی nn.Linear دقیقاً چه حسابی می‌کند
  • پارامترهای مدلت را بشماری و شکلشان را توضیح بدهی
  • خطای شکل را بخوانی و از رویش بفهمی کجا اشتباه شده

قبل از شروع#

از فصل ۳: حلقهٔ gradient descent و اینکه پارامتر چیست.

از سرنخ ترمِ ۵ فصل ۲: class، __init__، self — اینجا برای اولین بار واقعاً به‌کارشان می‌بری.

📓 نوت‌بوک: نوت‌بوک این فصل را در Colab باز کن — همهٔ کدهای این فصل آماده و به‌ترتیب داخلش هست.

۱. nn.Linear چیست#

import torch
import torch.nn as nn

torch.manual_seed(0)
layer = nn.Linear(in_features=3, out_features=1)

print("weight:", layer.weight)
print("bias  :", layer.bias)
print("shapes:", tuple(layer.weight.shape), tuple(layer.bias.shape))
weight: Parameter containing:
tensor([[-0.0043,  0.3097, -0.4752]], requires_grad=True)
bias  : Parameter containing:
tensor([-0.4249], requires_grad=True)
shapes: (1, 3) (1,)

سه چیز را ببین:

  • وزن‌ها از قبل ساخته شده‌اند، با مقادیرِ تصادفی. تو نساختی‌شان؛ nn.Linear در __init__ خودش ساخت.
  • requires_grad=True خودکار است. هر چیزی که Parameter باشد، ردیابی می‌شود.
  • شکلِ weight برابرِ (out, in) است، نه (in, out). این ترتیب برعکسِ شهودِ اکثرِ آدم‌هاست و منبعِ خطای شکل است؛ الان حفظش کن تا بعداً وقت هدر ندهی.

۲. با دست ثابتش کن#

x = torch.tensor([[1.0, 2.0, 3.0]])

print("خروجی لایه   :", layer(x))
print("دستی (x@W.T+b):", x @ layer.weight.T + layer.bias)
خروجی لایه   : tensor([[-1.2353]], grad_fn=<AddmmBackward0>)
دستی (x@W.T+b): tensor([[-1.2353]], grad_fn=<AddBackward0>)

چک کن: دو خروجی باید تا چهار رقمِ اعشار یکی باشند. اگر نبودند، weight.T را جا انداخته‌ای — و همان ترتیبِ (out, in) است که چند خط بالاتر گفتیم حفظش کن.

دقیقاً یک عدد. nn.Linear هیچ کارِ دیگری نمی‌کند: یک ضربِ ماتریسی و یک جمع — همان فصلِ ۳، فقط با چند ورودی به‌جای یکی.

آن grad_fn متفاوت (AddmmBackward0 در برابر AddBackward0) فقط یعنی PyTorch نسخهٔ بهینه‌ترِ «ضرب و جمع در یک عملیات» را به‌کار برده. نتیجه یکی است، مسیرِ محاسبه بهینه‌تر.

🌱 ریشه‌اش کجاست: x @ W.T یک ضربِ ماتریسی است، و اینکه چرا این عمل این‌طور تعریف شده و چه معنایی دارد، موضوعِ ریشه، ترم ۷ فصل ۵ است: ترکیبِ دو تبدیل. یک لایهٔ شبکه دقیقاً یک تبدیلِ خطی است، و شبکهٔ چندلایه ترکیبِ چندتاست — که در فصلِ بعد می‌بینی چرا به‌تنهایی کافی نیست.

۳. اولین nn.Module خودت#

class TinyNet(nn.Module):
    def __init__(self, n_features):
        super().__init__()
        self.hidden = nn.Linear(n_features, 8)
        self.out = nn.Linear(8, 1)

    def forward(self, x):
        return self.out(torch.relu(self.hidden(x)))


torch.manual_seed(0)
net = TinyNet(3)
print(net)
TinyNet(
  (hidden): Linear(in_features=3, out_features=8, bias=True)
  (out): Linear(in_features=8, out_features=1, bias=True)
)

چهار قاعده که در هر nn.Moduleی که تا آخرِ دوره می‌نویسی تکرار می‌شوند:

  • super().__init__() اجباری است و اولین خط. بدونش PyTorch نمی‌تواند پارامترها را ثبت کند و مدلت بی‌صدا هیچ پارامتری نخواهد داشت.
  • لایه‌ها در __init__ ساخته می‌شوند، نه در forward. ساختنشان در forward یعنی هر بار وزن‌های تازهٔ تصادفی — و مدل هرگز چیزی یاد نمی‌گیرد.
  • forward مسیرِ داده را می‌گوید، از ورودی تا خروجی.
  • net(x) را صدا می‌زنی، نه net.forward(x). ظاهراً یکی‌اند، ولی فراخوانیِ مستقیمِ forward قلاب‌های داخلیِ PyTorch را دور می‌زند و بعضی قابلیت‌ها را می‌شکند.

آن torch.relu را فعلاً بپذیر؛ فصلِ بعد کاملاً دربارهٔ همان است و آنجا می‌بینی که بدونش این شبکهٔ دولایه هیچ توانایی‌ای بیشتر از یک لایه ندارد.

۴. شمردنِ پارامترها#

total = sum(p.numel() for p in net.parameters())
print("تعداد کل پارامترها:", total)

for name, p in net.named_parameters():
    print(f"{name:>14}: shape={tuple(p.shape)} -> {p.numel()}")
تعداد کل پارامترها: 41
 hidden.weight: shape=(8, 3) -> 24
   hidden.bias: shape=(8,) -> 8
    out.weight: shape=(1, 8) -> 8
      out.bias: shape=(1,) -> 1

. هیچ پارامترِ پنهانی وجود ندارد؛ هر عددی که این شبکه یاد می‌گیرد در همین چهار جاست.

این تابعِ سه‌خطی را حفظ کن — در کلِ دوره بارها به‌کارش می‌بری. تعدادِ پارامتر اولین چیزی است که دربارهٔ ظرفیتِ یک مدل به تو می‌گوید، و در ترمِ ۲ می‌بینی که رابطهٔ مستقیمی با overfitting دارد.

📏 اندازه بگیر: قبل از آموزش، همیشه پارامترها را بشمار. اگر عددی که می‌بینی با انتظارت نمی‌خواند، مدلت آنی نیست که فکر می‌کنی — معمولاً یک لایه را در forward صدا نزده‌ای یا super().__init__() جا مانده. بشمردنش پنج ثانیه است و ساعت‌ها اشکال‌زدایی را حذف می‌کند.

۵. batch، و خطای شکل#

batch = torch.randn(5, 3)
out = net(batch)
print("ورودی:", tuple(batch.shape), "-> خروجی:", tuple(out.shape))

try:
    net(torch.randn(5, 4))
except RuntimeError as error:
    print("RuntimeError:", str(error)[:78])
ورودی: (5, 3) -> خروجی: (5, 1)
RuntimeError: mat1 and mat2 shapes cannot be multiplied (5x4 and 3x8)

پنج نمونه با هم رفتند و پنج جواب برگشت. بُعدِ اول همیشه batch است و لایه‌ها به آن کار ندارند — این قرارداد در کلِ PyTorch ثابت است.

و آن خطا را حرف‌به‌حرف بخوان: (5x4 and 3x8). عددهای وسط باید بخوانند — ۴ با ۳ نمی‌خواند. این پیام دقیقاً می‌گوید کجا اشتباه است، و اگر یاد بگیری بخوانی‌اش، نودرصدِ خطاهای شکل را در ده ثانیه حل می‌کنی.

🔧 اگر کار نکرد: سه خطای رایج و علتشان. mat1 and mat2 shapes cannot be multiplied: تعدادِ ویژگی‌های ورودی با in_features نمی‌خواند. expected scalar type Float but found Long: ورودی int64 است؛ .float() بزن (فصلِ ۱ بخشِ ۲). مدل پارامتری ندارد: super().__init__() را جا انداخته‌ای، یا لایه‌ها را در یک list معمولی گذاشته‌ای به‌جای nn.ModuleList.

🤖 از دستیارت بپرس: «چرا nn.Linear وزن را به شکلِ (out_features, in_features) نگه می‌دارد و نه برعکس؟» و بعد جوابش را با کد بسنج: layer.weight.shape را ببین و خودت x @ W.T را حساب کن. اگر دستیارت توضیحِ قانع‌کننده‌ای نداد، کد جوابِ قطعی را می‌دهد — و همین عادت، تفاوتِ یادگیری از دستیار با باور کردنِ دستیار است.

واژه‌های تازهٔ این فصل#

کلمه تلفظ به حروف فارسی یعنی چه
nn.Module ان‌ان ماژول کلاسِ پایهٔ هر مدلِ PyTorch
forward فوروارد مسیرِ داده از ورودی تا خروجی
parameter پارامتر tensorی که مدل یادش می‌گیرد
weight / bias وِیت / بایاس ضریب‌ها و مقدارِ ثابتِ یک لایه
batch بچ چند نمونه که با هم پردازش می‌شوند
in_features / out_features این/اوت فیچرز تعدادِ ورودی و خروجیِ یک لایه

تمرین‌ها

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

در فصل بعد#

شبکه‌ات دو لایه دارد، ولی هنوز یک راز باقی است: آن torch.relu وسطِ forward چه می‌کرد؟ فصلِ بعد نشان می‌دهد که بدونش، صد لایه هم دقیقاً به‌اندازهٔ یک لایه توانایی دارند — و آن را با کد ثابت می‌کند، نه با ادعا.

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

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