دليل NumPy الشامل في بايثون: بناء المصفوفات متعددة الأبعاد للتعلم الآلي

دقائق القراءة: 9

مقدمة إلى قوة NumPy في عالم التعلم الآلي

تُعد مكتبة NumPy (Numerical Python) حجر الزاوية في منظومة بايثون للعمليات الحسابية الرقمية واسعة النطاق، وتكتسب أهمية قصوى في مجال التعلم الآلي. إنها المكتبة المفضلة لعلماء البيانات والمطورين بفضل قدرتها الفائقة على التعامل مع مجموعات البيانات الضخمة بكفاءة لا مثيل لها.

تتميز NumPy بتقديمها لبنية بيانات تُعرف باسم المصفوفات متعددة الأبعاد (n-dimensional arrays)، والتي تُعد أسرع وأكثر مرونة من قوائم بايثون التقليدية (Python lists). يعود هذا التفوق إلى طريقة تخزين مصفوفات NumPy في مساحة متصلة من الذاكرة، مما يتيح للمعالج إجراء العمليات الحسابية بفعالية قصوى. في هذا الدليل الشامل، سنتعمق في أساسيات العمل مع NumPy، بدءًا من إنشاء المصفوفات وتحويلها، مرورًا بالعمليات الحسابية المتقدمة، وصولًا إلى توليد القيم العشوائية، وكل ذلك بهدف تمكينك من بناء حلول قوية للتعلم الآلي.

الشروع في العمل: تثبيت واستيراد NumPy

للبدء في استخدام NumPy، يجب عليك تثبيتها أولاً. تتوفر تعليمات تثبيت واضحة ومفصلة على الموقع الرسمي لمكتبة NumPy، وننصح بالرجوع إليها لضمان تثبيت سليم يتناسب مع بيئة عملك.

استيراد مكتبة NumPy

بعد التثبيت، الخطوة الأولى في أي نص برمجي يستخدم NumPy هي استيراد المكتبة. يُفضل استخدام الاسم المستعار np لتسهيل الكتابة:

import numpy as np

تحويل القوائم إلى مصفوفات NumPy

يمكنك بسهولة تحويل قوائم بايثون الموجودة لديك إلى مصفوفات NumPy باستخدام الدالة np.array(). هذه الدالة قادرة على التعامل مع القوائم ذات البعد الواحد والمتعدد الأبعاد، وستتولى NumPy مهمة تتبع أبعاد المصفوفة (shape) تلقائيًا.

arr = [1, 2, 3]
np.array(arr)

ينطبق هذا أيضًا على المصفوفات متعددة الأبعاد (المصفوفات المتداخلة):

nested_arr = [[1, 2],[3, 4],[5, 6]]
np.array(nested_arr)

إنشاء المصفوفات بكفاءة: دوال NumPy الأساسية

في العديد من سيناريوهات معالجة البيانات، ستحتاج إلى إنشاء مجموعات بيانات أو مصفوفات بقيم محددة. توفر NumPy مجموعة من الدوال القوية لتسهيل هذه العملية.

دالة np.arange() لتوليد نطاقات القيم

تُستخدم دالة np.arange() لتوليد سلسلة من القيم العددية ضمن نطاق محدد. تأخذ هذه الدالة ثلاثة وسائط: نقطة البداية (start)، نقطة النهاية (end – غير شاملة)، ومعامل اختياري يحدد المسافة بين كل قيمة وأخرى (step).

print(np.arange(0, 10)) # بدون معامل المسافة
# الناتج: [0 1 2 3 4 5 6 7 8 9]

print(np.arange(0, 10, 2)) # مع معامل المسافة
# الناتج: [0 2 4 6 8]

إنشاء مصفوفات من الأصفار والوحدات: np.zeros() و np.ones()

في كثير من الأحيان، ستحتاج إلى مصفوفات مملوءة بالكامل بالأصفار أو الوحدات، خاصة عند تهيئة الأوزان في الشبكات العصبية أو إجراء عمليات معينة. توفر NumPy دوال مخصصة لهذا الغرض.

print(np.zeros(3))
# الناتج: [0. 0. 0.]

print(np.ones(3))
# الناتج: [1. 1. 1.]

تدعم كلتا الدالتين إنشاء مصفوفات متعددة الأبعاد أيضًا، وذلك بتمرير شكل المصفوفة المطلوب كـ tuple (صفوف وأعمدة):

print(np.zeros((4, 5)))
# الناتج:
# [[0. 0. 0. 0. 0.]
#  [0. 0. 0. 0. 0.]
#  [0. 0. 0. 0. 0.]
#  [0. 0. 0. 0. 0.]]

