Градиентный спуск с помощью простого набора данных
Вступление
В этой статье я собираюсь продемонстрировать градиентный спуск в машинном обучении с помощью простого набора данных. Слово “Градиент” имеет несколько значений в зависимости от контекста, в котором оно используется. Однако в общем смысле это означает нечто, что постепенно меняется. На языке математики и машинного обучения В машинном обучении градиентный спуск – это алгоритм оптимизации, способный находить наиболее подходящие решения для широкого круга проблем. Он работает через повторение настройки параметров для минимизации функции стоимости.
На уроках математики в школах учат, что уравнение прямой имеет вид y = mx + c (обозначения могут отличаться в зависимости от книг), где y – точка на оси y, x – точка на оси x, m – наклон, а c – пересечение на оси y. Если пересечение по оси y равно 0, значение c можно игнорировать. В машинном обучении это уравнение является базовой задачей линейной регрессии. Если в двумерной плоскости есть только одна линия, наклон и пересечение можно найти с помощью формул, подставив их вместо m и c и решив уравнение. Следовательно, у него есть единственное решение.
На самом деле задачи машинного обучения включают в себя сотни или тысячи уравнений, которые чаще всего не согласуются ни с одним решением. Каждое уравнение – это “Наблюдение”, а каждая ось – “Признак”. Когда система уравнений содержит больше наблюдений, чем признаков, она называется переопределённой системой. Следовательно, большинство проблем машинного обучения связаны с чрезмерно детерминированной системой. Градиентный спуск используется для нахождения решения (коэффициента) для каждого признака, с которым могут согласиться все наблюдения, что сведёт к минимуму разницу между наблюдаемыми и прогнозируемыми результатами. Разница рассчитывается с использованием множества “Функций затрат”. Одним из них является среднеквадратичная ошибка (MSE), которая используется в задаче ML этой статьи.
Код
Для этой задачи градиентного спуска подготовлен простой синтетический набор данных. Все блоки кода написаны на языке программирования Python. Программа начинается с импорта необходимых пакетов Python:
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
Генерируется синтетический набор данных, который следует уравнению y = 2.3x
. Цель предварительной фиксации коэффициента и соответствующей генерации набора данных состоит в том, чтобы увидеть, как выполняется градиентный спуск, и в результате определить коэффициент. Излишне говорить, что реальная задача машинного обучения заранее не будет иметь коэффициентов.
Определение уравнения в Python и генерация набора данных:
def f(x):
return 2.3*x
x = np.arange(-20, 20, 2) #Generate feature x
Y = []
for x_i in x:
Y.append(np.round(f(x_i),2)) #Generate Observed value Y
fulldata = pd.Series(zip(x,Y))
n=len(fulldata)
Как упоминалось ранее, MSE – это функция затрат, которая математически представлена в виде,
Заменяющий y_bar,
Функция Python создана для облегчения расчёта стоимости путём предоставления необходимых входных параметров:
def costfunc(Y_i, x_i, m, n):
return (1/n)*sum(pow(Y_i-(m*x_i),2))
Как упоминалось ранее, градиентный спуск – это методология, позволяющая минимизировать крутизну склона. Функцией здесь является Cost Function, и градиентный спуск используется для минимизации её ошибки. В более общих чертах, градиентный спуск используется для нахождения значения m, которое приводит к наименьшему MSE.
Но как мы узнаем, является ли наклон “крутым” или “пологим” в определённой точке кривой функции затрат? Вот тут-то и появляются деривативы. Производные предоставляют возможность понять скорость изменения значения по сравнению с другим значением в определённый момент. В случае функции Cost производные предоставляют возможность определить величину MSE для экземпляра m. Следовательно, производные используются для настройки значения m до такой степени, чтобы это приводило к наименьшей ошибке. Применяя производные правила к нашей функции затрат (MSE), производное уравнение становится,
Вот как это будет выглядеть в виде функции на Python:
def der_costfunc(Y_i, x_i, m, n):
return (2/n)*sum(x_i*((m*x_i)-Y_i))
Установлена важная переменная, называемая Step (она же Скорость обучения), которая определяет, насколько сильно изменяется значение m на каждой итерации спуска. Значение этой переменной может отличаться в зависимости от задачи машинного обучения. Оно обычно задаётся в долях, таких как 0,01, 0,0001 и т.д.
Если значение Step задано слишком высоким, значение функции Cost может колебаться между несколькими пиками и никогда не сходиться. Это также может ввести людей в заблуждение, заставив их думать, что существует какая-то проблема с их программой или производной формулой, вместо того, чтобы осознать, что значение Step не подходит, и это препятствует конвергенции. И наоборот, если значение Step установлено слишком низким, функции Cost может потребоваться много времени, чтобы достичь минимума.
Другой переменной, которая используется при градиентном спуске, является tolerance. Поскольку градиентный спуск пытается уменьшить MSE с помощью нескольких итераций, должен быть способ определить, когда MSE является приемлемым, и остановить итерации. Один из способов – использовать tolerance. Может быть несколько способов определения tolerance для задачи градиентного спуска. В этой статье определяется разница в MSE между последовательными итерациями, и если она меньше определённого значения, итерации останавливаются.
Наконец, начальное значение коэффициента m устанавливается таким образом, чтобы его можно было корректировать на каждой итерации для уменьшения MSE. Помещаем всё это в код Python, как показано ниже:
ms = [] #Hold the coefficients for each iteration for plotting purpose
errors = [] #Hold the MSE for each iteration for plotting purpose
m = 0 #Initial value of the coefficient
step = 0.001 #Ensure step is appropriate else costfunc will wildly swing.
tol=0.0001 #Acceptable difference in MSE between consecutive iterations to stop the Descent
Теперь, когда начальные переменные заданы, можно запускать процесс градиентного спуска. Напомним, что процесс включает в себя изменение значения коэффициента до тех пор, пока Cost не окажется в пределах допустимого предела. Приведённые ниже шаги объясняют теоретический процесс нахождения соответствующего коэффициента, который уменьшает MSE:
1. Рассчитайте Cost, используя начальное значение коэффициента.
2. Вычислите значение производной для начального значения m, используя уравнение первой производной функции Cost. Умножьте это значение на значение Step, чтобы определить следующее значение коэффициента m.
3. Рассчитайте Cost ещё раз с новым значением коэффициента m. Найдите разницу между MSE, полученным на шаге 1, и шаге 3.
4. Повторяйте шаги 1-3 до тех пор, пока разница между последовательными затратами не окажется в пределах допустимого значения.
Применение этих шагов в виде кода на python:
prevcost = costfunc(Y, x, m, n)
for i in range(200):
der_m = der_costfunc(Y, x, m, n)
m = m - (der_m*step)
currcost = costfunc(Y, x, m, n)
ms.append(m)
errors.append(currcost)
if((prevcost-currcost) < tol):
print(f'm: {np.round(m,5)}, currcost: {currcost}, prevcost: {prevcost}')
plt.plot(ms, errors)
plt.xlabel("coefficient")
plt.ylabel("MSE")
plt.show()
break
prevcost = currcost
Приведённый выше код настроен на выполнение максимум 200 итераций, чтобы найти оптимальный коэффициент m, который уменьшает MSE. Количество итераций может быть изменено в зависимости от проблемы, но чем больше итераций, тем дольше выполняется код. Выполнение кода приводит к коэффициенту m, равному 2,29931, что близко к 2,3, который был установлен в наборе данных при его подготовке. Приведённый ниже график, также являющийся результатом выполнения приведённого выше кода, показывает, как MSE уменьшается с каждым значением коэффициента.
Ниже представлен полный код программы:
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
def f(x):
return 2.3*x
x = np.arange(-20, 20, 2)
Y = []
for x_i in x:
Y.append(np.round(f(x_i),2))
fulldata = pd.Series(zip(x,Y))
n=len(fulldata)
def costfunc(Y_i, x_i, m, n):
return (1/n)*sum(pow(Y_i-(m*x_i),2))
def der_costfunc(Y_i, x_i, m, n):
return (2/n)*sum(x_i*((m*x_i)-Y_i))
ms = [] #Hold the coefficients for each iteration for plotting purpose
errors = [] #Hold the MSE for each iteration for plotting purpose
m = 0 #Initial value of the coefficient
step = 0.001 #Ensure step is appropriate else costfunc will wildly swing.
tol=0.0001 #Acceptable difference in MSE between consecutive iterations to stop the Descent
prevcost = costfunc(Y, x, m, n)
for i in range(200):
der_m = der_costfunc(Y, x, m, n)
m = m - (der_m*step)
currcost = costfunc(Y, x, m, n)
ms.append(m)
errors.append(currcost)
if((prevcost-currcost) < tol):
print(f'm: {np.round(m,5)}, currcost: {currcost}, prevcost: {prevcost}')
plt.plot(ms, errors)
plt.xlabel("coefficient")
plt.ylabel("MSE")
plt.show()
break
prevcost = currcost
Заключение
В этой статье показано, как можно выполнить градиентный спуск с использованием простого набора данных. Коэффициент уже был установлен таким образом, чтобы его можно было сравнить с коэффициентом, сгенерированным в процессе, и понять идею. Это не относится к задачам машинного обучения. Используемый здесь набор данных прост и невелик. В реальных сценариях наборы данных могут быть огромными, и этот процесс может занять очень много времени. В таких случаях могут быть использованы другие разновидности градиентного спуска, такие как стохастический градиентный спуск и пакетный градиентный спуск. Набор данных обычно разделяется на обучающий и тестовый наборы, и этапы предварительной обработки применяются к этим наборам данных отдельно для повышения производительности модели.
Я надеюсь, что эта статья была полезна в качестве базового введения для начинающих программистов.