ادامهٔ Ridge Regression با SGD
همان Ridge Regression بخش قبل را میتوان با Stochastic Gradient Descent نیز پیادهسازی کرد:
>>> sgd_reg = SGDRegressor(penalty="l2", alpha=0.1 / m, tol=None,
... max_iter=1000, eta0=0.01, random_state=42)
...
>>> sgd_reg.fit(X, y.ravel()) # y.ravel() because fit() expects 1D targets
>>> sgd_reg.predict([[1.5]])
array([1.55302613])
فراپارامتر penalty نوع Regularization را مشخص میکند. مقدار "l2" به SGD میگوید جملهای متناسب با مربع Norm ℓ₂ بردار وزن به MSE اضافه کند. در SGDRegressor این جمله تقسیم بر m ندارد، به همین دلیل برای معادلشدن با Ridge(alpha=0.1) مقدار alpha=0.1/m داده شده است.
Lasso Regression
Least Absolute Shrinkage and Selection Operator یا Lasso نسخهٔ دیگری از رگرسیون خطی Regularized است. مانند Ridge جملهٔ Regularization به تابع هزینه اضافه میکند، اما بهجای مربع Norm ℓ₂ از Norm ℓ₁ وزنها استفاده میکند.
معادلهٔ ۴-۱۰. تابع هزینهٔ Lasso Regression
J(θ) = MSE(θ) + 2α Σi=1n |θᵢ|
ضریبهای این فرمول طوری انتخاب شدهاند که مقدار بهینهٔ α تا حد امکان به اندازهٔ مجموعهٔ آموزشی وابسته نباشد.
شکل ۴-۱۸. مدل خطی در سمت چپ و Polynomial در سمت راست با چند مقدار Lasso Regularization
ویژگی مهم Lasso این است که تمایل دارد وزن ویژگیهای کماهمیت را کاملاً صفر کند. در نمودار Polynomial با α برابر ۰٫۰۱، وزن ویژگیهای درجهٔ بالا حذف شده و مدل تقریباً درجهٔ سه به نظر میرسد. بنابراین Lasso بهصورت خودکار Feature Selection انجام میدهد و مدل Sparse با تعداد کمی وزن ناصفر تولید میکند.
شکل ۴-۱۹ شهود این رفتار را نشان میدهد. محورهای نمودار دو پارامتر مدلاند. در بالا-چپ خطوط تراز Loss ℓ₁ نمایش داده شدهاند. با حرکت به سمت محور، Loss خطی کاهش مییابد؛ اگر دو پارامتر مقدارهای متفاوتی داشته باشند، پارامتر کوچکتر زودتر به صفر میرسد و سپس حرکت در امتداد محور ادامه پیدا میکند. در بالا-راست، تابع هزینهٔ Lasso - یعنی MSE بهعلاوهٔ ℓ₁ - نشان داده شده است و مسیر Gradient Descent سریعاً یکی از پارامترها را صفر میکند.
شکل ۴-۱۹. مقایسهٔ Lasso و Ridge Regularization در فضای دو پارامتر
دو نمودار پایین همان ایده را با جریمهٔ ℓ₂ نشان میدهند. Loss ℓ₂ با نزدیکشدن به مبدأ نرم کاهش مییابد و Gradient Descent مسیر مستقیمتر و هموارتری میگیرد. در Ridge، گرادیانها نزدیک جواب بهینه کوچک میشوند و نوسان کاهش مییابد؛ به همین دلیل Ridge معمولاً نرمتر از Lasso همگرا میشود. با افزایش α جواب به مبدأ نزدیکتر میشود، اما وزنها لزوماً دقیقاً صفر نمیشوند.
تابع هزینهٔ Lasso در θᵢ = 0 مشتقپذیر نیست، اما میتوان در این نقاط از Subgradient استفاده کرد:
معادلهٔ ۴-۱۱. بردار Subgradient برای Lasso
g(θ,J) = ∇θMSE(θ) + 2α [sign(θ₁), sign(θ₂), …, sign(θₙ)]ᵀ
sign(θᵢ) = −1 if θᵢ<0, 0 if θᵢ=0, +1 if θᵢ>0
نمونهٔ Scikit-Learn:
>>> from sklearn.linear_model import Lasso
>>> lasso_reg = Lasso(alpha=0.1)
>>> lasso_reg.fit(X, y)
>>> lasso_reg.predict([[1.5]])
array([1.53788174])
میتوان بهجای آن از SGDRegressor(penalty="l1", alpha=0.1) نیز استفاده کرد.
Elastic Net Regression
Elastic Net حد واسط Ridge و Lasso است. جملهٔ Regularization ترکیبی وزندار از هر دو جریمه است و نسبت ترکیب با r کنترل میشود. اگر r=0 باشد دقیقاً Ridge و اگر r=1 باشد Lasso به دست میآید:
معادلهٔ ۴-۱۲. تابع هزینهٔ Elastic Net
J(θ) = MSE(θ) + r(2α Σ |θᵢ|) + (1−r)(α/m Σ θᵢ²)
در عمل تقریباً همیشه بهتر است حداقل کمی Regularization داشته باشید و از Linear Regression کاملاً بدون محدودیت دوری کنید. Ridge پیشفرض مناسبی است. اگر گمان میکنید فقط چند ویژگی واقعاً مفیدند، Lasso یا Elastic Net مناسبترند، زیرا وزن ویژگیهای بیفایده را به صفر میرسانند. در حالت عمومی Elastic Net معمولاً از Lasso مطمئنتر است، چون Lasso وقتی تعداد ویژگیها از تعداد نمونهها بیشتر باشد یا چند ویژگی همبستگی قوی داشته باشند میتواند رفتار ناپایداری نشان دهد.
>>> from sklearn.linear_model import ElasticNet
>>> elastic_net = ElasticNet(alpha=0.1, l1_ratio=0.5)
>>> elastic_net.fit(X, y)
>>> elastic_net.predict([[1.5]])
array([1.54333232])
در Scikit-Learn فراپارامتر l1_ratio همان r است.
Early Stopping
روش بسیار متفاوت دیگری برای Regularize کردن الگوریتمهای تکرارشونده مانند Gradient Descent این است که بهمحض رسیدن خطای Validation به کمینه، آموزش را متوقف کنیم. این روش Early Stopping نام دارد.
در شکل ۴-۲۰ یک Polynomial Regression با Degree بالا روی دادهٔ درجهٔ دو آموزش میبیند. در ابتدا با افزایش Epoch، خطای آموزش و Validation هر دو کاهش مییابند. پس از مدتی خطای Validation دیگر کاهش نمییابد و دوباره بالا میرود؛ یعنی مدل شروع به Overfit کردن دادهٔ آموزشی کرده است. Early Stopping مدل را در نقطهٔ کمینهٔ خطای Validation نگه میدارد.
شکل ۴-۲۰. Early Stopping؛ بهترین مدل در کمینهٔ خطای Validation انتخاب میشود.
پیادهسازی پایه:
from copy import deepcopy
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import StandardScaler
X_train, y_train, X_valid, y_valid = [...] # split the quadratic dataset
preprocessing = make_pipeline(PolynomialFeatures(degree=90, include_bias=False),
StandardScaler())
X_train_prep = preprocessing.fit_transform(X_train)
X_valid_prep = preprocessing.transform(X_valid)
sgd_reg = SGDRegressor(penalty=None, eta0=0.002, random_state=42)
n_epochs = 500
best_valid_rmse = float('inf')
for epoch in range(n_epochs):
sgd_reg.partial_fit(X_train_prep, y_train)
y_valid_predict = sgd_reg.predict(X_valid_prep)
val_error = mean_squared_error(y_valid, y_valid_predict, squared=False)
if val_error < best_valid_rmse:
best_valid_rmse = val_error
best_model = deepcopy(sgd_reg)
ابتدا Polynomial Featureها ساخته و ویژگیها Scale میشوند. سپس SGDRegressor بدون Regularization و با Learning Rate کوچک ایجاد میشود. در هر Epoch بهجای fit() از partial_fit() استفاده میشود تا آموزش تدریجی باشد. اگر RMSE Validation از بهترین مقدار قبلی کمتر شد، مدل با deepcopy() ذخیره میشود. برخلاف sklearn.base.clone() که فقط فراپارامترها را کپی میکند، Deep Copy پارامترهای آموختهشده را نیز نگه میدارد.
Logistic Regression
بعضی الگوریتمهای Regression برای Classification نیز قابل استفادهاند. Logistic Regression یا Logit Regression معمولاً برای تخمین احتمال تعلق یک نمونه به یک کلاس استفاده میشود؛ مثلاً احتمال Spam بودن یک ایمیل. اگر احتمال تخمینی از آستانه - معمولاً ۵۰ درصد - بیشتر باشد نمونه به کلاس مثبت ۱ و در غیر این صورت به کلاس منفی ۰ نسبت داده میشود. بنابراین شکل پایهٔ Logistic Regression یک طبقهبند دودویی است.
تخمین احتمالها
مانند Linear Regression، مدل ابتدا مجموع وزندار ویژگیها بهاضافهٔ Bias را محاسبه میکند، اما نتیجه را مستقیماً خروجی نمیدهد؛ بلکه تابع Logistic را روی آن اعمال میکند:
معادلهٔ ۴-۱۳. احتمال تخمینی Logistic Regression
p̂ = hθ(x) = σ(θᵀx)
تابع Logistic یا Sigmoid مقداری بین صفر و یک تولید میکند:
معادلهٔ ۴-۱۴. تابع Logistic
σ(t) = 1 / (1 + exp(−t))
شکل ۴-۲۱. تابع Logistic یا Sigmoid
وقتی احتمال p تخمین زده شد، مدل با آستانهٔ ۵۰ درصد پیشبینی میکند:
معادلهٔ ۴-۱۵. پیشبینی Logistic Regression با آستانهٔ ۵۰ درصد
ŷ = 0 if p̂ < 0.5; 1 if p̂ ≥ 0.5
چون σ(t)<0.5 برای t منفی و σ(t)≥0.5 برای t نامنفی است، مدل عملاً وقتی θᵀx مثبت باشد کلاس ۱ را پیشبینی میکند.
آموزش و تابع هزینه
هدف آموزش این است که مدل برای نمونههای مثبت احتمال بالا و برای منفیها احتمال پایین بدهد. هزینهٔ یک نمونه:
معادلهٔ ۴-۱۶. تابع هزینهٔ یک نمونه
c(θ) = −log(p̂) if y=1; −log(1−p̂) if y=0
وقتی مدل برای نمونهٔ مثبت احتمال نزدیک صفر بدهد، −log(p̂) بسیار بزرگ میشود؛ و برای نمونهٔ منفی اگر احتمال مثبت نزدیک یک باشد، −log(1−p̂) بزرگ میشود. در پیشبینی درست با اطمینان بالا، هزینه به صفر نزدیک میشود.
میانگین این هزینه روی همهٔ نمونهها Log Loss است:
معادلهٔ ۴-۱۷. تابع هزینهٔ Logistic Regression یا Log Loss
J(θ) = −(1/m) Σ [y(i) log(p̂(i)) + (1−y(i)) log(1−p̂(i))]
برای Log Loss راهحل بستهای مانند Normal Equation شناخته نشده است، اما تابع Convex است؛ بنابراین Gradient Descent، با نرخ یادگیری مناسب و زمان کافی، میتواند Global Minimum را پیدا کند. مشتق جزئی نسبت به پارامتر jام:
معادلهٔ ۴-۱۸. مشتق جزئی تابع هزینهٔ Logistic
∂J(θ)/∂θⱼ = (1/m) Σi=1m (σ(θᵀx(i)) − y(i)) xⱼ(i)
این رابطه بسیار شبیه مشتق MSE است: برای هر نمونه خطای پیشبینی در مقدار ویژگی ضرب میشود و سپس میانگین گرفته میشود. با تشکیل بردار گرادیان میتوان Batch GD اجرا کرد؛ برای SGD یک نمونه و برای Mini-Batch GD یک Batch کوچک در هر گام استفاده میشود.
مرز تصمیم با مجموعهدادهٔ Iris
برای نمایش Logistic Regression از مجموعهدادهٔ مشهور Iris استفاده میشود. این مجموعه طول و عرض Sepal و Petal را برای ۱۵۰ گل از سه گونهٔ Iris setosa، Iris versicolor و Iris virginica دارد.
شکل ۴-۲۲. نمونههایی از سه گونهٔ گیاه Iris
ابتدا داده را بارگذاری میکنیم:
>>> from sklearn.datasets import load_iris
>>> iris = load_iris(as_frame=True)
>>> list(iris)
['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names',
'filename', 'data_module']
>>> iris.data.head(3)
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
>>> iris.target.head(3) # note that the instances are not shuffled
0 0
1 0
2 0
Name: target, dtype: int64
>>> iris.target_names
array(['setosa', 'versicolor', 'virginica'], dtype='<U10')
طبقهبندی میسازیم که فقط بر اساس عرض گلبرگ تشخیص دهد گل از نوع virginica هست یا نه:
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
X = iris.data[["petal width (cm)"]].values
y = iris.target_names[iris.target] == 'virginica'
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
log_reg = LogisticRegression(random_state=42)
log_reg.fit(X_train, y_train)
احتمال تخمینی برای عرض گلبرگ از ۰ تا ۳ سانتیمتر:
X_new = np.linspace(0, 3, 1000).reshape(-1, 1) # reshape to get a column vector
y_proba = log_reg.predict_proba(X_new)
decision_boundary = X_new[y_proba[:, 1] >= 0.5][0, 0]
plt.plot(X_new, y_proba[:, 0], "b--", linewidth=2,
label="Not Iris virginica proba")
plt.plot(X_new, y_proba[:, 1], "g-", linewidth=2, label="Iris virginica proba")
plt.plot([decision_boundary, decision_boundary], [0, 1], "k:", linewidth=2,
label="Decision boundary")
[...] # beautify the figure: add grid, labels, axis, legend, arrows, and samples
plt.show()
شکل ۴-۲۳. احتمالهای تخمینی و مرز تصمیم بر اساس عرض گلبرگ
عرض گلبرگ virginica تقریباً ۱٫۴ تا ۲٫۵ سانتیمتر و برای دو گونهٔ دیگر عمدتاً ۰٫۱ تا ۱٫۸ است، پس همپوشانی وجود دارد. بالاتر از حدود ۲ سانتیمتر مدل با اطمینان زیاد virginica و زیر ۱ سانتیمتر با اطمینان زیاد «غیر virginica» را تخمین میزند. در میانه نامطمئن است؛ بااینحال predict() همیشه محتملترین کلاس را انتخاب میکند. مرز تصمیم جایی نزدیک ۱٫۶۵ سانتیمتر است که دو احتمال برابر ۵۰ درصد میشوند:
>>> decision_boundary
1.6516516516516517
>>> log_reg.predict([[1.7], [1.5]])
array([ True, False])
با دو ویژگی طول و عرض Petal، مرز تصمیم همچنان خطی است. خطوط موازی در شکل ۴-۲۴ نواحی احتمال متفاوت را نشان میدهند.
شکل ۴-۲۴. مرز تصمیم خطی برای Iris virginica با دو ویژگی
Softmax Regression
Logistic Regression را میتوان بهصورت مستقیم به بیش از دو کلاس تعمیم داد، بدون اینکه چند طبقهبند دودویی OvR یا OvO بسازیم. این مدل Softmax Regression یا Multinomial Logistic Regression نام دارد.
برای نمونهٔ x ابتدا برای هر کلاس k یک Score محاسبه میشود:
معادلهٔ ۴-۱۹. امتیاز Softmax برای کلاس k
sk(x) = θ(k)ᵀx
هر کلاس بردار پارامتر ویژهٔ خود را دارد و این بردارها معمولاً بهصورت سطرهای ماتریس پارامتر Θ ذخیره میشوند. سپس Softmax احتمال هر کلاس را با نماییکردن Scoreها و نرمالسازی آنها محاسبه میکند:
معادلهٔ ۴-۲۰. تابع Softmax
p̂k = exp(sk(x)) / Σj=1K exp(sj(x))
K تعداد کلاسهاست و خروجی تابع برای هر کلاس احتمال تخمینی تعلق نمونه به آن کلاس است.
مدل بهطور پیشفرض کلاسی را انتخاب میکند که بیشترین احتمال - و در نتیجه بیشترین Score - را دارد:
معادلهٔ ۴-۲۱. پیشبینی طبقهبند Softmax
ŷ = argmaxk p̂k = argmaxk sk(x) = argmaxk θ(k)ᵀx
Cross-Entropy
هدف آموزش Softmax این است که احتمال کلاس هدف بالا و احتمال سایر کلاسها پایین باشد. تابع هزینهٔ Cross-Entropy وقتی مدل برای کلاس صحیح احتمال پایین تخمین میزند آن را جریمه میکند:
معادلهٔ ۴-۲۲. تابع هزینهٔ Cross-Entropy
J(Θ) = −(1/m) Σi=1m Σk=1K yk(i) log(p̂k(i))
yk(i) احتمال هدف برای تعلق نمونهٔ i به کلاس k است که در طبقهبندی معمولی ۰ یا ۱ است. وقتی فقط دو کلاس داریم، این تابع دقیقاً به Log Loss Logistic Regression تبدیل میشود.
گرادیان Cross-Entropy نسبت به بردار پارامتر کلاس k:
معادلهٔ ۴-۲۳. بردار گرادیان Cross-Entropy برای کلاس k
∇θ(k)J(Θ) = (1/m) Σi=1m (p̂k(i) − yk(i)) x(i)
با محاسبهٔ گرادیان برای همهٔ کلاسها میتوان Gradient Descent یا هر بهینهساز دیگری را برای یافتن Θ به کار برد.
Softmax روی Iris
LogisticRegression در Scikit-Learn وقتی روی بیش از دو کلاس آموزش داده شود و Solver مناسب مانند lbfgs استفاده شود، Softmax را بهطور خودکار اجرا میکند. Regularization ℓ₂ نیز بهطور پیشفرض فعال است و با C کنترل میشود:
X = iris.data[["petal length (cm)", "petal width (cm)"]].values
y = iris["target"]
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
softmax_reg = LogisticRegression(C=30, random_state=42)
softmax_reg.fit(X_train, y_train)
برای گل با Petal به طول ۵ و عرض ۲ سانتیمتر:
>>> softmax_reg.predict([[5, 2]])
array([2])
>>> softmax_reg.predict_proba([[5, 2]]).round(2)
array([[0. , 0.04, 0.96]])
مدل کلاس ۲ یعنی Iris virginica را با احتمال ۹۶ درصد و versicolor را با ۴ درصد تخمین میزند.
شکل ۴-۲۵. مرزهای تصمیم Softmax Regression و خطوط احتمال برای کلاس Iris versicolor
مرز میان هر دو کلاس خطی است. خطوط منحنی روی نمودار مقدار احتمال کلاس versicolor را نشان میدهند. مدل حتی ممکن است کلاسی را انتخاب کند که احتمال آن کمتر از ۵۰ درصد است؛ برای نمونه در نقطهٔ تقاطع سه مرز، احتمال هر کلاس حدود ۳۳ درصد است ولی یکی از آنها بهعنوان بیشترین مقدار انتخاب میشود.
جمعبندی فصل ۴
در این فصل چند روش برای آموزش مدلهای خطی رگرسیونی و طبقهبندی بررسی شد: راهحل بسته برای Linear Regression، انواع Gradient Descent، Polynomial Regression، Learning Curve، Regularization با Ridge، Lasso و Elastic Net، Early Stopping و در پایان سازوکار Logistic و Softmax Regression. اکنون بخشی از جعبهٔ سیاه مدلهای یادگیری ماشین باز شده است و فصل بعد سراغ Support Vector Machine میرود.
تمرینهای فصل ۴
- اگر مجموعهٔ آموزشی میلیونها ویژگی داشته باشد، کدام الگوریتم آموزش Linear Regression مناسبتر است؟
- اگر ویژگیها مقیاسهای بسیار متفاوت داشته باشند، کدام الگوریتمها آسیب میبینند و چگونه؟ چه کاری باید انجام داد؟
- آیا Gradient Descent هنگام آموزش Logistic Regression ممکن است در Local Minimum گیر کند؟
- اگر همهٔ الگوریتمهای Gradient Descent بهاندازهٔ کافی اجرا شوند، آیا الزاماً به یک مدل میرسند؟
- اگر در Batch GD خطای Validation در هر Epoch پیوسته بالا برود، احتمالاً چه اتفاقی افتاده و چگونه باید آن را اصلاح کرد؟
- آیا در Mini-Batch GD بهتر است بهمحض اولین افزایش خطای Validation آموزش را متوقف کرد؟
- کدام نوع Gradient Descent سریعتر به نزدیکی جواب بهینه میرسد؟ کدامیک واقعاً همگرا میشود؟ چگونه میتوان بقیه را نیز همگرا کرد؟
- اگر در Polynomial Regression فاصلهٔ بزرگی میان خطای آموزش و Validation در Learning Curve ببینید، چه رخ داده است؟ سه راه برای رفع آن چیست؟
- در Ridge Regression اگر خطای آموزش و Validation تقریباً برابر و نسبتاً بالا باشند، مسئله Bias بالاست یا Variance بالا؟ α باید افزایش یابد یا کاهش؟
- چرا ممکن است بخواهید: الف) Ridge را بهجای Linear Regression بدون Regularization، ب) Lasso را بهجای Ridge، و ج) Elastic Net را بهجای Lasso استفاده کنید؟
- اگر بخواهید عکسها را هم از نظر Outdoor/Indoor و هم Daytime/Nighttime طبقهبندی کنید، دو Logistic Regression مناسبتر است یا یک Softmax Regression؟
- Batch Gradient Descent با Early Stopping را برای Softmax Regression فقط با NumPy و بدون Scikit-Learn پیادهسازی کنید و آن را روی مسئلهای مانند Iris به کار ببرید.
راهحل تمرینها در انتهای Notebook فصل در منبع تکمیلی کتاب قرار دارد.