← Назад к вопросам

Как регуляризуется линейная модель?

1.6 Junior🔥 111 комментариев
#Машинное обучение

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Регуляризация линейной модели: Теория и практика

Регуляризация — это один из наиболее важных инструментов в machine learning для предотвращения переобучения. За годы работы я видел множество случаев, когда правильная регуляризация спасла проект от провала. Расскажу в деталях о всех подходах.

1. Основная концепция: Почему регуляризация нужна

Без регуляризации линейная модель может построить очень сложные коэффициенты, которые идеально подходят к тренировочным данным, но плохо работают на новых данных.

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.preprocessing import PolynomialFeatures
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

# Создаём синтетические данные
np.random.seed(42)
X = np.linspace(0, 10, 100).reshape(-1, 1)
y = 3 * X[:, 0] + 5 + np.random.randn(100) * 3

# Полиномиальные признаки (высокая степень = высокое переобучение)
poly = PolynomialFeatures(degree=10)
X_poly = poly.fit_transform(X)

X_train, X_test, y_train, y_test = train_test_split(X_poly, y, test_size=0.2, random_state=42)

# Модель БЕЗ регуляризации (OLS)
model_ols = LinearRegression()
model_ols.fit(X_train, y_train)

train_error_ols = mean_squared_error(y_train, model_ols.predict(X_train))
test_error_ols = mean_squared_error(y_test, model_ols.predict(X_test))

print("===== ЛИНЕЙНАЯ РЕГРЕССИЯ БЕЗ РЕГУЛЯРИЗАЦИИ (OLS) =====")
print(f"Train MSE: {train_error_ols:.4f}")
print(f"Test MSE: {test_error_ols:.4f}")
print(f"Коэффициенты (первые 5): {model_ols.coef_[:5]}")
print(f"Норма коэффициентов (L2): {np.linalg.norm(model_ols.coef_):.2f}")
print(f"\nОче видно: модель переобучена (огромная разница между train и test)")

2. L2 Регуляризация (Ridge Regression)

Добавляем штраф за большие коэффициенты:

Loss = MSE + λ * Σ(β²)

from sklearn.linear_model import Ridge
import seaborn as sns

# Ridge регрессия с разными значениями alpha (λ)
alphas = [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]
ridge_models = {}
train_errors = []
test_errors = []
coeff_norms = []

for alpha in alphas:
    ridge = Ridge(alpha=alpha)
    ridge.fit(X_train, y_train)
    
    train_pred = ridge.predict(X_train)
    test_pred = ridge.predict(X_test)
    
    ridge_models[alpha] = ridge
    train_errors.append(mean_squared_error(y_train, train_pred))
    test_errors.append(mean_squared_error(y_test, test_pred))
    coeff_norms.append(np.linalg.norm(ridge.coef_))

print("\n===== RIDGE REGRESSION (L2) =====")
print(f"{'Alpha':<10} {'Train MSE':<12} {'Test MSE':<12} {'||β||_2':<10}")
print("-" * 45)
for i, alpha in enumerate(alphas):
    print(f"{alpha:<10.3f} {train_errors[i]:<12.4f} {test_errors[i]:<12.4f} {coeff_norms[i]:<10.2f}")

# Визуализация
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# График ошибок
axes[0].plot(np.log10(alphas), train_errors, 'o-', label='Train Error', linewidth=2)
axes[0].plot(np.log10(alphas), test_errors, 's-', label='Test Error', linewidth=2)
axes[0].set_xlabel('log10(λ)')
axes[0].set_ylabel('MSE')
axes[0].set_title('Ridge: Train vs Test Error')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# График норм коэффициентов
axes[1].plot(np.log10(alphas), coeff_norms, 'o-', color='green', linewidth=2)
axes[1].set_xlabel('log10(λ)')
axes[1].set_ylabel('||β||_2')
axes[1].set_title('Ridge: Норма коэффициентов')
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

print("\nВыводы из Ridge:")
print(f"- При λ=0 (no regularization): test error = {test_errors[0]:.4f}")
print(f"- При λ=1 (оптимальное): test error = {test_errors[3]:.4f}")
print(f"- При λ=100 (сильное): test error = {test_errors[5]:.4f}")
print(f"- Ridge уменьшает коэффициенты, но не обнуляет")

3. L1 Регуляризация (Lasso Regression)

Добавляем штраф за абсолютные значения коэффициентов:

Loss = MSE + λ * Σ|β|

