رضا مظاهری
رضا مظاهری
خواندن ۱۰ دقیقه·۲ سال پیش

پیاده سازی رگرسیون خطی ساده از صفر

1. درک مفهوم رگرسیون خطی ساده

۱.۱. مقدمه

به پیش بینی یک متغیر از روی تغییرات یک یا چند متغیر دیگر رگرسیون گفته می‌شود. به متغیر یا متغیر‌های پیش‌بینی کننده متغیر مستقل و به متغیر پیش‌بینی شده متغیر وابسته می‌گویند. اگر در این پیش بینی بین متغیر وابسته و مستقل رابطه‌ای خطی برقرار باشد به آن رگرسیون خطی می‌گویند و اگر تنها از یک متغیر مستقل استفاده کنیم به آن رگرسیون خطی ساده می‌گویند. متغیر وابسته را با y و متغیر مستقل را با x نشان می‌دهیم.

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

شکل شماره یک (۱): نحوه تولید شدن تابع h_θ (x)
شکل شماره یک (۱): نحوه تولید شدن تابع h_θ (x)

۲.۱. مدل (فرضیه) رگرسیون خطی ساده

رگرسیون خطی ساده را با معادله‌ی خط زیر نشان می‌دهیم که آن را مدل رگرسیون خطی ساده می‌نامیم. در مدل ارایه شده در زیر h_θ(x) متغیر وابسته و x متغیر مستقل می‌باشد، در اینجا ما سعی داریم با استفاده از h_θ(x)، x را پیش‌بینی کنیم.

شکل شماره دو (۲): مدل رگرسیون خطی ساده
شکل شماره دو (۲): مدل رگرسیون خطی ساده

مقادیر عرض از مبدأ و شیب خط معادله خط بالا که به ترتیب با نماد‌های θ₀ و θ₁ نمایش داده‌شده‌اند پارامتر‌های مدل نامیده می‌شوند که با پیدا کردن آن‌ها می‌توان پیش‌بینی را انجام داد به عبارت دیگر الگوریتم یادگیری این دو مقدار را برای ما پیدا می‌کند.

۳.۱. محاسبه‌ی پارامتر‌ها

ما باید دو پارامتر θ₀ و θ₁ را به گونه‌ای انتخاب کنیم که حاصل به دست آمده از مدل، برای هر یک از اعضای مجموعه دادگان آموزشی به مقدار واقعی آن بسیار نزدیک باشد، این موضوع را به کمک نمادهای ریاضی به شکل زیر نمایش می‌دهیم. عبارت بالانویسی شده‌ی (Super script) (i) در این نمادگذاری به شماره‌ی (Index) هر عضو در مجموعه دادگان آموزشی اشاره دارد و نباید آن را با توان اشتباه گرفت.

با توجه به این موضوع که حاصل عبارت بالا ممکن است منفی شود، ضروری است که آن را به توان ۲ برسانیم، همچنین با توجه به اینکه در مجموعه دادگان آموزشی ما m عضو وجود دارد حاصل جمع عبارت بالا برای تمامی این m عضو باید کمینه شود.

فرمول بالا حاصل جمعی است از تفاوت مقدار تخمین زده شده توسط فرضیه‌ی ارائه شده و مقدار واقعی برای هر عضو، با تقسیم آن بر عبارت m آن را به میانگین حاصل جمع این فواصل تبدیل می‌کنیم و جهت ساده سازی محاسبات ریاضی در آینده آن را بر ۲ نیز تقسیم می‌کنیم. عبارت به دست آمده را تابع هزینه‌ی (Cost Function) الگوریتم رگرسیون خطی ساده می‌نامیم و آن را با نماد (θ)j نمایش می‌دهیم.

شکل شماره سه (۳): تابع هزینه‌ی الگوریتم رگرسیون خطی ساده
شکل شماره سه (۳): تابع هزینه‌ی الگوریتم رگرسیون خطی ساده

اجازه دهید تا مطالب گفته شده در بالا را با یک مثال عددی روشن‌تر کنیم. مجموعه دادگان آموزشی زیر که دارای ۶ عضو می‌باشد را در نظر بگیرید.

شکل شماره‌ی چهار (۴): مجموعه دادگان آموزشی
شکل شماره‌ی چهار (۴): مجموعه دادگان آموزشی
شکل شماره‌ی پنج (۵): تعداد اعضای مجموعه دادگان آموزشی
شکل شماره‌ی پنج (۵): تعداد اعضای مجموعه دادگان آموزشی

