Линейная регрессия — это простой алгоритм машинного обучения, но также это ступенька в океан машинного обучения и глубокого обучения. Это связано с алгоритмом градиентного спуска (который является частью линейной регрессии), который является важным алгоритмом оптимизации в глубоком обучении.

Это вторая часть моей серии ML From Scratch,



ML From Scratch Part 01 —
K-Nearest Neighbor (KNN)

Реализация KNN с использованием Python.medium.com



Что такое модель?

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

Выше реклама LEGO, и мне это интересно. Модели также предназначены для этого (фиксация общих закономерностей в данных), но в настоящее время вышеприведенная Мона-Лиза имеет большее разрешение 😂 (gpt4 имеет ~ 1,7 триллиона параметров).

Предпосылки

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

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

Линейная регрессия

N = 50
x = np.linspace(0, 1, N)
y = x + np.random.randn(N)/4

plt.scatter(x, y, alpha=.75, edgecolors='k')
plt.show()

Рассмотрим значения x приведенных выше данных как функции, а значения y как некоторые выходные данные данных функций, как вы можете видеть, у нас есть четкая тенденция в данных, теперь как мы ее фиксируем?

Я объясню два метода, которые

  • Наименьших квадратов
  • Градиентный спуск

Цель обоих методов – найти наклон и точку пересечения линии, проходящей через данные, которая имеет наименьшее возможное расстояние от каждой из точек данных (с минимальной ошибкой) в n числе многомерных данных. >

Обычный метод наименьших квадратов (OLS)

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

Итак, теперь мы знаем, как рассчитать наклон и точки пересечения.

# Note: @ in np.ndarray means dot-product
X = np.vstack((np.ones(len(x)), x)).T
betas = np.linalg.inv(X.T@X) @ X.T @ y
yHat = X@betas

plt.plot(x, yHat, 'r', alpha=.3, linewidth=2)
plt.scatter(x, y, alpha=.75, edgecolors='k')
plt.show()

ура, мы нашли это…

Примечание. Этот метод по-прежнему работает, даже если у нас есть более чем трехмерные данные.

Итак, теперь мы можем предсказывать значения и прочее, но мы получили простую линию, потому что данные были очень простыми, что, если бы данные были такими, как показано ниже?

N = 100
x = np.linspace(-1, 1, N)
y = 2*x**4 + x**3 + 2*x**2 + np.random.randn(N)*.4
X = np.vstack((np.ones(len(x)), x)).T

betas = np.linalg.inv(X.T@X) @ X.T @ y
yHat = X@betas

plt.plot(x, yHat, 'r', alpha=.3, linewidth=2)
plt.scatter(x, y, alpha=.75, edgecolors='k')
plt.show()

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

Полиномиальная регрессия

Если вы понимаете линейную регрессию, то это просто кусок пирога.

Раньше мы не могли уловить тенденцию в наборе данных, заключавшуюся в том, что линия с простым уравнением (y = mx + b) может быть только прямой линией, а для формирования кривой линии нам нужно иметь полиномиальное уравнение.

Обратите внимание: в уравнении для полинома, если вы рассматриваете входные значения как константы, тогда это линейное уравнение, позвольте мне показать вам;

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

# from sklearn.preprocessing import PolynomialFeatures
import numpy as np
import matplotlib.pyplot as plt

def polynomial_features(X, order=2):
    if order < 2:
        return X

    output = np.array(X**1)

    # here we starts with 2 is becuase the first column is already calculated above
    for d in range(2, order + 1):
        output = np.hstack((output, X**d))

    return output

N = 100
x = np.linspace(-1, 1, N)
y = 2*x**4 + x**3 + 2*x**2 + np.random.randn(N)*.4
x = x.reshape(-1, 1)

X_poly = polynomial_features(x, order=4)
'''or use sklearn as shown below'''
# poly_feat = PolynomialFeatures(degree=4, include_bias=False)
# X_poly = poly_feat.fit_transform(x)

# add the np.ones to incorporate the intercept term
X_poly = np.hstack((X_poly, np.ones((len(X_poly), 1))))

# calculate the beta values
betas = np.linalg.inv(X_poly.T@X_poly) @ X_poly.T @ y
yHat = X_poly@betas

plt.plot(x, yHat, 'r', alpha=.3, linewidth=2)
plt.scatter(x, y, alpha=.75, edgecolors='k')
plt.show()

Но подождите, в приведенном выше примере мы сами сгенерировали данные (в образовательных целях), поэтому мы смогли решить, что степень/порядок 4 лучше всего подходят, но при работе с реальными данными, как мы решаем, какую степень/порядок использовать? выбирать?

Да, верно, посмотрите график удара, чтобы увидеть эффект выбора степени/порядка;

Некоторые степени имеют тенденцию к недостаточному соответствию, некоторые - к чрезмерному. Мы можем использовать Информационные критерии Байеса (BIC), чтобы решить эту проблему.

Байесовские информационные критерии

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

N = 100
x = np.linspace(-1, 1, N)
y = 2*x**4 + x**3 + 2*x**2 + np.random.randn(N)*.4
x = x.reshape(-1, 1)

def get_bic(x, y, order):
    poly_feat = PolynomialFeatures(degree=order, include_bias=True)
    X_poly = poly_feat.fit_transform(x)
    
    betas = np.linalg.inv(X_poly.T@X_poly) @ X_poly.T @ y
    yHat = X_poly@betas
    
    sse = sum((y-yHat) ** 2)
    n = len(y)
    bic = n*np.log(sse) + order*np.log(n)
    return bic