from sklearn.linear_model import Lasso

# Lasso с разными значениями alpha
alphas_lasso = [0.001, 0.01, 0.1, 0.5, 1.0, 2.0]
lasso_models = {}
train_errors_lasso = []
test_errors_lasso = []
num_nonzero = []
coeff_norms_lasso = []

for alpha in alphas_lasso:
    lasso = Lasso(alpha=alpha, max_iter=10000)
    lasso.fit(X_train, y_train)
    
    train_pred = lasso.predict(X_train)
    test_pred = lasso.predict(X_test)
    
    lasso_models[alpha] = lasso
    train_errors_lasso.append(mean_squared_error(y_train, train_pred))
    test_errors_lasso.append(mean_squared_error(y_test, test_pred))
    num_nonzero.append(np.sum(lasso.coef_ != 0))
    coeff_norms_lasso.append(np.linalg.norm(lasso.coef_, ord=1))

print("\n===== LASSO REGRESSION (L1) =====")
print(f"{'Alpha':<10} {'Train MSE':<12} {'Test MSE':<12} {'Non-zero':<12} {'||β||_1':<10}")
print("-" * 55)
for i, alpha in enumerate(alphas_lasso):
    print(f"{alpha:<10.3f} {train_errors_lasso[i]:<12.4f} {test_errors_lasso[i]:<12.4f} \
{num_nonzero[i]:<12} {coeff_norms_lasso[i]:<10.2f}")

print("\nКлючевое отличие Lasso:")
print("- Может обнулять коэффициенты (feature selection)")
print(f"- При α=0.001: {num_nonzero[0]} ненулевых коэффициентов из {len(lasso_models[0.001].coef_)}")
print(f"- При α=2.0: {num_nonzero[-1]} ненулевых коэффициентов из {len(lasso_models[2.0].coef_)}")

4. Elastic Net (комбинация L1 и L2)

Объединяет преимущества Ridge и Lasso:

Loss = MSE + λ₁ * Σ|β| + λ₂ * Σ(β²)

илиass

Loss = MSE + λ * (α * Σ|β| + (1-α) * Σ(β²))

from sklearn.linear_model import ElasticNet

print("\n===== ELASTIC NET (L1 + L2) =====")

# ElasticNet с разными соотношениями L1/L2
l1_ratios = [0.0, 0.25, 0.5, 0.75, 1.0]  # 0=pure Ridge, 1=pure Lasso
results_elastic = {}

for l1_ratio in l1_ratios:
    elastic = ElasticNet(alpha=0.1, l1_ratio=l1_ratio, max_iter=10000)
    elastic.fit(X_train, y_train)
    
    test_error = mean_squared_error(y_test, elastic.predict(X_test))
    num_nonzero = np.sum(elastic.coef_ != 0)
    
    results_elastic[l1_ratio] = {'test_error': test_error, 'nonzero': num_nonzero}
    
    model_type = "Ridge" if l1_ratio == 0 else "Lasso" if l1_ratio == 1 else "ElasticNet"
    print(f"L1-ratio={l1_ratio} ({model_type:<8}): Test MSE={test_error:.4f}, Non-zero={num_nonzero}")

print("\nElasticNet объединяет:")
print("- Способность Lasso выполнять feature selection (L1)")
print("- Стабильность Ridge при высокой мультиколлинеарности (L2)")

5. Выбор оптимального параметра регуляризации (Cross-Validation)

from sklearn.linear_model import RidgeCV, LassoCV
from sklearn.model_selection import cross_val_score

# Автоматический выбор alpha через cross-validation
ridge_cv = RidgeCV(alphas=np.logspace(-3, 3, 100), cv=5)
ridge_cv.fit(X_train, y_train)

print(f"\n===== ВЫБОР ПАРАМЕТРА ЧЕРЕЗ CV =====")
print(f"\nRidge:")
print(f"Оптимальный alpha: {ridge_cv.alpha_:.4f}")
print(f"Train MSE: {mean_squared_error(y_train, ridge_cv.predict(X_train)):.4f}")
print(f"Test MSE: {mean_squared_error(y_test, ridge_cv.predict(X_test)):.4f}")

lasso_cv = LassoCV(alphas=np.logspace(-3, 1, 100), cv=5, max_iter=10000)
lasso_cv.fit(X_train, y_train)