یک الگوریتم رگرسیون، با انجام یک سری محاسبات که در ادامه‌ی این مطلب بیشتر با آنها آشنا می‌شویم، برای مجموعه‌ی ما فرضیه‌ی زیر را پیشنهاد داده است؛ حال تابع هزینه را برای آن محاسبه می‌کنیم.

شکل شماره‌ی شش (۶): فرضیه‌ی پیشنهاد شده توسط الگوریتم رگرسیون خطی
شکل شماره‌ی شش (۶): فرضیه‌ی پیشنهاد شده توسط الگوریتم رگرسیون خطی
شکل شماره‌ی هفت (۷): مقادیر تخمین زده شده توسط فرضیه‌ی پیشنهادی
شکل شماره‌ی هفت (۷): مقادیر تخمین زده شده توسط فرضیه‌ی پیشنهادی
شکل شماره‌ی هشت (۸): محاسبه‌ی تابع هزینه برای فرضیه و مجموعه‌ی دادگان
شکل شماره‌ی هشت (۸): محاسبه‌ی تابع هزینه برای فرضیه و مجموعه‌ی دادگان

حاصل تابع هزینه برای (2-) = θ₀ و 2 = θ₁، مقدار ۰/۲۵ می‌باشد؛ با استفاده از مقادیر 1 = θ₀ و 1 = θ₁ و یا به عبارت دیگر فرضیه‌ی h_θ(x) = 1 + x مقدار تابع هزینه ۲۰ محاسبه می‌گردد. واضح است که با انتخاب مقادیر متفاوت برای پارامترهای عرض از مبدا و شیب خط مقدار تابع هزینه تغییر می‌کند. ما باید θ₀ و θ₁ را به گونه‌ای انتخاب کنیم که تابع هزینه کمینه شود.

نمودار زیر را ببنید، این نمودار مقادیر مختلف j(θ) را برای θ₀ و θ₁ های متفاوت به نمایش می‌گذارد. هدف در بخش بعدی پیدا کردن θ₀ و θ₁ هایی است که به ازای آنها تابع هزینه کمینه می‌شود.

شکل شماره نه (۹): مقدار تابع هزینه برای مقادیر مختلف پارامترهای عرض از مبدا و شیب خط
شکل شماره نه (۹): مقدار تابع هزینه برای مقادیر مختلف پارامترهای عرض از مبدا و شیب خط

۴.۱. گرادیان کاهشی

گفتیم که تابع هزینه نشان‌دهنده‌ی آن است که نقاط تخمین زده شده توسط فرضیه، از نقاط اصلی چقدر دور هستند همچنین فهمیدیم که با انتخاب مقادیر متفاوت برای پارامترهای فرضیه می‌توان مقدار تابع هزینه را تغییر داد. با توجه به اینکه هدف اصلی ما نزدیک کردن مقادیر تخمین زده شده به مقادیر واقعی می‌باشد باید تابع هزینه را کمینه (Minimum) کنیم.

الگوریتم گرادیان کاهشی یک الگوریتم بهینه سازی است که در بسیاری از قسمت‌های یادگیری ماشین مورد استفاده قرار می‌گیرد. تصور کنید که شخص نا‌بینایی در بالای یک قله قرار دارد و تصمیم دارد به یک میله‌‌ی پرچم که در دامنه قرار دارد برسد و درصورتی که میله‌ی پرچم را رد کند، تنبیه خواهد شد. وی ابتدا یک قدم بزرگ در جهت شیب پایین برمی‌دارد و سپس قدمی دیگر در جهت شیب پایین همینطور که شیب کوه کمتر می‌شود این فرد نیز طول قدم‌های خود را کوتاه‌تر می‌کند تا به پرچم برسد، الگوریتم گرادیان کاهشی نیز دقیقا مانند این شخص نا‌بینا عمل می‌کند.

شکل شماره ده (۱۰): حرکت شخص نا‌بینا از قله به سمت نقطه‌ی مینیمم که با دایر‌ه‌های سیاه روی نمودار j(θ) بر حسب θ_0 و θ_1
شکل شماره ده (۱۰): حرکت شخص نا‌بینا از قله به سمت نقطه‌ی مینیمم که با دایر‌ه‌های سیاه روی نمودار j(θ) بر حسب θ_0 و θ_1


در شکل بالا یک نمونه از الگوریتم گرادیان کاهشی به نمایش گذاشته شده. فرد نابینا در این تصویر از بالاترین نقطه سیاه حرکت کرده و به سمت نقطه Minimum حرکت می‌کند و در ابتدا طول قدم‌های برداشته شده بزرگ و در نزدیکی نقطه‌ی Minimum طول قدم‌ها کوچک می‌شود. در نمودار بالا محورهای Y, X نمایشگر پارامترهای
θ₀ و θ₁ و محور Z نمایشگر تابع هزینه (θ₀,θ₁)j می‌باشد.

