Гребневая регрессия (Ridge Regression)
Алгоритм гребневой регрессии (Ridge Regression, произносится как "ридж ригрешн") — это метод регуляризованной линейной регрессии, который добавляет штраф за величину коэффициентов модели для предотвращения переобучения и улучшения обобщающей способности.
Подробное описание
Гребневая регрессия решает задачу множественной линейной регрессии с L2-регуляризацией. В отличие от обычной линейной регрессии методом наименьших квадратов, ridge regression минимизирует не только сумму квадратов ошибок предсказания, но и сумму квадратов значений коэффициентов модели.
Постановка задачи: Дана обучающая выборка из n наблюдений, где каждое наблюдение состоит из p признаков и целевой переменной. Необходимо найти вектор коэффициентов β, который минимизирует функцию потерь с учётом регуляризации.
Входные данные:
- Матрица признаков X размера n × p
- Вектор целевых значений y размера n
- Параметр регуляризации α (lambda) ≥ 0
Выходные данные:
- Вектор коэффициентов модели β размера p + 1 (включая свободный член)
- Предсказанные значения ŷ
Ключевая идея: Добавление штрафа L2-нормы к функции потерь заставляет модель использовать меньшие по абсолютной величине коэффициенты, что снижает дисперсию модели и уменьшает риск переобучения, особенно когда признаки сильно коррелированы или когда количество признаков велико относительно количества наблюдений.
Исторический контекст: Метод был предложен Артуром Хорлом (Arthur Hoerl) и Робертом Кеннардом (Robert Kennard) в 1970 году как решение проблемы мультиколлинеарности в регрессионном анализе. Название "гребневая" происходит от формы поверхности функции потерь, которая напоминает гребень при визуализации в пространстве параметров.
Основные принципы
Математическая формулировка
Функция потерь гребневой регрессии имеет вид:
или в матричной форме:
Оптимальные коэффициенты находятся аналитически:
Где:
- \(y\) — вектор целевых значений размера n
- \(X\) — матрица признаков размера n × p
- \(\beta\) — вектор коэффициентов размера p
- \(\alpha\) — параметр регуляризации (controls strength of regularization)
- \(I\) — единичная матрица размера p × p
- \(||\cdot||_2\) — L2-норма (евклидова норма)
При \(\alpha = 0\) гребневая регрессия вырождается в обычную линейную регрессию методом наименьших квадратов. При увеличении \(\alpha\) коэффициенты стремятся к нулю, но никогда не достигают его точно (в отличие от Lasso-регрессии).
Блок-схема алгоритма
flowchart TD
A[Начало] --> B[Загрузка данных X, y]
B --> C[Стандартизация признаков]
C --> D[Выбор параметра α]
D --> E[Вычисление X^T X + αI]
E --> F[Вычисление X^T y]
F --> G[Решение системы: β = X^T X + αI^-1 X^T y]
G --> H[Получение коэффициентов β]
H --> I[Предсказание: ŷ = Xβ]
I --> J[Конец]
Пример реализации на Python
import numpy as np
from typing import Tuple
class RidgeRegression:
"""
Класс для реализации гребневой регрессии (Ridge Regression).
Parameters
----------
alpha : float, optional
Параметр регуляризации (по умолчанию 1.0).
Чем больше значение, тем сильнее регуляризация.
fit_intercept : bool, optional
Нужно ли использовать свободный член (по умолчанию True).
"""
def __init__(self, alpha: float = 1.0, fit_intercept: bool = True):
self.alpha = alpha
self.fit_intercept = fit_intercept
self.coefficients = None # Вектор коэффициентов beta
self.intercept = None # Свободный член (intercept)
def _add_intercept(self, X: np.ndarray) -> np.ndarray:
"""
Добавляет столбец единиц к матрице признаков для учёта свободного члена.
Parameters
----------
X : np.ndarray
Матрица признаков размера n_samples x n_features
Returns
-------
np.ndarray
Матрица с добавленным столбцом единиц размера n_samples x (n_features + 1)
"""
intercept_column = np.ones((X.shape[0], 1))
return np.hstack([intercept_column, X])
def fit(self, X: np.ndarray, y: np.ndarray) -> 'RidgeRegression':
"""
Обучает модель гребневой регрессии.
Вычисляет оптимальные коэффициенты по формуле:
beta = (X^T X + alpha * I)^(-1) X^T y
Parameters
----------
X : np.ndarray
Матрица признаков размера n_samples x n_features
y : np.ndarray
Вектор целевых значений размера n_samples
Returns
-------
self
Возвращает экземпляр класса для цепочки вызовов
"""
# Преобразуем входные данные в массивы numpy
X = np.asarray(X, dtype=np.float64)
y = np.asarray(y, dtype=np.float64)
# Проверяем размерности
if X.ndim == 1:
X = X.reshape(-1, 1)
if y.ndim == 2 and y.shape[1] == 1:
y = y.ravel()
n_samples, n_features = X.shape
# Если требуется, добавляем столбец для свободного члена
if self.fit_intercept:
X_augmented = self._add_intercept(X)
else:
X_augmented = X
# Создаём матрицу регуляризации
# Для свободного члена регуляризация не применяется
n_params = X_augmented.shape[1]
regularization_matrix = self.alpha * np.eye(n_params)
# Если есть свободный член, не регуляризуем его (первый элемент)
if self.fit_intercept:
regularization_matrix[0, 0] = 0
# Вычисляем X^T X
XtX = X_augmented.T @ X_augmented
# Добавляем регуляризацию: X^T X + alpha * I
regularized_XtX = XtX + regularization_matrix
# Вычисляем X^T y
Xty = X_augmented.T @ y
# Решаем систему уравнений для нахождения коэффициентов
# beta = (X^T X + alpha * I)^(-1) X^T y
try:
coefficients = np.linalg.solve(regularized_XtX, Xty)
except np.linalg.LinAlgError:
# Если матрица вырожденная, используем псевдообратную
coefficients = np.linalg.pinv(regularized_XtX) @ Xty
# Разделяем коэффициенты и свободный член
if self.fit_intercept:
self.intercept = coefficients[0]
self.coefficients = coefficients[1:]
else:
self.intercept = 0.0
self.coefficients = coefficients
return self
def predict(self, X: np.ndarray) -> np.ndarray:
"""
Делает предсказания для новых данных.
Parameters
----------
X : np.ndarray
Матрица признаков размера n_samples x n_features
Returns
-------
np.ndarray
Вектор предсказанных значений размера n_samples
"""
if self.coefficients is None:
raise ValueError("Модель ещё не обучена. Сначала вызовите метод fit().")
X = np.asarray(X, dtype=np.float64)
# Вычисляем предсказания: y_hat = X @ beta + intercept
predictions = X @ self.coefficients + self.intercept
return predictions
def score(self, X: np.ndarray, y: np.ndarray) -> float:
"""
Вычисляет коэффициент детерминации R².
Parameters
----------
X : np.ndarray
Матрица признаков
y : np.ndarray
Истинные значения целевой переменной
Returns
-------
float
Коэффициент детерминации R² (чем ближе к 1, тем лучше)
"""
y_pred = self.predict(X)
y = np.asarray(y, dtype=np.float64)
# Вычисляем сумму квадратов остатков (SS_res)
ss_res = np.sum((y - y_pred) ** 2)
# Вычисляем общую сумму квадратов (SS_tot)
ss_tot = np.sum((y - np.mean(y)) ** 2)
# Избегаем деления на ноль
if ss_tot == 0:
return 1.0 if ss_res == 0 else 0.0
# R² = 1 - SS_res / SS_tot
r_squared = 1 - (ss_res / ss_tot)
return r_squared
def standardize_features(X: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""
Стандартизирует признаки (вычитает среднее, делит на стандартное отклонение).
Важно: гребневая регрессия чувствительна к масштабу признаков,
поэтому стандартизация рекомендуется перед обучением.
Parameters
----------
X : np.ndarray
Матрица признаков
Returns
-------
Tuple[np.ndarray, np.ndarray, np.ndarray]
Стандартизированная матрица, вектор средних, вектор стандартных отклонений
"""
means = np.mean(X, axis=0)
stds = np.std(X, axis=0)
# Избегаем деления на ноль для константных признаков
stds[stds == 0] = 1.0
X_standardized = (X - means) / stds
return X_standardized, means, stds
if __name__ == "__main__":
# Пример использования гребневой регрессии
# Генерируем синтетические данные
np.random.seed(42)
n_samples = 100
n_features = 5
# Создаём матрицу признаков
X = np.random.randn(n_samples, n_features)
# Создаём истинные коэффициенты
true_coefficients = np.array([2.5, -1.3, 0.8, 0.0, 1.2])
true_intercept = 3.0
# Генерируем целевую переменную с шумом
noise = np.random.randn(n_samples) * 0.5
y = X @ true_coefficients + true_intercept + noise
print("=" * 60)
print("Пример использования гребневой регрессии")
print("=" * 60)
# Стандартизируем признаки
X_std, means, stds = standardize_features(X)
# Создаём и обучаем модель с разными значениями alpha
alphas = [0.0, 0.1, 1.0, 10.0, 100.0]
print("\nСравнение моделей с разными параметрами регуляризации:")
print("-" * 60)
print(f"{'Alpha':<10} {'R² Score':<12} {'Коэфф. (первые 3)':<30}")
print("-" * 60)
for alpha in alphas:
model = RidgeRegression(alpha=alpha, fit_intercept=True)
model.fit(X_std, y)
r2 = model.score(X_std, y)
coeffs = model.coefficients[:3] # Первые 3 коэффициента для демонстрации
print(f"{alpha:<10.1f} {r2:<12.4f} {[f'{c:.3f}' for c in coeffs]}")
# Выбираем оптимальную модель
print("\n" + "=" * 60)
print("Обучение финальной модели (alpha=1.0)")
print("=" * 60)
final_model = RidgeRegression(alpha=1.0, fit_intercept=True)
final_model.fit(X_std, y)
print(f"\nНайденные коэффициенты: {final_model.coefficients}")
print(f"Свободный член: {final_model.intercept:.4f}")
print(f"R² на обучающей выборке: {final_model.score(X_std, y):.4f}")
# Делаем предсказания для новых данных
X_new = np.random.randn(5, n_features)
X_new_std = (X_new - means) / stds
predictions = final_model.predict(X_new_std)
print(f"\nПредсказания для 5 новых наблюдений:")
for i, pred in enumerate(predictions):
print(f" Наблюдение {i+1}: {pred:.4f}")
# Демонстрация эффекта регуляризации
print("\n" + "=" * 60)
print("Эффект регуляризации на величину коэффициентов")
print("=" * 60)
model_no_reg = RidgeRegression(alpha=0.0, fit_intercept=True)
model_no_reg.fit(X_std, y)
model_strong_reg = RidgeRegression(alpha=100.0, fit_intercept=True)
model_strong_reg.fit(X_std, y)
print(f"\nБез регуляризации (alpha=0.0):")
print(f" Коэффициенты: {model_no_reg.coefficients}")
print(f" Норма коэффициентов: {np.linalg.norm(model_no_reg.coefficients):.4f}")
print(f"\nСильная регуляризация (alpha=100.0):")
print(f" Коэффициенты: {model_strong_reg.coefficients}")
print(f" Норма коэффициентов: {np.linalg.norm(model_strong_reg.coefficients):.4f}")
print("\n" + "=" * 60)
print("Примечание: при увеличении alpha коэффициенты уменьшаются,")
print("но никогда не становятся точно равными нулю.")
print("=" * 60)
Достоинства и недостатки
Достоинства:
- Предотвращает переобучение — L2-регуляризация ограничивает рост коэффициентов, что улучшает обобщающую способность модели на новых данных.
- Работает с мультиколлинеарностью — когда признаки сильно коррелированы, гребневая регрессия даёт более стабильные оценки коэффициентов по сравнению с обычной линейной регрессией.
- Аналитическое решение — в отличие от некоторых других методов машинного обучения, ridge regression имеет закрытую форму решения, что делает обучение быстрым и детерминированным.
- Численная устойчивость — добавление αI к матрице X^T X улучшает её обусловленность, что особенно важно при работе с плохо обусловленными матрицами.
- Сохраняет все признаки — в отличие от Lasso, гребневая регрессия не зануляет коэффициенты, что полезно когда все признаки потенциально информативны.
Недостатки:
- Не выполняет отбор признаков — все коэффициенты остаются ненулевыми, даже если некоторые признаки неинформативны. Это может ухудшить интерпретируемость модели при большом количестве признаков.
- Требует стандартизации признаков — поскольку регуляризация зависит от масштаба коэффициентов, признаки необходимо стандартизировать перед обучением, иначе результат будет зависеть от единиц измерения.
- Чувствительность к выбору α — производительность модели сильно зависит от правильного выбора параметра регуляризации, что требует использования кросс-валидации.
- Предположение о линейности — как и обычная линейная регрессия, предполагает линейную зависимость между признаками и целевой переменной.
- Не работает с категориальными признаками напрямую — требует предварительного кодирования категориальных переменных (one-hot encoding, label encoding и т.д.).
Области применения
- Экономика и финансы (прогнозирование цен активов, оценка рисков кредитования, анализ макроэкономических показателей)
- Биотехнологии и медицина (предсказание эффективности лекарств на основе молекулярных дескрипторов, анализ генетических данных с большим количеством признаков)
- Компьютерное зрение (регрессионные задачи в обработке изображений, предсказание координат объектов, оценка возраста по фотографии)
- Обработка текста (предсказание тональности текста с использованием bag-of-words или TF-IDF признаков, где количество признаков часто превышает количество наблюдений)
- Логистика (прогнозирование времени доставки, оптимизация маршрутов на основе множества факторов, предсказание спроса на товары)