در بسیاری از الگوریتمهای یادگیری ماشین (Machine Learning) عملاً یک مسئله بهینهسازی حل میشود. برای مثال در مسائل رگرسیون (Regression) هدف آن است که فاصله مقدار پیشبینیشده توسط الگوریتم یادگیری ماشین از مقدار واقعی آن کمینه شود. ازآنجاکه بسیاری از الگوریتمهای یادگیری ماشین در کامپیوتر با روشهای عددی پیادهسازی میشوند، الگوریتم گرادیان کاهشی (Gradient Descent) که با یک روش تکرارشونده خود را به مقدار بهینه نزدیک میکند، بسیار کاربردی است. در این مقاله به زبان ساده الگوریتم گرادیان کاهشی را توضیح خواهم داد.
رویکردهای محاسبات عددی در حل مسائل بهینهسازی، به دو دسته عمده تقسیم میشوند: رویکردهای بدون استفاده از مشتق (Derivative-free Methods) و رویکردهای مبتنی بر مشتقگیری (Derivative Methods).
در رویکرد اول مشتقگیری از تابع کاربرد ندارد، الگوریتمهایی مانند Golden Section Search و Successive Parabola Interpolation در این دسته جای میگیرند. در رویکرد دوم، مشتق تابع بهنوعی استفاده میگردد. الگوریتم گرادیان کاهشی در این دسته جای دارد. بنابراین در این رویکرد فرض مشتقپذیری تابع نیاز است.
تابع زیر را در بازه ۶- تا ۲+ در نظر بگیرید (شکل-۱):
هدف آن است تا کمینه این تابع را در بازه مذکور به دست آوریم. در حل مسائل بهینهسازی معمولاً فرض میشود که هدف پیدا کردن کمینه تابع است. واضح است که برای پیدا کردن بیشینه تابع کافی است الگوریتم را برای پیدا کردن کمینه تابع بکار ببریم.
درک شهودی از الگوریتم گرادیان کاهشی
رویکردی که الگوریتم گرادیان کاهشی برای حل مسئله به کار میبرد بسیار ساده و قابلفهم است. برای درک شهودی از این الگوریتم فرض کنید در یک کوهستان جنگلی مهآلود مانند شکل-۲ گیر افتادید و میخواهید راه خود را به سمت خط القعر کوهستان پیدا کنید. چراکه با احتمال زیاد در خط القعر کوهستان رودخانهای جریان دارد و با دنبال کردن رودخانه به یک آبادی میرسید.
الگوریتم گرادیان کاهشی در چنین شرایطی توصیه میکند که در آن نقطهای که هستید بهصورت ۳۶۰ درجه به اطراف خودتان نگاه کنید. طبیعتاً چون نمیتوانید تا دوردستها را ببینید فقط محدوده چندمتری اطراف شما مشخص است. سپس بررسی کنید اگر در کدام جهت حرکت کنید بیشترین کاهش ارتفاع را تجربه خواهید کرد. چند گام در آن جهت حرکت کنید.
بهاینترتیب شما در یک نقطه جدید قرار میگیرید. دوباره در نقطه جدید ۳۶۰ درجه به اطرافتان نگاه و در جهتی حرکت کنید که بیشترین کاهش ارتفاع ایجاد میشود. چند گام در آن جهت حرکت کنید و دوباره در نقطه جدید اطرافتان را بررسی و جهت حداکثر کاهش ارتفاع را شناسایی کنید. با ادامه این روند شما بهتدریج به خط القعر کوهستان میرسید (شکل-۳).
همین رویکرد را میتوان برای پیدا کردن نقطه کمینه تابع استفاده کرد. فرض کنید در نقطه ۱ ایستادیم (شکل-۴). در این مثال دو گزینه داریم. از نقطه ۱ به سمت چپ یا به سمت راست برویم. از روی تصویر مشخص است که حرکت به سمت چپ باعث کاهش میشود، پس یک گام به سمت چپ حرکت میکنیم.
از منظر ریاضی اگر بخواهیم متوجه شویم باید به سمت راست یا چپ حرکت کنیم، نیاز است به مشتق تابع در آن نقطه نگاه کنیم. به زبان ساده، مشتق تابع در هر نقطه نشان میدهد اگر بهاندازه به اضافه شود، زیاد خواهد شد یا کم. اگر دقت کنید شیبخط مماس در نقطه ۱ مثبت است؛ یعنی کاهش در نقطه ۱ (حرکت به سمت چپ) موجب کاهش میشود.
بهاینترتیب با حرکت به سمت چپ از نقطه ۱ به نقطه ۲ میرسیم. بطور مشابه در نقطه ۲ حرکت به سمت چپ، موجب کاهش میشود. پس به سمت چپ حرکت کرده و به نقطه ۳ میرسیم. این کار را ادامه میدهیم تا به نقطه کمینه نسبی تابع میرسیم. در آن نقطه حرکت به سمت چپ و یا راست هیچکدام باعث کاهش نمیشود و الگوریتم در نقطه کمینه نسبی متوقف میگردد.
نکته قابلتوجه این است که بسته به اینکه الگوریتم از چه نقطه اولیهای شروع میشود، ممکن است به کمینههای نسبی متفاوتی همگرا شود (شکل-۵). دراینباره بیشتر صحبت خواهم کرد.
الگوریتم گرادیان کاهشی برای توابع چندمتغیره
مثال بالا برای یک تابع یک متغیره بود. این رویکرد را میتوان برای توابع چندمتغیره بکار برد. تنها نکته آن است که از بردار گرادیان (Gradient Vector) باید استفاده کرد. بردار گرادیان مشتقات پارهای تابع را نسبت به متغیرهایش در هر نقطه محاسبه میکند:
اگر از ریاضیات پایه یادتان باشد، جهت بردار گرادیان در هر نقطه جهت حداکثر افزایش تابع را نشان میدهد. برای مثال اگر تابع یک تابع دومتغیره به شکل زیر باشد:
بردار گرادیان آن در هر نقطه به شکل زیر خواهد بود:
برای نقطه بردار گرادیان بهصورت زیر خواهد بود:
اگر در نقطه در جهت این بردار حرکت کنیم، بیشترین افزایش را در تجربه خواهیم کرد و اگر در خلاف جهت این بردار حرکت کنیم، بیشترین کاهش را در خواهیم دید (شکل-۶).
بنابراین در الگوریتم گرادیان کاهشی برای پیدا کردن جهت حداکثر کاهش در در هر نقطه، کافی است خلاف جهت گرادیان حرکت کنیم. فرمولبندی ریاضی الگوریتم گرادیان کاهشی بهصورت زیر است:
رابطه بالا به این معنی است در هر تکرار الگوریتم، از نقطهای که ایستادیم () بهاندازه یک گام مشخص () در خلاف جهت گرادیان در آن نقطه () حرکت میکنیم و به نقطه جدید () میرسیم.
به نرخ یادگیری میگویند. نرخ یادگیری تعیین میکند در هر تکرار چقدر در جهت کاهش گرادیان حرکت شود. اگر نرخ یادگیری خیلی کوچک باشد، منجر به این میشود که الگوریتم خیلی کند به نقطه کمینه نسبی همگرا شود و اگر خیلی بزرگ باشد ممکن است الگوریتم واگرا شود (شکل-۷). معمولاً نرخ یادگیری بین ۰٫۰۰۱ تا ۰٫۳ تعیین میشود.
پیادهسازی الگوریتم گرادیان کاهشی در پایتون
در این بخش من میخواهم به مسئله اولیهای که مطرح کردم برگردم و کمینه مطلق تابع را با الگوریتم گرادیان کاهشی پیدا کنم.
در ابتدا من تابع و مشتق آن را تعریف کردم:
1 2 3 4 5 6 | def f(x): return(x ** 4 + 7 * x ** 3 + 5 * x ** 2 - 17 * x + 3) #Gradient of f def g(x): return(4 * x ** 3 + 21 * x ** 2 + 10 * x - 17) |
در گام بعد نقاط اولیه شروع الگوریتم را تعریف کردم. با توجه به آنکه تضمینی نیست که حتماً الگوریتم از یک نقطه شروع اولیه به کمینه مطلق آن همگرا شود، من بهصورت تصادفی ۱۰ نقطه اولیه را در بازه ۶- تا ۲+ انتخاب کردم و برای همه آنها الگوریتم گرادیان کاهشی را پیادهسازی میکنم.
1 2 3 4 5 6 7 8 | #Random initialization of x import numpy as np np.random.seed(123) x = np.random.uniform(-6, 2, 10) x array([-0.42824652, -3.71088532, -4.18518837, -1.58948185, -0.24424824, -2.61514832, 1.84611359, -0.52136209, -2.15254479, -2.86305985]) |
سپس تابع گرادیان کاهشی را بر اساس رابطه ریاضی که در بالا آوردهام، نوشتم:
1 2 3 4 5 6 7 8 | #Gradient Descent Function #x_i+1 = x_i - gamma * g(x_i) def grad_descent(x, gamma = 0.01, iter = 100): all_x = [x] for i in range(iter): x = x - gamma * g(x) all_x.append(x) return(all_x[-1]) |
این تابع نقطه اولیه، نرخ یادگیری و تعداد تکرار الگوریتم را دریافت میکند آخرین عددی را که الگوریتم گرادیان کاهشی به آن رسیده بهعنوان خروجی نهایی برمیگرداند. درنهایت تابع را برای ۱۰ نقطه اولیه، با نرخ یادگیری ۰٫۰۱ و تکرار ۱۰۰ اجرا میکنم.
1 2 3 4 5 | res = grad_descent(x, gamma = 0.01, iter = 100) res array([ 0.66238088, -4.48026848, -4.48026848, -4.48026848, 0.66238088, -4.48026848, 0.66238088, 0.66238088, -4.48026848, -4.48026848]) |
همانطور که مشخص است نتایج این ۱۰ نقطه ما را به دو برای کمینه نسبی تابع رساند. برای آنکه بفهمیم کدام نقطه کمینه مطلق است کافی است مقدار را محاسبه کنیم. بهاینترتیب مشخص میشود کمینه مطلق تابع نقطه و است.
1 2 3 4 5 | f(res) array([ -3.83990263, -47.0747901 , -47.0747901 , -47.0747901 , -3.83990263, -47.0747901 , -3.83990263, -3.83990263, -47.0747901 , -47.0747901 ]) |
میتوانیم نقاط کمینه بهدستآمده را بر روی تصویر تابع بیندازیم تا از صحت نتایج مطمئن شویم (شکل-۸).
1 2 3 4 5 6 7 8 | #Plot f(x) and local minima import matplotlib.pyplot as plt x = np.arange(-6, 2, 0.01) y = f(x) plt.plot(x, y, color = 'red') plt.plot(res[0], f(res[0]), color = 'blue', marker = 'o') #frist minimum plt.plot(res[1], f(res[1]), color = 'blue', marker = 'o') #second minimum plt.grid() |
شکل-۸
منابع:
Deisenroth, M. P., Faisal, A. A., & Ong, C. S. (2020). “Mathematics for Machine Learning”, Cambridge University Press