Линейная регрессия: формула, метод наименьших квадратов и пример на Python

Линейная регрессия предсказывает число как сумму признаков с весами и подбирает коэффициенты методом наименьших квадратов.

Разбираем формулу, считаем коэффициенты вручную и в scikit-learn на наборе diabetes, повторяем расчёт градиентным спуском и проверяем модель по R², MAE и остаткам.

Статью написал:
Ваня Буявец, продюсер, основатель Checkroi
Ваня Буявец
Основатель Checkroi, продюсер, эксперт в выборе онлайн-курсов
Все 3177 статей автора Подписаться на Телеграм-канал
Одобрено экспертом:
Наташа Буявец, основатель Checkroi, эксперт по онлайн-курсам
Наташа Буявец
Основательница Checkroi, продюсер Youtube-каналов, эксперт по онлайн-курсам
Все 3831 экспертное мнение Подписаться на Телеграм-канал
Обложка: Линейная регрессия: формула, метод наименьших квадратов и пример на Python

Линейная регрессия предсказывает число как сумму признаков, умноженных на коэффициенты, плюс свободный член: y = a + b·x. Коэффициенты подбирают методом наименьших квадратов, то есть так, чтобы сумма квадратов ошибок на обучающих данных была минимальной. В Python это три строки: LinearRegression(), fit(), predict() из библиотеки scikit-learn.

Ниже разберём формулу, посчитаем коэффициенты вручную, повторим расчёт в scikit-learn и градиентным спуском, оценим качество модели и пройдём по ошибкам, на которых новички теряют больше всего времени. Если слово «регрессия» пока звучит незнакомо, начните с общего разбора регрессии в анализе данных: там история термина и пример с Excel.

CheckroiCheckroiПодборка курсов по машинному обучению150 курсов • 28 школСравните цены, школы, программу и найдите выгодные предложения по обучениюСравнить→

Что такое линейная регрессия и как выглядит её формула

Рой проводит прямую через облако точек на доске

Это модель обучения с учителем: у вас есть примеры, где известны и признаки, и правильный ответ, и модель учится восстанавливать ответ по признакам. Ответ всегда число: цена квартиры, выручка за месяц, время доставки. Если нужно угадать класс («спам или нет»), берут классификацию, о ней ниже.

Для одного признака формула простая:

y = a + b · x, где x обозначает признак, y прогноз, b наклон прямой, а a свободный член, то есть значение прогноза при x = 0.

Если признаков несколько, каждый получает свой вес, и формула превращается в множественную линейную регрессию: y = w₀ + w₁·x₁ + w₂·x₂ + … + wₙ·xₙ. Геометрически это уже не прямая, а плоскость или гиперплоскость, но смысл тот же.

Термин Что означает Как называется в scikit-learn
Признак (x) То, по чему предсказываем: площадь, возраст, бюджет рекламы колонка матрицы X
Целевая переменная (y) То, что предсказываем вектор y
Коэффициент, наклон (b, w) На сколько меняется прогноз, когда признак растёт на единицу coef_
Свободный член (a, w₀) Прогноз при нулевых признаках intercept_
Остаток Разница между фактом и прогнозом на конкретном примере y - model.predict(X)

Линейная регрессия близка к корреляции, но отвечает на другой вопрос. Корреляция говорит, насколько тесно две величины связаны, а регрессия даёт формулу, по которой одну можно посчитать из другой. Подробнее о разнице и типичных ловушках рассказано в статье о том, как считать корреляцию.

Как модель находит коэффициенты: метод наименьших квадратов

Через облако точек можно провести сколько угодно прямых. Метод наименьших квадратов (МНК, по-английски ordinary least squares) выбирает ту, у которой сумма квадратов остатков минимальна. Квадрат нужен, чтобы ошибки вверх и вниз не гасили друг друга, а крупные промахи штрафовались сильнее мелких.

Среднее этой суммы называют MSE (mean squared error, среднеквадратичная ошибка):

MSE = (1/n) · Σ (yᵢ − ŷᵢ)², а ŷᵢ означает прогноз модели для i-го примера.

Для одного признака у задачи есть точное решение, его выводят в любом учебнике статистики:

  1. Наклон: b = Σ (xᵢ − x̄)(yᵢ − ȳ) / Σ (xᵢ − x̄)².
  2. Свободный член: a = ȳ − b · x̄, x̄ и ȳ здесь средние значения.

Для многих признаков то же решение записывают в матричном виде: w = (XᵀX)⁻¹ Xᵀ y. Это и есть аналитическое решение линейной регрессии: никаких итераций, одна формула.

