به جرئت میتونم بگم ایده هایی که توی این پست قراره با هم برسی کنیم، بین تمام پست های مربوط به regression از همشون مهم تره و خیلی خیلی مهمه که این مفاهیم رو متوجه بشید.
فرض کنید تو مسئله ی تخمین قیمت خونه، به جز متراژ برای هر کدوم از خونه ها تعداد اتاق خواب ها رو هم بهمون میدادن. حالا باید بر اساس متراژ و تعداد اتاق خواب قیمت خونه رو پیدا کنیم. چه جوری باید تابع h رو تغییر بدیم که بتونه این متغییر رو هم در نظر بگیره؟
برای این کار میایم و یک متغییر دیگه به نام تتا 2 اضافه میکنیم که این متغییر در تعداد اتاق خواب ضرب بشه و با مقدار قبلی جمع بشه، یعنی تابع h ما به این صورت تغییر میکنه:
در واقع هر تعداد متغییر دیگه هم که داشته باشیم میتونیم بیایم و براش یه تتا ی جدید اضافه کنیم که تابع h بتونه بر اساس تمام اونها خروجی رو تخمین بزنه. یک کار دیگه هم که میتونیم انجام بدیم اینه که بیایم و ترکیبی از این پارامتر ها رو به h بدیم و برای اون ترکیب هم یک تتا ی جدید اضافه کنیم. مثلا ممکنه بخوایم تابع تتا بر اساس متراژ به توان 2، متراژ و تعداد اتاق خواب قیمت خونه رو تخمین بزنه، در این صورت تابع h این شکلی میشه:
بقیه ی کار ها دقیقا مثل قبل میشه، فقط با این تفاوت که موقع آپدیت کردن تتا ها باید مشتق J نسبت به هر کدوم از تتا رو حساب کنیم و تعداد معادلاتمون بیشتر میشه.
یه چیزی هم که میخوام اینجا دربارش بحث کنیم، اینه که میشه تابع h رو به این صورت هم نشون داد:
اینجا فقط اومدیم و یک شکل برای نشون دادن تابع h کشیدیم. نقاطی که سمت چپ هستن ورودی های تابع h، مقادیر روی فلش ها تتا ها و دایره هم داره نشون میده که تمام ورودی هاش باید با هم جمع بشن، یعنی در واقع اگر رو هر کدوم از اون نقطه ها مقدار بذاریم، میاد و در تتا ای که روی اون خطه هست ضرب میشه و در نهایت همشون با هم جمع میشن. (برای تتا 0 هم به طور ثابت عدد 1 رو به عنوان ورودی در نظر گرفتیم).
به این الگوریتم میگن رگرسیون چند متغییره، تقریبا میشه گفت هیچ چیز جدیدی نداره، فقط پیاده سازیش بر اساس کدی که دفعه ی قبل نوشتیم، طولانی تر میشه. (باید برای گرادیان هر کدوم از تتا ها بیایم و تابع مخصوص به خودشو بنویسیم *_*)
تو پست قبلی گفته بودم که هدف ما پیاده سازی یک کد تمیز نیست. چرا؟ چون با اون روشی که دفعه ی قبل داشتیم پیاده سازی رو انجام میدادیم خود به خود کار کردن با متییغر های زیاد خیلی سخت میشه و عملا کد تمیزی نمیشه نوشت.
یکی از کاربرد های vectorization برای همینه. اینجوری خیلی خیلی خیلی کد نویسی برامون راحت تر میشه. کاربرد بعدیش اینه که وقتی محاسبات رو به صورت برداری (ضرب بردار و ماتریس و کلا عملیات بین ماتریس ها) بنویسیم میشه اونها رو راحت تر روی GPU اجرا کرد. (اجرای کد های ماشین لرنینگ روی GPU باعث میشه سرعت محاسبات خیلی بالاتر بره.) و کاربرد آخر (که شاید این یکی از همشون مهم تر هم باشه) اینه که روابط رو میشه با جبر خطی تحلیل کرد و در کل آنالیز روابطی که داریم از نظر ریاضیاتی راحت تر میشه.
حالا این vectorization چی هست؟
قبل از اینکه بریم سراغ vectorization یه مرور خیلی سریع روی مفاهیم مهم جبر خطی که الان باهاشون کار داریم داشته باشیم و بعد بریم سراغ ادامه ی کارمون. (بعدا درباره ی جبر خطی، آمار، احتمال و بقیه ی ریاضایت مربوط به ماشین لرنینگ پست مینویسم ولی فعلا نیازی نیست خیلی وارد جزئیات بشیم و همین قدری که اینجا میگم برای ادامه ی کار کافیه)
اگر از درس های دبیرستانتون یادتون باشه، یک بردار n تا عدد کنار همه. (به زبون کامپیوتریا میشه گفت یک بردار در واقع یک آرایه ی n تایی از اعداده). مثلا مختصات هر نقطه که شامل 2 تا عدد x و y میشه، خودش یک برداره. به تعداد اعدادی که هر بردار داره میگیم بعد اون بردار، مثلا برداری که برای مکان تعریف کردیم چون 2 تا عدد داره 2-بعدی میشه.
یک سری عملیات بین بردار ها تعریف میشه که برای ما خیلی مهمن. (تمام عملیاتی که میخوام دربارشون صحبت کنم باید بین بردار هایی انجام بشن که ابعادشون یکیه مثلا جفتشون 2-بعدی هستن. یا جفتشون 3-بعدی هستن.)
برای ادامه ی مطلب فرض کنید دو تا بردار x و y رو داریم که بردار هایی 3-بعدی هستن و به این صورت تعریف میشن:
اولین عملیات جمع بین دو تا برداره. جمع بین دو تا بردار، یک بردار دیگس که هر کدوم از مولفه های اون (به د کدوم از اعداد توی بردار میگن مولفه) حاصل جمع مولفه های متناظر اون توی دو تا بردار دیگس *_*. یعنی اگر به حاصل جمع x و y بگیم z. در اون صورت مولفه ی اول z میشه جمع مولفه ی اول x با مولفه ی اول y. پس x + y اینطوری میشه:
تفریق دو تا بردار هم به همین صورت تعریف میشه:
ضرب مولفه ای بین دو تا بردار هم همین طور:
عملیات مهم بدی دات دوتا برداره. (فعلا با اینکه این عملیات چی کار میکنه کاری نداریم، بعدا دربارش توضیح میدم):
یک عملیات خیلی مهم دیگه هم داریم و اون هم ضرب دو تا ماتریسه. (ماتریس در واقع چند تا برداره که کنار هم قرار نوشتیمشون مثلا اگر 4 تا بردار 3 بعدی رو کنار هم بنویسیم به یک ماتریس 3 در 4 میرسیم). برای اینکه دو تا ماتریس رو تو هم ضرب کنیم لازمه که تعداد ستون های ماتریس اول با تعداد سطر های ماتریس دوم برابر باشه. مثلا اگر ماتریس اول یه چیزی در 2 باشه (مثلا 3 در 2، 4 در 2، 5 در 2 یا هر چیز دیگه ای)، ماتریس دوم باید 2 در یه چیزی باشه (مثلا 2 در 3، 2 در 4، 2 در 5 یا هر چیز دیگه ای).
حاصل ضرب دو تا ماتریس (به شرطی که بشه اون ها رو تو هم ضرب کرد) میشه یک ماتریس دیگه که تعداد سطر هاش با تعداد سطرهای ماتریس اول و تعداد ستون هاش با تعداد ستون های ماتریس دوم برابره. مثلا اگر یک مارتیس 5 در 2 رو ضرب کنیم تو یک ماریس 2 در 7، حاصل این عملیات میشه یک ماتریس 5 در 7.
ضرب دو تا ماتریس عملیات عجیب و غریبیه (البته دلیل داره که اینجوری تعریف شده)، و به این صورت تعریف میشه:
اینجا یک ماتریس 3 در 2 ضرب شده تو یک ماریس 2 در 3 در نتیجه حاصل میشه یک ماتریس 3 در 3. اگر دقیق به ماتریس خروجی نگاه کنید، میبینید که سطر i و j ماتریس حاصل برابره با دات بین سطر i ماتریس اول و ستون j ماتریس دوم!
توی شکل اگر خونه ی توی سطر 1 و ستون 1 ماتریس جواب رو در نظر بگیرید، حاصل دات بین سطر اول ماتریس اول و ستون اول ماتریس دومه. اگر خونه ی سطر 2 و ستون 3 ماتریس جواب رو در نظر بگیرید، حاصل بین دات سطر دوم ماتریس اول و ستون سوم ماتریس دومه.
یه عملیات مهم دیگه برای ماتریس، ترانهاده ی یک ماتریس، ترانهاده ی یک ماترسی اون رو 90 درجه میچرخونه. مثلا اگر ترانهاده ی یک بردار رو حساب کنید، تبدیل میشه به یک ماتریس 1 در تعداد ابعادش. (میتونید فرض کنید خود بردرار یک ماریس با تعداد ابعاد بردار در یک بعدیه. یعنی مثلا یک بردار 3 بعدی همون ماتریس 3 در 1 هست که اگر ترانهاده ی اون رو حساب کنید میشه یک ماتریس 1 در 3).
آخرین مطلب هم اینکه دات دو تا بردار رو میشه به صورت ضرب ماتریس ها نوشت که به این صورت میشه:
خب برگردیم سراغ بحث خودمون.
توی رگرسون (و بقیه ی الگوریتم هایی که در ادامه میبینیم) میتونیم در نظر بگیریم که تمام مقادیر یک ورودی (تو مثالی که داشتیم، متراژ، تعداد اتاق خواب، مربع متراژ و ...) رو میشه به صورت یک بردار در نظر گرفت، یعنی به ازای هر کدوم از x هایی که داریم یک بردار داریم که مولفه ی اول اون میشه متراژ، مولفه ی دوم اون میشه تعداد اتاق خواب و مولفه ی سوم اون میشه مربع متراژ. (فرض کنید برای هر کدوم از x ها n ها عدد داریم.)
(به هر کدوم از مولفه های این بردار ها یعنی مثلا متراژ خونه، میگیم فیچر. تو این مثال برای هر خونه 3 تا فیچر داریم)
در این صورت اگر تمام m تا xای که داریم رو کنار هم بذاریم یه ماتریس میسازیم به نام ماتریس X (با حرف بزرگ) که هر کدوم از ستون های اون یکی از داده هایی هستند که داریم. پس یک ماتریس n در m داریم.
میتونیم تتا ها رو هم توی یک بردار بذاریم و بهش بگیم ماتریس تتا.
در کل بردار تتا و ماتریس داده ها اینجوری میشن:
میتونیم مقادیر y رو هم تو یک بردار بذاریم و بعش بگیم بردار Y:
حالا اگر بخوایم مقدار h رو برای تمام ورودی هامون یک جا محاسبه کنیم، میتونیم X رو در تتا ضرب کنیم:
دو تا نکته تو این معادله هست، اول اینکه اینجا پارامتر تتا 0 رو در نظر نگرفتیم و دوم اینکه از علامت هت برای نشون دادن پیشبینی هامون استفاده کدیم. (یعنی y hat نشون میده که این مقادیر پیشبینی های ما برای y هستند و نه خود y)
برای اضافه کردن پارامتر تتا 0، فرض میکنیم هر کدوم از ورودی ها یک فیچر 1 دارن (برای همه ی ورودی ها ثابت و مساوی یک) و تتا صفر رو هم به بردار تتا اضافه میکنیم، در نتیجه بردار ها و ماتریس ها اینجوری میشن:
به این ایده که اومدیم و تمام ورودی ها رو به صورت ماتریس، خروجی ها رو به صورت بردار و پارامتر ها رو به صورت بردار در نظر گرفتیم و روابط رو بر اساس روابط بین ماتریس ها و بردار ها نوشتیم میگن vectorization.
برای اینکه بدونید چه قدر این ایده میتونه قدرمتند باشه و چه قدر به تحلیل بهتر کمک میکنه:
میتونیم بدون استفاده از gradient descent و مستقیما بیایم و بهترین مقدار تتا رو حساب کنیم. برای این کار کافیه مقداری که مقدار بردار تتا رو بر اساس معادله ی پایین حساب کنیم:
به این معادله میگم normal equation و در اصطلاح میگن closed-form solution برای linear regression هست. (اگر یادتون باشه تو پست های قبلی گفته بودم که برای این الگوریتم میشه معادله ی مشتق J مساوی صفر رو حل کرد).
خب برای این پست کافیه، تو پست بعدی میایم و با استفاده از vectorization دوباره کدمون رو بازنویسی میکنیم. تو پست های بعدی درباره ی اینکه از چه جوری میشه normal equation رو اثبات کرد توضیح میدم.