orders = np.arange(1, 21)
bic = [get_bic(x, y, o) for o in orders]
BEST_ORDER = orders[np.argmin(bic)]

plt.plot(orders, bic, 'ks-')
plt.xlabel('Orders')
plt.ylabel('BICs')
plt.title(f'{BEST_ORDER} is the best polynomial order for this data.')
plt.show()

Посмотрите, хотя мы сгенерировали данные с использованием полиномиальной функции 4-го порядка из-за добавления к ней случайных значений, данные немного различались, поэтому мы получили 6 как лучший полиномиальный порядок (что правильно для этих конкретных данных).

Проблемы использования метода наименьших квадратов

Итак, теперь вы знаете все об использовании метода наименьших квадратов для моделей линейной/полиномиальной регрессии, но это не является отраслевым стандартом, потому что у него есть некоторые проблемы; которые,

  1. Масштабируемость. Для небольших наборов данных поиск коэффициентов для такой модели (используя весь набор данных за раз) не представляет проблемы, но для больших наборов данных это может потребовать значительных вычислительных ресурсов и памяти.
  2. Гибкость. При множественной линейной регрессии (когда у вас есть несколько переменных-предикторов) решение в закрытой форме становится более сложным, и в некоторых случаях его невозможно вычислить.
  3. Регуляризация. Регуляризация помогает предотвратить переобучение в более сложных моделях, но ее сложно включить в решение закрытой формы.
  4. Онлайн-обучение. В сценариях, когда данные поступают в потоковом режиме (онлайн-обучение), закрытое решение потребует пересчета всего решения каждый раз, когда добавляются новые данные.
  5. Нелинейные модели. В нелинейных моделях нет решения в закрытой форме, например, в нейронных сетях.
  6. и т. д…

Все эти проблемы можно решить, используя градиентный спуск.

Градиентный спуск

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

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

  1. Сначала предскажите некоторые значения, используя общую линейную модель [y=mx+b] (веса инициализируются случайным образом).
  2. Затем, используя некоторую функцию потерь, мы вычисляем разницу между прогнозируемыми значениями и желаемыми значениями (потери).
  3. После этого с помощью исчисления мы найдем частные производные весов и смещений, которые участвуют в функции потерь.
  4. Наконец, мы настраиваем веса и смещения, используя вычисленные частные производные.
  5. Повторите всю операцию для n числа epochs.

Шаг 01

N = 100
X = (np.linspace(-1, 1, N) + np.random.randn(N)).reshape(-1, 1)
Y = X[:, 0] + np.random.randn(N)/2

n_samples, n_features = X.shape
weights = np.random.randn(n_features)
bias = 0

y_pred = X @ weights + bias # @ symbol in numpy.ndarray means 'dot product'

Шаг 02

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

Нам не нужно вычислять mse. Нам просто нужна частная производная весов и смещений mse потерь, поэтому мы можем перейти к следующему шагу, не вычисляя MSE в Python.

Шаг 03

Настоятельно рекомендуется, прежде чем двигаться дальше, узнать, что означают производная и частная производная, позвольте мне сделать краткое описание этого,

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

Для простоты понимания я даю вам подробный расчет частной производной по отношению к m и b в убытке MSE.

Поскольку члены 2/n и суммирования не зависят от m и b и не влияют на вычисление производной по этим переменным; поэтому мы сохранили эти термины вне расчета частной производной и поместили их обратно в конец (так что не запутайтесь).

# calculate the partial derivatives
dw = (2/n_samples) * X.T @ (y_pred - y)
db = (2/n_samples) * -np.sum(y_pred - y)

Теперь нам нужно вычесть каждый из весов и смещений с соответствующей частной производной, умноженной на постоянный член learning_rate;

Весь алгоритм

class LinearRegression:
    def __init__(self, learning_rate=0.001, n_epoch=1000):
        self.lr = learning_rate
        self.n_epoch = n_epoch
        self.weights = None  # slope
        self.bias = None  # y_intercept

    def fit(self, X, y):
        n_samples, n_features = X.shape
        self.weights = np.random.randn(n_features)
        self.bias = 0

        for _ in range(self.n_epoch):
            y_predicted = np.dot(X, self.weights) + self.bias

            dw = (2/n_samples) * X.T @ (y_predicted - y)
            db = (2/n_samples) * np.sum(y_predicted - y)

            self.weights -= self.lr * dw
            self.bias -= self.lr * db

    def predict(self, X):
        y_predicted = X @ self.weights + self.bias
        return y_predicted

N = 100
X = (np.linspace(-5, 5, N) + np.random.randn(N)*.6).reshape(-1, 1)
y = X[:, 0] + np.random.randn(N)


model = LinearRegression()
model.fit(X, y)
y_pred = model.predict(X)

plt.plot(X[:, 0], y_pred, label='predicted')
plt.plot(X[:, 0], y, 'o', label='data points')
plt.legend()
plt.show()

Мы также можем выполнить полиномиальную регрессию, как мы объясняли ранее, если вы выполните предварительную обработку входных переменных, а затем передадите их в алгоритм, тогда он будет работать как шарм,💖 вы можете сделать это самостоятельно, я оставляю это. вам (сделайте это как упражнение).



Заключение

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

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

Как я всегда говорю, при работе с реальными проектами вы не хотите беспокоиться обо всех занудных вещах, тогда вы можете использовать для этого такие библиотеки, как scikit-learn;

Ниже приведен код для работы с scikit-learn Python API,

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn import datasets

# create data
X, y = datasets.make_regression(
    n_samples=100, n_features=2, noise=20
)

# split for test & train
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

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

Добрый день, 😍 👋