Градиентный спуск, нормальное уравнение и математическая история.

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

# Import the required libraries
#
# import pandas
import pandas as pd
# import numpy
import numpy as np
np.seterr(all='warn')

# import matplotlib for visualization 
import matplotlib.pyplot as plt

# Read your collected data
data = pd.read_csv("car_pricing.csv")

# Project the first 5 rows of your data
data.head()

Давайте выскажем гипотезу, ладно?

Допустим, у нас есть волшебный пустой ящик. Этот волшебный ящик работает следующим образом. Мы кладем внутрь бумагу с записанным значением, и как только мы открываем ее снова, появляется новая бумага с предсказанием. И в этом идея.

Гипотеза - это формальный способ описания нашей функции прогноза. Это греческое слово «υπόθεση», означающее «идея или объяснение чего-то, что основано на известных фактах, но еще не доказано». Следовательно, наша цель - с учетом обучающего набора изучить функцию h: X → Y, чтобы h (x) был «хорошим» предиктором для соответствующего значения y.

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

Введите функцию стоимости

В предыдущем разделе мы сказали, что «… h (x) -« хороший »предиктор…». Однако мы еще не определили способ вычисления точности нашей функции гипотезы. Это измерение рассчитывается на основе функции стоимости. Как мы это делаем?

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

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

Метрика, которую мы будем использовать, - это MSE или среднеквадратичная ошибка. Он является самым популярным среди алгоритмов регрессии и выглядит следующим образом:

куда:

  • i - индекс выборки,
  • ŷ - прогнозируемое значение,
  • y - математическое ожидание,
  • n - количество выборок в наборе данных.

Как показано в уравнении, чем меньше разница между фактическим и прогнозируемым значениями y, тем ниже MSE. И это цель - иметь минимально возможную MSE из-за того, что функция стоимости использует MSE в качестве метрики для оценки средней разницы всех результатов гипотезы с входными данными из x и фактическим выходом y.

Наконец, последний элемент, на котором строится функция стоимости, - это линия регрессии. Мы пытаемся провести прямую линию (определяемую hθ (x)), проходящую через точки данных. Поскольку мы пытаемся провести прямую линию, наша формула будет такой:

Теперь мы готовы собрать функцию стоимости, заменив y_hat нашей гипотезой.

Нормальное уравнение

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

Нашей целевой переменной (зависимой переменной) будет цена, а независимой переменной будет мощность автомобиля.

Преобразуйте наши функции и целевые переменные в матрицы, векторы:

Умножьте наши характеристики на наши параметры:

Эх вуаля! Этот метод позволит нам сделать массивный прогноз для нашего набора данных и сделать нашу функцию стоимости:

быть более похожим на:

Чтобы еще больше упростить уравнение, мы будем использовать одно из матричных тождеств транспонирования (AB) ^ T = B ^ T A ^ T, а затем мы будем использовать ассоциативное умножение.

Поскольку второй и третий члены совпадают, мы можем добавить их:

Тогда возникает вопрос, как минимизировать это уравнение? Ответ кроется в анализе графов и теореме Ферма. Теорема об экстремальных значениях говорит нам, что минимум и максимум функции должны быть там, где производная либо равна нулю, либо не существует. Итак, нам нужно найти производную нового J (θ), равную 0.

Найдите производную от A:

Следовательно, для каждого параметра n:

И если мы конвертируем все эти производные в векторы, то:

Найдите производную от B:

Следовательно, для каждого параметра n:

И если мы конвертируем все эти производные в векторы, то:

Третий член не содержит параметров θ и, следовательно, производных от,

будет равно нулю:

Все детали готовы. Соберем головоломку из уравнений (1), (2) и (3):

Это теория нормального уравнения. Однако мы должны протестировать метод, чтобы доказать, что он работает правильно. Пора применить его к нашей проблеме ценообразования на автомобили. Сначала мы инициализируем некоторые переменные и данные.

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

# Initialize target variable
y = data['price'].to_numpy()

# initialize number of training examples
m = y.size

# Get your feautures
X = data
X = X[["horsepower"]].to_numpy()

# Add a column of intercept point
X = np.hstack((np.ones((m,1)), X))

Далее мы перейдем к реализации обычного уравнения:

def normalEquation(X, y):
    """
    Computes the solution to linear regression using the normal equation formula.
    
    Parameters
    ----------
    X : array_like
        The dataset of shape (m x n+1).
    
    y : array_like
        The value at each data point. A vector of shape (m, ).
    
    theta : array_like
            A vector of shape (n+1, ) which represents the parameters.
    """
    
    # Initialize theta
    theta = np.zeros(X.shape[1])
    
    # Apply the formula
    theta = np.dot((np.linalg.inv(np.dot(X.T,X))),np.dot(X.T,y))
    
    # Return the theta parameters
    return theta
# Apply the normal equation
theta = normalEquation(X,y)

# print theta to screen
print('Theta found by  normal equation: {0}, {1}'.format(theta[0], theta[1]))
Theta found by  normal equation: -3721.7614943227672, 163.263060969648
plt.scatter(data["horsepower"], data["price"])
plt.title("Our hypothesis fit")
plt.xlabel("horsepower")
plt.ylabel("price")

plt.plot(data["horsepower"], X.dot(theta), '-', label='Linear regression', color='red')
plt.legend(loc='upper right', shadow=True, fontsize='small', numpoints=1)

plt.show()

Наконец, мы сделаем прогноз цены на Yaris, который будет стоить около 10 000 евро.

predict = np.dot([1, 72], theta)
print('For horsepower = 72, we predict that Yaris ACTIVE 2020 should cost: {:.2f} euros/dollars \n'.format(predict))
For horsepower = 72, we predict that Yaris ACTIVE 2020 should cost: 8033.18 euros/dollars

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

