ضرب ماتریس ها در جاوا – به زبان ساده
در این راهنما به بررسی روش ضرب کردن دو ماتریس در هم در زبان برنامهنویسی جاوا خواهیم پرداخت. از آنجا که مفهوم ماتریس به صورت بومی در این زبان برنامهنویسی وجود ندارد، باید خودمان آن را پیادهسازی کنیم. همچنین با چند کتابخانه کار میکنیم تا با روش مدیریت دستکاری ماتریس در جاوا آشنا شویم. در نهایت به بررسی عملکرد راهحلهای مختلف نیز میپردازیم تا سریعترین رویکرد را بشناسیم.
طراحی مثال
در ابتدا یک مثال آماده میکنیم که بتوانیم در سراسر این راهنما به آن ارجاع بدهیم. قبل از هر چیز یک ماتریس 2×3 را تصور کنید.
اکنون ماتریس دومی را نیز تصور کنید که دو ردیف و چهار ستون دارد:
سپس با ضرب ماتریس اول در ماتریس دوم، یک ماتریس 4×3 حاصل میشود:
توجه کنید که نتیجه حاصلضرب از طریق ضرب آرایههای هر کدام از ماتریسها با فرمول زیر به دست آمده است:
که در آن r تعداد ردیفهای ماتریس A، c تعداد ستونهای ماتریس B و n تعداد ستونهای ماتریس A است که باید با تعداد ردیفهای ماتریس B مطابقت داشته باشد.
ضرب ماتریس ها در جاوا
در این بخش با روش کدنویسی ضرب ماتریسها آشنا میشویم.
پیادهسازی
کار خود را با پیادهسازی شخصی خودمان از ماتریسها آغاز میکنیم. ما این پیادهسازی را ساده حفظ میکنیم و صرفاً از دو آرایه دابل دوبعدی استفاده میکنیم.
اینها دو ماتریس مثال ما هستند در ادامه ماتریس نتیجه ضرب آنها را نیز ایجاد میکنیم:
اکنون که همه چیز آماده شده است، میتوانیم الگوریتم ضرب را پیادهسازی کنیم. ابتدا یک آرایه نتیجه خالی ایجاد میکنیم و حلقهای روی سلولهای آن تعریف میکنیم تا مقدار مورد نظر خود در هر یک از آنها ذخیره کنیم:
در نهایت، محاسبه یک سلول منفرد را پیادهسازی میکنیم. به این منظور از فرمولی که قبلاً ارائه کردیم استفاده میکنیم:
در نهایت، به بررسی نتیجه تطبیق الگوریتم با نتیجه مورد نظر خود میپردازیم:
EJML
نخستین کتابخانهای که بررسی میکنیم EJML است که اختصاری برای عبارت «کتابخانه کارآمد ماتریس جاوا» (Efficient Java Matrix Library) است. در زمان نگارش این مقاله این کتابخانه یکی از بهروزترین کتابخانههای ماتریس جاوا بوده است. هدف این کتابخانه آن است که تا حد امکان از نظر محاسبه و مصرف حافظه بهینه باشد.
ما باید وابستگی به کتابخانه را در فایل pom.xml خود اضافه کنیم:
چنان که مشاهده میکنید ما تقریباً از همان الگوی قبلی استفاده کردهایم، یعنی دو ماتریس بر اساس مثال خود ساختیم و نتیجه دستکاری آنها را با موردی که قبلاً محاسبه شده بود مطابقت دادیم.
بنابراین ماتریسهای خود را با استفاده از EJML میسازیم. به این منظور از کلاس SimpleMatrix استفاده میکنیم که این کتابخانه را در اختیار ما قرار میدهد.
این کلاس میتواند یک آرایه دابل دوبعدی به عنوان ورودی برای سازنده خود بگیرد:
اکنون ماتریس مورد نظر خود را برای دستکاری تعریف میکنیم:
اینک که همه چیز راهاندازی شده است به بررسی روش دستکاری دو ماتریس با هم میپردازیم. کلاس SimpleMatrix یک متد ()mult دارد که کلاس SimpleMatrix دیگری را به عنوان پارامتر میگیرد و حاصلضرب دو ماتریس را بازمیگرداند:
در ادامه بررسی میکنیم که آیا نتیجه به دست آمده با نتیجه مورد انتظار مطابقت دارد یا نه.
از آنجا که SimpleMatrix متد ()equals را override نمیکند، نمیتوانیم برای بررسی، صرفاً به آن تکیه کنیم. اما این کلاس یک متد جایگزین به نام ()isIdentical را نیز ارائه کرده است که نه تنها پارامتر ماتریس دیگر، بلکه یک پارامتر تلرانس خطای double نیز بازگشت میدهد که با استفاده از آن میتوان اختلافهای کوچک ناشی از دقت double را نادیده گرفت.
بدین ترتیب حاصلضرب ماتریس با کتابخانه EJML به دست میآید. در ادامه کتابخانههای دیگر را مورد بررسی قرار میدهیم:
ND4J
اکنون به بررسی کتابخانه ND4J میپردازیم. ND4J یک کتابخانه محاسباتی است و بخشی از پروژه deeplearning4j محسوب میشود. ND4J علاوه بر قابلیتهای مختلف، امکان محاسبات ماتریس را نیز ارائه کرده است. قبل از هر چیز باید وابستگی این کتابخانه را به دست آوریم:
توجه داشته باشید که ما از نسخه بتا استفاده میکنیم، زیرا به نظر میرسد که انتشار GA مقداری باگ دارد.
برای این که همه چیز ساده بماند نما دو آرایه دابل دوبعدی را بازنویسی نمیکنیم و صرفاً روی روش استفاده آنها در هر کتابخانه تمرکز میکنیم. بدین ترتیب در ND4J باید یک INDArray ایجاد کنیم. به این منظور متد ()Nd4j.create را فراخوانی کرده و آن را به یک آرایه دابل ارسال میکنیم که نماینده ماتریس ما است:
همانند بخش قبلی، سه ماتریس میسازیم که دو ماتریس قرار است در هم ضرب شوند و یکی دیگر نتیجه مورد انتظار است.
پس از آن باید عمل ضرب بین دو ماتریس اول را عملاً با استفاده از متد ()INDArray.mmul اجرا کنیم:
سپس دوباره بررسی میکنیم که نتیجه واقعی با نتیجه مورد انتظار مطابقت دارد یا نه. این بار میتوانیم روی بررسی برابری تکیه کنیم:
این نتیجه نشان میدهد که کتابخانه ND4J میتواند برای اجرای محاسبات ماتریسی مورد استفاده قرار گیرد.
Apache Commons
در این بخش به بررسی ماژول Math3 کتابخانه Apache Commons میپردازیم که محاسبات ریاضیاتی شامل عملیات دستکاری ماتریس را در اختیار ما قرار میدهد. این بار نیز باید وابستگی را در pom.xml تعیین کنیم:
زمانی که این کتابخانه راهاندازی شد، میتوانیم از اینترفیس RealMatrix و پیادهسازی Array2DRowRealMatrix آن برای ایجاد ماتریسهای معمولی استفاده کنیم. سازنده کلاس پیادهسازی، دو آرایه دابل دوبعدی به عنوان پارامترهایش میگیرد:
اینترفیس RealMatrix برای دستکاری ماتریس یک متد ()multiply ارائه میکند که پارامتر RealMatrix دیگری را میگیرد:
در نهایت میتوانیم بررسی کنیم که نتیجه مطابق انتظار ما بوده یا نه:
در ادامه یک کتابخانه دیگر را بررسی میکنیم.
LA4J
کتابخانهای که در این بخش بررسی میکنیم، LA4J نام دارد که اختصاری برای عبارت «جبر خطی برای جاوا» (Linear Algebra for Java) است. ابتدا باید وابستگی آن را به پروژه اضافه کنیم:
اکنون LA4J دقیقاً همانند کتابخانههای دیگر کار میکند. این کتابخانه یک اینترفیس Matrix با پیادهسازی Basic2DMatrix ارائه کرده است که دو آرایه دابل دوبعدی به عنوان ورودی میگیرد:
همانند ماژول Math3 در Apache Commons متد ضرب به نام ()multiply است و ماتریس دیگر را به عنوان پارامترش میگیرد:
این بار نیز به بررسی نتیجه با ماتریس مورد انتظار میپردازیم:
اکنون نگاهی به آخرین کتابخانه یعنی Colt میاندازیم.
Colt
Colt یک کتابخانه است که از سوی CERN توسعه یافته است. این کتابخانه قابلیتهایی ارائه کرده است که امکان اجرای محاسبات فنی و علمی را با عملکرد بالا در اختیار ما قرار میدهد.
همانند کتابخانههای قبلی باید ابتدا وابستگی صحیح را به پروژه اضافه کنیم:
به منظور ایجاد ماتریس با استفاده از Colt باید از کلاس DoubleFactory2D استفاده کنیم. این کلاس دارای سه وهله factory است که به ترتیب dense ،sparse و rowCompressed نام دارند. هر کدام از این وهلهها برای ایجاد نوع خاصی از ماتریسها بهینهسازی شدهاند.
ما در این راهنما از وهله dense استفاده میکنیم. این بار متدی که باید فراخوانی شود ()make نام دارد و دو آرایه دابل دوبعدی میگیرد و یک شیء DoubleMatrix2D تولید میکند:
زمانی که ماتریسهایمان وهلهسازی شدند، میتوانیم آنها را در هم صرب کنیم. این بار هیچ متدی روی شیء matrix برای انجام این کار وجود ندارد. ما باید یک وهله از کلاس Algebra بسازیم که دارای متد ()mult است. این متد دو ماتریس را به عنوان پارامتر میگیرد:
سپس نتیجه به دست آمده را با ماتریس مورد انتشار بررسی میکنیم:
بنچمارک
اکنون که کار بررسی روشهای مختلف دستکاری ماتریس به پایان رسیده است، نوبت آن رسیده که کارآمدترین روش را مشخص کنیم.
برای پیادهسازی تست عملکرد از کتابخانه بنچمارکی به نام JMH استفاده میکنیم. در ادامه یک کلاس بنچمارک را با گزینههای زیر پیکربندی میکنیم:
بدین ترتیب JMH دو اجرای کامل برای هر متد حاشیهنویسی شده با Benchmark@ تولید میکند که هر یک، پنج تکرار گرم کردن اولیه دارند که در محاسبه میانگین دخالت ندارند و 10 اجرای محاسباتی مبنای مقایسه هستند. به منظور محاسبه، زمان میانگین اجرای کتابخانههای مختلف برحسب میکروثانیه محاسبه میشوند.
سپس باید یک شیء state ایجاد کنیم که شامل آرایههای ما است:
بدین ترتیب مطمئن میشویم که مقداردهی اولیه آرایه بخشی از بنچمارک کردن نیست. پس از آن باید متدهایی ایجاد کنیم که دستکاری ماتریس را با استفاده از شیء MatrixProvider به عنوان منبع دادهها انجام میدهند. ما کد را در این جا تکرار نمیکنیم زیرا کتابخانهها را قبلاً مورد بررسی قرار دادهایم.
در نهایت فرایند بنچمارک کردن را با استفاده از متد main اجرا میکنیم. بدین ترتیب نتیجه زیر حاصل میشود:
چنان که دیدیم EJML و Colt با عملکردی در مدتزمان یکپنجم میکروثانیه برای هر عملیاتی کارایی بالایی دارند، در حالی که ND4j با زمانی کمی بیشتر از 10 میکروثانیه بر عملیات کارایی کمی دارد. عملکرد کتابخانههای دیگر در بین این دو کتابخانه قرار میگیرد.
سخن پایانی
در این مقاله، با روش ضرب ماتریسها در جاوا، از طریق کد خودمان و همچنین کتابخانههای دیگر آشنا شدیم. پس از بررسی همه راهحلها، یک بنچمارک از همه آنها تهیه کردیم و دیدیم که به جز ND4J همه کتابخانههای دیگر عملکرد مناسبی دارند. کد کامل این مقاله را میتوانید در این ریپوی گیتهاب (+) ملاحظه کنید.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای جاوا (Java)
- گنجینه آموزش های جاوا (Java)
- مجموعه آموزشهای برنامهنویسی
- ۱۰ مفهوم اصلی زبان جاوا که هر فرد مبتدی باید بداند
- آموزش مقدماتی جاوا (بخش اول) — از صفر تا صد
==