از توضیحات ارائه شده در بالا واضح است که طول قدم‌های ما در این الگوریتم با شیب نمودار ارتباط مستقیم دارد این ارتباط در فرمول ریاضی این الگوریتم نیز مشهود است؛ ما می‌دانیم که هر چه یک نمودار به نقاط اکسترمم (مینیمم و ماکسیمم) نزدیک می‌شود شیب آن کمتر می‌شود تا در نهایت در این نقاط شیب آن صفر می‌شود از طرفی باید بدانیم که که پارامتر α در این فرمول نشاندهنده‌ی طول قدم‌های برداشته شده می‌باشد. با ضرب شیب نمودار در مقدار α طول قدم‌های برداشته شده در ابتدا بزرگ و در نزدیکی نقطه‌ی مینیمم کوچک خواهد شد. توجه به ایننکته حائز اهمیت است که در صورت انتخاب مقدار بزرگ برای α ، الگوریتم نقطه‌ی بهینه را رد می‌کند و به اصطلاح همگرا نمی‌شود. در شکل زیر همگرا نشدن الگوریتم نشان داده شده است.

شکل شماره یازده (۱۱): همگرا نشدن الگوریتم گرادیان کاهشی و دورشدن از نقطه‌ی مینیمم
شکل شماره یازده (۱۱): همگرا نشدن الگوریتم گرادیان کاهشی و دورشدن از نقطه‌ی مینیمم


همچنین در صورت انتخاب مقداری بسیار کوچک برای α الگوریتم دفعات زیادی نیاز به گردش (Iteration) دارد. شکل شماره‌ی دوازده همگرا نشدن الگوریتم را در یک نمودار دو بعدی نشان می‌دهد.

شکل شماره دوازده (۱۲): در نمودارد سمت راست مقدار α بیش از اندازه بزرگ و در نمودار سمت چپ مقدار α بیش از اندازه کوچک انتخاب شده است.  این تصویر از وبسایت (www.jeremyjordan.me/nn-learning-rate) وام گرفته شده است.
شکل شماره دوازده (۱۲): در نمودارد سمت راست مقدار α بیش از اندازه بزرگ و در نمودار سمت چپ مقدار α بیش از اندازه کوچک انتخاب شده است. این تصویر از وبسایت (www.jeremyjordan.me/nn-learning-rate) وام گرفته شده است.


طرح کلی الگوریتم گرادیان کاهشی در زیر نشان داده شده است.

طرح کلی:

  • با هر θ₀ و θ₁ دلخواه شروع می‌کنیم:
  • آنقدر θ₀ و θ₁ را تغییر می‌دهیم تا مقدار (θ₀,θ₁)j کاهش یابد تا زمانی که به مینیمم برسد.

در زیر می‌توانیم فرمول گرادیان کاهشی را ببنیم؛ در این فرمول علامت نشان دهنده‌ی مشتق جزئی نسبت به متغیر θⱼ می‌باشد. و همینطور زیرنویس (Subscript) j اشاره‌گر (index) به هر کدام از پارامتر‌ها می‌باشد. به طور مثال در اینجا j یکبار ۰ و یکبار ۱ خواهد بود.

شکل شماره سیزده (۱۳): فرمول گرادیان کاهشی
شکل شماره سیزده (۱۳): فرمول گرادیان کاهشی


با اعمال فرمول بالا بر فرمول تابع هزینه در رگرسیون خطی ساده مقادیر θ₀ و θ₁ به شکل زیر محاسبه می‌گردند.

شکل شماره‌ی چهارده (۱۴): محاسبه مقادیر θ_0 و θ_1
شکل شماره‌ی چهارده (۱۴): محاسبه مقادیر θ_0 و θ_1


در مورد دو فرمول بالا دو نکته باید مد نظر قرار داده شود. اول آنکه مقادیر θ₀ و θ₁ در کد نوشته شده باید به طور همزمان به روز رسانی (Update) شوند در غیر اینصورت الگوریتم به درستی کار نمی‌کند. نکته دوم آنکه در عبارات xⁱ و yⁱ حرف i توان نیست بلکه شمارنده‌ی (index) هر عضو در مجموعه‌ی دادگان می‌باشد.

۲. پیاده سازی

