Линейная регрессия: частные производные MSE и градиентный спуск на Python
Автор: Казачкин Даниил Михайлович · Обновлено
Линейная регрессия предсказывает число как взвешенную сумму признаков. Она служит понятной отправной точкой для оценки цены, спроса или длительности: модель быстро обучается,…
Линейная регрессия предсказывает число как взвешенную сумму признаков. Она служит понятной отправной точкой для оценки цены, спроса или длительности: модель быстро обучается, коэффициенты можно исследовать, а результат удобно использовать как baseline для более сложных методов.
Как устроен прогноз
Для одного признака модель ищет прямую y = w × x + b. Коэффициент w показывает, насколько меняется прогноз при увеличении признака на единицу, а b задаёт свободный член. Для нескольких признаков складываются их отдельные вклады.
Во время обучения коэффициенты подбираются так, чтобы уменьшить функцию потерь. Обычный метод наименьших квадратов минимизирует сумму квадратов отклонений между известными ответами и прогнозами. Большие промахи получают особенно высокий вес.
Частная производная: меняем один параметр
Для наблюдения i обозначим прогноз как ŷᵢ = w xᵢ + b, а ошибку как eᵢ = ŷᵢ − yᵢ. Данные xᵢ и yᵢ считаем фиксированными. Выберем средний квадрат ошибки без дополнительного множителя 1/2:
J(w, b) = (1/n) Σᵢ (w xᵢ + b − yᵢ)²Частная производная по w отвечает на вопрос: как локально меняется J при изменении w, если b пока зафиксирован? Для производной по b фиксируем w. Это производные функции ошибки по параметрам модели, а не производная прогноза по входному x.
Применим правило цепочки к одному слагаемому. Производная квадрата eᵢ² по eᵢ равна 2eᵢ. Затем учитываем, как сам eᵢ зависит от выбранного параметра: ∂eᵢ/∂w = xᵢ, а ∂eᵢ/∂b = 1. Производная суммы равна сумме производных:
∂J/∂w = (2/n) Σᵢ (w xᵢ + b − yᵢ) xᵢ
∂J/∂b = (2/n) Σᵢ (w xᵢ + b − yᵢ)Вместе эти две компоненты образуют градиент. Если в другом учебнике функция потерь определена с коэффициентом 1/(2n), двойки в производных не будет. Это другая нормировка одной задачи, а не противоречие. Аналогичный вывод для MSE приведён в курсе Google; следующие числа и код — самостоятельный учебный пример.
Проверяем градиент вручную на трёх точках
Возьмём x = [1, 2, 3], y = [3, 5, 7] и начальные w = 1, b = 0.
| xᵢ | yᵢ | Прогноз | eᵢ | eᵢ xᵢ | eᵢ² |
|---|---|---|---|---|---|
| 1 | 3 | 1 | −2 | −2 | 4 |
| 2 | 5 | 2 | −3 | −6 | 9 |
| 3 | 7 | 3 | −4 | −12 | 16 |
Сумма ошибок равна −9, сумма произведений eᵢxᵢ — −20. Поэтому J = 29/3, производная по w равна −40/3, а по b равна −6. Отрицательный знак означает, что небольшое увеличение соответствующего параметра уменьшает ошибку в этой точке.
Градиентный спуск делает шаг против градиента: w_new = w − α × ∂J/∂w, b_new = b − α × ∂J/∂b. При α = 0.1 получаем w_new = 7/3 и b_new = 0.6. Оба значения нужно вычислить из одного старого состояния: изменение w до расчёта производной по b уже даст другой алгоритм.
Численная проверка и обучение без библиотек
Центральная конечная разность [J(w + h, b) − J(w − h, b)] / (2h) приблизительно проверяет производную по w. Во второй проверке изменяем только b. Код запускается обычным Python 3 и не требует NumPy или scikit-learn.
from math import isclose
x, y = [1.0, 2.0, 3.0], [3.0, 5.0, 7.0]
def mse(w, b):
return sum((w * xi + b - yi) ** 2 for xi, yi in zip(x, y)) / len(x)
def gradient(w, b):
errors = [w * xi + b - yi for xi, yi in zip(x, y)]
dw = 2 * sum(e * xi for e, xi in zip(errors, x)) / len(x)
db = 2 * sum(errors) / len(x)
return dw, db
w, b, h = 1.0, 0.0, 1e-5
dw, db = gradient(w, b)
numeric_dw = (mse(w + h, b) - mse(w - h, b)) / (2 * h)
numeric_db = (mse(w, b + h) - mse(w, b - h)) / (2 * h)
assert isclose(dw, numeric_dw, rel_tol=1e-7, abs_tol=1e-7)
assert isclose(db, numeric_db, rel_tol=1e-7, abs_tol=1e-7)
assert isclose(dw, -40 / 3) and isclose(db, -6)
print("gradient:", round(dw, 6), round(db, 6))
print("finite difference:", round(numeric_dw, 6), round(numeric_db, 6))
old_loss = mse(w, b)
w, b = w - 0.1 * dw, b - 0.1 * db
print("MSE before/after:", round(old_loss, 6), round(mse(w, b), 6))
assert mse(w, b) < old_loss
for _ in range(2000):
dw, db = gradient(w, b)
w, b = w - 0.1 * dw, b - 0.1 * db
assert isclose(w, 2.0, abs_tol=1e-6)
assert isclose(b, 1.0, abs_tol=1e-6)
print("parameters:", round(w, 6), round(b, 6))Ожидаемый вывод:
gradient: -13.333333 -6.0
finite difference: -13.333333 -6.0
MSE before/after: 9.666667 0.145185
parameters: 2.0 1.0Конечная разность помогает обнаружить потерянный множитель, неверный знак или ошибку индекса. Слишком маленький h усиливает влияние округления, слишком большой проверяет уже не локальное изменение. Совпадение в одной точке — полезная проверка, но не доказательство корректности любого кода; повторите её при других w и b.
Здесь существует точная прямая y = 2x + 1. В шумных данных нулевая ошибка обычно недостижима и не нужна. Большой шаг α способен увеличить MSE даже у выпуклой функции; выпуклость не делает любой learning rate безопасным. Масштабы признаков тоже влияют на удобный шаг.
Несколько признаков и библиотечное решение
Для признака j формула сохраняет смысл: ∂J/∂wⱼ = (2/n) Σᵢ eᵢ xᵢⱼ. Каждая компонента спрашивает о влиянии одного веса при фиксированных остальных. Так частные производные превращают анализ данных в вычислимую процедуру настройки параметров.
Ручной градиент объясняет обучение, но LinearRegression в следующем примере не выполняет написанный выше цикл: обычную задачу наименьших квадратов библиотека решает подходящим численным методом. Обе процедуры нацелены на ту же квадратичную ошибку, хотя путь получения коэффициентов различается. Три учебные точки служат проверке математики; качество на новых данных оцениваем уже отдельным разбиением.
Пример с синтетическими данными
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error
from sklearn.model_selection import train_test_split
rng = np.random.default_rng(42)
area = rng.uniform(20, 120, size=300)
rooms = rng.integers(1, 5, size=300)
price = 2.1 * area + 8.0 * rooms + rng.normal(0, 12, size=300)
X = np.column_stack([area, rooms])
X_train, X_test, y_train, y_test = train_test_split(
X, price, test_size=0.2, random_state=42
)
model = LinearRegression().fit(X_train, y_train)
predictions = model.predict(X_test)
print('MAE:', round(mean_absolute_error(y_test, predictions), 2))
print('Коэффициенты:', model.coef_.round(2))Здесь данные специально созданы почти линейными, поэтому модель должна восстановить вклады площади и комнат. На реальной выборке связь может зависеть от района, состояния, сезона и взаимодействия признаков.
Как читать коэффициенты
Положительный коэффициент означает рост прогноза при увеличении признака, если остальные значения фиксированы. Но величины коэффициентов нельзя напрямую сравнивать, когда признаки измерены в разных единицах. Один квадратный метр и один миллион рублей дохода имеют несопоставимый масштаб.
Коэффициент также не доказывает причинность. Модель может использовать признак как косвенный индикатор другой величины. Сильная корреляция между признаками делает отдельные веса нестабильными: прогноз остаётся приемлемым, а интерпретация каждого вклада меняется между выборками.
Когда прямой недостаточно
Линейная модель систематически ошибается, если связь имеет изгиб, порог или сложное взаимодействие. Можно добавить осмысленные преобразования, например квадрат площади, но каждое расширение увеличивает гибкость и риск переобучения.
Регуляризованные варианты Ridge и Lasso штрафуют слишком большие коэффициенты. Они особенно полезны при множестве связанных признаков, однако силу регуляризации нужно выбирать на валидации, а не на финальном тесте.
С чем сравнивать результат
Baseline для регрессии может всегда предсказывать медиану обучающих ответов. Если линейная модель не превосходит такое правило, проверьте постановку, признаки и разбиение. Сложный алгоритм не исправит цель, которую доступные данные почти не объясняют.
Практика: исследуйте остатки
Постройте прогноз цены по одному признаку и вычислите остатки y_test - predictions. Сравните среднюю ошибку для маленьких и больших объектов. Если знаки или величины заметно различаются, сформулируйте, какой нелинейный эффект или отсутствующий признак может объяснить структуру ошибок.
Что важно запомнить
- Линейная регрессия создаёт прогноз из суммы взвешенных признаков.
- Частные производные MSE показывают влияние каждого параметра при фиксированных остальных.
- Ручной градиент полезно проверять конечной разностью и маленьким расчётом на бумаге.
- Коэффициенты интерпретируются при фиксированных остальных признаках.
- Высокая корреляция признаков мешает устойчивому объяснению весов.
- Простая модель и baseline нужны до перехода к сложным алгоритмам.
Частые вопросы
Линейная регрессия работает только с одним признаком?
Нет. Она может принимать много столбцов и складывать их взвешенные вклады. Слово «линейная» относится к параметрам модели, а признаки могут включать заранее рассчитанные преобразования.
Нужно ли масштабировать признаки?
Для обычного метода наименьших квадратов прогноз часто получается и без масштабирования. Для регуляризации, сравнения коэффициентов и численной устойчивости стандартизация обычно полезна и должна входить в Pipeline.
Может ли линейная регрессия предсказывать отрицательную цену?
Да, модель не знает естественных границ цели. Если отрицательный ответ невозможен, оцените преобразование цели, другую модель или последующую проверку, не скрывая проблему простым обрезанием без анализа.
Связанные исследования
- DDPM: почему удаление шума может создавать новые данные — Исследование DDPM и собственный одномерный опыт: зашумление, оценка шума, обратные шаги и различие условного среднего и нового образца.