خلاصه هفته اول رو داخل پست قبلی نوشتمش، بدون مقدمه بریم سراغ هفته دوم!
فرض کن وقتی داری با دادهها کار میکنی، دو تا مفهوم خیلی مهم پیش میاد: یکی "فیچر" یا همون ویژگیهامون و یکی "وکتور" که همون برداره. حالا اگه بخوایم دقیقتر بگیم:
رگرسیون خطی چندگانه (Multiple Linear Regression) چیه؟
تو این روش، به جای اینکه بخوایم هر فیچر رو دستی ضرب کنیم تو ضریبش و بعد دونه دونه جمع بزنیم، یه ترفند باحال به اسم بردارسازی داریم. بردارسازی یعنی به جای اینکه دستی فیچرها رو ضرب و جمع کنی، از جمع داخلی دو تا بردار استفاده میکنی؛ یکی بردار فیچرها و یکی بردار ضرایب.
این روش یه فایده بزرگ داره: به جای اینکه برای هر فیچر حلقه for بزنی و کلی زمان بگذاری، میتونی از قدرت پردازشی کارت گرافیک (GPU) استفاده کنی که کار رو خیلی سریعتر پیش میبره. حالا اگه بخوای این کارو دستی انجام بدی، باید for بزنی که وقتی تعداد فیچرها زیاد بشه، زمان محاسبه خیلی طولانی میشه.
بردارسازی دو تا مزیت اصلی داره:
این باعث میشه هم برنامهت بهینهتر بشه و هم سریعتر اجرا بشه. خلاصه، بردارسازی یه جورایی جادوی سرعت تو محاسباته!
چرا بردارسازی سرعت رو بیشتر میکنه؟
حالا بریم سراغ اینکه چرا بردارسازی انقدر سرعت کار رو میبره بالا. وقتی از تابع Dot استفاده میکنیم، در اصل همه درایههای فیچرها و ضرایب به صورت موازی و همزمان در هم ضرب میشن و بعدش جمع میشن. اما اگه نخوایم از این روش استفاده کنیم، باید بریم سراغ حلقه for. حالا مشکل چیه؟ حلقه for یعنی اینکه هر عملیات باید مرحله به مرحله انجام بشه. یعنی هر ضرب و جمع باید تکتک و پشت سر هم اتفاق بیفته، که این کار زمان بیشتری میبره.
برای همین تو پروژههای بزرگ که تعداد فیچرها بالاست، بردارسازی یه باید محسوب میشه، نه یه انتخاب. چون اختلاف زمانی بین این دو روش خیلی چشمگیره. مثلاً فرض کن تعداد فیچرها زیاده:
الگوریتم Normal Equation یکی از روشهای محاسبه تو رگرسیون خطیه. این روش مزیتهایی داره:
اما یه نکته مهم اینه که اگه تعداد فیچرها زیاد بشه، مثلاً بالای 10 هزار تا، سرعت الگوریتم کم میشه. ولی نگران نباش، چون خودت مستقیم با این الگوریتم سر و کار نداری. اکثر کتابخونههایی که برای رگرسیون خطی طراحی شدن، مثل Scikit-Learn، به صورت داخلی این الگوریتم رو دارن و خودشون w و b رو حساب میکنن. پس تو فقط از اونها استفاده میکنی و لازم نیست نگران جزئیات الگوریتم باشی.
البته این الگوریتم به درد مصاحبههای شغلی میخوره، پس خوبه که بدونی چیه و چطوری کار میکنه.
تکنیکهایی برای سریعتر کردن گرادیان کاهشی !
حالا بریم سراغ اینکه چطور میتونیم گرادیان کاهشی (Gradient Descent) رو سریعتر کنیم. یکی از تکنیکهای مهم برای این کار اسکیل کردن فیچرها است.
فرض کن دو تا فیچر داریم:
خب حالا چون متراژ خونه عددهای بزرگتری داره، وزن مربوط به این فیچر باید کوچیکتر باشه و وزن تعداد اتاقها که عددهای کوچیکتری داره، بزرگتر. چون اگه اینطور نباشه، عددی که برای پیشبینی قیمت خونه به دست میاد، خیلی بزرگ و غیرواقعی میشه.
برای اینکه این مشکل رو حل کنیم، باید دادههای بزرگ رو اسکیل کنیم. وقتی فیچرها رنجهای متفاوتی دارن، گرادیان کاهشی نمیتونه بهینهترین جواب رو به راحتی پیدا کنه. حالا اگه بیایم هر دوتا فیچر رو به یه بازه مثل 0 تا 1 اسکیل کنیم، نمودار به یه دایره تبدیل میشه و کار گرادیان کاهشی برای رسیدن به مرکز (که همون نقطه بهینه است) خیلی آسونتر میشه.
برای اسکیل کردن فیچرها چند روش وجود داره:
یه روش ساده اینه که هر عدد رو به بزرگترین عدد در اون فیچر تقسیم کنیم. اینطوری همه مقادیر فیچر توی یه بازه بین 0 تا 1 قرار میگیرن.
تو این روش، اول میانگین مقادیر هر فیچر رو از خودش کم میکنیم، بعد نتیجه رو به طول بازه (بزرگترین عدد منهای کوچکترین عدد) تقسیم میکنیم. این روش باعث میشه دادهها توی یه بازه متعادلتری قرار بگیرن.
این روش هم میانگین رو از هر مقدار فیچر کم میکنه، ولی به جای تقسیم بر طول بازه، به انحراف معیار تقسیم میکنه. این باعث میشه دادهها نرمالتر و پراکندگی کمتری داشته باشن.
هر کدوم از این روشها بسته به نوع دادههات و مسئلهای که داری، میتونن مناسب باشن. اما در نهایت هدف همهشون اینه که فیچرها تو یه بازهی معقول باشن تا گرادیان کاهشی سریعتر و دقیقتر عمل کنه.
اسکیل کردن دوباره و نرخ یادگیری در گرادیان کاهشی
گاهی اوقات حتی بعد از اینکه دادهها رو اسکیل کردیم، ممکنه باز هم مقیاسشون با هم یکسان نشه. در این شرایط باید دادهها رو یه بار دیگه اسکیل کنیم تا همه فیچرها در یک مقیاس درست قرار بگیرن. این مرحله برای اطمینان از اینه که الگوریتم بتونه بهترین کارایی رو داشته باشه.
یکی از مهمترین سوالها اینه که چطور متوجه بشیم گرادیان کاهشی داره خوب عمل میکنه یا نه؟ برای این کار از نمودار کاست فانکشن (تابع هزینه) در برابر تعداد چرخشها (Iterations) استفاده میکنیم. اگه نمودار همگرا بشه، یعنی به مرور زمان و با بیشتر شدن تعداد چرخشها، مقدار کاست فانکشن کمتر و کمتر بشه و به یه نقطه ثابت برسه، یعنی همه چیز داره درست پیش میره.
حالا که میخوایم از گرادیان کاهشی استفاده کنیم، باید یه چیز مهم رو درست انتخاب کنیم: آلفا یا همون نرخ یادگیری. نرخ یادگیری تعیین میکنه که الگوریتم با چه سرعتی به سمت جواب حرکت کنه. اما یه موضوع جالب اینه که نمیتونیم از قبل دقیق بگیم که بعد از چند چرخش الگوریتم همگرا میشه. این ممکنه تو یه مسئله 30 چرخش طول بکشه، تو یه مسئله دیگه شاید 100 هزار چرخش!
برای اینکه بفهمیم الگوریتم همگرا شده یا نه، از یه تست به نام تست همگرایی خودکار استفاده میکنیم. اگه تغییرات کاست فانکشن بعد از تعداد زیادی چرخش خیلی کم بود، مثلاً کمتر از یه مقدار کوچیک به نام اپسیلون (ε)، میتونیم بگیم که الگوریتم همگرا شده و مقادیر بهینه برای w و b پیدا شدن.
حالا ممکنه یه وقتایی نمودار کاست فانکشن به درستی همگرا نشه. چند حالت اینجا پیش میاد:
انتخاب آلفا و مهندسی فیچرها
انتخاب مقدار مناسب آلفا (نرخ یادگیری) خیلی مهمه. اگه آلفا خیلی کوچیک باشه، الگوریتم گرادیان کاهشی خیلی آروم جلو میره و ممکنه زمان زیادی طول بکشه تا به جواب برسه. از طرفی، اگه آلفا خیلی بزرگ باشه، الگوریتم به جای همگرا شدن، نوسان میکنه یا حتی واگرا میشه و هیچوقت به جواب نمیرسه.
راهحل چیه؟ برای پیدا کردن بهترین آلفا، معمولاً از یه مقدار خیلی کوچیک، مثلاً 0.0001 شروع میکنیم و به الگوریتم اجازه میدیم چندین بار اجرا بشه. بعد از این، مقدار آلفا رو سه برابر میکنیم و دوباره آزمایش میکنیم. این فرایند رو تا جایی ادامه میدیم که نمودار کاست فانکشن شروع به نوسان شدید کنه. وقتی نوسانها شدید بشن، متوجه میشیم که آلفا دیگه خیلی بزرگ شده و حداکثر مقدار مجاز برای آلفا رو پیدا کردیم. این روش کمک میکنه تا بهینهترین آلفا رو پیدا کنیم.
مهندسی فیچر یکی از مهمترین قسمتهای یادگیری ماشینیه که میتونه تاثیر زیادی روی عملکرد مدل داشته باشه. مثلاً فرض کن دو تا فیچر داریم:
میتونیم یه مدل خطی بسازیم که فقط از این دو فیچر استفاده کنه. اما اگه بیایم یه فیچر جدید، مثلاً مساحت (حاصل ضرب طول و عرض) رو به مدل اضافه کنیم، عملکرد مدل ممکنه خیلی بهتر بشه. این کار یکی از مثالهای مهندسی فیچر هست.
به صورت کلی، مهندسی فیچر یعنی ما بیایم از دادههای موجود، فیچرهای جدید و مفیدتری بسازیم تا مدل بهتر و دقیقتری داشته باشیم.
یکی دیگه از تکنیکهای مهم تو مدلسازی، استفاده از رگرسیون چندجملهای است. تو این روش، ما به جای اینکه فقط از توان یک فیچرها استفاده کنیم، توانهای بالاتری مثل توان دوم، سوم و بیشتر رو هم به مدل اضافه میکنیم. اما باید مواظب باشیم! برای مثال:
وقتی شروع به استفاده از توانهای بالاتر میکنیم، مسئله اسکیل کردن فیچرها خیلی مهم میشه. چرا؟ چون مقادیر فیچرها ممکنه خیلی بزرگ بشن و اگه این فیچرها اسکیل نشن، الگوریتم گرادیان کاهشی نمیتونه درست کار کنه. بنابراین باید همه فیچرها رو به مقادیر کوچکتر و قابل قیاس اسکیل کنیم.
علاوه بر توانهای بالا، ما میتونیم از توانهای کسری مثل ریشه دوم یا حتی ریشههای با فرجههای مختلف استفاده کنیم. این روشها هم میتونن به بهبود عملکرد مدل کمک کنن، مخصوصاً وقتی که رابطه بین متغیرها خطی نیست.
حالا که تمام مباحث رو مرور کردیم، بذار یه جمعبندی داشته باشیم: