فصل ۴: آموزش مدلها
تا اینجا مدلهای یادگیری ماشین و الگوریتمهای آموزش آنها عمدتاً مانند جعبهٔ سیاه به کار گرفته شدند. بدون دانستن جزئیات داخلی توانستیم سامانهٔ رگرسیون را بهینه کنیم، طبقهبند تصویر رقم را بهبود دهیم و حتی یک طبقهبند Spam طراحی کنیم. در بسیاری از کاربردها واقعاً میتوان بدون فهم همهٔ جزئیات پیادهسازی کارهای مفیدی انجام داد.
بااینحال، فهم سازوکار داخلی مدلها کمک میکند سریعتر مدل مناسب، الگوریتم آموزشی درست و فراپارامترهای معقول را انتخاب کنید. این دانش برای اشکالزدایی و تحلیل خطا نیز بسیار مفید است و بسیاری از موضوعات این فصل برای فهم، ساخت و آموزش شبکههای عصبی در بخش دوم کتاب ضروریاند.
فصل با رگرسیون خطی آغاز میشود و دو راه کاملاً متفاوت برای آموزش آن را بررسی میکند:
- استفاده از یک معادلهٔ بسته که مستقیماً پارامترهای بهینهٔ مدل را محاسبه میکند؛ یعنی پارامترهایی که تابع هزینه را روی مجموعهٔ آموزشی کمینه میکنند.
- استفاده از روش بهینهسازی تکرارشوندهٔ Gradient Descent که پارامترها را مرحلهبهمرحله تغییر میدهد تا تابع هزینه کمینه شود و در نهایت به همان ناحیهٔ جواب برسد.
انواع Batch، Mini-Batch و Stochastic Gradient Descent نیز معرفی میشوند. سپس Polynomial Regression برای دادههای غیرخطی، Learning Curve برای تشخیص Overfitting و Underfitting، روشهای Regularization و در پایان Logistic Regression و Softmax Regression بررسی خواهند شد.
رگرسیون خطی
در فصل ۱ مدل سادهٔ رضایت از زندگی را بهصورت life_satisfaction = θ₀ + θ₁ × GDP_per_capita دیدیم. این فقط یک تابع خطی از ویژگی ورودی است و θ₀ و θ₁ پارامترهای مدلاند.
بهطور کلی مدل خطی پیشبینی را با جمع وزندار ویژگیهای ورودی بهعلاوهٔ یک ثابت به نام Bias یا Intercept میسازد:
معادلهٔ ۴-۱. پیشبینی مدل رگرسیون خطی
ŷ = θ₀ + θ₁x₁ + θ₂x₂ + ⋯ + θₙxₙ
- ŷ مقدار پیشبینیشده است.
- n تعداد ویژگیهاست.
- xᵢ مقدار ویژگی iام است.
- θⱼ پارامتر jام مدل است؛ شامل Bias یعنی θ₀ و وزنهای ویژگیها θ₁ ... θₙ.
همین رابطه را میتوان به شکل برداری بسیار فشرده نوشت:
معادلهٔ ۴-۲. پیشبینی رگرسیون خطی به شکل برداری
ŷ = hθ(x) = θ · x
در اینجا hθ تابع فرضیه با پارامترهای θ، بردار θ شامل Bias و همهٔ وزنها، و بردار x شامل x₀ ... xₙ است که x₀ = 1. ضرب نقطهای θ · x همان جمع وزندار پارامترها و ویژگیهاست.
آموزش مدل یعنی انتخاب پارامترهایی که بهترین برازش را روی مجموعهٔ آموزشی ایجاد کنند. برای رگرسیون، RMSE معیار رایجی است، اما برای بهینهسازی رگرسیون خطی معمولاً MSE را کمینه میکنیم؛ چون کمینهکنندهٔ یک تابع مثبت، ریشهٔ دوم آن را نیز کمینه میکند.
معادلهٔ ۴-۳. تابع هزینهٔ MSE برای رگرسیون خطی
MSE(X, hθ) = (1/m) Σi=1m (θᵀx(i) − y(i))²
برای سادهکردن نمادگذاری، از اینجا اغلب بهجای MSE(X,hθ) فقط MSE(θ) نوشته میشود.
معادلهٔ نرمال
برای یافتن θ که MSE را کمینه کند یک راهحل بسته وجود دارد که مستقیماً جواب را میدهد و «معادلهٔ نرمال» نامیده میشود:
معادلهٔ ۴-۴. معادلهٔ نرمال
θ̂ = (XᵀX)−1Xᵀy
θ̂ مقداری از θ است که تابع هزینه را کمینه میکند و y بردار مقادیر هدف از نمونهٔ اول تا mام است.
برای آزمایش، دادهای تقریباً خطی تولید میکنیم:
import numpy as np
np.random.seed(42) # to make this code example reproducible
m = 100 # number of instances
X = 2 * np.random.rand(m, 1) # column vector
y = 4 + 3 * X + np.random.randn(m, 1) # column vector
شکل ۴-۱. مجموعهدادهٔ خطی تصادفی تولیدشده
اکنون θ را با معادلهٔ نرمال محاسبه میکنیم. np.linalg.inv() وارون ماتریس و عملگر @ ضرب ماتریسی را انجام میدهد:
from sklearn.preprocessing import add_dummy_feature
X_b = add_dummy_feature(X) # add x0 = 1 to each instance
theta_best = np.linalg.inv(X_b.T @ X_b) @ X_b.T @ y
تابع مولد داده در اصل y = 4 + 3x₁ + Gaussian noise بود. جواب بهدستآمده:
>>> theta_best
array([[4.21509616],
[2.77011339]])
انتظار ایدهآل θ₀ = 4 و θ₁ = 3 بود، اما نویز مانع بازیابی دقیق پارامترها شد. هرچه مجموعهداده کوچکتر و نویزیتر باشد، تخمین دقیق دشوارتر است.
با θ بهینه میتوان برای xهای جدید پیشبینی ساخت:
>>> X_new = np.array([[0], [2]])
>>> X_new_b = add_dummy_feature(X_new) # add x0 = 1 to each instance
>>> y_predict = X_new_b @ theta_best
>>> y_predict
array([[4.21509616],
[9.75532293]])
import matplotlib.pyplot as plt
plt.plot(X_new, y_predict, "r-", label="Predictions")
plt.plot(X, y, "b.")
[...] # beautify the figure: add labels, axis, grid, and legend
plt.show()
شکل ۴-۲. پیشبینیهای مدل رگرسیون خطی
رگرسیون خطی با Scikit-Learn
>>> from sklearn.linear_model import LinearRegression
>>> lin_reg = LinearRegression()
>>> lin_reg.fit(X, y)
>>> lin_reg.intercept_, lin_reg.coef_
(array([4.21509616]), array([[2.77011339]]))
>>> lin_reg.predict(X_new)
array([[4.21509616],
[9.75532293]])
Scikit-Learn جملهٔ Bias را در intercept_ و وزن ویژگیها را در coef_ جدا نگه میدارد. LinearRegression بر پایهٔ scipy.linalg.lstsq() یا Least Squares ساخته شده است:
>>> theta_best_svd, residuals, rank, s = np.linalg.lstsq(X_b, y, rcond=1e-6)
>>> theta_best_svd
array([[4.21509616],
[2.77011339]])
این تابع رابطهٔ θ̂ = X⁺y را محاسبه میکند که X⁺ شبهوارون Moore-Penrose است. میتوان آن را مستقیماً با np.linalg.pinv() نیز گرفت:
>>> np.linalg.pinv(X_b) @ y
array([[4.21509616],
[2.77011339]])
شبهوارون با تجزیهٔ مقدار منفرد یا SVD محاسبه میشود. SVD ماتریس X را به حاصلضرب سه ماتریس UΣVᵀ تجزیه میکند. سپس با صفرکردن مقادیر بسیار کوچک Σ، وارونکردن مقادیر ناصفر و Transpose مناسب، X⁺ ساخته میشود. این روش از محاسبهٔ مستقیم معادلهٔ نرمال کارآمدتر است و حالتهای مرزی را بهتر مدیریت میکند. اگر XᵀX وارونپذیر نباشد - مثلاً وقتی تعداد نمونهها از ویژگیها کمتر است یا برخی ویژگیها تکراریاند - معادلهٔ نرمال مشکل دارد، اما شبهوارون همیشه تعریف میشود.
پیچیدگی محاسباتی
معادلهٔ نرمال وارون ماتریس (n+1) × (n+1) را محاسبه میکند و پیچیدگی وارونسازی معمولاً حدود O(n2.4) تا O(n³) است. دو برابرشدن تعداد ویژگیها میتواند زمان را تقریباً ۵٫۳ تا ۸ برابر کند.
روش SVD استفادهشده در LinearRegression تقریباً O(n²) است؛ پس دو برابرشدن ویژگیها تقریباً چهار برابر هزینه دارد. هر دو روش وقتی تعداد ویژگیها بسیار بزرگ، مثلاً ۱۰۰ هزار، باشد کند میشوند. نکتهٔ مثبت این است که از نظر تعداد نمونههای آموزشی تقریباً خطیاند، یعنی O(m)، و اگر داده در حافظه جا شود مجموعههای بزرگ را نسبتاً خوب مدیریت میکنند.
پس از آموزش، پیشبینی بسیار سریع است و هزینهٔ آن نسبت به تعداد نمونههای مورد پیشبینی و تعداد ویژگیها خطی است.
Gradient Descent
Gradient Descent الگوریتم بهینهسازی عمومی برای یافتن جوابهای بهینه در طیف گستردهای از مسائل است. ایدهٔ اصلی این است که پارامترها را بهصورت تکراری تغییر دهیم تا تابع هزینه کمینه شود.
فرض کنید در کوهستان و مه غلیظ گم شدهاید و فقط شیب زمین زیر پا را حس میکنید. برای رسیدن سریع به کف دره باید در جهت تندترین سرازیری حرکت کنید. Gradient Descent همین کار را میکند: گرادیان محلی تابع خطا را نسبت به بردار پارامتر θ اندازه میگیرد و در جهت مخالف گرادیان حرکت میکند. وقتی گرادیان صفر شود به کمینه رسیدهایم.
در عمل θ با مقادیر تصادفی مقداردهی اولیه میشود؛ سپس گامبهگام طوری اصلاح میشود که تابع هزینه، مانند MSE، کاهش یابد تا الگوریتم همگرا شود.
شکل ۴-۳. پارامترها تصادفی آغاز میشوند و برای کمینهکردن هزینه بارها اصلاح میشوند؛ اندازهٔ گام با شیب تابع هزینه متناسب است.
نرخ یادگیری
فراپارامتر مهم Gradient Descent اندازهٔ گامها یا Learning Rate است. اگر نرخ یادگیری بسیار کوچک باشد، الگوریتم برای همگرایی به تکرارهای زیادی نیاز دارد:
شکل ۴-۴. نرخ یادگیری بسیار کوچک
اگر نرخ یادگیری بیش از حد بزرگ باشد، هر گام ممکن است از دره عبور کند و به سمت دیگر بپرد؛ حتی ممکن است الگوریتم واگرا شود و هزینه هر بار بزرگتر گردد:
شکل ۴-۵. نرخ یادگیری بسیار بزرگ میتواند الگوریتم را واگرا کند.
همهٔ تابعهای هزینه کاسهای منظم نیستند. ممکن است فرورفتگی، Ridge، Plateau و ناهمواریهای مختلف داشته باشند. اگر مقداردهی اولیه در ناحیهٔ نامناسبی باشد، الگوریتم میتواند به Local Minimum برسد یا مدت زیادی روی Plateau حرکت کند؛ و اگر زود متوقف شود هرگز به Global Minimum نرسد.
شکل ۴-۶. دو مشکل مهم Gradient Descent: کمینهٔ محلی و Plateau
خوشبختانه تابع هزینهٔ MSE برای رگرسیون خطی Convex است. اگر هر دو نقطهای را روی منحنی انتخاب کنید، پارهخط میان آنها پایینتر از منحنی نمیرود. نتیجه این است که Local Minimum وجود ندارد و فقط یک Global Minimum داریم. تابع نیز پیوسته است و شیب آن ناگهان جهش نمیکند. بنابراین اگر نرخ یادگیری بیش از حد بزرگ نباشد و زمان کافی بدهیم، Gradient Descent میتواند به هر اندازه دلخواه به کمینهٔ سراسری نزدیک شود.
با وجود Convex بودن، اگر مقیاس ویژگیها بسیار متفاوت باشد، سطح هزینه بهجای کاسهٔ تقریباً گرد، کشیده میشود. در این حالت Gradient Descent ممکن است ابتدا تقریباً عمود بر جهت کمینه حرکت کند و سپس راه طولانی در درهای تخت طی کند.
شکل ۴-۷. Gradient Descent با ویژگیهای هممقیاس در سمت چپ و بدون مقیاسبندی مناسب در سمت راست
این تصویر همچنین معنای «آموزش مدل» را روشن میکند: جستوجو در فضای پارامترهای مدل برای یافتن ترکیبی که تابع هزینه را کمینه کند. هرچه تعداد پارامترها بیشتر باشد، ابعاد فضای جستوجو بیشتر و مسئله سختتر میشود.
Batch Gradient Descent
برای پیادهسازی Gradient Descent باید مشتق تابع هزینه نسبت به هر پارامتر θⱼ را محاسبه کنیم؛ یعنی اگر θⱼ کمی تغییر کند هزینه چقدر تغییر خواهد کرد. این همان مشتق جزئی است.
معادلهٔ ۴-۵. مشتق جزئی تابع هزینه نسبت به θⱼ
∂MSE(θ)/∂θⱼ = (2/m) Σi=1m (θᵀx(i) − y(i)) xⱼ(i)
بهجای محاسبهٔ جداگانهٔ همهٔ مشتقها، بردار گرادیان همه را یکجا در خود دارد:
معادلهٔ ۴-۶. بردار گرادیان تابع هزینه
∇θMSE(θ) = (2/m) Xᵀ(Xθ − y)
بردار گرادیان جهت سربالایی را نشان میدهد، پس برای پایینرفتن باید آن را از θ کم کنیم. نرخ یادگیری η اندازهٔ این گام را کنترل میکند:
معادلهٔ ۴-۷. یک گام Gradient Descent
θnext = θ − η∇θMSE(θ)
پیادهسازی ساده:
eta = 0.1 # learning rate
n_epochs = 1000
m = len(X_b) # number of instances
np.random.seed(42)
theta = np.random.randn(2, 1) # randomly initialized model parameters
for epoch in range(n_epochs):
gradients = 2 / m * X_b.T @ (X_b @ theta - y)
theta = theta - eta * gradients
هر عبور کامل از مجموعهٔ آموزشی یک Epoch نامیده میشود. جواب نهایی:
>>> theta
array([[4.21509616],
[2.77011339]])
دقیقاً همان جواب معادلهٔ نرمال به دست آمده است. شکل ۴-۸ بیست گام نخست با سه نرخ یادگیری را مقایسه میکند: سمت چپ نرخ بسیار کم است و همگرایی طولانی میشود؛ وسط مناسب است و در چند Epoch به جواب میرسد؛ سمت راست بسیار زیاد است و الگوریتم واگرا میشود.
شکل ۴-۸. Gradient Descent با چند نرخ یادگیری متفاوت
برای یافتن نرخ مناسب میتوان از Grid Search استفاده کرد، اما بهتر است تعداد Epochها محدود باشد تا مدلهای بسیار کند زود کنار گذاشته شوند. برای تعداد Epoch نیز یک راه عملی این است که مقدار بزرگی تعیین شود، اما وقتی Norm بردار گرادیان از عدد بسیار کوچک ε - Tolerance - کمتر شد آموزش متوقف شود، چون الگوریتم تقریباً به کمینه رسیده است.
نرخ همگرایی
وقتی تابع هزینه Convex باشد و شیب آن ناگهان تغییر نکند - مانند MSE - Batch Gradient Descent با نرخ یادگیری ثابت در نهایت به جواب بهینه همگرا میشود. برای رسیدن به فاصلهٔ ε از بهینه ممکن است حدود O(1/ε) تکرار لازم باشد. اگر Tolerance را ده برابر کوچکتر کنید تا جواب دقیقتر شود، زمان میتواند تقریباً ده برابر بیشتر شود.