معرفی Numpy؛ ساده و گیرا

سلام. توی این مقاله می خوام کتابخانه نامپای (Numpy) رو معرفی کنم. توی مقاله قبلی بهتون راجع به اینکه زبان برنامه نویسی پایتون و کتابخانه هاش برای ما مهندس ها مثل یک هدیه می مونه توضیح دادم. لب کلام اون مقاله این بود که ما با کمک پایتون می تونیم با سرعت و بهره وری بیشتری با داده های حجیم (big data) کار کنیم. برای رسیدن به این نتیجه، کتابخانه نامپای پای ثابت کار ماست.

اولین شگفتی نامپای اشیایی هستند به اسم ndarray؛ یا همون آرایه های نامپای که ممکنه قبلا شنیده باشید. به کمک نامپای می تونیم اشیایی بسازیم که در واقع آرایه هایی چندبعدی از داده ها هستند. یه چیزی شبیه ماتریس ها یا تانسورها. (یادش بخیر مقاومت مصالح و تانسور تنش ) البته شاید پیش خودتون بگید اینکه چیز مهمی نیست. ما خودمون لیست و تاپل مذگان داریم که کلی هم باهاش آرایه های چند بعدی و تو در تو ساختیم! من این حرف شما رو رد نمی کنم. در حالت عادی و برای ذخیره کردن تعداد کمی داده و اعمال عملیات های جزئی روی اون، خودم هم از لیست و مشابه اون (iterable objects) استفاده می کنم. اما اگر حجم داده ها بالا بره چی؟ اگر سرعت محاسبات مهم باشه چی؟

« ادعا می شه که آرایه های پایتون تا 50 برابر سریعتر از آرایه های ساخته شده از لیست ها هستند».

کتابخانه نامپای علاوه بر فراهم کردن آرایه ها، یک سری عملگرها و متدهایی برای عملیات ها و محاسبات سریع روی آرایه ها در اختیار میذاره. عملیات هایی نظیر: محاسبات ریاضی، منطقی، تغییر توزیع وشکل داده ها، مرتب سازی داده، کار با تبدیل فوریه، جبر خطی، آمار و احتمال، ساخت توزیع داده ها و ایجاد انواع داده های رندم و نمایش اون ها.

در واقع ndarray یه کلاس داخل کتابخانه نامپای هست که شامل خواص و متدهای مشخصیه و اشیای آرایه ای از همین کلاس الگو می گیرند و ساخته می شوند.

شمای آرایه های نامپای
شمای آرایه های نامپای


حالا که یه معرفی اجمالی از نامپای داشتیم (که امیدوارم شما رو تا اینجای متن شارژ نگه داشته باشه) بریم ریزتر تفاوت آرایه های نامپای رو با آرایه های استاندارد پایتون بررسی کنیم:

  • ما وقتی یه لیست پایتونی می سازیم، طول این لیست در حین برنامه می تونه کم و زیاد بشه، اما ndarray ها اینطوری نیستند. ما وقتی مثلا یه آرایه 4در4 می سازیم، ابعاد این آرایه همیشه ثابت هست و در صورت اعمال تغییر، در واقع داریم این آرایه رو حذف می کنیم و یکی دیگه می سازیم.
  • بر خلاف لیست های پایتون، اجزای آرایه های نامپای همه باید از یک نوع داده باشند؛ مثلا همه از نوع int. ضمن اینکه مثلا اگر یه آرایه دوبعدی دارید، طول آرایه های یک بعدی زیر مجموعه اون، همه باید یکسان باشه. یعنی نمی شه که سطر اول 4، سطر دوم 5 و ... عضو داشته باشند. البته یه "راه در رو" داره که اینجا مجال توضیحش نیست. توی کامنت در خدمت دوستان هستم ؛)
  • متدها و عملگرهایی که زیر مجموعه آرایه های نامپای هستند، علاوه بر این که بهره وری و سرعت بالاتری رو به ارمغان میارن، طول کدهای ما رو هم کمتر می کنند. شما در نظر بگیر برنامه نویسی با پایتون خودش یه جور خلاصه نویسیه، با نامپای از اونم خلاصه تر میشه در حالی که حجم داده ها هم بزرگتره!
  • کلی از برنامه های علمی و مهندسی که امروزه نوشته و منتشر می شن از آرایه های نامپای استفاده می کنند. شاید ورودی ها رو از شما به صورت لیست بگیرن (اگر فایل نگیرن!) اما تبدیلش می کنند به ndarray و بعد عملیات رو روش انجام می دن. نهایتا هم یه ndarray بر می گردونند که طبیعتا برای کار باهاش محتاج نامپای هستیم.

