Реализация базового градиентного спуска в Python для пошагового поиска параметров!

Что такое линейная регрессия?

Линейная регрессия создает линейную функцию для моделирования взаимосвязи между двумя переменными¹. Линейная регрессия — это то, что Excel и Google Таблицы используют для создания «линии тренда» для определенного набора данных. В этой статье рассматриваются некоторые аспекты интуиции и математики, лежащие в основе линейной регрессии, и будет показан один из способов реализации линейной регрессии в Python с использованием метода, называемого градиентным спуском, который обычно используется в машинном обучении.

Основы линейной регрессии

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

Цель линейной регрессии — каким-то образом найти m, наклон, и b, точку пересечения по оси y. Вместо m и b мы будем называть эти константы тета 1 и тета 0 соответственно. theta1 и theta0 называются параметрами . В линейной регрессии и градиентном спуске прогностическая линейная модель называется гипотезой и выражается следующим образом:

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

params = [0,0]

Теперь предположим, что у вас есть набор данных размером n с входными данными (x) и выходными данными (y). В коде мы будем моделировать входные данные, создавая случайный одномерный массив NumPy.

x = np.random.rand(100,1)

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

# test constants for line
TEST_SLOPE = 5
TEST_Y_INT = 7
y = TEST_SLOPE*x+np.random.randn(100,1) + TEST_Y_INT

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

Функция стоимости

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

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

При реализации градиентного спуска с функцией стоимости расчет упрощается, чтобы немного изменить функцию стоимости (показано ниже):

Теперь о реализации функции стоимости в коде Python.

# x is x data set (list)
# y is y data set (list)
# params is list of parameters
def cost(x,y, params):
    theta0 = params[0]
    theta1 = params[1]
    sum = 0
    for i in range(len(x)):
        sum += math.pow(theta1*x[i]+theta0 - y[i],2)
    cost = sum*ALPHA/len(x)/2
    return cost

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

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

Давайте сначала скажем, что нам нужно было найти только один параметр, а наша функция стоимости была упрощена до J(theta) вместо J(theta0, theta1).

Теперь для простоты возьмем J(тета) = тета2. Наша цель — найти значение тета, которое минимизирует эту функцию. В этом случае решение довольно очевидно: тета min = 0 даст нам минимальное значение.

В градиентном спуске мы сначала выбираем случайное значение тета. Например, если бы алгоритм градиентного спуска начинался с тета = 2, то он каким-то образом в конечном итоге должен был бы уменьшать тета, пока не достигнет минимального значения 0. Градиентный спуск достигает этого путем взятия производной функции стоимости в тета, а затем настраивая тета на ее производную, умноженную на скаляр, называемый скоростью обучения, представленный символом 𝛼.

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

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

Когда имеется более одного параметра, вычисления, необходимые для минимизации функции стоимости, становятся немного более сложными. Здесь нам нужно будет использовать частичное дифференцирование. Частичное дифференцирование похоже на дифференцирование тем, что оно сообщает нам поведение наклона и вычисляется аналогично. В трехмерном пространстве частичное дифференцирование позволяет нам вычислить наклон как по оси x, так и по оси y.

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

Обратите внимание на знак :=. Мы одновременно настроим каждый параметр в соответствии с его соответствующей частной производной. Из-за этого я использовал временный массив для хранения параметров. Ниже приведен код Python для обновления параметров.

for i in range(ITERATIONS):
    # initialize temporary parameters --> simultaneous update
temp0 = params[0]
    temp1 = params[1]
    temp = [temp0, temp1]
    # adjust parameters with gradient descent
    params[0] = temp0 - costDeriv(x,y,temp, 0)
    params[1] = temp1 - costDeriv(x,y,temp, 1)

Функция costDeriv вычисляет выражение

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

Обратите внимание, что частная производная по тета1 умножается на x(i). Это связано с тем, что в уравнении гипотезы theta1 умножается на x, поэтому получение производной дает x.

Итак, окончательные уравнения:

Я реализовал costDeriv в Python следующим образом:

def costDeriv(x, y, params, condition):
    theta0 = params[0]
    theta1 = params[1]
    sum = 0
    for i in range(len(x)):
        if(condition == 0):
            sum += (theta1*x[i]+theta0 - y[i])
        else:
            sum += (theta1*x[i]+theta0-y[i])*x[i]
    adjustment = sum*ALPHA/len(x)
    return adjustment

Определение скорости обучения (𝛼): скорость обучения, если она слишком велика, может превышать минимум и расходиться. Если он слишком мал, для сходимости градиентного спуска может потребоваться много времени. Было забавно поиграть со скоростью обучения, чтобы увидеть, насколько хорошо работает каждая из них. Я дам ссылку на статью ЗДЕСЬ⁴, если вам интересно, как выбрать оптимальную скорость обучения.

Определение количества итераций: при определении количества итераций вы хотите, чтобы функция идеально сходилась к двум числам для линейной регрессии. Опять же, я поиграл с количеством итераций, но я дам ссылку на другую статью ЗДЕСЬ⁵ для более подробного объяснения итераций и сходимости линейной регрессии.

Визуализация линейной регрессии с использованием градиентного спуска

На рисунке ниже показан путь, который проходит градиентный спуск от начального предположения параметров ([0,0]) до сходимости за i итераций.

Процитированные работы

[1]: http://www.stat.yale.edu/Courses/1997-98/101/linreg.htm

[2]: Антон Бивенс, Раннее трансцендентальное исчисление Дэвиса (глава 13.3)

[3]: Из курса Эндрю Нг по машинному обучению, предлагаемого Coursera.

[4]: https://machinelearningmastery.com/learning-rate-for-deep-learning-neural-networks/

[5]: https://www.stat.cmu.edu/~ryantibs/convexopt-F13/scribes/lec6.pdf