print(np.ones((4, 5)))
# الناتج:
# [[1. 1. 1. 1. 1.]
#  [1. 1. 1. 1. 1.]
#  [1. 1. 1. 1. 1.]
#  [1. 1. 1. 1. 1.]]

مصفوفة الوحدة (Identity Matrix): np.eye()

مصفوفة الوحدة هي مصفوفة مربعة تكون جميع عناصر قطرها الرئيسي واحدًا، وبقية العناصر أصفارًا. تُستخدم هذه المصفوفة بشكل شائع في الجبر الخطي. يمكنك توليدها باستخدام دالة np.eye():

np.eye(5)
# الناتج:
# [[1., 0., 0., 0., 0.],
#  [0., 1., 0., 0., 0.],
#  [0., 0., 1., 0., 0.],
#  [0., 0., 0., 1., 0.],
#  [0., 0., 0., 0., 1.]]

توليد نقاط متباعدة بالتساوي: np.linspace()

تُعد دالة np.linspace() مفيدة لإنشاء مصفوفة تحتوي على عدد محدد من النقاط المتباعدة بالتساوي بين قيمتين محددتين (شاملة للطرفين).

print(np.linspace(0, 10, 3))
# الناتج: [ 0.  5. 10.]

في المثال أعلاه، الوسيط الأول والثاني هما نقطتا البداية والنهاية على التوالي، بينما الوسيط الثالث هو عدد النقاط المطلوبة بينهما. لنرى نفس النطاق مع 20 نقطة:

print(np.linspace(0, 10, 20))
# الناتج:
# [ 0.          0.52631579  1.05263158  1.57894737  2.10526316
#   2.63157895  3.15789474  3.68421053  4.21052632  4.73684211
#   5.26315789  5.78947368  6.31578947  6.84210526  7.36842105
#   7.89473684  8.42105263  8.94736842  9.47368421 10.        ]

توليد الأرقام العشوائية في NumPy

يُعد توليد الأرقام العشوائية جزءًا لا يتجزأ من العديد من خوارزميات التعلم الآلي، سواء لتهيئة الأوزان، أو تقسيم البيانات، أو محاكاة الظواهر. توفر NumPy دوال مدمجة قوية لهذا الغرض.

قبل البدء في توليد الأرقام العشوائية، دعنا نلقي نظرة سريعة على نوعين رئيسيين من التوزيعات الاحتمالية:

رسم بياني يوضح التوزيع الطبيعي والتوزيع المنتظم للبيانات

التوزيع الطبيعي (Normal Distribution)

في التوزيع الطبيعي القياسي، تتجمع القيم حول المتوسط (القمة في المنتصف)، وتتناقص تدريجيًا كلما ابتعدنا عن المتوسط. يُعرف هذا التوزيع أيضًا باسم ‘منحنى الجرس’ (bell curve) وهو مفهوم إحصائي بالغ الأهمية لأنه يظهر في العديد من الظواهر الطبيعية.

التوزيع المنتظم (Uniform Distribution)

إذا كانت جميع القيم في التوزيع لديها نفس الاحتمالية للحدوث، فإننا نتحدث عن توزيع منتظم. على سبيل المثال، عند رمي قطعة نقود، فإن احتمال الحصول على ‘وجه’ أو ‘ذيل’ متساوٍ، مما يجعله مثالًا على التوزيع المنتظم.

الآن بعد أن تعرفت على كيفية عمل التوزيعين الرئيسيين، دعنا نولد بعض الأرقام العشوائية:

توليد أرقام عشوائية بتوزيع منتظم: np.random.rand()

لتوليد أرقام عشوائية بتوزيع منتظم (بين 0 و 1)، استخدم الدالة rand() من الوحدة np.random:

print(np.random.rand(10)) # مصفوفة ذات بعد واحد
# الناتج: [0.46015141 0.89326339 0.22589334 0.29874476 0.5664353  0.39257603
#          0.77672998 0.35768031 0.95087408 0.34418542]

print(np.random.rand(3, 4)) # مصفوفة 3x4
# الناتج:
# [[0.63775985 0.91746663 0.41667645 0.28272243]
#  [0.14919547 0.72895922 0.87147748 0.94037953]
#  [0.5545835  0.30870297 0.49341904 0.27852723]]

توليد أرقام عشوائية بتوزيع طبيعي: np.random.randn()

لتوليد أرقام عشوائية بتوزيع طبيعي قياسي (متوسط 0 وانحراف معياري 1)، استخدم الدالة randn() من الوحدة np.random:

print(np.random.randn(10))
# الناتج: [-1.02087155 -0.75207769 -0.22696798  0.86739858  0.07367362 -0.41932541
#           0.86303979  0.13739312  0.13214285  1.23089936]

