Тема линейной регресии рассмотрена множество раз в различных источниках, но, как говорится, "нет такой избитой темы, которую нельзя ударить еще раз". В данной статье рассмотрим указанную тему, используя как математические выкладки, так и код python, пытаясь соблюсти баланс на грани простоты и должном уровне для понимания математических основ.

Линейная регрессия представляется из себя регриссионную модель зависимости одной (объясняемой, зависимой) переменной от другой или нескольких других переменных (фактров, регрессоров, независимых переменных) с линейной функцией зависимости. Рассмотрим модель линейной регрессии, при которой зависимая переменная зависит лишь от одного фактора, тогда функция, описывающуя зависимость y от x будет иметь следующий вид:

f(x)=w_0+w_1∗x

и задача сводится к нахождению весовых коэффициентов w0 и w1, таких что такая прямая максимально "хорошо" будет описывать исходные данные. Для этого зададим функцию ошибки, минимизация которой обеспечит подбор весов w0 и w1, используя метод наименьших квадратов:

MSE=\frac{1}{n}*\sum\limits_{i=0}^n(y_i−f(x_i))^2

или подставив уравнение модели

MSE=\frac{1}{n}*\sum\limits_{i=0}^n(y_i−w_0−w_1∗x_i)^2

Минимизируем функцию ошибки MSE найдя частные производные по w0 и w1

\frac{\partial MSE(w_0,w_1)} {\partial w_0} = −\frac{2}{n}*\sum\limits_{i=0}^n(y_i−w_0−w_1∗x_i)\frac{\partial MSE(w_0,w_1)}{\partial w_1} = −\frac{2}{n}*\sum\limits_{i=0}^n((y_i−w_0−w_1∗x_i)∗x_i)

И приравняв их к нулю получим систему уравнений, решение которой обеспечит минимизацию функции потерь MSE.

\begin{equation*} \begin{cases} 0=−\frac{2}{n}*\sum\limits_{i=0}^n(y_i−w_0−w_1∗x_i)\\ 0=−\frac{2}{n}*\sum\limits_{i=0}^n((y_i−w_0−w_1∗x_i)∗x_i) \end{cases} \end{equation*}

Раскроем сумму и с учетом того, что -2/n не может равняться нулю, приравняем к нулю вторые множители

\begin{equation*} \begin{cases} 0=−w_0*n + \sum\limits_{i=0}^n y_i−w_1∗\sum\limits_{i=0}^n x_i\\ 0=\sum\limits_{i=0}^n(y_i*x_i) - w_0*\sum\limits_{i=0}^n x_i −w_1∗\sum\limits_{i=0}^n x_i^2 \end{cases} \end{equation*}

Выразим w0 из первого уравнения

w_0 = \frac{\sum\limits_{i=0}^n y_i}{n} - w_1 \frac{\sum\limits_{i=0}^n x_i}{n}

Подставив во второе уравнение решим относительно w1

0=\sum\limits_{i=0}^n(y_i*x_i) - (\frac{\sum\limits_{i=0}^n y_i}{n} - w_1 \frac{\sum\limits_{i=0}^n x_i}{n})*\sum\limits_{i=0}^n x_i −w_1∗\sum\limits_{i=0}^n x_i^20=\sum\limits_{i=0}^n(y_i*x_i) - \frac{\sum\limits_{i=0}^n (y_i\sum\limits_{i=0}^n x_i)}{n} + w_1 \frac{\sum\limits_{i=0}^n (x_i\sum\limits_{i=0}^n x_i)}{n} −w_1∗\sum\limits_{i=0}^n x_i^2

И выразив w1 последнего уравнения получим

w_1 = \frac{\frac{\sum\limits_{i=0}^n(x_i\sum\limits_{i=0}^ny_i)}{n} - \sum\limits_{i=0}^n(y_i*x_i)}{\frac{\sum\limits_{i=0}^n(x_i\sum\limits_{i=0}^nx_i)}{n} - \sum\limits_{i=0}^nx_i^2}

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

f1

f2

f3

y

x11

x12

x13

y1

...

...

...

...

x1n

x2n

x3n

yn

Для вычисления интерсепта (коэффициента w0) необходимо к таблице добавить столбец слева с фактором f0 все значения которого равны 1 (единичный вектор-столбец). И тогда столбцы f0-f3 (по количеству столбцов не ограничены, можно считать fn) можно выделить в матрицу X, целевую переменную в матрицу-столбец y, а искомые коэффициенты можно представить в виде вектора w.

X=\begin{bmatrix} x_{01}& x_{11} & x_{12} & x_{13}\\ ...& ... & ... & ... \\x_{0n} & x_{1n} & x_{2n} & x_{3n}\end{bmatrix} y = \begin{bmatrix} y_0 \\ ... \\ y_n \end{bmatrix} w = \begin{bmatrix} w_0 & w_1 & w_2 & w_3\end{bmatrix}