Считаем вручную на реальных данных

Возьмём набор diabetes, который входит в scikit-learn и скачивать его не нужно. По описанию в документации, в нём 442 пациента с диабетом, десять исходных показателей (возраст, пол, индекс массы тела, среднее давление и шесть анализов сыворотки крови) и целевую величину: количественную меру прогрессирования болезни через год. Набор взят из статьи Эфрона, Хасти, Джонстона и Тибширани «Least Angle Regression» в Annals of Statistics за 2004 год, ссылка на неё есть в том же описании.

Предскажем прогрессирование болезни по одному признаку, индексу массы тела (ИМТ). Формулы выше переводятся в код почти дословно:

from sklearn.datasets import load_diabetes

X, y = load_diabetes(return_X_y=True, scaled=False)
x = X[:, 2]                  # индекс массы тела, кг/м²

x_mean, y_mean = x.mean(), y.mean()
b = ((x - x_mean) * (y - y_mean)).sum() / ((x - x_mean) ** 2).sum()
a = y_mean - b * x_mean

print(f"Пациентов: {len(x)}")
print(f"b = {b:.2f}")
print(f"a = {a:.2f}")

Вывод:

Пациентов: 442
b = 10.23
a = -117.77

Читается так: каждая лишняя единица ИМТ в среднем добавляет к мере прогрессирования около 10 пунктов. Свободный член −117,77 смысла сам по себе не имеет: ИМТ, равного нулю, не бывает, а в данных он лежит от 18 до 42,2. Об этом подробнее в разделе про ошибки.

Параметр scaled=False возвращает признаки в исходных единицах. По умолчанию scikit-learn отдаёт их нормированными, и тогда наклон выходит 949,44, что для человека нечитаемо.

Канал основателя Checkroi Вани Буявца3 800 человек читают мой Телеграм-канал про нейросетиСобрал промпты для Claude Code и ChatGPT, разборы ИИ-инструментов и лайфхаки по продвижению бизнеса в одном местеПрисоединиться

Линейная регрессия на Python в scikit-learn: пошагово

Вручную формулу пишут один раз, чтобы понять механику. В работе берут класс LinearRegression: по документации scikit-learn, это обычный МНК, который подбирает коэффициенты так, чтобы минимизировать сумму квадратов остатков.

  1. Установите библиотеку: pip install scikit-learn. NumPy подтянется сам.
  2. Подготовьте X и y. X всегда двумерный: в строках примеры, в колонках признаки. Даже если признак один, нужна таблица из одной колонки, отсюда X[:, [2]] с двойными скобками.
  3. Создайте модель и обучите её методом fit(X, y).
  4. Посмотрите коэффициенты в атрибутах coef_ и intercept_.
  5. Получите прогноз методом predict() и оцените качество методом score().
from sklearn.datasets import load_diabetes
from sklearn.linear_model import LinearRegression

X, y = load_diabetes(return_X_y=True, scaled=False)
X_bmi = X[:, [2]]            # двумерный массив из одной колонки

model = LinearRegression()
model.fit(X_bmi, y)

print(f"b = {model.coef_[0]:.2f}")
print(f"a = {model.intercept_:.2f}")
print(f"R² = {model.score(X_bmi, y):.3f}")
print(f"Прогноз при ИМТ 30: {model.predict([[30]])[0]:.0f}")

Вывод:

b = 10.23
a = -117.77
R² = 0.344
Прогноз при ИМТ 30: 189

Коэффициенты совпали с ручным расчётом до сотых, значит, формулы выше и scikit-learn делают одно и то же. Метод score() у LinearRegression возвращает коэффициент детерминации R². Значение 0,344 означает, что один ИМТ объясняет около трети разброса в прогрессировании болезни. Дальше добавим остальные признаки и посмотрим, сколько это даст.

Данные из CSV удобнее грузить через pandas: df = pd.read_csv("data.csv"), затем X = df[["площадь"]] и y = df["цена"]. Как работать с таблицами, выбирать колонки и чистить пропуски, разобрано в обзоре библиотеки pandas.

Градиентный спуск для линейной регрессии

Ноутбук с графиком убывающей ошибки градиентного спуска

Формула (XᵀX)⁻¹ Xᵀ y хороша, пока данных немного. Когда признаков тысячи, а строк миллионы, обращать матрицу дорого, и коэффициенты ищут по шагам, градиентным спуском. Тем же приёмом обучают нейросети, например свёрточные сети для картинок, поэтому на линейной регрессии его удобнее всего понять.

