در این فصل چه یاد میگیری#
numpy را بلدی و کارش را خوب انجام میدهد. پس چرا PyTorch یک نوعِ دادهٔ دیگر ساخت؟ جوابِ کوتاه: دو چیز که numpy ندارد و یادگیری عمیق بدونشان ممکن نیست — اجرا روی GPU، و ردیابیِ خودکارِ مشتق. در این فصل با tensor کار میکنی، تلههای dtype و shape را میبینی، و پایهای میسازی که کلِ دوره رویش بنا میشود.

آخر این فصل میتوانی:
tensorبسازی وshapeوdtypeوdeviceرا بخوانی- بینِ
numpyوtorchرفتوبرگشت کنی و بدانی کِی حافظه مشترک است - شکلِ tensor را تغییر بدهی و خطای شکل را بخوانی
- بگویی
tensorدقیقاً چه چیزی بهndarrayاضافه کرده
قبل از شروع#
از سرنخ ترمِ ۲: numpy، shape، عملیاتِ برداری، broadcasting.
سلولِ راهاندازی: هستهٔ تصادف را قفل میکند و laptops_clean.csv سرنخ را میسازد — در فصلِ ۹ لازمش داریم.
📓 نوتبوک: نوتبوک این فصل را در Colab باز کن — همهٔ کدهای این فصل آماده و بهترتیب داخلش هست.
💡 نکته: کلِ این فصل روی CPU در چند ثانیه اجرا میشود. GPU لازم نداری — و این قاعدهٔ کلِ ترمِ ۱ است.
۱. tensor یعنی آرایه، با دو چیزِ اضافه#
numpy.ndarray |
torch.Tensor |
|
|---|---|---|
| آرایهٔ چندبعدی | ✓ | ✓ |
عملیاتِ برداری و broadcasting |
✓ | ✓ |
| اجرا روی GPU | ✗ | ✓ |
| ردیابیِ خودکارِ مشتق | ✗ | ✓ |
همان دو ردیفِ آخر تمامِ دلیلِ وجودِ PyTorch است. بقیهاش تقریباً همان numpy است که میشناسی — و همین باعث میشود انتقالت آسان باشد.
import torch
scalar = torch.tensor(3.5)
vector = torch.tensor([1.0, 2.0, 3.0])
matrix = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
for name, t in [("عدد", scalar), ("بردار", vector), ("ماتریس", matrix)]:
print(f"{name}: shape={tuple(t.shape)} | ndim={t.ndim} | dtype={t.dtype}")
عدد: shape=() | ndim=0 | dtype=torch.float32
بردار: shape=(3,) | ndim=1 | dtype=torch.float32
ماتریس: shape=(2, 2) | ndim=2 | dtype=torch.float32
به shape=() نگاه کن: یک عددِ تنها، shape خالی دارد، نه (1,). این تفاوت بعداً واقعاً به دردسر میخورد — بهویژه وقتی loss که یک عددِ تنهاست را با یک بردارِ تکعضوی اشتباه بگیری.
🌱 ریشهاش کجاست: بردار و ماتریس اینجا فقط ظرفِ عدد نیستند؛ در ادامهٔ دوره تبدیلاند. اگر میخواهی بدانی چرا ضربِ ماتریسی اینطور تعریف شده و چه ربطی به ترکیبِ دو تبدیل دارد، ریشه، ترم ۷ فصل ۱ — بردار از پایه شروع میکند و فصل ۵ همان ترم دقیقاً همین سؤال را جواب میدهد.
۲. dtype: اولین جایی که گیر میکنی#
from_int = torch.tensor([1, 2, 3])
from_float = torch.tensor([1.0, 2.0, 3.0])
print("از عدد صحیح:", from_int.dtype)
print("از اعشاری :", from_float.dtype)
print("جمعشان :", (from_int + from_float).dtype)
از عدد صحیح: torch.int64
از اعشاری : torch.float32
جمعشان : torch.float32
آن نقطهٔ کوچک بعد از 1 نوعِ کلِ tensor را عوض میکند. و این مهمترین تفاوتِ عملیِ torch با numpy در روزِ اولت است: numpy پیشفرضش float64 است و torch پیشفرضش float32.
دلیلش هم صرفهجویی نیست، سرعت است: GPUها با float32 چند برابر سریعترند و برای یادگیری عمیق آن دقت کاملاً کافی است.
⚠️ مواظب باش:
torch.int64در جای اشتباه یکی از رایجترین خطاهای روزهای اول است. شبکهها روی اعشار کار میکنند و دادنِ یک tensorِ صحیح به یک لایه خطای صریح میدهد. قاعدهٔ ساده: دادهٔ ورودیِ شبکه همیشهfloat32، برچسبِ طبقهبندی همیشهint64. این دو را حفظ کن؛ نودرصدِ خطاهایdtypeهمینجاست.
۳. numpy و torch: رفتوبرگشت#
import numpy as np
a = np.array([1.0, 2.0, 3.0])
t = torch.from_numpy(a)
print("numpy -> torch:", t, t.dtype)
a[0] = 99.0
print("بعد از تغییر numpy:", t)
back = t.numpy()
print("torch -> numpy:", back, type(back).__name__)
numpy -> torch: tensor([1., 2., 3.], dtype=torch.float64) torch.float64
بعد از تغییر numpy: tensor([99., 2., 3.], dtype=torch.float64)
torch -> numpy: [99. 2. 3.] ndarray
دو چیزِ مهم در همین شش خط:
dtypeمنتقل شد.numpyآرایهاشfloat64بود، پس tensor همfloat64شد — نهfloat32پیشفرض. اگر بعداً به یک لایه بدهیاش، خطا میگیری.- حافظه مشترک است. یک عدد را در
numpyعوض کردیم و tensor هم عوض شد.torch.from_numpyکپی نمیکند.
🔧 اگر کار نکرد: اگر میخواهی نسخهٔ مستقل داشته باشی،
torch.tensor(a)بنویس نهtorch.from_numpy(a)— اولی کپی میکند. و برای تبدیلِ نوع،.float()یا.to(torch.float32)را بگذار:torch.from_numpy(a).float(). اشتراکِ حافظه سریع است، ولی اگر ندانیاش، باگی میسازد که ساعتها دنبالش میگردی.
۴. shape: جایی که بیشترِ خطاها میآیند#
x = torch.arange(12.0)
print("شکل اولیه:", tuple(x.shape))
print("reshape(3,4):", tuple(x.reshape(3, 4).shape))
print("reshape(3,-1):", tuple(x.reshape(3, -1).shape))
print("unsqueeze(0):", tuple(x.unsqueeze(0).shape))
try:
x.reshape(5, 3)
except RuntimeError as error:
print("RuntimeError:", str(error)[:60])
شکل اولیه: (12,)
reshape(3,4): (3, 4)
reshape(3,-1): (3, 4)
unsqueeze(0): (1, 12)
RuntimeError: shape '[5, 3]' is invalid for input of size 12
سه ابزار که در کلِ دوره بهکارت میآیند:
reshapeشکل را عوض میکند، به شرطی که تعدادِ کلِ خانهها ثابت بماند. میشود، نمیشود.-1یعنی «خودت حساب کن».reshape(3, -1)میگوید سه سطر بده و ستونها را خودت دربیاور.unsqueeze(0)یک بُعدِ بهطولِ ۱ اضافه میکند. این را حفظ کن: مدلها انتظارِbatchدارند، و برای دادنِ یک نمونهٔ تنها باید بُعدِbatchرا دستی اضافه کنی.
✅ چک کن: آن پیامِ خطا را حفظ کن —
shape '[5, 3]' is invalid for input of size 12. در کلِ این دوره بارها میبینیاش، و همیشه یک معنا دارد: عددی که خواستی با تعدادِ واقعیِ خانهها نمیخواند. اولین کاری که باید بکنی چاپ کردنِshapeقبل و بعدِ هر تبدیل است، نه حدس زدن.
۵. دو ضرب که با هم فرق دارند#
A = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
B = torch.tensor([[10.0, 20.0], [30.0, 40.0]])
print("A * B (خانهبهخانه):\n", A * B)
print("A @ B (ضرب ماتریسی):\n", A @ B)
A * B (خانهبهخانه):
tensor([[ 10., 40.],
[ 90., 160.]])
A @ B (ضرب ماتریسی):
tensor([[ 70., 100.],
[150., 220.]])
* هر خانه را در خانهٔ متناظرش ضرب میکند. @ ضربِ ماتریسیِ واقعی است: سطر در ستون، و جمع.
تقریباً هر لایهٔ یک شبکهٔ عصبی یک @ است. وقتی در فصلِ ۴ به nn.Linear برسیم، خواهی دید که کارِ اصلیاش دقیقاً همین یک عملیات است.
۶. broadcasting، همانطور که در numpy بود#
rows = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
bias = torch.tensor([10.0, 20.0, 30.0])
print("broadcast:\n", rows + bias)
print("shape:", tuple((rows + bias).shape))
broadcast:
tensor([[11., 22., 33.],
[14., 25., 36.]])
shape: (2, 3)
بردارِ سهتایی روی هر دو سطر پخش شد. این دقیقاً همان قاعدهٔ سرنخ ترمِ ۲ است و در torch بیتغییر کار میکند.
و کاربردش اینجا خیلی مشخص است: یک لایهٔ شبکه به همهٔ نمونههای batch یک bias مشترک اضافه میکند — و همین broadcasting است که آن را در یک خط ممکن میکند.
۷. device: جایی که tensor زندگی میکند#
device = "cuda" if torch.cuda.is_available() else "cpu"
print("دستگاه:", device)
big = torch.randn(3, 3)
print("روی:", big.device)
دستگاه: cpu
روی: cpu
خروجیِ خطِ اول به ماشینِ تو بستگی دارد — روی Colab بدونِ GPU، cpu است و با GPU، cuda.
آن خطِ اول را حفظ کن؛ الگوی استانداردِ کلِ دوره است. بهجای اینکه در کد بنویسی cuda (که روی ماشینِ بدونِ GPU میشکند)، میپرسی و بعد تصمیم میگیری. و tensorها پیشفرض روی CPU ساخته میشوند؛ برای بردنشان روی GPU باید صریح بگویی big.to(device).
⚠️ مواظب باش: دو tensor که روی دو دستگاهِ متفاوتاند با هم کار نمیکنند و خطای
Expected all tensors to be on the same deviceمیدهند. در ترمِ ۲ میبینی که این رایجترین خطای اولین آموزشِ GPUدار است، و همیشه یک علت دارد: مدل را منتقل کردهای و داده را نه، یا برعکس.
۸. تکرارپذیری، از همین اولین فصل#
torch.manual_seed(0)
first = torch.randn(2, 2)
torch.manual_seed(0)
second = torch.randn(2, 2)
print("دو بار با همان seed یکی است:", bool(torch.equal(first, second)))
print(first)
دو بار با همان seed یکی است: True
tensor([[ 1.5410, -0.2934],
[-2.1788, 0.5684]])
سلولِ راهاندازیِ این ترم همین کار را برایت کرده، و برای همین است که عددهای بالا با عددهای تو یکی درآمد.
📏 اندازه بگیر: شبکههای عصبی از وزنهای تصادفی شروع میکنند، پس دو اجرای بدونِ
seedنتیجهٔ متفاوت میدهند — و برخلافِ سرنخ که تفاوتش کوچک بود، اینجا میتواند تفاوتِ «آموزش دید» و «اصلاً همگرا نشد» باشد. از همین فصل، هیچوقت بدونِseedآزمایش نکن. ترمِ ۲ فصلِ ۶ کلِ ماجرا را باز میکند، از جمله جایی که حتیseedهم کافی نیست.
واژههای تازهٔ این فصل#
| کلمه | تلفظ به حروف فارسی | یعنی چه |
|---|---|---|
| tensor | تنسور | آرایهٔ چندبعدیِ PyTorch، با GPU و مشتقِ خودکار |
| dtype | دیتایپ | نوعِ عددیِ خانهها (float32، int64، …) |
| shape | شیپ | اندازهٔ هر بُعد |
| device | دیوایس | جایی که tensor در آن ذخیره و پردازش میشود |
| reshape | ریشیپ | تغییرِ شکل بدونِ تغییرِ محتوا |
| broadcasting | برادکستینگ | پخشِ خودکارِ یک tensorِ کوچک روی یک بزرگتر |
تمرینها
اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.
در فصل بعد#
tensor را داری. حالا آن قابلیتِ دومی که در جدولِ بخشِ ۱ بود: autograd. یک خط کد مینویسی و PyTorch مشتقِ کلِ محاسبهات را — هر چقدر هم پیچیده — خودش حساب میکند. و میبینی که هیچ جادویی در کار نیست، فقط قاعدهٔ زنجیرهایِ ریشه ترمِ ۸.
به آخر این فصل رسیدی!
اگر ساختی و جواب داد، این دکمه مال توست.