print(np.random.randn(3, 4))
# الناتج:
# [[ 1.61013773  1.37400445  0.55494053  0.23133522]
#  [ 0.31290971 -0.30866402  0.33093618  0.34868954]
#  [-0.11659865 -1.22311073  0.36676476  0.40819545]]

توليد أعداد صحيحة عشوائية: np.random.randint()

لتوليد أعداد صحيحة عشوائية بين قيمة دنيا (شاملة) وقيمة عليا (غير شاملة)، استخدم الدالة randint() من الوحدة np.random:

print(np.random.randint(1, 100, 10))
# الناتج: [64 37 62 27  4 33 23 52 70  7]

print(np.random.randint(1, 100, (2, 3)))
# الناتج:
# [[92 42 38]
#  [87 69 38]]

تحديد قيمة البذرة (Seed Value) لتكرارية النتائج

في بعض الأحيان، قد تحتاج إلى أن تكون الأرقام العشوائية التي تولدها قابلة للتكرار في كل مرة تقوم فيها بتشغيل الكود. هذا مفيد لأغراض الاختبار وتصحيح الأخطاء. يمكنك تحقيق ذلك عن طريق تعيين قيمة بذرة (seed value) باستخدام الدالة np.random.seed():

np.random.seed(42)
print(np.random.rand(4))
# الناتج: [0.37454012 0.95071431 0.73199394 0.59865848]

عند استخدام قيمة بذرة محددة، ستحصل دائمًا على نفس تسلسل الأرقام العشوائية عند كل عملية تشغيل، مما يضمن تكرارية النتائج.

إعادة تشكيل المصفوفات وتقطيع البيانات

كعالم بيانات، ستعمل بشكل متكرر على إعادة تشكيل مجموعات البيانات لتناسب أنواعًا مختلفة من العمليات الحسابية. في هذا القسم، سنتناول كيفية التعامل مع أشكال المصفوفات واستخراج البيانات منها.

الحصول على شكل المصفوفة وإعادة تشكيلها

للحصول على شكل المصفوفة (أبعادها)، استخدم الخاصية shape:

arr = np.random.rand(2, 2)
print(arr)
# الناتج:
# [[0.19890857 0.00806693]
#  [0.48199837 0.55373954]]
print(arr.shape)
# الناتج: (2, 2)

لإعادة تشكيل مصفوفة، استخدم الدالة reshape(). يجب أن يكون العدد الإجمالي للعناصر متطابقًا بين الشكل الأصلي والشكل الجديد.

print(arr.reshape(1, 4))
# الناتج: [[0.19890857 0.00806693 0.48199837 0.55373954]]

print(arr.reshape(4, 1))
# الناتج:
# [[0.19890857]
#  [0.00806693]
#  [0.48199837]
#  [0.55373954]]

لتغيير شكل المصفوفة بشكل دائم، يجب عليك إعادة تعيين المصفوفة المعاد تشكيلها إلى المتغير الأصلي. من المهم ملاحظة أن reshape() يعمل فقط إذا كان الهيكل الجديد منطقيًا؛ لا يمكنك إعادة تشكيل مصفوفة 2×2 إلى مصفوفة 3×1، على سبيل المثال، لأن عدد العناصر لا يتطابق.

تقطيع البيانات (Slicing) من مصفوفات NumPy

يعمل استخراج البيانات من مصفوفات NumPy بطريقة مشابهة لقوائم بايثون. دعنا نلقي نظرة على كيفية تقطيع المصفوفات:

myarr = np.arange(0, 11)
print(myarr)
# الناتج: [ 0  1  2  3  4  5  6  7  8  9 10]

sliced = myarr[0:5]
print(sliced)
# الناتج: [0 1 2 3 4]

نقطة هامة: عند تقطيع مصفوفة NumPy، فإن الجزء المقطوع (slice) لا يُنشئ نسخة مستقلة من البيانات، بل هو مجرد ‘عرض’ (view) يشير إلى المصفوفة الأصلية. هذا يعني أن أي تغيير يتم إجراؤه على الجزء المقطوع سينعكس على المصفوفة الأصلية:

sliced[:] = 99
print(sliced)
# الناتج: [99 99 99 99 99]

print(myarr)
# الناتج: [99 99 99 99 99  5  6  7  8  9 10]

كما ترى في المثال أعلاه، على الرغم من أننا قمنا بتعيين جزء من myarr إلى المتغير sliced، إلا أن تغيير قيمة sliced أثر على المصفوفة الأصلية myarr. لإنشاء جزء مستقل من المصفوفة لا يؤثر على الأصل، استخدم الدالة copy():

sliced = myarr.copy()[0:5]

يعمل تقطيع المصفوفات متعددة الأبعاد بطريقة مماثلة للمصفوفات ذات البعد الواحد:

