الگوریتم‌های ضرب ماتریس

از testwiki
پرش به ناوبری پرش به جستجو

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

اگر از تعریف ضرب ماتریس به صورت مستقیم استفاده کنیم برای ضرب دو ماتریس الگو:Math زمان الگو:Math طول خواهد کشید که به صورت الگو:Math می‌توانیم نمایش دهیم. الگوریتم‌هایی با زمان اجرای بهتری برای این‌کار ارائه شده‌اند. برای مثال در ده سال ۱۹۶۹ استراسن در این زمینه الگوریتمی بر پایه ماتریس های 2×2 ارائه داد اما به‌طور کلی هنوز نمی‌دانیم بهترین الگوریتم برای این کار چیست. (در واقع پیچیدگی زمانی آن مشخص نیست)[۱]

الگوریتم پیمایشی

طبق تعریف ضرب ماتریس می‌توانیم یک الگوریتم برای این کار ارائه دهیم. به ازای ماتریس A که یک ماتریس n×m است و ماتریس B که یک ماتریس m×p است می‌خواهیم ماتریس C=AB که ماتریسی n×p است را محسابه کنیم. طبق تعریف ضرب ماتریس ci,j=k=1mai,k×bk,j بنابراین به ازای هر دوتایی (i,j) می‌توانیم با O(m) مقدار ci,j را محاسبه کنیم. یعنی در کل الگوریتمی با زمان اجرای O(n×m×p) خواهیم داشت. اگر فرض کنیم هر سه ماتریس n×n هستند آنگاه الگوریتم O(n3) خواهد بود.

الگو:چپ‌چین

1 Input: matrices الگو:Mvar and الگو:Mvar
2 Let الگو:Mvar be a new matrix of the appropriate size
3 For الگو:Mvar from 1 to الگو:Mvar:
4 For الگو:Mvar from 1 to الگو:Mvar:
5 Let الگو:Math
6 For الگو:Mvar from 1 to الگو:Mvar:
7 Set الگو:Math
8 Set الگو:Math
9 Return الگو:Mvar

الگو:پایان چپ‌چین

بررسی رفتار حافظه نهان (کش)

یک ترتیب سطری و ستونی از ماتریس

در الگوریتم بالا ترتیب حلقه‌ها را می‌توانیم جابه‌جا کنیم. اگر چه این جابه‌جایی در مدت زمان اجرای الگوریتم تأثیری نخواهد داشت اما این ترتیب در بحث‌های مربوط به دسترسی حافظه (access pattern) و مسائل مربوط به استفاده از حافظه نهان پردازنده مهم است. مثلاً اینکه ماتریس‌ها به ترتیب سطری یا ستونی (یا ترکیبی از این دو) ذخیره شوند در زمان حافظهٔ نهان پردازنده تأثیر گذار خواهد بود.

حتی اگر حالت بهینه را در نظر بگیریم که حافظهٔ کش شرکت پذیر کامل با M سطر حافظهٔ b بیتی باشد و ماتریس‌های A و B به صورت سطری ذخیره شده‌باشند، این الگوریتم بهینه نخواهد بود. هنگامی که n>Mb از آنجایی که ماتریس‌ها به صورت سطری ذخیره شده‌اند هر پیمایش حلقهٔ داخلی در الگوریتم (یک پیمایش روی سطر ماتریس اول و ستون ماتریس دوم) یک خطای کش به هنگام دسترسی به خانه‌ی‌های ماتریس دوم به همراه خواهد داشت؛ و این به این معناست که الگوریتم در بدترین حالت حاوی Θ(n3) خطای کش خواهد بود. امروزه -یعنی از سال ۲۰۱۰- خطای کش‌ها به نسبت اعمال پردازنده تأثیر بیشتری روی زمان اجرا می‌گذارند بنابراین بهتر است با روشی این خطای کش‌ها را کاهش دهیم.

برای حل این مشکل ماتریس‌ها را به بلوک‌هایی از اردر M×M تایی تقسیم می‌کنیم. با اینکار کل یک زیرجدول در حافظهٔ کش قرار می‌گیرد و این بلوک‌ها در هم ضرب می‌شوند. ضرب هر بلوکی هیچ خطای کشی به همراه نخواهد داشت.[۲]

الگو:چپ‌چین