Α Еще один способ узнать параметры, которые заставляют нашу гипотезу работать с нашими данными, - это градиентный спуск. Я воспользуюсь примером из моего предыдущего поста, чтобы дать вам представление о методе. Представьте, что вы стоите на вершине долины с мячом в руке. Чтобы узнать, как далеко может уйти этот шар, мы позволяем ему катиться в самую глубокую часть долины. Поскольку в начале движения мяча уклон круче, мяч спускается быстрее. Однако, как только он достигнет дна, поверхность станет более вертикальной, и мяч замедлится. Мы знаем, что добились успеха, когда наша функция затрат находится в самом низу ямки на нашем графике, то есть когда ее значение минимально.

Математически метод зависит от касательной, создаваемой мячом, и функции стоимости. Когда наклон отрицательный, значение θᵢ увеличивается, а когда оно положительное, значение θᵢ уменьшается. Итак, давайте создадим функцию на основе этой теории.

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

где,

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

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

На первом графике α слишком мало, что замедляет работу нашего метода. С другой стороны, в случае, если α слишком велико, шаги каждой итерации будут такими большими, что они многократно превышают минимум. Поэтому рекомендуется попробовать разные значения α от -1 до 1, чтобы найти золотую середину. Обычно инженеры машинного обучения используют α = 0,01 или α = 0,1 и увеличивают или уменьшают скорость обучения оттуда.

В результате наш алгоритм градиентного спуска по двум параметрам будет:

Это была теория градиентного спуска. Пора перевести это в код!

Совет: нормализуйте данные: z = (xᵢ - μ) / σ

# Initialize target variable
y = data['price'].to_numpy()

# initialize number of training examples
m = y.size

# Reshape y or transpose it
y = y.reshape(m, 1)

# Get your feautures
X = data
X = X[["horsepower"]].to_numpy()

# Normalize your data
X_norm = X.copy()
mu = np.zeros(X.shape[1])
sigma = np.zeros(X.shape[1])

mu = np.mean(X, axis = 0)
sigma = np.std(X, axis = 0)
X_norm = (X - mu) / sigma
X = X_norm

# Add a column of intercept
X = np.hstack((np.ones((m,1)), X))

# Initialize fitting parameters
theta = np.zeros((2,1))

# Initialize gradient descent settings
iterations = 2000;
alpha = 0.001;
def computeCost(X, y, theta):
    """
    Compute cost for linear regression.
    
    Parameters
    ----------
    X : array_like
        The values that represents the input features + the intercept point
        of shape (m x n+1), where m is the number of examples, and n is the
        number of features.
    y : array_like
        The target variable. It is a vector of shape (m, ).
    
    theta : array_like
        A vector of shape (n+1, ) which represents the parameters.
    
    Returns
    -------
    J : float - The value of the cost function.
    
    """
    
    # number of training examples
    m = np.size(y, axis=0)
    
    # Initialize cost variable
    J = 0
    
    # initialize hypothesis
    h_theta = np.dot(X, theta)
    
    # Calculate the cost
    J = (1/(2 * m)) * np.sum(np.square(np.dot(X, theta) - y))
    
    # Return Cost
    return J

def gradientDescent(X, y, theta, alpha, num_iters):
    """
    Performs gradient descent to learn theta
    
    Parameters
    ----------
    X : array_like
        The values that represents the input features + the intercept point
        of shape (m x n+1), where m is the number of examples, and n is the
        number of features.
    y : array_like
        The target variable. It is a vector of shape (m, ).
    
    theta : array_like
        A vector of shape (n+1, ) which represents the parameters.
    
    Returns
    -------
    theta: array_like - The optimal parameters
    J_history : array_like - The history of the cost.
    
    """
    # number of training examples
    m = np.size(y, axis=0)
    
    # Initialize some useful values
    J_history = []
    
    # are passed by reference to functions
    theta = theta.copy()
    
    for iter in range(num_iters):
        # initialize hypothesis
        h_theta = np.dot(X, theta)

        # calculate the thetas
        # (2,1) = (2,1) - ( 2, 1454  ).dot(1454x1 )
        theta = theta - (alpha / m) * (X.T).dot(h_theta - y)
        
        # Save the cost J in every iteration
        J_history.append(computeCost(X, y, theta))
    
    return theta, J_history

Выведите тэта-стоимость и тэту и сравните результаты с нормальным уравнением.

# run gradient descent
theta, J_history = gradientDescent(X, y, theta, alpha, iterations)

# print theta to screen
print('Theta found by gradient descent: {0}, {1}'.format(theta[0], theta[1]))
Theta found by gradient descent: [11481.70029204], [5569.60294698]

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

plt.title("Cost over iterations")
plt.plot(J_history, label='Cost function', color='red')
[<matplotlib.lines.Line2D at 0x11b80238>]

plt.scatter(data["horsepower"], data["price"])
plt.title("Our hypothesis fit")
plt.xlabel("horsepower")
plt.ylabel("price")

plt.plot(data["horsepower"], X.dot(theta), '-', label='Linear regression', color='red')
plt.legend(loc='upper right', shadow=True, fontsize='small', numpoints=1)

plt.show()

X_yaris = (72 - mu) / sigma
X_yaris = np.array([1, X_yaris[0]])

predict = X_yaris.dot(theta)
print('For horsepower = 72, we predict that Yaris ACTIVE 2020 should cost: {:.2f} euros/dollars \n'.format(predict[0]))
For horsepower = 72, we predict that Yaris ACTIVE 2020 should cost: 6947.09 euros/dollars

Заключение

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

Надеюсь, вам понравилось это так же, как и мне! Вы также можете найти блокнот Jyputer на Github, спасибо!