در این فصل چه یاد میگیری#
هیچ مفهومِ تازهای نیست. کاری که میکنی این است: هشت فصلِ این ترم را روی دادهٔ واقعیِ سرنخ بهکار میبری — همان جدولِ قیمتِ لپتاپ — و شبکهات را در برابرِ همان خطِ پایهای که در سرنخ ترمِ ۳ گرفتی میسنجی.
و نتیجهای میگیری که احتمالاً انتظارش را نداری. آن نتیجه، نه شکستِ این فصل، بلکه کلِ دلیلِ وجودِ ترمِ ۲ است.

آخر این فصل میتوانی:
- دادهٔ جدولیِ واقعی را برای یک شبکه آماده کنی
- یک آموزشِ کامل با ثبتِ منحنیِ آموزش و آزمون اجرا کنی
- شبکه را در برابرِ خطِ پایه و مدلِ ساده منصفانه بسنجی
- علامتِ
overfittingرا در عددها ببینی
قبل از شروع#
از کلِ ترمِ ۱. این فصل هیچ چیزِ تازهای معرفی نمیکند.
از سرنخ ترمِ ۳: train_test_split، خطِ پایه، MAE — و عددهایی که آنجا گرفتی.
📓 نوتبوک: نوتبوک این فصل را در Colab باز کن — همهٔ کدهای این فصل آماده و بهترتیب داخلش هست.
۱. دادهٔ جدولی برای یک شبکه#
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader
from sklearn.model_selection import train_test_split
df = pd.read_csv("laptops_clean.csv")
print("خانههای خالی:", df.isna().sum().to_dict())
X = pd.get_dummies(df[["year", "ram_gb", "brand", "city"]], columns=["brand", "city"], dtype=float)
y = df["price"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
fill = X_train.median()
X_train = X_train.fillna(fill)
X_test = X_test.fillna(fill)
print("شکل ورودی پس از one-hot:", X.shape)
print("بعد از پر کردن، خالی مانده:", int(X_train.isna().sum().sum()))
خانههای خالی: {'brand': 0, 'city': 0, 'year': 0, 'ram_gb': 25, 'price': 0}
شکل ورودی پس از one-hot: (238, 12)
بعد از پر کردن، خالی مانده: 0
آن ۲۵ خانهٔ خالی را جدی بگیر. در سرنخ SimpleImputer داخلِ Pipeline خودش پرشان میکرد. PyTorch چنین چیزی ندارد — و اگر nan وارد شبکه شود، loss هم nan میشود و هیچ خطایی نمیگیری، فقط یک ستونِ nan روی صفحه میبینی. این دقیقاً همان دستهٔ خطاهای فصلِ ۸ است.
و ترتیب مهم است: میانه را از دادهٔ آموزش گرفتیم و روی هر دو بهکار بردیم. برعکسش نشتی است — همان درسِ سرنخ ترمِ ۳ فصلِ ۴.
get_dummies هم همان کارِ OneHotEncoder را میکند: چهار ستونِ اولیه به دوازده ستونِ عددی تبدیل شد، چون شبکه فقط عدد میفهمد.
۲. نرمالسازی و تبدیل به tensor#
mu, sigma = X_train.mean(), X_train.std().replace(0, 1)
y_mu, y_sigma = y_train.mean(), y_train.std()
def to_tensor(frame, series):
xs = torch.tensor(((frame - mu) / sigma).values, dtype=torch.float32)
ys = torch.tensor(((series - y_mu) / y_sigma).values, dtype=torch.float32).unsqueeze(1)
return xs, ys
xtr, ytr = to_tensor(X_train, y_train)
xte, yte = to_tensor(X_test, y_test)
print("آموزش:", tuple(xtr.shape), "| آزمون:", tuple(xte.shape))
print("میانگین ورودی نرمالشده:", round(float(xtr.mean()), 4))
آموزش: (190, 12) | آزمون: (48, 12)
میانگین ورودی نرمالشده: 0.0
سه تصمیم در این هشت خط، و هر سه از فصلهای قبل میآیند:
.replace(0, 1)روی انحرافِ معیار. بعضی ستونهایone-hotممکن است در دادهٔ آموزش ثابت باشند و انحرافشان صفر — و تقسیم بر صفرnanمیدهد. این یک خطِ دفاعیِ کوچک با اثرِ بزرگ است.- هدف هم نرمال شد. قیمتها دهها میلیوناند و
MSELossرویشان عددهای نجومی میدهد. نرمال کردنِ هدف، گرادیانها را در بازهٔ سالم نگه میدارد. .unsqueeze(1)روی هدف. بدونش شکلِ(190,)میشد و همان تلهٔbroadcastingفصلِ ۸ بخشِ ۴ پیش میآمد.
۳. مدل#
torch.manual_seed(0)
model = nn.Sequential(
nn.Linear(xtr.shape[1], 32), nn.ReLU(),
nn.Linear(32, 16), nn.ReLU(),
nn.Linear(16, 1),
)
print("پارامترها:", sum(p.numel() for p in model.parameters()))
پارامترها: 961
۹۶۱ پارامتر برای ۱۹۰ نمونهٔ آموزش. این عدد را نگه دار — در بخشِ ۵ دوباره سراغش میآییم.
۴. آموزش، با ثبتِ هر دو منحنی#
loader = DataLoader(TensorDataset(xtr, ytr), batch_size=32, shuffle=True)
opt = torch.optim.Adam(model.parameters(), lr=0.01)
loss_fn = nn.MSELoss()
def mae_toman(m, xs, ys):
"""MAE را به تومان برمیگرداند تا با عددهای سرنخ قابلِ مقایسه باشد."""
m.eval()
with torch.no_grad():
return float(((m(xs) - ys).abs().mean()) * y_sigma)
for epoch in range(1, 61):
model.train()
for xb, yb in loader:
opt.zero_grad()
loss_fn(model(xb), yb).backward()
opt.step()
if epoch in (1, 10, 30, 60):
print(f"epoch {epoch:>2}: MAE آموزش = {round(mae_toman(model, xtr, ytr)):>8} | "
f"MAE آزمون = {round(mae_toman(model, xte, yte)):>8}")
epoch 1: MAE آموزش = 6387884 | MAE آزمون = 6595534
epoch 10: MAE آموزش = 1423511 | MAE آزمون = 1753220
epoch 30: MAE آموزش = 1225251 | MAE آزمون = 1842329
epoch 60: MAE آموزش = 1105180 | MAE آزمون = 1888727
به این چهار سطر خوب نگاه کن — مهمترین چیزِ کلِ این فصل همینجاست.
- تا
epoch۱۰، هر دو پایین میآیند. مدل واقعاً دارد یاد میگیرد. - از آن به بعد، MAE آموزش پایین میآید و MAE آزمون بالا میرود.
یعنی از حدودِ epoch ۱۰ به بعد، مدل دیگر «الگو» یاد نمیگیرد — دارد ۱۹۰ نمونهٔ آموزش را حفظ میکند. نامش overfitting است و در سرنخ ترمِ ۳ فصلِ ۸ مفهومش را دیدی؛ اینجا برای اولین بار با چشمِ خودت در عددهای یک شبکه میبینیاش.
و آن ۹۶۱ پارامتر در برابرِ ۱۹۰ نمونه، دقیقاً همین را پیشبینی میکرد: مدل ظرفیتِ حفظ کردن دارد، پس حفظ میکند.
📏 اندازه بگیر: از همینجا به بعد، هیچوقت فقط
lossآموزش را چاپ نکن. یک عددِ آموزشِ در حالِ کاهش هیچچیز دربارهٔ کیفیتِ مدل نمیگوید — فقط میگوید مدل دارد دادهات را حفظ میکند. دو منحنی، همیشه، کنارِ هم.
۵. حالا مقایسهٔ صادقانه#
from sklearn.dummy import DummyRegressor
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_absolute_error
base = DummyRegressor(strategy="median").fit(X_train, y_train)
ridge = Ridge(alpha=0.1).fit((X_train - mu) / sigma, y_train)
print("خط پایه (میانه):", round(mean_absolute_error(y_test, base.predict(X_test))))
print("خطی (سرنخ ت۳) :", round(mean_absolute_error(y_test, ridge.predict((X_test - mu) / sigma))))
print("شبکهٔ عصبی :", round(mae_toman(model, xte, yte)))
خط پایه (میانه): 7793750
خطی (سرنخ ت۳) : 1377488
شبکهٔ عصبی : 1888727
شبکهٔ عصبی از مدلِ خطیِ سرنخ بدتر شد. ۱٫۸۹ میلیون در برابرِ ۱٫۳۸ میلیون — حدودِ ۳۷ درصد بدتر.
نُه فصل کار کردی و نتیجه از یک مدلِ سهخطیِ ترمِ قبل بدتر درآمد. این را کسی از تو پنهان نمیکند، چون دقیقاً همان چیزی است که باید یاد بگیری.
سه دلیل، و هر سه واقعیاند:
- داده کوچک است. ۱۹۰ نمونهٔ آموزش برای ۹۶۱ پارامتر خیلی کم است. شبکهها وقتی میدرخشند که داده زیاد باشد.
- رابطه تقریباً خطی است. قیمت با سال و حافظه تقریباً یکنواخت بالا میرود. مدلِ خطی دقیقاً برای همین ساخته شده و انعطافِ اضافیِ شبکه فقط جا برای حفظ کردن میدهد.
- هیچ انضباطی روی آموزش نگذاشتیم. نه توقفِ زودهنگام، نه
regularization، نه تنظیمِ نرخ. همان سه چیزی که کلِ ترمِ ۲ دربارهٔ آنهاست.
✅ چک کن: به عددِ
epoch۱۰ برگرد: MAE آزمون آنجا ۱٬۷۵۳٬۲۲۰ بود، بهتر از ۱٬۸۸۸٬۷۲۷ نهایی. یعنی اگر همانجا متوقف میشدیم، مدلِ بهتری داشتیم. این سادهترین شکلِearly stoppingاست و ترمِ ۲ فصلِ ۲ کاملاً بازش میکند. پیدا کردنِ همین یک نکته در جدولِ خودت، مهارتی است که این فصل میخواهد به تو بدهد.
۶. گزارشِ صادقانه#
| مسئله | برآورد قیمت لپتاپ دستدوم از چهار ویژگی |
| داده | ۲۳۸ ردیف، ۱۹۰ آموزش و ۴۸ آزمون، random_state=0 |
| مدل | شبکهٔ سهلایه، ۹۶۱ پارامتر |
| آموزش | ۶۰ epoch، Adam، lr=0.01، batch=32 |
| نتیجه روی آزمون | MAE برابرِ ۱٬۸۸۸٬۷۲۷ تومان |
| خط پایه | ۷٬۷۹۳٬۷۵۰ (میانه) |
| بهترین مدلِ رقیب | ۱٬۳۷۷٬۴۸۸ (خطی، سرنخ ترمِ ۳) |
| مشاهدهٔ اصلی | از epoch ۱۰ به بعد overfitting شروع شد |
| نتیجهگیری | برای این داده، شبکهٔ عصبی انتخابِ درستی نیست |
آن سطرِ آخر را با همین صراحت بنویس. یک گزارشِ حرفهای وقتی که مدلِ سادهتر برنده میشود همین را میگوید — و کسی که این را ننویسد، دیر یا زود سامانهای میسازد که پیچیده است و کار نمیکند.
🔧 اگر کار نکرد:
lossبرابرِnanشد — تقریباً حتماًfillnaرا جا انداختهای؛torch.isnan(xtr).any()را چک کن. MAE عددِ نجومی است — هدف را نرمال نکردهای یا موقعِ برگرداندن درy_sigmaضرب نکردهای. عددهایت با کتاب نمیخواند —torch.manual_seed(0)یاrandom_state=0جا مانده.
🤖 از دستیارت بپرس: جدولِ منحنیِ بخشِ ۴ را به دستیارت بده و بپرس: «از این عددها چه چیزی میفهمی و چه کاری پیشنهاد میدهی؟» اگر
overfittingو توقفِ زودهنگام را نگفت، جوابش ناقص است. و بعد بپرس: «برای این اندازهٔ داده، شبکهٔ عصبی انتخابِ درستی است؟» — جوابِ صادقانه «نه» است، و ببین دستیارت هم صادق است یا فقط تشویقت میکند.
تمرینها
اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.
در فصل بعد#
ترمِ ۱ اینجا تمام میشود. سه چیز را با خودت ببر:
- هیچچیز در PyTorch جادو نیست.
optimizer.step()را خودت با دست نوشتی،nn.Linearرا با یک ضربِ ماتریسی ثابت کردی، وCrossEntropyLossرا باsoftmaxو لگاریتم بازسازی کردی. lossآموزش بهتنهایی هیچچیز نمیگوید.- مدلِ پیچیدهتر خودبهخود بهتر نیست — باید در برابرِ خطِ پایه و مدلِ ساده ثابت کند.
و همان جدولِ بخشِ ۴، سؤالِ ترمِ بعد را میسازد: چطور جلوی حفظ کردن را بگیریم و شبکه را وادار کنیم بهجای حافظه، الگو یاد بگیرد؟ ترمِ ۲ کاملاً دربارهٔ همین است.
به آخر این فصل رسیدی!
اگر ساختی و جواب داد، این دکمه مال توست.