01 Input: matrices الگو:Mvar and الگو:Mvar
02 Let الگو:Mvar be a new matrix of the appropriate size
03 Pick a tile size الگو:Math
04 For الگو:Mvar from 1 to الگو:Mvar in steps of الگو:Mvar:
05 For الگو:Mvar from 1 to الگو:Mvar in steps of الگو:Mvar:
06 For الگو:Mvar from 1 to الگو:Mvar in steps of الگو:Mvar:
07 Multiply الگو:Math and الگو:Math into الگو:Math, that is:
08 For الگو:Mvar from الگو:Mvar to الگو:Math:
09 For الگو:Mvar from الگو:Mvar to الگو:Math:
10 Let الگو:Math
11 For الگو:Mvar from الگو:Mvar to الگو:Math:
12 Set الگو:Math
13 Set الگو:Math
14 Return الگو:Mvar

الگو:پایان چپ‌چین

تعداد خطاهای کش در این مدل برابر n3bM خواهدبود. مخرج b×M باعث می‌شود در پردازنده‌های جدید خطاهای کش در زمان اجرا تأثیر گذار نشوند و صرفاً تحلیل زمانی الگوریتم تأثیر گذار باشد.[۳]

الگوریتم تقسیم و حل

حال سعی می‌کنیم روشی تقسیم و حل برای ضرب ماتریس‌ها ارائه دهیم. ابتدا فرض کنید ماتریس‌هایمان n×n هستند. در این روش مطابق زیر ماتریس‌ها را به چهار بلوک تقسیم می‌کنیم که اندازهٔ آن‌ها تقریباً برابرند.

C=(C11C12C21C22),A=(A11A12A21A22),B=(B11B12B21B22).

اگر ماتریس‌های‌مان اندازه‌شان توانی از دو باشد (یعنی ماتریسی با ابعاد 2n×2n ) می‌توانیم این الگوریتم را به کار بگیریم. به صورت زیر دو ماتریس را در هم ضرب می‌کنیم.

(C11C12C21C22)=(A11A12A21A22)(B11B12B21B22)=(A11B11+A12B21A11B12+A12B22A21B11+A22B21A21B12+A22B22)

این الگوریتم شامل ۸ ضرب ماتریس‌های کوچکتر 2n1×2n1 خواهد بود که به‌صورت بازگشتی محاسبه می‌شود و پایهٔ آن ضرب اسکالر c1,1=a1,1×b1,1 است. همچنین جمع کردن ماتریس‌ها به صورتی که در بالا گفته‌شده Θ(n2) طول خواهد کشید.

توجه کنید اگر ماتریس ما ابعادش توانی از ۲ نبود باز هم می‌توانیم این الگوریتم را به کار ببریم. کافیست با اضافه کردن سطرهایی تمام صفر و ستون‌هایی تمام صفر به پایین و راست ماتریس ابعادش را توانی از دو کنیم. با اضافه کردن آن‌ها اندازهٔ ماتریس حداکثر ۴ برابر خواهد شد (تعداد ستون‌ها و سطرها هر کدام ۲ برابر خواهند شد) بنابراین تفاوتی در تحلیل زمانی ایجاد نمی‌شود به‌علاوه افزودن سطر و ستون تمام صفر تأثیری در حاصل‌ضرب نخواهدگذاشت.

با توجه به مطالب گفته‌شده برای تحلیل زمانی آن می‌توانیم رابطهٔ زیر را بنویسیم:

T(1)=Θ(1);
T(n)=8T(n/2)+Θ(n2)،

بر طبق قضیه‌ی اصلی [۴] می‌توانیم این الگوریتم را تحلیل زمانی کنیم. می‌دانیم f(n)=Θ(n2)𝒪(nlog28ε) بنابراین T(n)=Θ(n3) خواهد بود.

در نتیجه این الگوریتم با الگوریتم ابتدایی‌ای که بررسی کردیم تفاوتی از نظر زمانی ندارد.

ماتریس‌های غیر مربعی

این الگوریتم در عمل برای ماتریس‌های غیر مربعی سریع‌تر عمل می‌کند. کافیست به جای تقسیم کردن هر دو ماتریس به چهار تکه یکی از ماتریس‌ها را به دو تکهٔ برابر (یا تقریباً برابر) با تقسیم کردن سطرها یا ستون‌ها تقسیم کنیم. در زیر می‌توانید الگوریتم چنین کاری را ببینید: الگو:چپ‌چین

 Inputs: matrices الگو:Mvar of size الگو:Math, الگو:Mvar of size الگو:Math.
 Base case: if الگو:Math is below some threshold, use an unrolled version ofthe iterative algorithm.
 Recursive cases:
 If الگو:Math, split الگو:Mvar horizontally:
       C=(A1A2)B=(A1BA2B)
 Else, If الگو:Math, split الگو:Mvar vertically:
       C=A(B1B2)=(AB1AB2)
 Otherwise, الگو:Math. Split الگو:Mvar vertically and الگو:Mvar horizontally:
       C=(A1A2)(B1B2)=A1B1+A2B2