Тогда функцию потерь

MSE=\sum\limits_{i=0}^n(y_i−f(x_i))^2

можно представить в следующем виде

MSE = (y - X*w)^T(y - X*w)

Представим в виде скалярного произведения < > и вычислим производную используя дифференциал

\partial (<(y-X*w),(y-X*w)>)

используя правило

\partial(<x,x>) = <2x, \partial x>

приведем формулу к следующему виду

(<2*(y-X*w), \partial (y-X*w)>)

Поскольку дифференциал разницы равен разнице дифференциалов, дифференциал константы (y) равен нулю и константу (в данном случае матрицу X) можно вынести за знак дифференциала, получим

(<2*(y-X*w),  X*\partial w>)

Используя свойство скалярного произведения перенесем матрицу X справа налево незабыв транспонировать ее

(<2*X^T*(y-X*w),  \partial w>)

Собственно, то что слева и есть дифференциал, найдем экстремум приравняв его к нулю и решив по параметрам w

2*X^T*(X*w-y)=0

раскроем скобки и перенесем значения без w вправо

X^T*X*w=X^T*y

Домножим слева обе стороны равенства на обратную матрицу произведения транспонированной матрицы X на X для выражения вектора w, тогда получим

w=(X^T*X)^{-1}*X^T*y

Аналитическое решение получено, переходим к реализации на python.

#импорт необходимых библиотек
import numpy as np
from sklearn.linear_model import LinearRegression

#зададим начальные условия
f0 = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
f1 = np.array([1.1, 2.1, 3.1, 4.4, 5.2, 6.4, 7.1, 8.2, 9.4, 10.5])
f2 = np.array([1.4, 2.3, 3.4, 4.1, 5.5, 6.2, 7.3, 8.4, 9.2, 10.1])
f3 = np.array([1.2, 2.2, 3.4, 4.2, 5.3, 6.2, 7.3, 8.4, 9.2, 10.3])
y = np.array([[1.2], [2.2], [3.3], [4.3], [5.2], [6.3], [7.2], [8.3], [9.3], [10.2]])
w = np.array([np.nan, np.nan, np.nan, np.nan])
X = np.array([f0, f1, f2, f3]).T

#рассчитаем коэффициенты используя выведенную формулу
coef_matrix = np.dot(np.dot(np.linalg.inv(np.dot(X.T, X)), X.T), y)
print(f'Коэффициенты рассчитанные по формуле {coef_matrix.T[0]}')
#Коэффициенты рассчитанные по формуле [0.05994939 0.42839296 0.09249473 0.46642055]

#проверим расчет используя библиотеку sklearn
model = LinearRegression().fit(X, y)
coef_sklearn = model.coef_.T
coef_sklearn[0] = model.intercept_
print(f'Коэффициенты рассчитанные с использованием библиотеки sklearn {coef_sklearn.T[0]}')
#Коэффициенты полученные с рассчитанные библиотеки sklearn [0.05994939 0.42839296 0.09249473 0.46642055]

Надеюсь эта статья помогла заглянуть под капот одного из базовых методов машинного обучения - линейной регрессии и станет первой ступенью в этот увлекательный мир: математика машинного обучения.

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


  1. orrollo
    07.04.2022 00:29
    +1

    Аналитическое решение получено, переходим к реализации на python

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

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


    1. iukash Автор
      07.04.2022 00:40

      На счет дополнения данных - градиентным спуском также придется еще раз итерационно проходить и я не уверен, что это будет быстрее чем расчет матриц, а вот вырожденность после умножения дествительно может быть проблемой, наверное в идеале сначала считать определитель матрицы полученной в результате умножения X*X^Tи если он равен нулю выбирать градиентный спуск. Но я не претендовал на идеальную реализацию, цель была лишь разобрать именно данный метод расчета. Спасибо за уточнение!


    1. tarekd
      07.04.2022 01:37

      решает ли проблему псевдо инверсия pinv?


  1. Cryptomathic
    07.04.2022 10:05
    +1

    ...необходимо к таблице добавить столбец слева с фактором f0 все значения которого равны 0.

    Наверное единичный столбец.

    Почему в МНК сумма не делится на количество значений? В одной статье вообще видел деление на 2n.


    1. iukash Автор
      07.04.2022 10:14

      Весьма справедливые замечания, жаль нельзя откорректировать статью! Спасибо!!!


      1. iukash Автор
        09.04.2022 10:41

        Сразу не увидел возможность редактирования - внес изменения! Спасибо за уточнение!