در این فصل چه یاد میگیری#
autograd جهت را میدهد. حالا باید قدم برداری — و کلِ یادگیریِ ماشین همین است: تکرارِ «مشتق بگیر، کمی در جهتِ کاهش برو». در این فصل آن حلقه را خودت مینویسی، در پنج خط و بدونِ هیچ optimizerی، و بعد نرخِ یادگیری را آنقدر بالا میبری تا با چشمِ خودت ببینی کجا منفجر میشود.
این فصل عمداً قبل از optimizer.step() میآید. کسی که ترتیب را برعکس یاد بگیرد، تا آخرِ عمر آن یک خط را طلسم میداند.

آخر این فصل میتوانی:
- حلقهٔ
gradient descentرا از پایه بنویسی - بگویی نرخِ یادگیری چه میکند و چرا هم کوچکش بد است هم بزرگش
- واگراییِ ناشی از نرخِ زیاد را تشخیص بدهی
- یک مدلِ خطی را با دست آموزش بدهی
قبل از شروع#
از فصل ۲: backward، .grad، zero_grad، no_grad.
از ریشه ترمِ ۸ فصل ۸: الگوریتمِ gradient descent — همان چیزی که اینجا کد میشود.
📓 نوتبوک: نوتبوک این فصل را در Colab باز کن — همهٔ کدهای این فصل آماده و بهترتیب داخلش هست.
۱. پنج خطی که همهچیز است#
میخواهیم را طوری پیدا کنیم که کمینه شود. جوابش را میدانیم — چهار — و همین خوب است: میخواهیم ببینیم الگوریتم خودش پیدایش میکند یا نه.
import torch
w = torch.tensor(0.0, requires_grad=True)
lr = 0.1
for step in range(5):
loss = (w - 4.0) ** 2 # ۱. جلو: خطا را حساب کن
loss.backward() # ۲. عقب: مشتق را بگیر
with torch.no_grad():
w -= lr * w.grad # ۳. قدم: در جهتِ کاهش برو
w.grad.zero_() # ۴. پاک کن، برای دورِ بعد
print(f"قدم {step + 1}: w = {w.item():.4f} | loss = {loss.item():.4f}")
قدم 1: w = 0.8000 | loss = 16.0000
قدم 2: w = 1.4400 | loss = 10.2400
قدم 3: w = 1.9520 | loss = 6.5536
قدم 4: w = 2.3616 | loss = 4.1943
قدم 5: w = 2.6893 | loss = 2.6844
w از صفر دارد به سمتِ چهار میرود و loss یکنواخت پایین میآید.
هر چهار خطِ داخلِ حلقه لازماند و هیچکدام تزیینی نیست:
lossرا حساب کن — و همینجا گراف ساخته میشود.backward— گرادیان درw.gradمینشیند.- قدم داخلِ
no_grad— چون خودِ بهروزرسانی نباید بخشی از گرافِ بعدی شود. zero_grad— وگرنه گرادیانها انباشته میشوند (فصلِ ۲ بخشِ ۳).
علامتِ منفی هم دلیل دارد: گرادیان جهتِ بیشترین افزایش را میدهد و ما کاهش میخواهیم، پس خلافش میرویم. اسمِ الگوریتم هم از همین میآید: descent، یعنی سرازیری.
🌱 ریشهاش کجاست: این حلقه دقیقاً همان الگوریتمی است که ریشه، ترم ۸ فصل ۸ با کاغذ و ماشینحساب میسازد، و اینکه گرادیان جهتِ بیشترین شیب است در فصل ۷ همان ترم ثابت میشود. اگر آن دو فصل را دیده باشی، این پنج خط برایت هیچ ابهامی ندارد.
۲. نرخِ یادگیری: تنها عددی که واقعاً مهم است#
for lr in [0.01, 0.1, 0.9, 1.1]:
w = torch.tensor(0.0, requires_grad=True)
trace = []
for step in range(20):
loss = (w - 4.0) ** 2
loss.backward()
with torch.no_grad():
w -= lr * w.grad
w.grad.zero_()
if step < 4:
trace.append(round(w.item(), 2))
print(f"lr={lr}: چهار قدم اول {trace} ... بعد از ۲۰ قدم w={w.item():.2f}")
lr=0.01: چهار قدم اول [0.08, 0.16, 0.24, 0.31] ... بعد از ۲۰ قدم w=1.33
lr=0.1: چهار قدم اول [0.8, 1.44, 1.95, 2.36] ... بعد از ۲۰ قدم w=3.95
lr=0.9: چهار قدم اول [7.2, 1.44, 6.05, 2.36] ... بعد از ۲۰ قدم w=3.95
lr=1.1: چهار قدم اول [8.8, -1.76, 10.91, -4.29] ... بعد از ۲۰ قدم w=-149.35
چهار رفتارِ کاملاً متفاوت، از یک عدد.
0.01— خیلی کوچک. درست حرکت میکند، ولی بعد از بیست قدم هنوز در راه است. با داده و مدلِ واقعی یعنی ساعتها انتظار.0.1— درست. یکنواخت و سریع به جواب میرسد.0.9— پُرنوسان، ولی همگرا. به عددهایش دقت کن: از ۰ به ۷٫۲ میپرد (از جواب رد میشود)، بعد به ۱٫۴۴ برمیگردد، بعد ۶٫۰۵. از دو طرف حولِ جواب نوسان میکند و آرامآرام نزدیک میشود.1.1— واگرا. هر پرش از قبلی بزرگتر است و به منفیِ ۱۴۹ میرسد. با ادامه دادن بهinfو بعدnanمیرسد.
و یک چیزِ ظریف: 0.1 و 0.9 بعد از بیست قدم دقیقاً به یک عدد رسیدند. تصادفی نیست، و برای این تابعِ خاص میشود دقیقاً نشانش داد. گرادیانِ برابرِ است، پس هر قدم این است:
یعنی فاصله تا جواب در هر قدم در عددِ ثابتِ ضرب میشود — و سرنوشتِ کلِ ماجرا فقط به قدرمطلقِ همان عدد بستگی دارد:
| نرخ | ضریب | قدرمطلق | چه میشود |
|---|---|---|---|
| ۰٫۰۱ | ۰٫۹۸ | ۰٫۹۸ | خیلی آرام کوچک میشود |
| ۰٫۱ | ۰٫۸ | ۰٫۸ | یکنواخت به جواب میرسد |
| ۰٫۹ | ۰٫۸- | ۰٫۸ | همان سرعت، ولی علامت هر بار عوض میشود |
| ۱٫۱ | ۱٫۲- | ۱٫۲ | فاصله هر قدم بزرگتر میشود — واگرایی |
دو ردیفِ وسط قدرمطلقِ یکسانی دارند و برای همین بعد از بیست قدم به یک عدد رسیدند — یکی آرام میخزد و دیگری از دو طرف نوسان میکند، ولی هر دو با یک سرعت نزدیک میشوند. و مرزِ واگرایی همانجایی است که قدرمطلق از ۱ رد میشود، یعنی برای این تابع.
پس «سریعتر همگرا شد» تنها معیارِ خوبی نیست؛ مسیرِ رسیدن هم مهم است، چون در مسائلِ واقعی آن نوسان میتواند به بیثباتی تبدیل شود.
⚠️ مواظب باش: آن عددِ ۱ مالِ همین تابع است، نه یک ثابتِ جهانی. مرزِ واگرایی به شکلِ تابع بستگی دارد — برای تابعی با انحنای تندتر، همان نرخ میتواند فاجعه باشد. هیچوقت نرخی را که روی یک مسئله جواب داده، بدونِ آزمایش به مسئلهٔ دیگری نبر.
📏 اندازه بگیر: نرخِ یادگیری تنها
hyperparameterی است که اگر یکی را باید تنظیم کنی، همان است. قاعدهٔ عملی: از یک عدد شروع کن و ده برابر و یکدهمش را هم امتحان کن، نه ۵ درصد کم و زیاد. اگرlossبالا میرود یاnanمیشود، اولین کار نصف کردنِ نرخ است. ترمِ ۲ فصلِ ۴ روشِ سیستماتیکِ پیدا کردنش را میدهد.
۳. حالا با داده: یک مدلِ خطیِ واقعی#
X = torch.tensor([[1.0], [2.0], [3.0], [4.0]])
Y = torch.tensor([[3.0], [5.0], [7.0], [9.0]]) # رابطهٔ واقعی: y = 2x + 1
w = torch.tensor(0.0, requires_grad=True)
b = torch.tensor(0.0, requires_grad=True)
lr = 0.05
for step in range(200):
pred = w * X + b
loss = ((pred - Y) ** 2).mean()
loss.backward()
with torch.no_grad():
w -= lr * w.grad
b -= lr * b.grad
w.grad.zero_()
b.grad.zero_()
if step in (0, 9, 49, 199):
print(f"قدم {step + 1:>3}: w={w.item():.3f} b={b.item():.3f} loss={loss.item():.5f}")
قدم 1: w=1.750 b=0.600 loss=41.00000
قدم 10: w=2.084 b=0.754 loss=0.01046
قدم 50: w=2.046 b=0.865 loss=0.00313
قدم 200: w=2.005 b=0.986 loss=0.00003
رابطهٔ واقعی بود و مدل به و رسید. هیچجا به آن نگفتیم جواب چیست؛ فقط چهار نقطه دادیم و گفتیم خطا را کم کن.
و به سرعتِ کارش نگاه کن: loss از ۴۱ به ۰٫۰۱ در ده قدم رسید، و بقیهٔ ۱۹۰ قدم صرفِ صیقل دادنِ همان جواب شد. این الگو تقریباً همیشه تکرار میشود — بیشترِ بهبود در چند درصدِ اولِ آموزش اتفاق میافتد.
✅ چک کن:
wباید به ۲ وbبه ۱ نزدیک شود وlossیکنواخت پایین بیاید. اگرlossدر جایی بالا رفت، یک از این سه است: نرخ زیادی بزرگ است،zero_gradرا جا انداختهای، یا بهروزرسانی را بیرونِno_gradنوشتهای. هر سهشان نشانهٔ یکسانی دارند و علتِ متفاوتی — به ترتیب چکشان کن.
۴. آنچه اتفاق میافتد اگر zero_grad نباشد#
w = torch.tensor(0.0, requires_grad=True)
lr = 0.1
for step in range(3):
loss = (w - 4.0) ** 2
loss.backward()
with torch.no_grad():
w -= lr * w.grad
print(f"قدم {step + 1} (بدون zero_grad): w = {w.item():.4f} | grad = {w.grad.item():.4f}")
قدم 1 (بدون zero_grad): w = 0.8000 | grad = -8.0000
قدم 2 (بدون zero_grad): w = 2.2400 | grad = -14.4000
قدم 3 (بدون zero_grad): w = 4.0320 | grad = -17.9200
بخشِ ۱ را با همین سه قدم مقایسه کن: آنجا w بعد از سه قدم ۱٫۹۵ بود، اینجا ۴٫۰۳.
و اینجا نکتهٔ خطرناک: در این مثالِ خاص، جوابِ غلط تصادفاً به جوابِ درست نزدیکتر شد. با ادامهٔ حلقه از آن رد میشود و بیثبات میشود، ولی اگر فقط سه قدم میدیدی، فکر میکردی همهچیز بهتر هم شده.
درسِ واقعی این است: باگی که نتیجهاش گاهی بهتر بهنظر میرسد، از باگی که خطا میدهد خیلی خطرناکتر است. و همین قاعدهٔ سومِ این دوره است — «وقتی کار کرد، اول شک کن».
۵. کجای این تصویر بعداً عوض میشود#
| قطعه | الان | در فصلهای بعد |
|---|---|---|
| پارامترها | w و b دستی |
nn.Module (فصلِ ۴) |
| محاسبهٔ خطا | فرمولِ دستی | nn.MSELoss (فصلِ ۶) |
قدم و zero_grad |
چهار خطِ دستی | optimizer.step() (فصلِ ۶) |
| داده | چهار نقطه در حافظه | DataLoader (فصلِ ۷) |
| ایده | گرادیان بگیر، قدم بردار | دقیقاً همان |
آن ردیفِ آخر پیامِ کلِ فصل است. هر چیزی که در ادامه یاد میگیری، فقط بستهبندیِ راحتترِ همین حلقه است. وقتی در فصلِ ۶ بنویسی optimizer.step()، دقیقاً میدانی پشتش چه اتفاقی میافتد — و همان دانستن، تفاوتِ کسی است که میتواند اشکالزدایی کند با کسی که فقط کد را کپی میکند.
🔧 اگر کار نکرد:
a leaf Variable that requires grad is being used in an in-place operationیعنیw -= ...را بیرونِno_gradنوشتهای.lossبرابرِnanشد یعنی نرخ زیادی بزرگ است — نصفش کن.w.gradبرابرِNoneاست یعنیbackwardهنوز صدا زده نشده یاrequires_gradجا مانده.
🤖 از دستیارت بپرس: «برای تابعِ چه نرخِ یادگیریای باعثِ واگرایی میشود و چرا دقیقاً همان عدد؟» جوابش را با آزمایش بسنج: نرخ را از ۰٫۹ تا ۱٫۱ با گامهای کوچک بالا ببر و مرزِ دقیق را خودت پیدا کن. بعد بپرس آیا این مرز به شکلِ تابع بستگی دارد یا عددی جهانی است — و جوابش را با یک تابعِ دیگر مثلِ امتحان کن، نه اینکه باور کنی.
واژههای تازهٔ این فصل#
| کلمه | تلفظ به حروف فارسی | یعنی چه |
|---|---|---|
| gradient descent | گرِیدینت دیسنت | تکرارِ «مشتق بگیر، در جهتِ کاهش قدم بردار» |
| learning rate | لرنینگ ریت | اندازهٔ هر قدم |
| convergence | کانورجنس | نزدیک شدن به جواب و ماندن آنجا |
| divergence | دایورجنس | دور شدن از جواب، تا inf و nan |
| step | استپ | یک بار بهروزرسانیِ پارامترها |
تمرینها
اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.
در فصل بعد#
w و b دستی برای یک خط قابلِ تحمل است. برای شبکهای با هزاران پارامتر نه. فصلِ بعد nn.Module را میآورد: راهی برای بستهبندیِ پارامترها و محاسبه در یک شیء — و میبینی که اولین شبکهٔ عصبیات دقیقاً همان است، فقط با نامِ دیگر.
به آخر این فصل رسیدی!
اگر ساختی و جواب داد، این دکمه مال توست.