أطلق العنان لقوة محاكاة البيانات وتحليلها. تعلم كيفية إنشاء عينات عشوائية من توزيعات إحصائية مختلفة باستخدام مكتبة NumPy في Python. دليل عملي لعلماء البيانات والمطورين.
غوص عميق في أخذ العينات العشوائية باستخدام NumPy في Python: إتقان التوزيعات الإحصائية
في عالم علم البيانات والحوسبة الشاسع، تعد القدرة على توليد أرقام عشوائية ليست مجرد ميزة؛ إنها حجر الزاوية. من محاكاة النماذج المالية المعقدة والظواهر العلمية إلى تدريب خوارزميات تعلم الآلة وإجراء اختبارات إحصائية قوية، تعتبر العشوائية المُنظَّمة هي المحرك الذي يدفع الفهم والابتكار. في صميم هذه القدرة في بيئة بايثون يكمن NumPy، الحزمة الأساسية للحوسبة العلمية.
بينما يعرف العديد من المطورين وحدة `random` المضمنة في بايثون، فإن وظيفة أخذ العينات العشوائية في NumPy هي محرك قوي، حيث تقدم أداءً فائقًا، ومجموعة أوسع من التوزيعات الإحصائية، وميزات مصممة لتلبية المتطلبات الصارمة لتحليل البيانات. سيأخذك هذا الدليل في غوص عميق في وحدة `numpy.random` في NumPy، ينتقل بك من المبادئ الأساسية إلى إتقان فن أخذ العينات من مجموعة متنوعة من التوزيعات الإحصائية الحاسمة.
لماذا يعتبر أخذ العينات العشوائية مهمًا في عالم قائم على البيانات
قبل أن نتعمق في الكود، من الضروري فهم لماذا هذا الموضوع بالغ الأهمية. أخذ العينات العشوائية هو عملية اختيار مجموعة فرعية من الأفراد من داخل مجتمع إحصائي لتقدير خصائص المجتمع بأكمله. في سياق الحوسبة، يتعلق الأمر بتوليد بيانات تحاكي عملية معينة من العالم الحقيقي. فيما يلي بعض المجالات الرئيسية التي لا غنى عنها:
- المحاكاة: عندما يكون الحل التحليلي معقدًا للغاية، يمكننا محاكاة عملية آلاف أو ملايين المرات لفهم سلوكها. هذا هو أساس طرق مونت كارلو، المستخدمة في مجالات تتراوح من الفيزياء إلى التمويل.
- تعلم الآلة: العشوائية حاسمة لتهيئة أوزان النموذج، وتقسيم البيانات إلى مجموعات تدريب واختبار، وإنشاء بيانات اصطناعية لزيادة مجموعات البيانات الصغيرة، وفي الخوارزميات مثل الغابات العشوائية (Random Forests).
- الاستدلال الإحصائي: تعتمد تقنيات مثل التمهيد (bootstrapping) واختبارات التباديل (permutation tests) على أخذ العينات العشوائية لتقييم عدم اليقين في التقديرات واختبار الفرضيات دون وضع افتراضات قوية حول توزيع البيانات الأساسي.
- اختبار A/B: يمكن أن تساعد محاكاة سلوك المستخدم في ظل سيناريوهات مختلفة الشركات على تقدير التأثير المحتمل للتغيير وتحديد حجم العينة المطلوب لتجربة حية.
يوفر NumPy الأدوات اللازمة لأداء هذه المهام بكفاءة ودقة، مما يجعله مهارة أساسية لأي محترف بيانات.
جوهر العشوائية في NumPy: الكائن `Generator`
الطريقة الحديثة للتعامل مع توليد الأرقام العشوائية في NumPy (منذ الإصدار 1.17) هي من خلال الفئة `numpy.random.Generator`. هذا تحسين كبير على الطرق القديمة المتوارثة. للبدء، تقوم أولاً بإنشاء نسخة من كائن `Generator`.
الممارسة القياسية هي استخدام `numpy.random.default_rng()`:
import numpy as np
# Create a default Random Number Generator (RNG) instance
rng = np.random.default_rng()
# Now you can use this 'rng' object to generate random numbers
random_float = rng.random()
print(f"A random float: {random_float}")
القديم مقابل الجديد: `np.random.RandomState` مقابل `np.random.Generator`
قد ترى كودًا قديمًا يستخدم دوال مباشرة من `np.random`، مثل `np.random.rand()` أو `np.random.randint()`. تستخدم هذه الدوال نسخة عامة ووراثية من `RandomState`. وعلى الرغم من أنها لا تزال تعمل من أجل التوافق مع الإصدارات السابقة، إلا أن نهج `Generator` الحديث مفضل لعدة أسباب:
- خصائص إحصائية أفضل: يستخدم الكائن `Generator` الجديد خوارزمية حديثة وأكثر قوة لتوليد الأرقام شبه العشوائية (PCG64) التي تتمتع بخصائص إحصائية أفضل من خوارزمية Mersenne Twister (MT19937) الأقدم المستخدمة بواسطة `RandomState`.
- لا توجد حالة عامة: استخدام كائن `Generator` صريح (`rng` في مثالنا) يتجنب الاعتماد على حالة عامة مخفية. هذا يجعل الكود الخاص بك أكثر معيارية وقابلية للتنبؤ وأسهل في التصحيح، خاصة في التطبيقات أو المكتبات المعقدة.
- الأداء وواجهة برمجة التطبيقات: واجهة برمجة تطبيقات `Generator` أنظف وغالبًا ما تكون ذات أداء أفضل.
أفضل الممارسات: لجميع المشاريع الجديدة، ابدأ دائمًا بإنشاء مثيل لمولد باستخدام `rng = np.random.default_rng()`.
ضمان قابلية التكرار: قوة البذرة (Seed)
لا تولد أجهزة الكمبيوتر أرقامًا عشوائية حقيقية؛ بل تولد أرقامًا شبه عشوائية. يتم إنشاؤها بواسطة خوارزمية تنتج تسلسلًا من الأرقام يبدو عشوائيًا ولكنه، في الواقع، محدد بالكامل بواسطة قيمة أولية تسمى بذرة (seed).
هذه ميزة رائعة للعلم والتطوير. من خلال توفير نفس البذرة للمولد، يمكنك التأكد من الحصول على نفس التسلسل الدقيق من الأرقام "العشوائية" في كل مرة تقوم فيها بتشغيل الكود الخاص بك. هذا أمر حاسم لـ:
- البحث القابل للتكرار: يمكن لأي شخص تكرار نتائجك بدقة.
- التصحيح: إذا حدث خطأ بسبب قيمة عشوائية معينة، يمكنك تكراره باستمرار.
- المقارنات العادلة: عند مقارنة نماذج مختلفة، يمكنك التأكد من تدريبها واختبارها على نفس تقسيمات البيانات العشوائية.
إليك كيفية تعيين بذرة:
# Create a generator with a specific seed
rng_seeded = np.random.default_rng(seed=42)
# This will always produce the same first 5 random numbers
print("First run:", rng_seeded.random(5))
# If we create another generator with the same seed, we get the same result
rng_seeded_again = np.random.default_rng(seed=42)
print("Second run:", rng_seeded_again.random(5))
الأساسيات: طرق بسيطة لتوليد بيانات عشوائية
قبل الغوص في التوزيعات المعقدة، دعنا نغطي اللبنات الأساسية المتاحة في كائن `Generator`.
أرقام الفاصلة العائمة العشوائية: `random()`
تولد الدالة `rng.random()` أرقام فاصلة عائمة عشوائية في الفترة نصف المفتوحة `[0.0, 1.0)`. هذا يعني أن 0.0 قيمة ممكنة، لكن 1.0 ليست كذلك.
# Generate a single random float
float_val = rng.random()
print(f"Single float: {float_val}")
# Generate a 1D array of 5 random floats
float_array = rng.random(size=5)
print(f"1D array: {float_array}")
# Generate a 2x3 matrix of random floats
float_matrix = rng.random(size=(2, 3))
print(f"2x3 matrix:\n{float_matrix}")
أعداد صحيحة عشوائية: `integers()`
تعد الدالة `rng.integers()` طريقة متعددة الاستخدامات لتوليد أعداد صحيحة عشوائية. تأخذ وسيطين `low` و `high` لتعريف النطاق. النطاق يشمل `low` ويستثني `high`.
# Generate a single random integer between 0 (inclusive) and 10 (exclusive)
int_val = rng.integers(low=0, high=10)
print(f"Single integer: {int_val}")
# Generate a 1D array of 5 random integers between 50 and 100
int_array = rng.integers(low=50, high=100, size=5)
print(f"1D array of integers: {int_array}")
# If only one argument is provided, it's treated as the 'high' value (with low=0)
# Generate 4 integers between 0 and 5
int_array_simple = rng.integers(5, size=4)
print(f"Simpler syntax: {int_array_simple}")
أخذ العينات من بياناتك الخاصة: `choice()`
غالبًا، لا ترغب في توليد أرقام من الصفر، بل في أخذ عينات من مجموعة بيانات أو قائمة موجودة. تعد الدالة `rng.choice()` مثالية لذلك.
# Define our population
options = ["apple", "banana", "cherry", "date", "elderberry"]
# Select one random option
single_choice = rng.choice(options)
print(f"Single choice: {single_choice}")
# Select 3 random options (sampling with replacement by default)
multiple_choices = rng.choice(options, size=3)
print(f"Multiple choices (with replacement): {multiple_choices}")
# Select 3 unique options (sampling without replacement)
# Note: size cannot be larger than the population size
unique_choices = rng.choice(options, size=3, replace=False)
print(f"Unique choices (without replacement): {unique_choices}")
# You can also assign probabilities to each choice
probabilities = [0.1, 0.1, 0.6, 0.1, 0.1] # 'cherry' is much more likely
weighted_choice = rng.choice(options, p=probabilities)
print(f"Weighted choice: {weighted_choice}")
استكشاف التوزيعات الإحصائية الرئيسية باستخدام NumPy
الآن نصل إلى جوهر قوة أخذ العينات العشوائية في NumPy: القدرة على سحب عينات من مجموعة واسعة من التوزيعات الإحصائية. فهم هذه التوزيعات أساسي لنمذجة العالم من حولنا. سنغطي الأكثر شيوعًا وفائدة.
التوزيع المنتظم: كل نتيجة متساوية الاحتمال
ما هو: التوزيع المنتظم هو الأبسط. يصف موقفًا تكون فيه كل نتيجة ممكنة في نطاق مستمر متساوية الاحتمال. فكر في مؤشر دوار مثالي لديه فرصة متساوية للهبوط على أي زاوية.
متى تستخدمه: غالبًا ما يُستخدم كنقطة بداية عندما لا يكون لديك معرفة مسبقة تفضل نتيجة على أخرى. وهو أيضًا الأساس الذي تُشتق منه التوزيعات الأخرى الأكثر تعقيدًا غالبًا.
دالة NumPy: `rng.uniform(low=0.0, high=1.0, size=None)`
# Generate 10,000 random numbers from a uniform distribution between -10 and 10
uniform_data = rng.uniform(low=-10, high=10, size=10000)
# A histogram of this data should be roughly flat
import matplotlib.pyplot as plt
plt.hist(uniform_data, bins=50, density=True)
plt.title("Uniform Distribution")
plt.xlabel("Value")
plt.ylabel("Probability Density")
plt.show()
التوزيع الطبيعي (الغاوسي): منحنى الجرس
ما هو: ربما يكون التوزيع الأهم في جميع الإحصائيات. يتميز التوزيع الطبيعي بمنحنى متماثل على شكل جرس. تميل العديد من الظواهر الطبيعية، مثل طول الإنسان، وأخطاء القياس، وضغط الدم، إلى اتباع هذا التوزيع بسبب نظرية النهاية المركزية.
متى تستخدمه: استخدمه لنمذجة أي عملية تتوقع فيها أن تتجمع القيم حول متوسط مركزي، مع كون القيم المتطرفة نادرة.
دالة NumPy: `rng.normal(loc=0.0, scale=1.0, size=None)`
- `loc`: المتوسط ("المركز") للتوزيع.
- `scale`: الانحراف المعياري (مدى انتشار التوزيع).
# Simulate adult heights for a population of 10,000
# Assume a mean height of 175 cm and a standard deviation of 10 cm
heights = rng.normal(loc=175, scale=10, size=10000)
plt.hist(heights, bins=50, density=True)
plt.title("Normal Distribution of Simulated Heights")
plt.xlabel("Height (cm)")
plt.ylabel("Probability Density")
plt.show()
حالة خاصة هي التوزيع الطبيعي المعياري، الذي له متوسط 0 وانحراف معياري 1. يوفر NumPy اختصارًا مناسبًا لذلك: `rng.standard_normal(size=None)`.
توزيع ذو الحدين: سلسلة من التجارب بنتيجة "نعم/لا"
ما هو: ينمذج توزيع ذو الحدين عدد "النجاحات" في عدد ثابت من التجارب المستقلة، حيث تحتوي كل تجربة على نتيجتين محتملتين فقط (مثل، نجاح/فشل، رؤوس/ذيول، نعم/لا).
متى تستخدمه: لنمذجة سيناريوهات مثل عدد الرؤوس في 10 رميات عملة، أو عدد العناصر المعيبة في دفعة من 50، أو عدد العملاء الذين ينقرون على إعلان من أصل 100 مشاهد.
دالة NumPy: `rng.binomial(n, p, size=None)`
- `n`: عدد التجارب.
- `p`: احتمال النجاح في تجربة واحدة.
# Simulate flipping a fair coin (p=0.5) 20 times (n=20)
# and repeat this experiment 1000 times (size=1000)
# The result will be an array of 1000 numbers, each representing the number of heads in 20 flips.
num_heads = rng.binomial(n=20, p=0.5, size=1000)
plt.hist(num_heads, bins=range(0, 21), align='left', rwidth=0.8, density=True)
plt.title("Binomial Distribution: Number of Heads in 20 Coin Flips")
plt.xlabel("Number of Heads")
plt.ylabel("Probability")
plt.xticks(range(0, 21, 2))
plt.show()
توزيع بواسون: عد الأحداث في الزمان أو المكان
ما هو: ينمذج توزيع بواسون عدد المرات التي يحدث فيها حدث ضمن فترة زمنية أو مكانية محددة، على افتراض أن هذه الأحداث تحدث بمعدل متوسط ثابت معروف وهي مستقلة عن الوقت منذ الحدث الأخير.
متى تستخدمه: لنمذجة عدد العملاء الواصلين إلى متجر في ساعة، أو عدد الأخطاء المطبعية في صفحة، أو عدد المكالمات التي يتلقاها مركز اتصال في دقيقة.
دالة NumPy: `rng.poisson(lam=1.0, size=None)`
- `lam` (لامدا): متوسط معدل الأحداث لكل فترة.
# A cafe receives an average of 15 customers per hour (lam=15)
# Simulate the number of customers arriving each hour for 1000 hours
customer_arrivals = rng.poisson(lam=15, size=1000)
plt.hist(customer_arrivals, bins=range(0, 40), align='left', rwidth=0.8, density=True)
plt.title("Poisson Distribution: Customer Arrivals per Hour")
plt.xlabel("Number of Customers")
plt.ylabel("Probability")
plt.show()
التوزيع الأسي: الوقت بين الأحداث
ما هو: يرتبط التوزيع الأسي ارتباطًا وثيقًا بتوزيع بواسون. إذا وقعت الأحداث وفقًا لعملية بواسون، فإن الوقت بين الأحداث المتتالية يتبع توزيعًا أسيًا.
متى تستخدمه: لنمذجة الوقت حتى وصول العميل التالي، أو العمر الافتراضي لمصباح كهربائي، أو الوقت حتى التحلل الإشعاعي التالي.
دالة NumPy: `rng.exponential(scale=1.0, size=None)`
- `scale`: هذا هو معكوس معلمة المعدل (لامدا) من توزيع بواسون. `scale = 1 / lam`. لذا، إذا كان المعدل 15 عميلًا في الساعة، فإن متوسط الوقت بين العملاء هو 1/15 من الساعة.
# If a cafe receives 15 customers per hour, the scale is 1/15 hours
# Let's convert this to minutes: (1/15) * 60 = 4 minutes on average between customers
scale_minutes = 4
time_between_arrivals = rng.exponential(scale=scale_minutes, size=1000)
plt.hist(time_between_arrivals, bins=50, density=True)
plt.title("Exponential Distribution: Time Between Customer Arrivals")
plt.xlabel("Minutes")
plt.ylabel("Probability Density")
plt.show()
التوزيع اللوغاريتمي الطبيعي: عندما يكون اللوغاريتم طبيعيًا
ما هو: التوزيع اللوغاريتمي الطبيعي هو توزيع احتمالي مستمر لمتغير عشوائي يكون لوغاريتمه موزعًا طبيعيًا. يكون المنحنى الناتج ملتويًا نحو اليمين، مما يعني أن له ذيلًا طويلًا إلى اليمين.
متى تستخدمه: هذا التوزيع ممتاز لنمذجة الكميات التي تكون دائمًا موجبة والتي تمتد قيمها على عدة مراتب من الحجم. تشمل الأمثلة الشائعة الدخل الشخصي، أسعار الأسهم، وتعداد السكان في المدن.
دالة NumPy: `rng.lognormal(mean=0.0, sigma=1.0, size=None)`
- `mean`: متوسط التوزيع الطبيعي الأساسي (وليس متوسط الإخراج اللوغاريتمي الطبيعي).
- `sigma`: الانحراف المعياري للتوزيع الطبيعي الأساسي.
# Simulate income distribution, which is often log-normally distributed
# These parameters are for the underlying log scale
income_data = rng.lognormal(mean=np.log(50000), sigma=0.5, size=10000)
plt.hist(income_data, bins=100, density=True, range=(0, 200000)) # Cap range for better viz
plt.title("Lognormal Distribution: Simulated Annual Incomes")
plt.xlabel("Income")
plt.ylabel("Probability Density")
plt.show()
تطبيقات عملية في علم البيانات وما بعده
فهم كيفية توليد هذه البيانات ليس سوى نصف المعركة. القوة الحقيقية تأتي من تطبيقها.
المحاكاة والنمذجة: طرق مونت كارلو
تخيل أنك تريد تقدير قيمة باي (Pi). يمكنك فعل ذلك باستخدام أخذ العينات العشوائية! الفكرة هي رسم دائرة داخل مربع. ثم، توليد آلاف النقاط العشوائية داخل المربع. ستكون نسبة النقاط التي تقع داخل الدائرة إلى العدد الإجمالي للنقاط متناسبة مع نسبة مساحة الدائرة إلى مساحة المربع، والتي يمكن استخدامها لحساب قيمة باي.
هذا مثال بسيط لطريقة مونت كارلو: استخدام أخذ العينات العشوائية لحل المشكلات المحددة. في العالم الحقيقي، تُستخدم هذه الطريقة لنمذجة مخاطر المحافظ المالية، وفي فيزياء الجسيمات، والجداول الزمنية المعقدة للمشاريع.
أسس تعلم الآلة
في تعلم الآلة، العشوائية المُنظَّمة موجودة في كل مكان:
- تهيئة الأوزان: عادة ما يتم تهيئة أوزان الشبكة العصبية بأرقام عشوائية صغيرة مستخلصة من توزيع طبيعي أو منتظم لكسر التماثل والسماح للشبكة بالتعلم.
- زيادة البيانات: للتعرف على الصور، يمكنك إنشاء بيانات تدريب جديدة عن طريق تطبيق تدويرات أو تحولات أو تغييرات لون عشوائية صغيرة على الصور الموجودة.
- البيانات الاصطناعية: إذا كان لديك مجموعة بيانات صغيرة، يمكنك أحيانًا توليد نقاط بيانات جديدة وواقعية عن طريق أخذ عينات من التوزيعات التي تحاكي بياناتك الحالية، مما يساعد على منع التجاوز (overfitting).
- التنظيم (Regularization): تقنيات مثل الإسقاط (Dropout) تقوم بإلغاء تنشيط جزء من الخلايا العصبية عشوائيًا أثناء التدريب لجعل الشبكة أكثر قوة.
اختبار A/B والاستدلال الإحصائي
افترض أنك أجريت اختبار A/B ووجدت أن تصميم موقع الويب الجديد الخاص بك لديه معدل تحويل أعلى بنسبة 5%. هل هذا تحسن حقيقي أم مجرد حظ عشوائي؟ يمكنك استخدام المحاكاة لمعرفة ذلك. من خلال إنشاء توزيعين ذوي الحدين بنفس معدل التحويل الأساسي، يمكنك محاكاة آلاف اختبارات A/B لمعرفة عدد المرات التي يحدث فيها فرق بنسبة 5% أو أكثر بالصدفة وحدها. هذا يساعد على بناء الفهم لمفاهيم مثل قيم p والدلالة الإحصائية.
أفضل الممارسات لأخذ العينات العشوائية في مشاريعك
لاستخدام هذه الأدوات بفعالية واحترافية، ضع هذه الممارسات الأفضل في الاعتبار:
- استخدم دائمًا المولد الحديث: ابدأ نصوصك البرمجية بـ `rng = np.random.default_rng()` . تجنب دوال `np.random.*` القديمة في الكود الجديد.
- استخدم البذرة (Seed) للقابلية للتكرار: لأي تحليل أو تجربة أو تقرير، استخدم بذرة لمولدك (`np.random.default_rng(seed=...)`). هذا أمر غير قابل للتفاوض لعمل موثوق وقابل للتحقق.
- اختر التوزيع الصحيح: خذ وقتًا للتفكير في العملية الواقعية التي تقوم بنمذجتها. هل هي سلسلة من التجارب بنتيجة نعم/لا (ذو الحدين)؟ هل هو الوقت بين الأحداث (الأسّي)؟ هل هو مقياس يتجمع حول متوسط (طبيعي)؟ الاختيار الصحيح حاسم لمحاكاة ذات مغزى.
- استفد من التوجيه (Vectorization): NumPy سريع لأنه ينفذ العمليات على المصفوفات بأكملها دفعة واحدة. قم بتوليد جميع الأرقام العشوائية التي تحتاجها في استدعاء واحد (باستخدام معلمة `size`) بدلاً من استخدام حلقة.
- تصوّر، تصوّر، تصوّر: بعد توليد البيانات، قم دائمًا بإنشاء مخطط بياني (histogram) أو أي رسم بياني آخر. يوفر هذا فحصًا سريعًا للتأكد من أن شكل البيانات يتطابق مع التوزيع الذي قصدت أخذ العينات منه.
الخلاصة: من العشوائية إلى البصيرة
لقد سافرنا من المفهوم الأساسي لمولد الأرقام العشوائية ذي البذرة إلى التطبيق العملي لأخذ العينات من مجموعة متنوعة من التوزيعات الإحصائية. إن إتقان وحدة `random` في NumPy هو أكثر من مجرد تمرين تقني؛ إنه يتعلق بفتح طريقة جديدة لفهم العالم ونمذجته. إنه يمنحك القوة لمحاكاة الأنظمة، واختبار الفرضيات، وبناء نماذج تعلم آلة أكثر قوة وذكاءً.
القدرة على توليد بيانات تحاكي الواقع هي مهارة أساسية في مجموعة أدوات عالم البيانات الحديث. من خلال فهم خصائص هذه التوزيعات والأدوات القوية والفعالة التي يوفرها NumPy، يمكنك الانتقال من تحليل البيانات البسيط إلى النمذجة والمحاكاة المتطورة، وتحويل العشوائية المنظمة إلى بصيرة عميقة.