Что такое линейная регрессия
Линейная регрессия — один из старейших и наиболее фундаментальных алгоритмов в машинном обучении. Несмотря на простоту, именно с её изучения начинается понимание оптимизации, функций потерь и обобщения модели. Все сложные алгоритмы — нейронные сети, метод опорных векторов, бустинг — имеют те же базовые принципы.
Задача: по матрице признаков X (n объектов × d признаков) предсказать вещественный вектор целевых значений y. Модель строит линейное отображение:
ŷ = X · w + b
где:
X — матрица признаков (n × d)
w — вектор весов (d × 1)
b — свободный член (bias)
ŷ — вектор предсказаний
Для удобства свободный член включают в вектор весов, добавляя к матрице X столбец из единиц:
ŷ = X̃ · w̃ где X̃ = [1 | X], w̃ = [b; w]
Функция потерь: среднеквадратичная ошибка
Качество модели измеряется через среднеквадратичную ошибку (MSE) на обучающей выборке:
L(w) = (1/n) · ‖Xw - y‖²
= (1/n) · Σᵢ (ŷᵢ - yᵢ)²
MSE выбрана не произвольно — она соответствует максимальному правдоподобию при предположении, что остатки (ошибки) распределены нормально с нулевым средним. Это даёт глубокий статистический смысл: линейная регрессия с MSE является оптимальной оценкой в классе несмещённых линейных оценщиков (теорема Гаусса–Маркова).
Метод наименьших квадратов: аналитическое решение
MSE — квадратичная функция весов, выпуклая. Её минимум достигается в единственной точке, которую можно найти аналитически, приравняв градиент к нулю:
∇L(w) = (2/n) · Xᵀ(Xw - y) = 0
Xᵀ Xw = Xᵀ y
w* = (XᵀX)⁻¹ Xᵀ y ← нормальное уравнение
Реализация в Python:
import numpy as np
class OLSRegression:
def fit(self, X, y):
# Добавляем столбец единиц (bias)
X_b = np.c_[np.ones(len(X)), X]
# Нормальное уравнение
self.w_ = np.linalg.pinv(X_b.T @ X_b) @ X_b.T @ y
return self
def predict(self, X):
X_b = np.c_[np.ones(len(X)), X]
return X_b @ self.w_
# Пример
X_train = np.array([[1.2], [2.4], [3.1], [4.8], [5.5]])
y_train = np.array([2.3, 4.1, 5.2, 8.4, 9.7])
model = OLSRegression()
model.fit(X_train, y_train)
print("Веса:", model.w_) # [0.52, 1.74] примерно
print("Предсказание для x=3.0:", model.predict([[3.0]]))
Сложность МНК и когда он неприменим
Инверсия матрицы XᵀX имеет сложность O(d³). При d = 1000 признаков это уже ~10⁹ операций. При d = 100 000 (например, NLP-задачи) — вычислительно неприемлемо. В таких случаях применяют итерационные методы: градиентный спуск или метод сопряжённых градиентов.
- ✓ Точное решение за 1 шаг
- ✓ Нет гиперпараметров
- ✗ O(d³) по времени
- ✗ O(d²) по памяти
- ✗ Плохо при d > 10 000
- ✓ Работает при любом d
- ✓ Масштабируется на GPU
- ✗ Нужно подобрать lr
- ✗ Много итераций
- ✗ Требует нормализации
Градиентный спуск для линейной регрессии
Градиент MSE по весам:
∇L(w) = (2/n) · Xᵀ (Xw - y)
Шаг обновления весов на итерации t:
w(t+1) = w(t) - η · ∇L(w(t))
= w(t) - (2η/n) · Xᵀ (Xw(t) - y)
Здесь η — шаг обучения (learning rate). Выбор η критически важен: слишком большой приведёт к расходимости, слишком маленький — к медленной сходимости.
class GDLinearRegression:
def __init__(self, lr=0.01, n_iter=1000):
self.lr = lr
self.n_iter = n_iter
def fit(self, X, y):
n, d = X.shape
X_b = np.c_[np.ones(n), X]
self.w_ = np.zeros(d + 1)
self.loss_history_ = []
for i in range(self.n_iter):
y_pred = X_b @ self.w_
residuals = y_pred - y
grad = (2 / n) * X_b.T @ residuals
self.w_ -= self.lr * grad
mse = np.mean(residuals ** 2)
self.loss_history_.append(mse)
return self
def predict(self, X):
X_b = np.c_[np.ones(len(X)), X]
return X_b @ self.w_
Важность нормализации признаков
Градиентный спуск сходится значительно быстрее, когда все признаки имеют одинаковый масштаб. Причина: функция потерь принимает форму вытянутого «оврага» при разных масштабах, и алгоритм «петляет» вместо прямого спуска.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_train)
# Всегда используйте те же параметры нормализации для тестовой выборки
X_test_scaled = scaler.transform(X_test)
Интерпретация коэффициентов
Каждый вес wⱼ показывает: при увеличении j-го признака на 1 единицу (при фиксированных остальных) предсказание изменяется на wⱼ единиц. Это делает линейную регрессию легко интерпретируемой.
Важно: интерпретация корректна только при выполнении предположений модели. Прежде всего — отсутствии мультиколлинеарности. Если признаки сильно коррелируют, коэффициенты становятся нестабильными и их интерпретация теряет смысл.
Диагностика модели: анализ остатков
Чтобы убедиться, что линейная регрессия подходит для задачи, анализируют остатки εᵢ = yᵢ - ŷᵢ:
- График остатков vs предсказания — должен выглядеть как случайный «белый шум» без структуры. Паттерн (U-образный, расширяющийся) указывает на нелинейность или гетероскедастичность.
- Q-Q график — проверяет нормальность остатков. Отклонение от диагонали говорит о выбросах или тяжёлых хвостах.
- Гистограмма остатков — должна быть близка к нормальной.
import matplotlib.pyplot as plt
y_pred = model.predict(X_test)
residuals = y_test - y_pred
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].scatter(y_pred, residuals, alpha=0.5)
axes[0].axhline(0, color='red', linestyle='--')
axes[0].set_xlabel("Предсказание")
axes[0].set_ylabel("Остаток")
axes[0].set_title("Остатки vs предсказания")
axes[1].hist(residuals, bins=30, edgecolor='black')
axes[1].set_title("Распределение остатков")
plt.tight_layout()
plt.show()
Метрики качества регрессии
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
import numpy as np
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f"MSE: {mse:.4f}")
print(f"RMSE: {rmse:.4f}") # В тех же единицах, что y
print(f"MAE: {mae:.4f}") # Устойчива к выбросам
print(f"R²: {r2:.4f}") # 1.0 = идеально, 0 = как среднее
R² (коэффициент детерминации) показывает долю дисперсии целевой переменной, объяснённой моделью. R² = 0.87 означает, что модель объясняет 87% вариации y.
Когда линейная регрессия работает хорошо
Линейная регрессия эффективна при соблюдении условий:
- Между признаками и целевой переменной действительно линейная зависимость
- Остатки случайны, независимы и примерно нормально распределены
- Нет мультиколлинеарности между признаками
- Дисперсия остатков постоянна (гомоскедастичность)
Если зависимость нелинейна — рассмотрите полиномиальные признаки или перейдите к деревьям решений. Если признаков слишком много и многие из них нерелевантны — используйте Lasso-регрессию.
Итог урока
Линейная регрессия — это не просто «старый алгоритм». Это фундамент для понимания оптимизации (нормальное уравнение и градиентный спуск), функций потерь (MSE), регуляризации (Ridge, Lasso) и статистической интерпретации моделей. Понимание её математики — необходимое условие для осмысленной работы с любым ML-алгоритмом.