Выйти за рамки LinearRegression (). Fit (x, y)

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

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

где Y - прогнозируемое значение, θ₀ - параметр смещения, θ₁ - это значение параметра, а x - значение функции.

Чтобы проиллюстрировать, что означают эти параметры, представьте, что вы едете на работу в такси. Вскоре после того, как вы сядете в такси, стоимость проезда не будет начинаться с нуля. Вместо этого она будет начинаться с некоторых значений, скажем, 5 долларов. Эти 5 долларов представляют собой член смещения в уравнении регрессии (θ₀). Прогнозируемое значение Y - это цена, которую вы должны заплатить, а значение функции x - это пройденное расстояние. , а θ₁ - это наклон того, насколько больше вам нужно заплатить по мере увеличения расстояния поездки.

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

import numpy as np
np.random.seed(1)
x = 2 * np.random.rand(100,1)
y = 4+ 2 * x + np.random.rand(100,1)

Между тарифом на такси и расстоянием проезда существует линейная тенденция. Следовательно, мы можем связать стоимость проезда на такси и расстояние в пути как линейную функцию:

Теперь проблема в том, как получить оптимальное значение θ₀ и θ₁, чтобы наша модель линейной регрессии работала Лучший? Для этого мы можем использовать Scikit-learn.

Построение модели линейной регрессии с помощью Scikit-learn

Если вам нужно решить задачу линейной регрессии, как в примере выше, самый простой способ найти оптимальное значение θ₀ и θ₁ - это вызов функции LinearRegression из библиотеки Scikit-learn. Проблема может быть решена всего четырьмя строками кода следующим образом:

from sklearn.linear_model import LinearRegression
lm = LinearRegression()
#train the model
lm.fit(x,y)
print(lm.intercept_, lm.coef_)
## Output: [4.49781698] [[1.98664487]]

что дает нам соотношение между тарифом на такси и расстоянием в пути следующим образом:

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

Линейная регрессия - больше, чем просто черный ящик

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

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

Было бы здорово узнать, что скрывается под капотом и как Scikit-learn предлагает решение taxi_fares = 4.49 + 1.98 * travel_distance? Как они узнают, что 4,49 - это оптимальное значение для член смещения и 1,98 является оптимальным значением для наклона?

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

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

  • Использование подхода итеративной оптимизации под названием «градиентный спуск».
  • Использование уравнения «замкнутой формы», называемого нормальным уравнением.

Градиентный спуск для линейной регрессии

Вкратце, алгоритм градиентного спуска находит оптимальное решение для задачи линейной регрессии, настраивая параметры модели θ (член смещения и наклон), чтобы минимизировать функцию ошибки или стоимости. в отношении обучающих данных итеративно.

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

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

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

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

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

Но вопрос в том, почему «почти» гарантировано, что градиентный спуск окажется в глобальном минимуме, если нет локальных оптимумов?

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

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

Реализация градиентного спуска

После всего объяснения градиентного спуска давайте реализуем его с помощью кода, используя пример изучаемого примера: стоимость проезда на такси и расстояние в пути.

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

куда:

В приведенной выше математической записи J - это функция стоимости, m - количество наблюдений или данных, - это прогнозируемое значение линейной регрессии, θ₀ - член смещения, θ₁ - наклон, x - значение характеристики, а y - фактическое прогнозируемое значение. ценить. Мы можем реализовать приведенные выше уравнения в коде:

def computeCostFunction(x, y, theta, num_observations):
    
    linear_function= np.matmul(x,theta)
    error = np.subtract(linear_function,y) 
    error_squared = error.transpose()**2 
    cost_function = (0.5/num_observations)*(np.sum(error_squared))
    
    return cost_function

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

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

Когда вы знаете градиент каждого параметра θⱼ, теперь вы можете обновить каждый параметр θⱼ за одну итерацию. Здесь скорость обучения α играет роль в алгоритме градиентного спуска.

Наконец, мы можем реализовать приведенные выше уравнения в коде:

intercept_x = np.ones((len(x),1)) #add bias term
x = np.column_stack((intercept_x,x))
alpha = 0.05 #learning rate
iterations = 25000
theta = np.random.randn(2,1) #random initialization of parameters
num_observations = 1000
def gradientDescent(x, y, theta, alpha, num_observations, iterations):
    
    cost_history = np.zeros((iterations,1))
    
    for i in range (iterations):
        
        linear_function= np.matmul(x,theta)
        error = np.subtract(linear_function,y)
        gradient = np.matmul(x.transpose(),error)
        theta = theta - (alpha * (1/num_observations) * gradient)
        
        cost_history[i] = computeCostFunction(x, y, theta,              num_observations)
        
    return theta, cost_history
theta, cost_history = gradientDescent(x, y, theta, alpha, num_observations, iterations)
print(theta)
#Output: array([[4.49781697],[1.98664487]])

Результат Gradient Descent выглядит так же, как и результат, полученный с помощью Scikit-learn. Разница в том, что теперь вы знаете, что находится под капотом.

Существует еще один простой подход к поиску оптимального решения вашей задачи линейной регрессии - это нормальное уравнение.

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

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

В приведенном выше уравнении θ - оптимальное значение параметров регрессии, x - характеристика, а Y - прогнозируемое значение. Мы можем реализовать приведенное выше уравнение в коде:

def normalEquation(x, y):
    
    x_transpose = x.transpose()
    x = np.matmul(x_transpose, x)
    x_inverse = np.linalg.inv(x)
    x_final = np.matmul(x_inverse,x_transpose)
    
    theta = np.matmul(x_final,y)
    
    return theta
theta = normalEquation(x, y)
print(theta)
#Output: array([[4.49781698],[1.98664487]])

И снова результат совпал с тем, который мы получили из библиотеки Scikit-learn и оптимизации Gradient Descent. Теперь вы можете задать вопрос: если нормальное уравнение очень простое, то почему бы нам не использовать его вместо градиентного спуска? Давай обсудим это.

Нормальное уравнение или градиентный спуск?

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

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

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

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

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

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

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

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

Как улучшить характеристики градиентного спуска?

Основным недостатком Gradient Descent является то, что он использует все обучающие данные для вычисления функции стоимости на каждой итерации. Это будет проблематично, если у нас будет огромное количество наблюдений или обучающих данных. Для решения проблемы вместо этого обычно применяется стохастический градиентный спуск (SGD) или мини-пакетный градиентный спуск.

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

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

Для простой или множественной задачи линейной регрессии обычно достаточно градиентного спуска. Однако для более сложных приложений, таких как алгоритм нейронных сетей, предпочтительнее использовать мини-пакетный градиентный спуск.