Идея: начинаем с a = 0 и b = 0, считаем ошибку, смотрим, в какую сторону её уменьшает изменение каждого коэффициента (это и есть градиент MSE), и делаем маленький шаг туда. Размер шага задаёт скорость обучения (learning rate).

import numpy as np
from sklearn.datasets import load_diabetes

X, y = load_diabetes(return_X_y=True)
x = X[:, 2]

a, b = 0.0, 0.0
lr = 0.5                      # шаг обучения
n = len(x)

for step in range(1, 10_001):
    y_pred = a + b * x
    error = y_pred - y
    grad_a = 2 / n * error.sum()
    grad_b = 2 / n * (error * x).sum()
    a -= lr * grad_a
    b -= lr * grad_b
    if step in (1, 100, 1_000, 10_000):
        mse = (error ** 2).mean()
        print(f"шаг {step:>7}: a = {a:7.2f}, b = {b:7.2f}, MSE = {mse:8.1f}")

Вывод:

шаг       1: a =  152.13, b =    2.15, MSE =  29074.5
шаг     100: a =  152.13, b =  192.43, MSE =   5192.8
шаг    1000: a =  152.13, b =  850.86, MSE =   3912.5
шаг   10000: a =  152.13, b =  949.44, MSE =   3890.5

К десятитысячному шагу спуск пришёл к тем же коэффициентам, что даёт scikit-learn на нормированных данных (949,44 и 152,13). В колонке MSE стоит ошибка до обновления коэффициентов на этом шаге.

Свободный член встал на место за один шаг. Причина в данных: в нормированной версии набора каждый признак центрирован, то есть его среднее равно нулю (так сказано в описании набора), и оптимальное a просто равно среднему y. На ненормированных признаках так не будет.

Градиентный спуск чувствителен к масштабу признаков, поэтому здесь мы взяли нормированную версию. На сырых значениях ИМТ от 18 до 42 тот же шаг 0,5 не сходится: мы проверили, к шестой итерации MSE вырастает с 29 тысяч до 10³².

Множественная регрессия и оценка качества модели

Рой радуется удачной проверке модели

Один признак объяснил треть разброса. Возьмём все десять и оценим модель честно: на данных, которых она не видела при обучении. Для этого выборку делят на обучающую и тестовую часть.

from sklearn.datasets import load_diabetes
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_absolute_error, root_mean_squared_error

