المصفوفات مع NumPy
درس 2 من 9
2المصفوفات مع NumPy
الصورة الكبيرةالأرقام التي يتعلّم منها النموذج لا تعيش في قوائم بايثون، بل في مصفوفات لها شكل ونوع وقواعد بث. هذا الدرس يعلّمك قراءة الثلاثة قبل أن تقابلها في رسالة خطأ.
ماذا تشاهد في الرسم: الجدول (3, 4) ثابت، والمتجه (4,) يمرّ على صفوفه صفًّا بعد صف. هذا هو البث: البعد الأخير متطابق، فيُعاد المتجه على كل صف دون نسخ ولا حلقة.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
القائمة في بايثون مثل صفّ من الصناديق المختلفة، كل صندوق تفتحه وحده. مصفوفة NumPy مثل علبة بيض: كل الخانات بنفس الحجم ومتلاصقة، فتستطيع أن تفعل الشيء نفسه بالبيض كله دفعة واحدة. لهذا تكون أسرع بمئات المرات من حلقة تمرّ على الأرقام واحدًا واحدًا.
ndarray كتلة ذاكرة متصلة بنوع واحد dtype وشكل shape. العمليات متجهة vectorised تُنفّذ في C دون حلقة بايثون. والبث broadcasting يمدّ الأبعاد ذات الطول 1 تلقائيًا، فتجمع مصفوفة (3, 4) مع متجه (4,) مباشرة.
قبل أن تبدأ
🐍أساسيات بايثون
تكتب قائمة وحلقة for ودالة بسيطة، وتعرف كيف تشغّل سكربتًا أو دفترًا.
🔢الصفوف والأعمدة
تعرف أن الجدول صفوف وأعمدة، فالشكل (3, 4) سيبدو لك مألوفًا من أول نظرة.
الفكرة الجوهرية
هذا الدرس يبني المصفوفة من ثلاث أفكار: كتلة واحدة من نوع واحد، ثم الشكل والنوع، ثم البث. كل فكرة منها ستقابلها لاحقًا في رسالة خطأ.
1المصفوفة: كتلة واحدة ونوع واحد
قائمة بايثون تحفظ مؤشرات مبعثرة، وكل عنصر فيها قد يكون من أي نوع. المصفوفة تحفظ العناصر متجاورة في كتلة واحدة، وكلها من نوع واحد.
انظر إلى الرسم: أربعة أسعار في كتلة واحدة، وكلها float64. لا يحتاج المفسّر أن يسأل عن نوع كل عنصر.
لهذا السبب يجمع NumPy الكتلة كلها في مرور واحد على المستوى الأدنى، بينما تحتاج القائمة حلقة تفسيرية على كل عنصر.
2الشكل والنوع
الشكل shape تُبل يعدّ العناصر في كل بعد. (3, 4) ثلاثة صفوف وأربعة أعمدة، و(4,) متجه بأربعة عناصر ليس صفًّا ولا عمودًا.
والنوع dtype يقول ما نوع الأرقام في الكتلة. اطبع الاثنين، ثم طبّق عملية على المتجه كله دفعة واحدة.
import numpy as np prices = np.array([12.5, 9.0, 31.75, 4.25]) print(prices.shape) print(prices.dtype) print(float(prices.sum())) print(float(prices.mean())) with_vat = prices * 1.15 print([round(float(x), 2) for x in with_vat])
لاحظ آخر سطر: الضريبة حُسبت على المتجه كله بتعبير واحد، بلا حلقة. ولاحظ 14.37 لا 14.38، لأن 12.5 × 1.15 لا يُمثَّل بدقة في الثنائي. هذه ليست علّة في NumPy بل في الفاصلة العائمة نفسها، وستقابلها في كل مكان.
3النوع يحدّد الذاكرة والدقة
كل نوع يأخذ عددًا ثابتًا من البايتات لكل عنصر. انظر إلى الرسم: float64 يأخذ ثمانية أضعاف int8.
النوع الصغير يوفّر الذاكرة لكنه يدفع ثمنًا: int8 ينهار عند 128. وفي التعلّم العميق ستختار بين float32 وfloat16 لهذا السبب بالضبط.
4البث بين شكلين مختلفين
عملية بين شكلين مختلفين تنجح إذا توافقت الأبعاد. تُقارن الأبعاد من اليمين إلى اليسار، ويتوافق البعدان إذا تساويا أو كان أحدهما 1.
هذا يوفّر عليك نسخًا يدويًا وحلقات. جرّب حالتين: واحدة متوقّعة، وواحدة تفاجئ الناس.
import numpy as np table = np.arange(12).reshape(3, 4) column_means = table.mean(axis=0) centred = table - column_means print(table.shape, column_means.shape, centred.shape) print([round(float(x), 1) for x in column_means]) print([round(float(x), 1) for x in centred[0]]) row = np.array([1, 2, 3]) column = np.array([[10], [20]]) print((row + column).shape)
طرح متجه بشكل (4,) من جدول بشكل (3, 4) نجح لأن البعد الأخير متطابق. وجمع (3,) مع (2, 1) أنتج (2, 3)، وهي الحالة التي تفاجئ الناس: البث لا يشتكي، بل يوسّع ويصمت.
كيف يعمل، خطوة بخطوة: كيف يقرّر NumPy إن كان البث ممكنًا
يحاذي الشكلين من اليمين، ففي (3, 4) و(4,) يتقابل 4 مع 4.
يقارن كل بعدين متقابلين: يتوافقان إذا تساويا أو كان أحدهما 1.
البعد الناقص يُعامل كأنه 1، والبعد الذي قيمته 1 يتمدّد ليساوي الآخر دون نسخ في الذاكرة.
إذا تعارض بعدان، مثل 3 مقابل 4، يرفض العملية برسالة خطأ، وإلا يُخرج الشكل الناتج، مثل (5, 3) من (5, 1) و(1, 3).
أعمق قليلًا
| المقارنة | قائمة بايثون | مصفوفة NumPy |
|---|---|---|
| الأنواع | أي نوع في أي موضع | نوع واحد لكل المصفوفة |
| الذاكرة | مؤشرات مبعثرة | كتلة متّصلة |
| الجمع على كل العناصر | حلقة تفسيرية | عملية واحدة على المستوى الأدنى |
| الضرب في رقم | يكرّر القائمة | يضرب كل عنصر |
مثال محلول
(4,) و(4, 1) و(1, 4). الثلاثة تحمل نفس الأرقام وتتصرّف بثلاث طرق مختلفة عند البث، وأغلب رسائل الخطأ عن الأشكال تعود إلى هذا تحديدًا. اطبع shape قبل أن تفترض.
جرّب بلا كود
خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| المصفوفة مجرد قائمة بايثون أسرع. | القائمة تقبل أي نوع في أي موضع، والمصفوفة نوع واحد في كتلة متّصلة. لذلك * 2 تكرّر القائمة لكنها تضاعف كل عنصر في المصفوفة. |
| (4,) و(1, 4) و(4, 1) شيء واحد. | تحمل الأرقام نفسها وتتصرّف بثلاث طرق مختلفة في البث، وأغلب أخطاء الأشكال تعود إلى هذا الخلط. |
| float64 هو الخيار الآمن دائمًا. | يكلّف ثمانية أضعاف ذاكرة int8، وفي التعلّم العميق ستختار بين float32 وfloat16 لهذا السبب. النوع قرار ذاكرة ودقة، لا قيمة افتراضية تنساها. |
جرّب بنفسك
عندك table بشكل (3, 4) وتريد طرح متوسط كل صف من ذلك الصف. كتبت table - table.mean(axis=1). ماذا يصير؟
mean(axis=1) يعطي شكل (3,)، والبث يقارن من اليمين فيضعه مقابل الأعمدة الأربعة لا الصفوف الثلاثة، فيفشل. الحل أن تحفظ البعد: table.mean(axis=1, keepdims=True) يعطي (3, 1) فينجح الطرح بالمعنى الذي تريده.
بطاقات مراجعة سريعة
shape بالضبط(3, 4) تعني ثلاثة صفوف وأربعة أعمدة، و(4,) تعني متجهًا بأربعة عناصر لا صفًّا ولا عمودًا.1، وإلا فالعملية مرفوضة.dtypeint8 ينهار عند 128 بينما float64 يأكل ثمانية أضعاف الذاكرة.تحقق من فهمك
ما شكل ناتج جمع مصفوفة (5, 1) مع مصفوفة (1, 3)؟
(5, 3). تُقارن الأبعاد من اليمين: 1 مع 3 فيتوسّع الأول، ثم 5 مع 1 فيتوسّع الثاني. النتيجة جدول كامل من خمسة صفوف وثلاثة أعمدة، وهي الحيلة التي تبني بها جدول كل الأزواج دون حلقة واحدة.
ماذا يعطي np.array([1, 2, 3]) * 2 مقارنة بـ [1, 2, 3] * 2؟
المصفوفة تعطي [2, 4, 6] لأنها تضاعف كل عنصر، والقائمة تعطي [1, 2, 3, 1, 2, 3] لأنها تكرّر نفسها.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
توثيق رسمي2
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.