اذهب إلى المحتوى

ما بعد مكتبة NumPy في بايثون


Rahaf Hammed

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

لنفكر على سبيل المثال، في تمرين مثير للاهتمام على النحو التالي:

اقتباس

اكتب الشيفرة المختصرة لحساب جميع التخصيصات "القانونية" لأربعة أرصدة stocks بحيث تكون التخصيصات في كتلة واحدة 1.0، ومجموع التخصيصات هو 10.0.

جُمعت إجابات مختلفة من المجتمع، وأسفرت الحلول المقترحة عن نتائج مفاجئة. لكن لنبدأ بكتابة التعليمات البرمجية بلغة بايثون لحل هذا التمرين:

def solution_1():
    # Brute force
    # 14641 (=11*11*11*11) iterations & tests
    Z = []
    for i in range(11):
        for j in range(11):
            for k in range(11):
                for l in range(11):
                    if i+j+k+l == 10:
                        Z.append((i,j,k,l))
    return Z

هذا الحل هو الأبطأ لأنه يتطلب 4 حلقات، كما أنه يختبر جميع المجموعات المختلفة والبالغ عددها 11641 والمكونة من 4 أعداد صحيحة بين 0 و 10 للاحتفاظ فقط بالمجموعات التي يكون مجموعها مساوٍ إلى 10. يمكننا طبعًا التخلص من الحلقات باستخدام أداة itertools، لكن التعليمات تظل بطيئة:

import itertools as it

def solution_2():
    # Itertools
    # 14641 (=11*11*11*11) iterations & tests
    return [(i,j,k,l)
            for i,j,k,l in it.product(range(11),repeat=4) if i+j+k+l == 10]

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

def solution_3():
    return [(a, b, c, (10 - a - b - c))
            for a in range(11)
            for b in range(11 - a)
            for c in range(11 - a - b)]

يستخدم كاتب هذا التمرين أفضل حل بالاعتماد على NumPy استراتيجية مختلفة مع مجموعة مقيدة من الاختبارات:

def solution_4():
    X123 = np.indices((11,11,11)).reshape(3,11*11*11)
    X4 = 10 - X123.sum(axis=0)
    return np.vstack((X123, X4)).T[X4 > -1]

إذا قيّمنا مدة تنفيذ هذه التوابع نحصل على:

>>> timeit("solution_1()", globals())
100 loops, best of 3: 1.9 msec per loop
>>> timeit("solution_2()", globals())
100 loops, best of 3: 1.67 msec per loop
>>> timeit("solution_3()", globals())
1000 loops, best of 3: 60.4 usec per loop
>>> timeit("solution_4()", globals())
1000 loops, best of 3: 54.4 usec per loop

كما تلاحظ حل Numpy هو الأسرع ولكن الحل باستخدام بايثون قابل للمقارنة. سنحاول الآن إضافة تعديل صغير على حل بايثون:

def solution_3_bis():
    return ((a, b, c, (10 - a - b - c))
            for a in range(11)
           for b in range(11 - a)
            for c in range(11 - a - b))

وسنحصل على النتيجة:

>>> timeit("solution_3_bis()", globals())
10000 loops, best of 3: 0.643 usec per loop

اكتسبنا هنا عاملًا قدره 100 فقط عن طريق استبدال قوسين مربعين بأقواس عادية. كيف يعقل ذلك؟ يمكن العثور على التفسير من خلال النظر في نوع الكائن المُعاد:

>>> print(type(solution_3()))
<class 'list'>
>>> print(type(solution_3_bis()))
<class 'generator'>

يعيد التابع ()solution_3_bis مولدًا generator يمكن استخدامه لإنشاء القائمة الكاملة أو للتكرار على جميع العناصر المختلفة. على أي حال، تأتي عملية التسريع الضخمة من عدم الاستقرار في القائمة الكاملة، وبالتالي من المهم أن نتساءل عما إذا كنت بحاجة إلى مثيل فعلي لنتيجتك أو إذا كان المولد البسيط ينفذ المهمة.

مكتبة Numpy وأخواتها

هناك العديد من حزم بايثون الأخرى إلى جانب Numpy، والتي تستحق النظر لأنها تتناول أصناف مشابهة ولكن مختلفة من المشكلات باستخدام التقنيات المختلفة (التجميع، الجهاز الافتراضي، وحدة معالجة الرسوميات GPU، الضغط، إلخ). قد تكون حزمة واحدة أفضل من الأخرى اعتمادًا على مشكلتك المحددة وأجهزتك الخاصة. دعونا نوضح استخدامهم من خلال مثال بسيط جدًا، إذ نريد حساب تعبير بناءً على متجهين عددين float:

