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

Как работает L2 регуляризация?

2.0 Middle🔥 241 комментариев
#Глубокое обучение#Машинное обучение

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

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

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

Как работает L2 регуляризация?

L2 регуляризация (также известная как Ridge regression или Tikhonov regularization) — один из ключевых методов борьбы с переобучением. Давайте разберём механику и применение.

1. Основной принцип L2 регуляризации

L2 регуляризация штрафует большие коэффициенты модели.

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import Ridge, LinearRegression
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

# Теория: обычная функция потерь (без регуляризации)
# Loss = MSE = (1/n) * sum((y_pred - y_true)^2)

# С L2 регуляризацией (Ridge):
# Loss = MSE + lambda * (1/n) * sum(w^2)
# где lambda (alpha) — сила штрафа
# w — коэффициенты модели

# Визуально:
print("L2 регуляризация добавляет штраф за большие коэффициенты:")
print("Loss = (y - y_pred)^2 + lambda * w^2")
print("\nВнутренняя интерпретация:")
print("- Без штрафа: модель подстраивает w как угодно высоко")
print("- С штрафом: модель балансирует между точностью и простотой")

2. Математическое объяснение

# Давайте посмотрим как lambda влияет на коэффициенты

X, y = make_regression(n_samples=100, n_features=20, noise=10, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

alphas = [0.001, 0.01, 0.1, 1, 10, 100]  # lambda параметры
results = []

for alpha in alphas:
    # Обучаем Ridge с разными lambda
    ridge = Ridge(alpha=alpha)
    ridge.fit(X_train, y_train)
    
    # Предсказания
    y_pred_train = ridge.predict(X_train)
    y_pred_test = ridge.predict(X_test)
    
    # Метрики
    train_mse = mean_squared_error(y_train, y_pred_train)
    test_mse = mean_squared_error(y_test, y_pred_test)
    
    # Размер коэффициентов
    coef_norm = np.linalg.norm(ridge.coef_)  # L2 норма
    
    results.append({
        'alpha': alpha,
        'train_mse': train_mse,
        'test_mse': test_mse,
        'coef_norm': coef_norm,
        'coefficients': ridge.coef_
    })
    
    print(f"Alpha={alpha:7.3f}: Train MSE={train_mse:.2f}, Test MSE={test_mse:.2f}, Coef L2 norm={coef_norm:.2f}")

# Визуализируем
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

# График зависимости MSE от alpha
axes[0].plot([r['alpha'] for r in results], [r['train_mse'] for r in results], 'o-', label='Train MSE')
axes[0].plot([r['alpha'] for r in results], [r['test_mse'] for r in results], 's-', label='Test MSE')
axes[0].set_xscale('log')
axes[0].set_xlabel('Alpha (lambda)')
axes[0].set_ylabel('MSE')
axes[0].set_title('MSE vs Regularization Strength')
axes[0].legend()
axes[0].grid(True)

# График зависимости нормы коэффициентов от alpha
axes[1].plot([r['alpha'] for r in results], [r['coef_norm'] for r in results], 'o-', color='red')
axes[1].set_xscale('log')
axes[1].set_xlabel('Alpha (lambda)')
axes[1].set_ylabel('L2 norm of coefficients')
axes[1].set_title('Coefficient Magnitude vs Regularization')
axes[1].grid(True)

plt.tight_layout()
plt.show()

# Вывод: при увеличении alpha, коэффициенты становятся меньше

3. Сравнение с обычной линейной регрессией

# Обычная линейная регрессия (без регуляризации)
lr = LinearRegression()
lr.fit(X_train, y_train)

y_pred_lr_train = lr.predict(X_train)
y_pred_lr_test = lr.predict(X_test)

lr_train_mse = mean_squared_error(y_train, y_pred_lr_train)
lr_test_mse = mean_squared_error(y_test, y_pred_lr_test)
lr_coef_norm = np.linalg.norm(lr.coef_)

print("\n=== Сравнение Linear Regression vs Ridge ===")
print(f"\nLinear Regression (no regularization):")
print(f"Train MSE: {lr_train_mse:.2f}")
print(f"Test MSE: {lr_test_mse:.2f}")
print(f"Coef L2 norm: {lr_coef_norm:.2f}")
print(f"Overfitting gap: {lr_test_mse - lr_train_mse:.2f}")

# Оптимальный Ridge
ridge_optimal = Ridge(alpha=1.0)
ridge_optimal.fit(X_train, y_train)

y_pred_ridge_train = ridge_optimal.predict(X_train)
y_pred_ridge_test = ridge_optimal.predict(X_test)

ridge_train_mse = mean_squared_error(y_train, y_pred_ridge_train)
ridge_test_mse = mean_squared_error(y_test, y_pred_ridge_test)
ridge_coef_norm = np.linalg.norm(ridge_optimal.coef_)

print(f"\nRidge Regression (alpha=1.0):")
print(f"Train MSE: {ridge_train_mse:.2f}")
print(f"Test MSE: {ridge_test_mse:.2f}")
print(f"Coef L2 norm: {ridge_coef_norm:.2f}")
print(f"Overfitting gap: {ridge_test_mse - ridge_train_mse:.2f}")

print(f"\nУлучшение на тестовом наборе: {(lr_test_mse - ridge_test_mse) / lr_test_mse * 100:.1f}%")

4. Геометрическая интерпретация

# L2 регуляризация = ограничение нормы коэффициентов
# Это эквивалентно нахождению решения в гиперсфере радиуса C

# Визуализация для 2D случая
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

# Слева: без регуляризации
w = np.linspace(-3, 3, 100)
loss_no_reg = w**2  # простая квадратичная функция потерь

axes[0].plot(w, loss_no_reg, 'b-', linewidth=2, label='Loss function')
axes[0].axvline(x=0, color='r', linestyle='--', alpha=0.5)
axes[0].scatter([0], [0], color='r', s=100, zorder=5, label='Optimal without regularization')
axes[0].set_xlabel('Weight w')
axes[0].set_ylabel('Loss')
axes[0].set_title('Without Regularization')
axes[0].legend()
axes[0].grid(True)

# Справа: с L2 регуляризацией
lambda_val = 0.5
loss_l2 = w**2 + lambda_val * w**2  # loss + lambda * w^2

axes[1].plot(w, loss_l2, 'b-', linewidth=2, label='Loss + L2 penalty')
axes[1].plot(w, w**2, 'g--', alpha=0.5, label='Original loss')
axes[1].plot(w, lambda_val * w**2, 'r--', alpha=0.5, label=f'L2 penalty (lambda={lambda_val})')
axes[1].axvline(x=0, color='purple', linestyle='--', alpha=0.5)
axes[1].scatter([0], [0], color='purple', s=100, zorder=5, label='Optimal with L2')
axes[1].set_xlabel('Weight w')
axes[1].set_ylabel('Loss')
axes[1].set_title('With L2 Regularization')
axes[1].legend()
axes[1].grid(True)

plt.tight_layout()
plt.show()

print("Геометрия: L2 регуляризация сужает область возможных коэффициентов")
print("Решение смещается в сторону нуля (но не точно на ноль)")

5. В контексте классификации (Logistic Regression)

from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification
from sklearn.metrics import roc_auc_score, classification_report

X, y = make_classification(n_samples=1000, n_features=50, n_informative=20, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Логистическая регрессия без регуляризации
lr_no_reg = LogisticRegression(penalty=None, random_state=42, max_iter=1000)
lr_no_reg.fit(X_train, y_train)

y_pred_no_reg = lr_no_reg.predict_proba(X_test)[:, 1]
auc_no_reg = roc_auc_score(y_test, y_pred_no_reg)

print(f"Logistic Regression without regularization:")
print(f"AUC: {auc_no_reg:.4f}")
print(f"Coef L2 norm: {np.linalg.norm(lr_no_reg.coef_):.2f}")

# С L2 регуляризацией
lr_l2 = LogisticRegression(penalty='l2', C=1.0, solver='lbfgs', random_state=42, max_iter=1000)
lr_l2.fit(X_train, y_train)

y_pred_l2 = lr_l2.predict_proba(X_test)[:, 1]
auc_l2 = roc_auc_score(y_test, y_pred_l2)

print(f"\nLogistic Regression with L2 (C=1.0):")
print(f"AUC: {auc_l2:.4f}")
print(f"Coef L2 norm: {np.linalg.norm(lr_l2.coef_):.2f}")

# Параметр C обратно пропорционален lambda
# C = 1 / lambda
# Больше C = слабее регуляризация

print("\nПримечание: в sklearn используется параметр C (обратный lambda)")
print("C = 1 / lambda")
print("Поэтому: большой C = слабая регуляризация")
print("         маленький C = сильная регуляризация")

6. Выбор оптимального параметра lambda

from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler

# Нужно масштабировать признаки перед Ridge!
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# Cross-validation для выбора alpha
alphas = np.logspace(-3, 3, 50)
cv_scores = []

for alpha in alphas:
    ridge = Ridge(alpha=alpha)
    scores = cross_val_score(ridge, X_train_scaled, y_train, cv=5, scoring='neg_mean_squared_error')
    cv_scores.append(-scores.mean())  # берём отрицание для получения MSE

best_alpha = alphas[np.argmin(cv_scores)]

print(f"Best alpha (lambda) found by CV: {best_alpha:.4f}")

# График
plt.figure(figsize=(10, 6))
plt.plot(alphas, cv_scores, 'o-')
plt.axvline(x=best_alpha, color='r', linestyle='--', label=f'Best alpha={best_alpha:.4f}')
plt.xscale('log')
plt.xlabel('Alpha (lambda)')
plt.ylabel('Cross-validation MSE')
plt.title('Alpha Selection via Cross-Validation')
plt.legend()
plt.grid(True)
plt.show()

# Обучаем финальную модель
ridge_final = Ridge(alpha=best_alpha)
ridge_final.fit(X_train_scaled, y_train)
test_score = ridge_final.score(X_test_scaled, y_test)
print(f"\nFinal model R2 score: {test_score:.4f}")

7. Практический пример: борьба с переобучением

# Датасет с малым числом примеров и большим числом признаков
n_samples = 100
n_features = 200

X_small = np.random.randn(n_samples, n_features)
# Целевая переменная зависит только от 5 признаков + шум
y_small = X_small[:, 0] + X_small[:, 1] + X_small[:, 2] + np.random.randn(n_samples) * 0.5

X_train_small, X_test_small, y_train_small, y_test_small = train_test_split(
    X_small, y_small, test_size=0.2, random_state=42
)

# Обычная регрессия (переобучение)
lr_small = LinearRegression()
lr_small.fit(X_train_small, y_train_small)
train_r2_lr = lr_small.score(X_train_small, y_train_small)
test_r2_lr = lr_small.score(X_test_small, y_test_small)

print("Small dataset (100 samples, 200 features):")
print(f"Linear Regression:")
print(f"  Train R2: {train_r2_lr:.4f}")
print(f"  Test R2: {test_r2_lr:.4f}")
print(f"  Overfitting gap: {train_r2_lr - test_r2_lr:.4f}")

# Ridge regression
ridge_small = Ridge(alpha=10.0)
ridge_small.fit(X_train_small, y_train_small)
train_r2_ridge = ridge_small.score(X_train_small, y_train_small)
test_r2_ridge = ridge_small.score(X_test_small, y_test_small)

print(f"\nRidge Regression (alpha=10):")
print(f"  Train R2: {train_r2_ridge:.4f}")
print(f"  Test R2: {test_r2_ridge:.4f}")
print(f"  Overfitting gap: {train_r2_ridge - test_r2_ridge:.4f}")

print(f"\nRidge улучшил test R2 на {(test_r2_ridge - test_r2_lr):.4f}!")

8. L2 в нейронных сетях

import tensorflow as tf
from tensorflow import keras

# L2 регуляризация в Keras
model_no_reg = keras.Sequential([
    keras.layers.Dense(128, activation='relu', input_shape=(50,)),
    keras.layers.Dense(64, activation='relu'),
    keras.layers.Dense(1)
])

model_l2 = keras.Sequential([
    keras.layers.Dense(128, activation='relu', input_shape=(50,),
                       kernel_regularizer=keras.regularizers.l2(0.001)),  # L2 на каждый слой
    keras.layers.Dense(64, activation='relu',
                       kernel_regularizer=keras.regularizers.l2(0.001)),
    keras.layers.Dense(1)
])

print("L2 regularization in neural networks:")
print("keras.regularizers.l2(0.001)  # добавляет штраф 0.001 * sum(w^2)")

Ключевые выводы

  1. L2 штрафует большие коэффициенты через добавление lambda * sum(w^2) к функции потерь
  2. Борьба с переобучением — распределяет вес более равномерно вместо того, чтобы полагаться на несколько больших
  3. Параметр alpha (lambda) нужно выбирать через cross-validation
  4. В sklearn используется C = 1/lambda (обратное значение)
  5. Масштабирование признаков критично перед применением Ridge
  6. Компромисс между точностью (train) и обобщением (test)
  7. Работает везде — линейной регрессии, логистической регрессии, нейронных сетях
Как работает L2 регуляризация? | PrepBro