الوحدة 2 · الانحدار
الـ Regularization باستخدام Ridge وLasso
20 دقراءة 4 دتمرين Python
الدرس في لمحة
تحكّم في الـ overfitting بمعاقبة المعاملات الكبيرة باستخدام Ridge وLasso، ووحّد مقاييس الـ features لتكون العقوبة عادلة، واستخدم أصفار Lasso اختيارًا تلقائيًا للـ features.
- 1عقوبة تُضاف إلى دالة الخسارة
- 2وحّد المقاييس أولًا، ثم عاقِب
- 3Lasso يختار الـ features نيابةً عنك
- 4ترويض المعاملات المترابطة
- 5Ridge أم Lasso؟
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
في الدرس السابق، أعطت الـ polynomial features من الدرجة 2 نموذجَ السكري 65 عمودًا، فانخفضت نتيجة الاختبار من 0.49 إلى 0.42. كان بإمكانك التخلّص من الأعمدة الإضافية. لكن الـ regularization (التنظيم) يقدّم ما هو أفضل: احتفظ بها كلها، لكن اجعل النموذج يدفع ثمن كل وحدة من المعاملات يستخدمها. الأعمدة التي تستحق مكانها تنال وزنًا؛ والبقية تتقلّص.
عقوبة تُضاف إلى دالة الخسارة
تصغّر المربعات الصغرى العادية الخطأ المربّع ولا شيء غيره. أما الانحدار المنظَّم فيصغّر الخطأ المربّع مضافًا إليه عقوبةٌ تكبر مع حجم المعاملات:
- Ridge يضيف
alpha * sum(coef ** 2)، وهي عقوبة L2. - Lasso يضيف
alpha * sum(abs(coef))، وهي عقوبة L1 (كما أن scikit-learn يعاير حدّ الخطأ بطريقة مختلفة قليلًا، ولهذا تختلف قيمalphaالجيدة بين النموذجين).
يحدّد alpha سعر الصرف. عند 0 تعود إلى المربعات الصغرى. ومع ازدياده، يجب على المعامل الكبير أن يشتري خفضًا كبيرًا في الخطأ كي يستحق وجوده. والمعاملات التي لا تلائم إلا الضجيج لا تستطيع ذلك، فتتقلّص. القيم المربّعة تجعل Ridge يقلّص كل شيء بسلاسة؛ والقيم المطلقة تجعل Lasso يدفع المعاملات الضعيفة حتى الصفر.
وحّد المقاييس أولًا، ثم عاقِب
تجمع العقوبة المعاملات كما لو كانت قابلة للمقارنة. وهي ليست كذلك إلا إذا اشتركت الـ features في مقياس واحد. قِس الإنفاق بالسنتات بدل الدولارات وسيصغر معامله مئة مرة، فلا تكاد العقوبة تلحظه. ضع دائمًا StandardScaler قبل أي نموذج معاقَب.
from sklearn.datasets import load_diabetes
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
X, y = load_diabetes(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
def degree2(model):
return make_pipeline(PolynomialFeatures(degree=2, include_bias=False), StandardScaler(), model)
print("no penalty:", round(degree2(LinearRegression()).fit(X_train, y_train).score(X_test, y_test), 3))
for alpha in [1, 10, 100, 1000]:
m = degree2(Ridge(alpha=alpha)).fit(X_train, y_train)
print(f"Ridge alpha={alpha:<5} train {m.score(X_train, y_train):.3f} test {m.score(X_test, y_test):.3f}")مع ارتفاع alpha تنخفض نتيجة التدريب باطّراد (إذ يُمنح النموذج حرية أقل)، بينما تصعد نتيجة الاختبار من 0.42 إلى 0.50 عند alpha=100، وهذا أفضل من النموذج العادي ذي الـ features العشرة. ادفعها إلى 1,000 فتنخفض النتيجتان معًا: صار النموذج الآن في الـ underfitting. يحوّل الـ regularization المفاضلةَ بين الـ overfitting والـ underfitting إلى مقبض تديره.
Lasso يختار الـ features نيابةً عنك
أجرِ التجربة نفسها مع Lasso وعُدّ المعاملات التي تنتهي إلى الصفر بالضبط.
from sklearn.datasets import load_diabetes
from sklearn.linear_model import Lasso
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
X, y = load_diabetes(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
for alpha in [0.1, 1, 2, 5]:
m = make_pipeline(PolynomialFeatures(degree=2, include_bias=False), StandardScaler(),
Lasso(alpha=alpha)).fit(X_train, y_train)
zeros = (m[-1].coef_ == 0).sum()
print(f"Lasso alpha={alpha:<4} test R2 {m.score(X_test, y_test):.3f} zero coefficients: {zeros} of 65")
names = m[0].get_feature_names_out()
kept = [n for n, c in zip(names, m[-1].coef_) if c != 0]
print("kept at alpha=5:", kept)عند alpha=2 يبلغ Lasso قيمة R² على الاختبار تساوي 0.53، وهي أفضل نتيجة انحدار في هذا القسم، مع تجاهله 39 عمودًا من أصل 65. وعند alpha=5 يحتفظ بـ 11 عمودًا: مؤشر كتلة الجسم وضغط الدم وs5 من النماذج السابقة، إضافة إلى بضعة تفاعلات مثل bmi bp. يصل m[-1] إلى آخر خطوة في الـ pipeline، ويُعطي m[0].get_feature_names_out() أسماء الأعمدة المولّدة.
ترويض المعاملات المترابطة
أتذكر s1 عند -918 وs2 عند +508 في الانحدار العادي، قياسَي دم مترابطَين يشدّ أحدهما عكس الآخر؟ مع الـ features الموحّدة المقاييس تظهر القصة نفسها على نطاق أصغر، والعقوبة تحسمها.
يوزّع Ridge الوزن بين الـ features المترابطة بشكل معقول بدل أن يتركها يلغي بعضها بعضًا؛ بينما يميل Lasso إلى الاحتفاظ بأحدها وحذف الآخر. وفي الحالتين تصبح المعاملات مستقرة بما يكفي كي لا ينقلب اتجاهها مع تغيير صغير في بيانات التدريب.
Ridge أم Lasso؟
كلاهما يعالج الداء نفسه، فالاختيار يرجع إلى ما تريده من النموذج النهائي. يفوز Ridge عادةً حين تحمل features كثيرة شيئًا قليلًا من الإشارة لكلٍّ منها، وهذا شائع مع القياسات المتداخلة، مثل قراءات الدم الست هنا. ويفوز Lasso حين تهمّ features قليلة والبقية ضجيج، ويسلّمك قائمة مشتريات أقصر من المدخلات، وهذا مهم حين يجب جمع كل feature وتنظيفه ومراقبته في بيئة الإنتاج. وحين تأتي الـ features في مجموعات مترابطة، قد تكون عادة Lasso في الاحتفاظ بعضو واحد، باعتباطية إلى حدّ ما، غير مستقرة من تدريب إلى آخر. ويمزج ElasticNet العقوبتين لهذه الحالة بالضبط. وعلى مجموعة بيانات بهذا الحجم، الإجابة الأمينة هي أن تجرّب الاثنين على بيانات تحقق وتحتفظ بالفائز، ثم تفضّل الأبسط حين يتعادلان.
بهذا يُختتم الانحدار. وفكرة العقوبة نفسها تعود مباشرة في القسم القادم: LogisticRegression منظَّم افتراضيًا، بمعامل C يعمل كمقلوب alpha. يعيد القسم 3 الهدفَ إلى انقطاع العملاء، نعم أو لا.
أهم النقاط
- يضيف الـ regularization إلى دالة الخسارة عقوبةً على حجم المعاملات، فيتنازل النموذج عن قليل من التوافق مع التدريب مقابل معاملات أبسط وأكثر استقرارًا.
- يعاقب Ridge مربعات المعاملات فيقلّصها جميعًا؛ ويعاقب Lasso قيمها المطلقة فيجعل بعضها صفرًا بالضبط.
alphaهو قوة العقوبة: 0 يعني المربعات الصغرى العادية، والقيم الكبيرة جدًا تسطّح النموذج حتى الـ underfitting.- وحّد مقاييس الـ features قبل النموذج المعاقَب، وإلا عاقبت العقوبةُ الـ features على وحداتها لا على فائدتها.
- اختر
alphaعلى بيانات تحقق أو بالتحقق المتقاطع، بالطريقة نفسها التي تختار بها أي hyperparameter.