الموتر
درس 1 من 8
1الموتر
الصورة الكبيرةالموتر هو مصفوفة NumPy التي تعرف كيف تشتقّ نفسها وكيف تنتقل إلى بطاقة الرسوميات. ثلاث صفات تحكم كل موتر: الشكل والنوع والجهاز، وأغلب أخطائك في PyTorch ستكون خلافًا على واحدة منها.
ماذا تشاهد في الرسم: كل رقم في الشكل له معنى ورقم بُعد تحته. حين تكتب dim=1 فأنت تختار بُعد القنوات، وهو الذي يختفي من الشكل. قراءة الشكل هكذا تكشف نصف أخطاء PyTorch قبل التشغيل.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
الموتر علبة أرقام بأي عدد من الأبعاد: رقم واحد، أو صف، أو جدول، أو كومة جداول مثل صورة ملوّنة. أهم سؤال تسأله عنه دائمًا: ما شكله؟ كم صفًّا وكم عمودًا وكم طبقة. أغلب الأخطاء في التعلّم العميق شكل لا يطابق شكلًا آخر.
torch.Tensor له shape وdtype (مثل float32) وdevice (cpu أو cuda). العمليات تتطلب أشكالًا متوافقة وأجهزة متطابقة، وview وreshape وpermute تغيّر الشكل دون نسخ غالبًا. طباعة x.shape بعد كل خطوة أسرع طريقة للتصحيح.
قبل أن تبدأ
🧮المصفوفات مع NumPy
تعرف الشكل والفهرسة والبث من درس المصفوفات، فالموتر يعمل بنفس الطريقة تقريبًا.
📉المشتقة والميل
تعرف ما الميل ولماذا نحتاجه للتدريب، فالموتر يستطيع تتبّع العمليات ليحسبه لاحقًا.
الفكرة الجوهرية
الموتر مصفوفة تعرفها من NumPy، ومعها صفتان إضافيتان. في هذا الدرس تقرأ صفاته الثلاث، ثم تفهم البعد الذي يختفي، ثم تغيّر الشكل، ثم ترى كيف يقرّر النوع حجم الذاكرة.
1الشكل والنوع والجهاز
كل موتر له ثلاث صفات: الشكل يقول كم عنصرًا في كل بعد، والنوع يقول كيف يُخزَّن كل رقم، والجهاز يقول أين يعيش. أغلب أخطائك في PyTorch خلاف على واحدة منها.
البعد الأول في الشكل تقريبًا دائمًا حجم الدفعة، لأن النموذج يعالج عدة أمثلة معًا. والنوع float32 للأوزان والمدخلات، وtorch.long لأرقام الفئات.
انظر إلى الرسم: الموتر x في المثال التالي شكله (2, 3)، أي صفّان في كل صف ثلاثة أرقام. اطبع هذه الصفات الثلاث قبل أي شيء آخر.
to(device).2البعد الذي يختفي: dim
عمليات مثل sum وmean تطوي بعدًا كاملًا في رقم واحد. أنت تختار هذا البعد بـdim. انظر إلى الرسم: النافذة تمرّ على الأعمدة، وكل عمود من رقمين يصير رقمًا واحدًا.
هذا الكود يحتاج PyTorch على جهازك، فالناتج المعروض مسجَّل من تشغيل حقيقي. ثبّته بـpip install torch وشغّله بنفسك.
import torch x = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) print(x.shape, x.dtype, x.device) print(x.sum(dim=0)) print(x.sum(dim=1)) print(x.mean(dim=1, keepdim=True).shape)
dim=0 يجمع عبر الصفوف فيبقى عمود لكل نتيجة (3 قيم)، وdim=1 يجمع عبر الأعمدة فيبقى رقم لكل صف (قيمتان). القاعدة: البعد الذي تذكره هو الذي يختفي. وkeepdim=True يبقيه بطول 1، وهذا ما تحتاجه عادة لتطرح المتوسط من كل صف بالبث.
3تغيير الشكل: view وpermute
view وreshape يغيّران الشكل دون أن يغيّرا البيانات، وpermute يبدّل ترتيب الأبعاد، وunsqueeze يضيف بعدًا بطول 1. انظر إلى الرسم: دفعة صور تصير دفعة متجهات.
قبل أي طبقة خطية، تحتاج تحويل كل صورة إلى متجه. -1 تعني «احسب هذا البعد بنفسك».
import torch
batch = torch.randn(32, 3, 28, 28)
flat = batch.view(32, -1)
print("images:", tuple(batch.shape))
print("flat: ", tuple(flat.shape))
print("one channel:", tuple(batch[:, 0].shape))
print("channels last:", tuple(batch.permute(0, 2, 3, 1).shape))
try:
batch.view(32, 100)
except RuntimeError as e:
print("error:", str(e).split(".")[0])
3 × 28 × 28 = 2352، فكل صورة صارت متجهًا بطول 2352. permute(0, 2, 3, 1) ينقل القنوات إلى الآخر، وهو الترتيب الذي تستخدمه مكتبات الصور مثل Pillow وmatplotlib. وآخر سطر يُظهر رسالة الخطأ المفيدة: 75264 عنصرًا لا يمكن أن تصير 32 × 100.
4النوع يقرّر الذاكرة
كل رقم float32 يأخذ 4 بايت، وكل رقم float16 يأخذ 2. انظر إلى الرسم: نفس المليون رقم، بنصف الذاكرة.
import torch
big = torch.ones(1000, 1000)
print(big.dtype, big.element_size() * big.nelement() / 1e6, "MB")
half = big.to(torch.float16)
print(half.dtype, half.element_size() * half.nelement() / 1e6, "MB")
device = "cuda" if torch.cuda.is_available() else "cpu"
print("device:", device)
مليون رقم float32 تأخذ 4 ميغابايت، ونصفها في float16. نموذج بسبعة مليارات معامل يأخذ 28 غيغابايت بـfloat32 و14 بـfloat16، وهذا الفرق هو بالضبط الفرق بين بطاقة رسوميات تكفي وأخرى لا تكفي. سنعود لهذا في درس التسريع ودرس التكميم.
خلط الأنواع في عملية واحدة إما يرفع خطأ أو يحوّل بصمت. الأعداد الصحيحة للفهارس والفئات، والكسرية لكل ما عداها.
كيف يعمل، خطوة بخطوة: كيف تقرأ شكل موتر وتغيّره
اطبع x.shape و x.dtype و x.device قبل أي شيء، فهذه الصفات الثلاث تحكم كل عملية.
اقرأ الشكل بصوت عالٍ: (32, 3, 28, 28) تعني 32 صورة، لكل منها 3 قنوات، كل قناة 28 في 28.
في عمليات مثل sum و mean، البعد الذي تذكره هو الذي يختفي، إلا مع keepdim=True فيبقى بطول 1.
لتسطيح الصور استخدم view(32, -1)، ولإعادة ترتيب الأبعاد استخدم permute، لا view.
مثال محلول
إن فهمت درس المصفوفات في مرحلة البيانات، فأنت تعرف 80% من الموترات. الفرق أن الموتر يحمل معه الجهاز الذي يعيش عليه، ويمكن أن يتذكّر العمليات التي صنعته ليحسب الميل لاحقًا.
٢. اقرأ الشكل كجملة
(32, 3, 28, 28) تُقرأ: «32 صورة، لكل صورة 3 قنوات لون، كل قناة 28 في 28 بكسل». تعوّد أن تقول الشكل بصوت عالٍ هكذا، وستكتشف نصف أخطائك قبل أن تشغّل الكود.
view وpermute. x.view(32, 28, 28, 3) على موتر (32, 3, 28, 28) لا يرفع خطأ، لأن عدد العناصر نفسه، لكنه يخلط البكسلات بالقنوات فتصير الصور ضجيجًا. لتبديل ترتيب الأبعاد استخدم permute دائمًا.
forward، مثل # (B, 3, 28, 28) -> (B, 2352). هذه العادة الصغيرة توفّر عليك ساعات عند العودة للكود بعد شهر.
جرّب بلا كود
sum(dim=1)؟ وما شكله بعد view(8, -1)؟ الجواب: (8, 10, 10) لأن بعد القنوات اختفى، و(8, 300) لأن 3 × 10 × 10 = 300.خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| الموتر شيء مختلف تمامًا عن مصفوفة NumPy. | هو نفس الفكرة وأغلب العمليات نفسها. يضيف شيئين فقط: الجهاز الذي يعيش عليه، وتتبّع العمليات لحساب الميل. |
| إن لم يظهر خطأ فإعادة التشكيل صحيحة. | view(32, 28, 28, 3) على موتر (32, 3, 28, 28) لا يرفع خطأ لأن عدد العناصر واحد، لكنه يخلط البكسلات بين القنوات فتصير الصور ضجيجًا. |
| نوع الأرقام تفصيل لا يؤثّر في شيء. | مليون رقم float32 يأخذ 4 ميغابايت و float16 نصفها. نموذج بسبعة مليارات معامل يأخذ 28 غيغابايت أو 14، وهذا قد يكون الفرق بين بطاقة تتّسع له وأخرى لا. |
جرّب بنفسك
موتر صور بشكل (8, 3, 64, 64). ما شكل x.mean(dim=1)؟
البعد 1 هو القنوات، فيُطوى ويختفي، ويبقى لكل صورة متوسط قنواتها الثلاث لكل بكسل، أي صورة رمادية. الخيار الأخير هو ما تحصل عليه مع keepdim=True.
ظهر لك الخطأ: Expected all tensors to be on the same device, but found cuda:0 and cpu. ما الأرجح؟
العملية تحتاج كل الموترات على نفس الجهاز. النمط الصحيح: model.to(device) مرة واحدة، وxb, yb = xb.to(device), yb.to(device) داخل الحلقة لكل دفعة.
بطاقات مراجعة سريعة
dim في عملية مثل sumkeepdim=True فيبقى بطول 1.dtypefloat32 هو الافتراضي للتدريب، وfloat16 أو bfloat16 نصف الذاكرة، والأعداد الصحيحة للفهارس والفئات. خلط الأنواع في عملية واحدة يرفع خطأ أو يحوّل بصمت..to(device)، وإلا حصلت على خطأ «Expected all tensors to be on the same device».تحقق من فهمك
موتر بشكل (16, 10) تريد طرح متوسط كل صف منه. لماذا تحتاج keepdim=True؟
بدونه شكل المتوسط (16,)، وقواعد البث تقارن الأبعاد من اليمين: 10 مقابل 16، فلا يتوافقان ويرفع خطأ (أو، لو تصادف أن العددين متساويان، يطرح الخطأ بصمت). مع keepdim=True الشكل (16, 1)، فيُبثّ على الأعمدة العشرة ويُطرح متوسط كل صف من صفّه.
ما شكل الناتج إن طبّقت batch.permute(0, 2, 3, 1) على موتر شكله (32, 3, 28, 28)؟
(32, 28, 28, 3): القنوات انتقلت إلى الآخر، وهو الترتيب الذي تستخدمه مكتبات مثل Pillow و matplotlib.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
توثيق رسمي3
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.