البته با ذکر مثال درک این موارد بالا راحت تره برامون. این مثال رو از سایت خود نامپای کش رفتم:

اگر دوتا لیست از اعداد داشته باشیم و بخوایم درایه های متناظر رو با هم ضرب کنیم و داخل یه لیست جدید بریزیم، با روش های استاندارد پایتون احتمالا یه کدی داریم مثل این:

c = []
for i in range(len(a)):
       c.append(a[i]*b[i])

که اگر این دوتا لیست یه میلیون درایه داشته باشند، لوپ زدن خیلی از ما هزینه محاسباتی میگیره. خوب تو دلتون می گید:"چه کاریه؟ میرم با C می نویسم که سریع تره!" هر جو راحتی. اینم از کد همین مساله با سینتکس C(که البته خلاصش کردم):

for (i = 0; i < rows; i++): {
       c[i] = a[i]*b[i];
}

البته ما با C شاید هزینه های محاسباتی رو کاهش دادیم، اما خودمون رو از مزایای کدنویسی با پایتون محروم کردیم. بگذریم؛ اگر آرایه های ورودی ما 2 بعدی بودن چی؟ باید کدمون رو اینطوری گسترش بدیم:

for (i = 0; i < rows; i++): {
        for (j = 0; j < columns; j++): {
               c[i][j] = a[i][j]*b[i][j];
         }
}

خب هر چی مثال جانبی زدم بسه دیگه، بریم سر اصل مطلب؛ خانم ها و آقایان، این شما و اینم کد معادل این مسأله به کمک نامپای:

c = a * b

الان توضیحی می مونه؟ نه انصافا میخوای دیگه چی از جیب من درآد؟ با این عملگر ما فارغ از shape آرایه ها، درایه های متناظر رو ضرب می کنیم و ازش یه آرایه جدید می سازیم.

همه این توضیحات یعنی الهه نامپای هم سرعت C رو داره و هم جذابیت پایتون.



حالا اصلا چرا اینقدر نامپای سریعه؟ چون:

همه چی رو بردار می بینه (Vectorization) و قابلیت توزیع داره.( broadcasting)

یه بردار با درایه هاش توصیف میشه. وقتی بردار رو با زوایای فضایی و طول بردار توصیف می کنیم، کارمون خیلی سخت تر از وقتیه که اون رو با بردارهای یکه معرفی می کنیم. در واقع دلیل اینکه ما خیلی صریح هیچ حلقه تکرار یا ایندکس گذاری و امثالهم رو توی محاسبات نامپای نمی بینیم دلیلش همین برداری کردن داده هاست. (البته ضمن اجرای کد، لوپ و اینا هست که ما درگیرش نیستیم و اصن به ما چه!) خلاصه که مزایای برداری سازی از این قراره:

  • خوانایی کد بهتر میشه.
  • کد کمتر، باگ کمتر، زندگی شادتر :)
  • ظاهر کدمون خیلی شبیه معادلات ریاضیاتی و کانتنیومی می شه که کد کردن مسائل رو راحت میکنه.
  • حلقه های for کمتر

منظورم از قابلیت توزیعی (ترجمه بهتری داشتید پیشنهاد بدید لطفا) اینه که عملگرها به صورت ضمنی رفتار درایه به درایه دارن. مثلا توی مساله بالا، کد ما کار نداشت که ابعاد a و b چقدره! اون ها رو یه سری درایه می دید که قرار دو به دو ضرب بشن.

پس نامپای با کلاس ndarray با برداری کردن داده ها و توزیع پذیری متدها و عملگرها، سرعت، خوانایی، بهره وری و سادگی نوشتاری رو به همه ما هدیه میده.

راستی شما ترغیب شدید نامپای رو پایتون یاد بگیرید؟ خیلی خوب میشه برای بهتر شدن کارم نظر شما رو داشته باشم.