В первой части серии я реализую модель линейной регрессии. Я предполагаю, что вы уже знакомы с LR, MSE и градиентным спуском.

Однако ниже приведены некоторые ключевые моменты, касающиеся модели:

  1. Предполагается линейная связь между предикторами (X) и предсказанием (Y)
  2. Для оценки коэффициентов обычно оптимизируют функцию ошибок MSE.
  3. Для оптимизации функции ошибок мы используем градиентный спуск.

Реализация

Ниже представлен скелет класса. Вы должны реализовать 3 метода с именами: __y_hat(), __current_error_derivative() и __update_weights. Попробуйте заполнить его и сравните свою реализацию с моей.

import numpy as np


# Skeleton
class MyLinearRegression:
    def __init__(self, x: np.ndarray, y: np.ndarray, lr: float = 0.1, iterations: int = 1000):
        # Y = b1x1+ b2x2 + b3x3 ... + b0

        assert x.shape[0] == y.shape[0]
        self._n = x.shape[0]
        self._y = y

        # Adding extra column of ones to make operation easier
        # Instead of Y = B.X ... + C ; It becomes Y = B.X
        self._x = np.c_[x, np.ones(x.shape[0])]

        # Last element of _b is b0
        self._b = np.zeros(shape=(x.shape[1]+1))

        self._lr = lr
        self._iterations = iterations

    @property
    def __y_hat(self):
        # Return prediction
        pass

    def __current_error_derivative(self):
        # d(E)/d(B) = 2/n * (Yhat - Y) * X
        pass
  
    def __current_error(self):
        # E = (Y - Yhat)**2
        pass

    def __update_weights(self):
        pass

    @property
    def coefficient_(self):
        return self._b[:-1]

    @property
    def intercept_(self):
        return self._b[-1]

    def fit(self):
        for i in range(self._iterations):
            print(self.__current_error())

    def predict(self):
        return self.__y_hat

if __name__ == "__main__":
    X = np.array([[1, 1], [1, 2], [2, 2], [2, 3]])
    y = np.dot(X, np.array([1, 2])) + 3
    reg = MyLinearRegression(X, y)
    reg.fit()
    print(reg.coefficient_, reg.intercept_)

    from sklearn.linear_model import LinearRegression
    r = LinearRegression().fit(X, y)
    print(r.coef_, r.intercept_)
# My implementation
class MyLinearRegression:
    def __init__(self, x: np.ndarray, y: np.ndarray, lr: float = 0.1, iterations: int = 1000):
        # Y = b1x1+ b2x2 + b3x3 ... + b0

        assert x.shape[0] == y.shape[0]
        self._n = x.shape[0]
        self._y = y

        # Adding extra column of ones to make operation easier
        # Instead of Y = B.X ... + C ; It becomes Y = B.X
        self._x = np.c_[x, np.ones(x.shape[0])]

        # Last element of _b is b0
        self._b = np.zeros(shape=(x.shape[1] + 1))

        self._lr = lr
        self._iterations = iterations

    @property
    def __y_hat(self):
        return np.dot(self._x, self._b)

    def __update_weights(self):
        self._b = self._b - self._lr * self.__current_error_derivative()

    def __current_error(self):
        # E = (Y - Yhat)**2
        return ((y - self.__y_hat) ** 2).mean(axis=0)

    def __current_error_derivative(self):
        # d(E)/d(B) = 2/n * (Yhat - Y) * X
        # return (np.dot(self._x.T, self.__y_hat - self._y)).mean() !!! Wrong !!!
        return (self._x.T * (self.__y_hat - self._y)).mean(axis=1)

    @property
    def coefficient_(self):
        return self._b[:-1]

    @property
    def intercept_(self):
        return self._b[-1]

    def fit(self):
        for i in range(self._iterations):
            self.__update_weights()

    def predict(self):
        return self.__y_hat