print(f"\nLasso:")
print(f"Оптимальный alpha: {lasso_cv.alpha_:.4f}")
print(f"Train MSE: {mean_squared_error(y_train, lasso_cv.predict(X_train)):.4f}")
print(f"Test MSE: {mean_squared_error(y_test, lasso_cv.predict(X_test)):.4f}")
print(f"Ненулевых коэффициентов: {np.sum(lasso_cv.coef_ != 0)}/{len(lasso_cv.coef_)}")

# Визуализация CV процесса
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Ridge CV path
alphas_path = np.logspace(-3, 3, 100)
ridge_scores = []
for alpha in alphas_path:
    ridge = Ridge(alpha=alpha)
    ridge.fit(X_train, y_train)
    ridge_scores.append(mean_squared_error(y_test, ridge.predict(X_test)))

axes[0].plot(np.log10(alphas_path), ridge_scores, 'b-', linewidth=2)
axes[0].axvline(np.log10(ridge_cv.alpha_), color='r', linestyle='--', label=f'Optimal α={ridge_cv.alpha_:.4f}')
axes[0].set_xlabel('log10(α)')
axes[0].set_ylabel('Test MSE')
axes[0].set_title('Ridge: CV Selection Path')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# Lasso CV path
alphas_lasso_path = np.logspace(-3, 1, 100)
lasso_scores = []
for alpha in alphas_lasso_path:
    lasso = Lasso(alpha=alpha, max_iter=10000)
    lasso.fit(X_train, y_train)
    lasso_scores.append(mean_squared_error(y_test, lasso.predict(X_test)))

axes[1].plot(np.log10(alphas_lasso_path), lasso_scores, 'g-', linewidth=2)
axes[1].axvline(np.log10(lasso_cv.alpha_), color='r', linestyle='--', label=f'Optimal α={lasso_cv.alpha_:.4f}')
axes[1].set_xlabel('log10(α)')
axes[1].set_ylabel('Test MSE')
axes[1].set_title('Lasso: CV Selection Path')
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

6. Сравнение методов регуляризации

МетодФормула штрафаСвойстваКогда использовать
Ridge (L2)λ∑β²Уменьшает все коэффициентыПри мультиколлинеарности
Lasso (L1)λ∑|β|Обнуляет некоторыеFeature selection важен
ElasticNetλ(α∑|β| + (1-α)∑β²)Комбинирует обаБаланс между двумя
None (OLS)-Максимум fitМного данных, мало признаков
# Прямое сравнение всех методов
print("\n===== ИТОГОВОЕ СРАВНЕНИЕ =====")
print(f"{'Model':<20} {'Train MSE':<12} {'Test MSE':<12} {'||β||_1':<12} {'||β||_2':<12}")
print("-" * 70)

models_comparison = {
    "OLS": model_ols,
    "Ridge (α=1.0)": ridge_models[1.0],
    "Lasso (α=0.1)": lasso_cv,
    "ElasticNet": ElasticNet(alpha=0.1, l1_ratio=0.5, max_iter=10000).fit(X_train, y_train),
}

for name, model in models_comparison.items():
    train_error = mean_squared_error(y_train, model.predict(X_train))
    test_error = mean_squared_error(y_test, model.predict(X_test))
    l1_norm = np.linalg.norm(model.coef_, ord=1)
    l2_norm = np.linalg.norm(model.coef_, ord=2)
    
    print(f"{name:<20} {train_error:<12.4f} {test_error:<12.4f} {l1_norm:<12.2f} {l2_norm:<12.2f}")

7. Практические рекомендации

Как выбрать регуляризацию:

  1. Начните с Ridge — самый стабильный и простой
  2. Если нужна интерпретируемость — используйте Lasso для feature selection
  3. Если есть корреляции между признаками — ElasticNet с L1_ratio=0.5
  4. Всегда используйте Cross-Validation для выбора параметра λ
  5. Нормализуйте признаки перед регуляризацией (RobustScaler или StandardScaler)
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

# Best practice: Pipeline со стандартизацией и регуляризацией
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('ridge', RidgeCV(alphas=np.logspace(-3, 3, 100), cv=5))
])

pipeline.fit(X_train, y_train)
print(f"\nPipeline test MSE: {mean_squared_error(y_test, pipeline.predict(X_test)):.4f}")

Ловушки:

  • Не забудьте нормализовать признаки
  • Параметр λ очень чувствителен к масштабу данных
  • CV с малым количеством фолдов даёт нестабильный результат
  • Lasso может быть нестабильным, если признаков больше, чем выборок
Как регуляризуется линейная модель? | PrepBro