Градиентный спуск, нормальное уравнение и математическая история.
В мире, где данные становятся более ценными, чем золото, машинное обучение пытается использовать эти данные для маркетинга, удовлетворения потребностей клиентов, решения проблем и многих других причин. Однако возникает вопрос, как извлечь максимально возможное значение из заданного набора данных? Это вопрос, которым я попытаюсь ответить в этой статье.
# 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, спасибо!