الگو:پایان چپ‌چین

بررسی رفتار حافظه نهان (کش)

الگوریتم گفته‌شده در این بخش تقسیم‌بندی را تا جایی می‌تواند ادامه دهد که کل ماتریس در حافظهٔ کش جا شوند و بنابراین از نظر تعداد خطاهای کش مانند روش تقسیم‌بندی بلوکی عمل می‌کند. با این تفاوت که در آن الگوریتم خود پیاده‌سازی الگوریتم با توجه به اندازهٔ کش پردازندهٔ هدف انجام می‌شود (پارامتر Θ(sqrtM) ای را باید در خود متن الگوریتم تعیین کنیم) در حالیکه این الگوریتم برای کش‌های پویا با اندازه‌های مختلف بهینه‌تر عمل خواهد کرد.

تعداد خطاهای کش در این الگوریتم با M خط حافظهٔ کش که هر خط b بیت دارد به صورت زیر خواهد بود:

Θ(m+n+p+mn+np+mpb+mnpbM)

الگوریتم‌های بهتر از Θ(n3)

کوچکترین ωای در زمان‌های مختلف که الگوریتم ضرب ماتریس با 𝒪(nω) وجود داشته‌است.

الگوریتم‌هایی وجود دارند که زمان اجرای بهتری از الگوریتم‌های فوق دارند. اولین الگوریتم کشف شده که اینگونه بود الگوریتم استراسن بود که در سال ۱۹۶۹ توسط وولکر استراسن (Volker Strassen) کشف شد. این الگوریتم به «الگوریتم سریع ضرب ماتریس» نیز معروف است. این الگوریتم بر مبنای ضرب دو ماتریس 2×2 با ۷ عملیات ضرب است که در عوض تعداد بیشتری جمع و عملیات ریاضی این‌چنینی لازم دارد. با استفاده از این ایده به‌صورت بازگشتی الگوریتمی از 𝒪(nlog27)𝒪(n2.807) به ما می‌دهد. این الگوریتم پیچیده‌است و ضرایب ثابت آن در تحلیل زمانی به اندازه‌ای زیاد است که تنها برای ماتریس‌های بزرگ کارامدتر از الگوریتم‌های قبلی عمل خواهد کرد.

سریعترین الگوریتم با 𝒪(nk) الگوریتمی است که از تعمیم الگوریتم کوپراسمیت–وینوگارد به‌دست آمده و از نظر زمانی 𝒪(n2.3728639) می‌باشد. این الگوریتم توسط François Le Gall کشف شد و به قدری ضرایب زیادی دارد و سربار الگوریتم بالاست که تنها برای ماتریس‌های بسیار بزرگی که هم‌اکنون در علوم کامپیوتر کاربردی ندارند، کارامد خواهد بود.

با توجه به این‌که باید حداقل روی همهٔ اعضای دو ماتریس n×n پیمایش انجام بشود کران‌پایین Ω(n2) برای الگوریتم‌های ضرب ماتریس وجود دارد. راز (Raz) ثابت کرد که کران پایین Ω(n2logn) نیز برای هر الگوریتم ضرب ماتریس نیز وجود دارد.[۵][۶][۷]

همچنین الگوریتم الگوریتم فریوالد یک الگوریتم احتمالی و مونت کارلو است که در 𝒪(n2) چک می‌کند که آیا ضرب دو ماتریس A,B برابر C هست یا نه.

جستارهای وابسته

منابع

الگو:پانویس

  1. https://en.wikipedia.org/wiki/Matrix_multiplication_algorithm
  2. الگو:Cite web
  3. الگو:Cite book
  4. Cormen, Thomas H. ; Leiserson, Charles E. ; Rivest, Ronald L. ; Stein, Clifford (2009) [1990]. Introduction to Algorithms (3rd ed.). MIT Press and McGraw-Hill. ISBN 0-262-03384-4.
  5. Alon, Shpilka, Umans, On Sunflowers and Matrix Multiplication
  6. Henry Cohn, Robert Kleinberg, Balázs Szegedy, and Chris Umans. Group-theoretic Algorithms for Matrix Multiplication. الگو:Arxiv. Proceedings of the 46th Annual Symposium on Foundations of Computer Science, 23–25 October 2005, Pittsburgh, PA, IEEE Computer Society, pp. 379–388.
  7. Henry Cohn, Chris Umans. A Group-theoretic Approach to Fast Matrix Multiplication. الگو:Arxiv. Proceedings of the 44th Annual IEEE Symposium on Foundations of Computer Science, 11–14 October 2003, Cambridge, MA, IEEE Computer Society, pp. 438–449.