در پیاده سازی زیر از یک دیتاست که از سایت کگل گرفته شده است استفاده کرده‌ام. برای دسترسی به این دیتاست از این لینک استفاده کنید. کد رو در پایین براتون قرار دادم برای دانلودش و اجراش توسط خودتون از این لینک استفاده کنید. ولی پیشنهادم اینه که از این نسخه‌ای که توی گیتهاب (Github) گذاشتم استفاده کنید. نسخه‌ای که در زیر می‌بینید بر‌اساس قرارداد‌های متن بالاست و جهت سادگی و به شکل بی‌سلیقه‌ای غیرحرفه‌ای نوشته شده اما کد موجود در مخزن گیتهاب بسیار تمیزتر و جمع و جور تر می‌باشد.

برای پیاده سازی ابتدا باید کتابخانه‌هایی که به آنها نیاز داریم را به کد وارد کنیم، کتابخانه pandas جهت خواندن فایل csv.که داده‌هامان در آن قرار دارد و کتابخانه numpy برای کار کردن راحتتر با اعداد و آرایه‌ها همچنین کتابخانه matplotlib.pyplot برای مصور سازی نتیجه‌ و دادگان.

import pandas as pd import numpy as np import matplotlib.pyplot as plt


حال باید مجموعه دادگان را به کد وارد کنیم با استفاده از pandas فایل csv را خوانده و و آن را در دو متغیر X و y قرار می‌دهیم.

dataset = pd.read_csv('Salary_Data.csv') X = dataset.iloc[:, :-1].values y = dataset.iloc[:, -1].values

برای تست دقت الگوریتمی که آن را پیاده سازی می‌کنیم نیاز داریم تا داده‌ها را به داده‌های آموزش و تست تقسیم بندی کنیم در اینجا از کتابخانه scikit برای اینکار استفاده می‌کنیم و ۲۵ درصد از داده‌ها را در مجموعه تست و بقیه آن‌هارا در مجموعه آموزش قرار می‌دهیم.

from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.25)

حال وقت پیاده سازی الگوریتم رگرسیون خطی ساده رسیده است. همانطور که در متن بالا به آن اشاره کردیم وظیفه ما یافتن دو مقدار theta_0و theta_1 می‌باشد، برای یافتن دو مقدار بالا به الگوریتم گرادیان کاهشی ۱۰۰۰ چرخش فرصت می‌دهیم در صورتی که الگوریتم در این ۱۰۰۰ چرخش در حلقه نتواند به مقدار مشخصی همگرا شود از حلقه خارج می‌شویم تا فر‌آیند یافتن ضرائب طولانی نشود.

epochs = 1000 theta_0 = 0 theta_1 = 0 alpha = 0.01 m = len(X_train) for i in range(0, epochs): temp_0 = (theta_0 - (alpha * (1 / m) * sum([((theta_0 + theta_1 * X_train[j]) - y_train[j]) for j in range(0, m)]))) theta_1 = (theta_1 - (alpha * (1 / m) * sum([((theta_0 + theta_1 * X_train[j]) - y_train[j]) * X_train[j] for j in range(0, m)]))) if abs(temp_0 - theta_0) < 0.0000001: break theta_0 = temp_0

شرط همگرایی را در قطعه کد بالا به اینصورت تعریف می‌کنیم که مقدار فعلی theta_0 با مقدار قبلی کمتر از ۰.۰۰۰۰۰۰۱ اختلاف داشته باشند به عبارت دیگر اگر مقدار فعلی و قبلی ضریب theta_0 کمتر از این مقدار باهم اختلاف داشته باشند الگوریتم را همگرا درنظر گرفته و استفاده از break از حلقه خارج می‌شویم.

حال که مقدار ضرائب را داریم وقت آن رسیده تا عملکرد الگوریتم را محک بزنیم برای این کار مقادیر قسمتی از مجموعه دادگان را که به شکل test کنار گذاشته بودیم را با استفاده از ضرائب به دست آمده در بالا محاسبه می‌کنیم.

y_pred = [theta_0 + theta_1 * X_test[k] for k in range(0, len(X_test))]

و با استفاده از معیار r2 دقت رگرسیون را می‌سنجیم.

from sklearn.metrics import r2_score print(r2_score(y_test, y_test_pred))

همچنین از دو قطعه کد زیر جهت مصور سازی نتایج استفاده می‌کنیم.

plt.scatter(X_train, y_train, color = 'red') plt.plot(X_train, y_train_pred, color = 'blue') plt.title('Salary vs Experience (Test set)') plt.xlabel('Years of Experience') plt.ylabel('Salary') plt.show()


plt.scatter(X_test, y_test, color = 'red') plt.plot(X_test, y_test_pred, color = 'blue') plt.title('Salary vs Experience (Test set)') plt.xlabel('Years of Experience') plt.ylabel('Salary') plt.show()



رگرسیون خطیگرادیان کاهشی
شاید از این پست‌ها خوشتان بیاید