X, y = load_diabetes(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

model = LinearRegression().fit(X_train, y_train)
y_pred = model.predict(X_test)

print(f"Обучающих: {len(X_train)}, тестовых: {len(X_test)}")
print(f"R² на тесте:  {r2_score(y_test, y_pred):.3f}")
print(f"MAE на тесте: {mean_absolute_error(y_test, y_pred):.1f}")
print(f"RMSE на тесте: {root_mean_squared_error(y_test, y_pred):.1f}")

Вывод:

Обучающих: 353, тестовых: 89
R² на тесте:  0.453
MAE на тесте: 42.8
RMSE на тесте: 53.9

На тесте R² вырос с 0,344 до 0,453. Если посчитать R² на тех же данных, на которых модель училась, выйдет 0,528, но такой цифре верить нельзя: модель видела ответы. Именно поэтому качество проверяют на отложенной выборке.

Метрика Что показывает Как читать наш результат
R² Какую долю разброса y объясняет модель 0,453: почти половину
MAE Средняя ошибка по модулю, в единицах y прогноз ошибается в среднем на 42,8 пункта
RMSE Корень из MSE, сильнее штрафует крупные промахи 53,9 при MAE 42,8: часть промахов заметно крупнее средней

Про R² полезно помнить одно свойство. В документации r2_score сказано: лучший возможный результат 1,0, а значение может быть отрицательным, потому что модель может оказаться сколь угодно хуже. Модель, которая всегда предсказывает среднее y, получает ровно 0. Отрицательный R² на тесте означает, что ваша модель проигрывает даже такому прогнозу.

Где линейная регрессия стоит среди других алгоритмов и когда её пора менять на деревья или бустинг, мы разбирали в обзоре моделей машинного обучения.

Допущения модели и график остатков

МНК всегда выдаст какие-то коэффициенты, даже если линейная модель данным не подходит. Поэтому после обучения проверяют, выполняются ли допущения, на которых построена модель:

  • Линейность. Зависимость y от признаков примерно прямая. Если связь изогнутая, добавляют квадраты признаков или берут другую модель.
  • Независимость наблюдений. Строки не повторяют друг друга и не идут цепочкой во времени. Для временных рядов это допущение нарушается чаще всего.
  • Одинаковый разброс остатков (гомоскедастичность). Ошибка не должна расти вместе с прогнозом.
  • Нормальность остатков. Важна, если вы считаете доверительные интервалы и p-значения, для самого прогноза менее критична. Как читать p-значение и не обмануться, разобрано в статье про A/B-тесты.
  • Нет сильной мультиколлинеарности. Признаки не повторяют друг друга.

Про последний пункт в руководстве scikit-learn по линейным моделям сказано прямо: оценки МНК опираются на независимость признаков. Когда признаки коррелированы, оценка становится сильно чувствительной к случайным ошибкам в целевой переменной. На практике это выглядит так: добавили похожий признак, и коэффициенты прыгнули, а знак у одного сменился.

Что показывает график остатков

График остатков строят как точечную диаграмму, где по горизонтали прогноз, а по вертикали остаток. У хорошей модели точки лежат ровной полосой вокруг нуля. Воронка, которая расширяется вправо, говорит о растущем разбросе. Дуга говорит о нелинейной связи, которую прямая не уловила.

Ту же проверку можно сделать числами, без картинки:

import numpy as np
from sklearn.datasets import load_diabetes
from sklearn.linear_model import LinearRegression

X, y = load_diabetes(return_X_y=True)
model = LinearRegression().fit(X, y)
residuals = y - model.predict(X)

print(f"Средний остаток: {residuals.mean():.2f}")
low = model.predict(X) < np.median(model.predict(X))
print(f"Разброс остатков, малые прогнозы:   {residuals[low].std():.1f}")
print(f"Разброс остатков, большие прогнозы: {residuals[~low].std():.1f}")

Вывод:

Средний остаток: -0.00
Разброс остатков, малые прогнозы:   46.7
Разброс остатков, большие прогнозы: 59.4

Средний остаток равен нулю, так и должно быть у МНК со свободным членом. А вот разброс у больших прогнозов примерно на 27 % выше, чем у малых: признак той самой воронки. Для учебного примера это допустимо, но в реальной задаче стоит попробовать логарифм целевой переменной.

Ваня БуявецКанал основателя Checkroi Вани БуявцаЗабирайте промпты и обучение по нейросетям в моём Телеграм-каналеБольше 3 800 человек уже применяют Claude Code, ChatGPT и другие нейросети в работе, учёбе, бизнесе и жизниПерейти в канал

Частые ошибки и что с ними делать

Одномерный массив вместо таблицы

Симптом: fit() падает с ошибкой:

ValueError: Expected 2D array, got 1D array instead:

Причина: передали X[:, 2] вместо X[:, [2]].

Решение: двойные скобки или x.reshape(-1, 1), как подсказывает само сообщение об ошибке.

Прогноз за пределами данных

Модель из примера выше честно ответит на вопрос про ИМТ 10:

print(f"Прогноз при ИМТ 10: {model.predict([[10]])[0]:.0f}")
# Прогноз при ИМТ 10: -15

Отрицательного прогрессирования болезни не бывает. В обучающих данных ИМТ лежал от 18 до 42,2, и за этими границами прямая продолжается, а реальная зависимость нет.

Решение: не экстраполировать далеко, а если приходится, проверять прогноз здравым смыслом.

Слишком большой шаг градиентного спуска

Тот же спуск на нормированных данных, но со скоростью 1,5 вместо 0,5:

шаг 1: a = 456.4, MSE = 29074.5
шаг 2: a = -456.4, MSE = 98480.7
шаг 3: a = 1369.2, MSE = 376188.6
шаг 4: a = -2282.0, MSE = 1487102.4
шаг 5: a = 5020.4, MSE = 5930838.6

Коэффициент скачет через оптимум, и с каждым шагом всё дальше.

Решение: уменьшить шаг в несколько раз и нормировать признаки, например через StandardScaler.

Коэффициент как причина

Коэффициент 10,23 при ИМТ говорит, что у пациентов с большим индексом прогрессирование в среднем выше. Он не доказывает, что снижение веса на единицу ИМТ снизит меру на 10 пунктов: на неё могут влиять другие факторы, связанные с весом. Причинность проверяют экспериментом, регрессия на наблюдениях её не даёт.

Линейная регрессия для классификации

Если закодировать классы как 0 и 1 и обучить LinearRegression, модель будет выдавать и числа больше единицы, и отрицательные, а вероятностью они не являются. Для классов берут логистическую регрессию: она пропускает ту же линейную комбинацию через сигмоиду и возвращает вероятность от 0 до 1.

Когда линейной регрессии мало

У базовой модели есть родственники, которые чинят её слабые места. Названия встретятся в любом курсе по машинному обучению:

Модель Что меняет Когда брать
Ridge Штрафует большие коэффициенты (L2-регуляризация) Признаки коррелированы, коэффициенты нестабильны
Lasso Штраф L1, обнуляет лишние коэффициенты Признаков много, нужно отобрать важные
Полиномиальная регрессия Добавляет квадраты и произведения признаков Связь изогнутая, а модель хочется оставить простой
Логистическая регрессия Переводит линейную комбинацию в вероятность Нужно предсказать класс, а не число

Все четыре в scikit-learn вызываются так же, как LinearRegression: fit(), predict(), score(). Освоив одну модель, вы почти бесплатно получаете остальные. Обзор видов регрессии с примерами есть в статье о регрессии, а с чего начинать путь в профессии, рассказывает материал о том, как стать ML-инженером.

Где освоить машинное обучение системно

С линейной регрессии начинается почти любая программа по Data Science, дальше идут деревья, бустинг и нейросети. Если хотите пройти этот путь с наставником, проектами и проверкой кода, сравните курсы по машинному обучению: в подборке ниже цены, длительность и рейтинги школ.

КурсШколаСтоимость со скидкойВ рассрочкуДлитель­ностьОбзор курса от Checkroi
Machine Learning с нуля до Junior
Перейти на сайт курса
SkillboxSkillbox123 543 ₽4384 ₽/мес.9 месяцевОбзор курса
Бакалавриат «Data Science & Machine Learning»
Перейти на сайт курса
SkillboxSkillbox75 000 ₽ - 48 месяцевОбзор курса
Машинное обучение
Перейти на сайт курса
НетологияНетология48 000 ₽2598 ₽/мес.10 месяцевОбзор курса
Machine Learning. Basic
Перейти на сайт курса
OTUSOTUS60 000 ₽8613 ₽/мес.6 месяцевОбзор курса
Machine Learning Engineer с нуля
Перейти на сайт курса
SkillboxSkillbox69 000 ₽ - 9 месяцевОбзор курса

Больше программ — в полном каталоге курсов по машинному обучению

Если вы только выбираете направление, прочитайте, чем занимается Data Scientist и какие задачи он решает каждый день.

Часто задаваемые вопросы

Что такое линейная регрессия в машинном обучении простыми словами?

Это модель, которая предсказывает число по признакам: умножает каждый признак на свой коэффициент, складывает и добавляет свободный член. Коэффициенты она подбирает по примерам с известными ответами, поэтому это обучение с учителем.

Как выглядит формула линейной регрессии?

Для одного признака y = a + b·x, где b означает наклон, а a свободный член. Для нескольких признаков y = w₀ + w₁·x₁ + … + wₙ·xₙ, а коэффициенты методом наименьших квадратов находят по формуле w = (XᵀX)⁻¹Xᵀy.

Что такое свободный член линейной регрессии?

Это прогноз модели при нулевых значениях всех признаков, в scikit-learn он лежит в атрибуте intercept_. Если ноль вне диапазона данных, как ИМТ, равный нулю, свободный член отдельного смысла не имеет и нужен только для того, чтобы прямая прошла правильно.

Когда нужен градиентный спуск, а когда хватит аналитического решения?

Пока признаков немного и данные помещаются в память, хватает точной формулы, её и использует LinearRegression. Градиентный спуск берут на очень больших данных и при обучении по частям; перед ним признаки нормируют, иначе спуск может разойтись.

Какой R² считается хорошим?

Универсального порога нет, он зависит от того, сколько случайного шума в данных. Надёжный ориентир другой: считайте R² на тестовой выборке и сравнивайте с прогнозом средним значением, у которого R² равен 0. Отрицательный R² значит, что модель проигрывает даже такому прогнозу.

Можно ли использовать линейную регрессию для классификации?

Технически можно закодировать классы нулём и единицей, но прогнозы выйдут за пределы от 0 до 1 и не будут вероятностями. Для классов берут логистическую регрессию: она переводит ту же линейную комбинацию признаков в вероятность.

Читайте Checkroi первым в Google
Добавьте Checkroi в избранные источники — и наши разборы курсов и обзоры школ будут показываться выше в вашей выдаче Google.
Оставить комментарий
0 комментариев
Форма комментария

Оставьте комментарий

Напишите, что думаете. Нам важно ваше мнение!