import numpy as np
a = np.random.uniform(0, 1, 1000).astype(np.float32)
b = np.random.uniform(0, 1, 1000).astype(np.float32)
c = 2*a + 3*b

حزمة NumExpr

توفر حزمة numexpr إجراءات للتقييم السريع لتعبيرات المصفوفة من ناحية العناصر باستخدام جهاز افتراضي يعتمد على المتجهات vector-based، وهي مشابهة لحزمة SciPy، لكنها لا تتطلب خطوة ترجمة منفصلة لشيفرات C أو ++C.

import numpy as np
import numexpr as ne

a = np.random.uniform(0, 1, 1000).astype(np.float32)
b = np.random.uniform(0, 1, 1000).astype(np.float32)
c = ne.evaluate("2*a + 3*b")

مصرف Cython

Cython هو مصرّف ثابت محسن لكل من لغة برمجة بايثون ولغة كايثون Cython الموسعة (استنادًا إلى Pyrex). يجعل مصرّف كايثون كتابة امتدادات C لبايثون سهلة مثل بايثون نفسها.

import numpy as np

def evaluate(np.ndarray a, np.ndarray b):
    cdef int i
    cdef np.ndarray c = np.zeros_like(a)
    for i in range(a.size):
        c[i] = 2*a[i] + 3*b[i]
    return c

a = np.random.uniform(0, 1, 1000).astype(np.float32)
b = np.random.uniform(0, 1, 1000).astype(np.float32)
c = evaluate(a, b)

مصرف Numba

يمنحك مصرّف Numba القدرة على تسريع تطبيقاتك من خلال وظائف عالية الأداء مكتوبة مباشرةً بلغة بايثون. مع بعض التعليقات التوضيحية يمكن تجميع تعليمات بايثون array-oriented و math-heavy في الوقت المناسب لتعليمات الآلة الأصلية، على غرار C و ++C وفورتران Fortran، دون الحاجة إلى تبديل اللغات أو مترجم بايثون.

from numba import jit
import numpy as np

@jit
def evaluate(np.ndarray a, np.ndarray b):
    c = np.zeros_like(a)
    for i in range(a.size):
        c[i] = 2*a[i] + 3*b[i]
    return c

a = np.random.uniform(0, 1, 1000).astype(np.float32)
b = np.random.uniform(0, 1, 1000).astype(np.float32)
c = evaluate(a, b)

مكتبة Theano

مكتبة Theano هي مكتبة بايثون تتيح لك تحديد التعبيرات الرياضية التي تتضمن مصفوفات متعددة الأبعاد وتحسينها وتقييمها بكفاءة. تتميز Theano بالتكامل الوثيق مع الاستخدام غير المستقر والشفاف لوحدة معالجة الرسوميات GPU ، والتمايز الرمزي symbolic differentiation الفعال، وتحسينات السرعة والثبات، وإنشاء شيفرة C الديناميكية، واختبار الوحدة الشامل والتحقق الذاتي.

import numpy as np
import theano.tensor as T

x = T.fvector('x')
y = T.fvector('y')
z = 2*x + 3*y
f = function([x, y], z)

a = np.random.uniform(0, 1, 1000).astype(np.float32)
b = np.random.uniform(0, 1, 1000).astype(np.float32)
c = f(a, b)

PyCUDA

يتيح لك PyCUDA الوصول إلى واجهة برمجة تطبيقات الحساب المتوازي CUDA الخاصة بشركة Nvidia من بايثون.

import numpy as np
import pycuda.autoinit
import pycuda.driver as drv
from pycuda.compiler import SourceModule

mod = SourceModule("""
    __global__ void evaluate(float *c, float *a, float *b)
    {
      const int i = threadIdx.x;
      c[i] = 2*a[i] + 3*b[i];
    }
""")

evaluate = mod.get_function("evaluate")

a = np.random.uniform(0, 1, 1000).astype(np.float32)
b = np.random.uniform(0, 1, 1000).astype(np.float32)
c = np.zeros_like(a)

evaluate(drv.Out(c), drv.In(a), drv.In(b), block=(400,1,1), grid=(1,1))

مكتبة PyOpenCL

تتيح لك PyOpenCL الوصول إلى وحدات معالجة الرسومات وأجهزة الحوسبة المتوازية الأخرى من بايثون.

import numpy as np
import pyopencl as cl