my_matrix = np.random.randint(1, 30, (3, 3))
print(my_matrix)
# الناتج:
# [[21  1 20]
#  [22 16 27]
#  [24 14 22]]

print(my_matrix[0]) # طباعة صف واحد (الصف 0)
# الناتج: [21  1 20]

print(my_matrix[0][0]) # طباعة قيمة واحدة (الصف 0، العمود 0)
# الناتج: 21

print(my_matrix[0, 0]) # طريقة بديلة لطباعة قيمة من الصف 0، العمود 0
# الناتج: 21

العمليات الحسابية المتقدمة على المصفوفات

تُعرف NumPy بسرعتها الفائقة عند إجراء العمليات الحسابية المعقدة على المصفوفات الكبيرة متعددة الأبعاد. دعنا نستعرض بعض العمليات الأساسية.

new_arr = np.arange(1, 11)
print(new_arr)
# الناتج: [ 1  2  3  4  5  6  7  8  9 10]

الجمع والطرح

يمكنك إجراء عمليات الجمع والطرح على المصفوفة بأكملها بقيمة واحدة:

print(new_arr + 5)
# الناتج: [ 6  7  8  9 10 11 12 13 14 15]

print(new_arr - 5)
# الناتج: [-4 -3 -2 -1  0  1  2  3  4  5]

ويمكنك أيضًا جمع أو طرح مصفوفتين من نفس الشكل:

print(new_arr + new_arr)
# الناتج: [ 2  4  6  8 10 12 14 16 18 20]

الضرب والقسمة

تُجرى عمليات الضرب والقسمة عنصرًا بعنصر (element-wise):

print(new_arr * new_arr)
# الناتج: [  1   4   9  16  25  36  49  64  81 100]

print(new_arr / new_arr)
# الناتج: [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]

في حالة حدوث أخطاء القسمة على صفر، ستقوم NumPy بتحويل القيمة الناتجة إلى NaN (ليس رقمًا).

دوال حسابية مدمجة

توفر NumPy أيضًا العديد من الدوال الحسابية المدمجة لحساب قيم مثل المجموع، الجذر التربيعي، المتوسط، التباين، والانحراف المعياري:

  • المجموع: np.sum()
  • الجذر التربيعي: np.sqrt()
  • المتوسط: np.mean()
  • التباين: np.var()
  • الانحراف المعياري: np.std()

عند العمل مع المصفوفات ثنائية الأبعاد، ستحتاج غالبًا إلى حساب المجموع أو المتوسط أو التباين على مستوى الصفوف أو الأعمدة. يمكنك استخدام المعامل الاختياري axis لتحديد ما إذا كنت تريد اختيار صف (axis=1) أو عمود (axis=0).

arr2d = np.arange(25).reshape(5, 5)
print(arr2d)
# الناتج:
# [[ 0  1  2  3  4]
#  [ 5  6  7  8  9]
#  [10 11 12 13 14]
#  [15 16 17 18 19]
#  [20 21 22 23 24]]

print(arr2d.sum()) # مجموع كل العناصر
# الناتج: 300

print(arr2d.sum(axis=0)) # مجموع الأعمدة
# الناتج: [50 55 60 65 70]

print(arr2d.sum(axis=1)) # مجموع الصفوف
# الناتج: [ 10  35  60  85 110]

العمليات الشرطية والتصفية

يمكنك أيضًا إجراء تصفية شرطية باستخدام NumPy عن طريق استخدام تدوين الأقواس المربعة. إليك مثال يوضح كيفية استخراج العناصر التي تفي بشرط معين:

arr = np.arange(0, 10)
# الناتج: [0 1 2 3 4 5 6 7 8 9]

print(arr > 4)
# الناتج: [False False False False False  True  True  True  True  True]

print(arr[arr > 4])
# الناتج: [5 6 7 8 9]

الخلاصة التقنية

تُعد مكتبة NumPy أداة لا غنى عنها في ترسانة أي مطور أو عالم بيانات يعمل في مجال التعلم الآلي ومعالجة البيانات الرقمية. إن قدرتها على التعامل مع المصفوفات متعددة الأبعاد بكفاءة عالية وسرعة فائقة، مقارنة بقوائم بايثون التقليدية، تجعلها الخيار الأمثل للعمليات الحسابية المعقدة. من خلال فهم أساسياتها، مثل إنشاء المصفوفات، وإعادة تشكيلها، وإجراء العمليات الحسابية والشرطية، يمكن للمستخدمين تسريع تطوير نماذج التعلم الآلي وتحسين أداء التطبيقات بشكل كبير. إن إتقان NumPy يفتح الأبواب أمام فهم أعمق للبيانات ويُمكن من بناء حلول تحليلية وتنبؤية أكثر قوة ودقة.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *