Комментарии (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. Практические рекомендации
Как выбрать регуляризацию:
- Начните с Ridge — самый стабильный и простой
- Если нужна интерпретируемость — используйте Lasso для feature selection
- Если есть корреляции между признаками — ElasticNet с L1_ratio=0.5
- Всегда используйте Cross-Validation для выбора параметра λ
- Нормализуйте признаки перед регуляризацией (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 может быть нестабильным, если признаков больше, чем выборок