a = np.random.uniform(0, 1, 1000).astype(np.float32)
b = np.random.uniform(0, 1, 1000).astype(np.float32)
c = np.empty_like(a)

ctx = cl.create_some_context()
queue = cl.CommandQueue(ctx)

mf = cl.mem_flags
gpu_a = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=a)
gpu_b = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=b)

evaluate = cl.Program(ctx, """
    __kernel void evaluate(__global const float *gpu_a;
                           __global const float *gpu_b;
                           __global       float *gpu_c)
    {
        int gid = get_global_id(0);
        gpu_c[gid] = 2*gpu_a[gid] + 3*gpu_b[gid];
    }
""").build()

gpu_c = cl.Buffer(ctx, mf.WRITE_ONLY, a.nbytes)
evaluate.evaluate(queue, a.shape, None, gpu_a, gpu_b, gpu_c)
cl.enqueue_copy(queue, c, gpu_c)

Scipy وأخواتها

إذا كان هناك عدة حزم إضافية لمكتبة Numpy، فهناك الملايين من الحزم الإضافية لـ scipy؛ إذ ربما يكون لكل مجال من مجالات العلوم مجموعته الخاصة، ومعظم الأمثلة التي كنا ندرسها حتى الآن كان من الممكن حلها في استدعائين أو ثلاثة استدعاءات باستخدام الحزمة ذات الصلة. لكن طبعًا لم يكن هذا هو الهدف. تعد برمجة الوظائف بنفسك عمومًا تمرينًا جيدًا إذا كان لديك بعض وقت الفراغ، وتتمثل أكبر صعوبة في هذه المرحلة في العثور على هذه الحزم ذات الصلة. فيما يلي قائمة قصيرة جدًا من الحزم التي جرى صيانتها واختبارها جيدًا والتي قد تبسط حياتك العلمية (اعتمادًا على مجالك). هناك طبعًا الكثير من الخيارات وذلك حسب احتياجاتك الخاصة، ومن المحتمل ألا تكون مضطرًا إلى برمجة كل شيء بنفسك.

مكتبة scikit-learn

scikit-learn هي مكتبة تعلم آلي مجانية للغة برمجة بايثون، وتتميز بخوارزميات تصنيف وانحدار وتجميع مختلفة بما في ذلك دعم خوارزمية أجهزة المتجهات vector machines، والغابات العشوائية random forests وتعزيز التدرج gradient boosting و k-means و DBSCAN، وهذه الحزمة مصممة للتفاعل مع مكتبات بايثون الرقمية والعلمية numpy و SciPy.

مكتبة scikit-image

scikit-image عبارة عن حزمة بايثون مخصصة لمعالجة الصور، واستخدام المصفوفات المتداخلة مثل كائنات صور. يصف هذا المقال كيفية استخدام scikit-image في مهام معالجة الصور المختلفة، ويركز على الارتباط بوحدات بايثون النمطية العلمية الأخرى مثل numpy و SciPy.

مكتبة SymPy

SymPy هي مكتبة بايثون للرياضيات الرمزية، وتهدف إلى أن تصبح كل ميزات النظام الحاسوبي جبرية -أو اختصارًا CAS- مع الحفاظ على الشيفرة بسيطة قدر الإمكان حتى تكون مفهومة وقابلةً للتوسّع بسهولة. كُتبت كامل مكتبة SymPy بلغة بايثون.

حزمة Astropy

مشروع Astropy هو جهد مجتمعي لتطوير حزمة أساسية واحدة لعلم الفلك astronomy في بايثون وتعزيز إمكانية التشغيل البيني بين حزم علم الفلك في بايثون.

حزمة Cartopy

Cartopy هي حزمة بايثون مصممة لتسهيل رسم الخرائط لتحليل البيانات والتصوير. تستفيد Cartopy من المكتبات القوية PROJ.4 والمكتوبة والمحددة الشكل ولها واجهة رسم بسيطة وبديهية لحزمة matplotlib لإنشاء خرائط جاهزة النشر.

محاكي Brian

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

مكتبة Glumpy

Glumpy هي مكتبة تصور تفاعلي مبنية على OpenGL في بايثون، وهدفها هو تسهيل إنشاء تمثيلات مرئية سريعة وقابلة للتطوير وجميلة وتفاعلية وديناميكية.

الخلاصة

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

ترجمة -وبتصرّف- للفصل Beyond NumPy من كتاب From Python to Numpy لصاحبه Nicolas P. Rougier.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

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

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...