Краткое и ясное введение

Что такое градиент?

Начнем с очевидного первого вопроса: что такое градиент? Если вы давно не посещали курс исчисления в третьем семестре (мой случай) или вообще не знакомы с исчислением, я дам краткое объяснение. Если вы уже знакомы с этими концепциями, пожалуйста, пропустите эту статью.

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

Градиенты используются в основном в многомерном исчислении. В случае одномерного исчисления это просто первая производная в выбранной точке. В многомерном исчислении градиент представляет собой вектор производных в каждом направлении (вдоль осей переменных). Поскольку нас интересует только наклон по одной оси и нас не интересуют остальные, эти производные называются частными производными.

Например, градиент функции трех переменных определяется следующим образом:

Если функция:

затем мы берем производную по x, рассматривая другие переменные как константы (где производная константы равна нулю):

а затем производную по y с учетом констант x и z:

а затем производную по z с константами x и y:

Наконец, мы сложим их все вместе, чтобы получить градиент:

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

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

Градиентный спуск работает не для всех функций. Функция затрат (или потерь), которую необходимо минимизировать, должна быть одновременно дифференцируемой и выпуклой.

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

Вот несколько примеров общих дифференцируемых функций:

Вот типичные недифференцируемые функции:

Выпуклая функция — это функция, вторая производная которой всегда больше нуля.

Давайте посмотрим на простой пример:

Первая производная:

Вторая производная:

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

Алгоритм градиентного спуска

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

Уравнение ниже описывает, что делает алгоритм градиентного спуска: b — следующая позиция нашего спуска, а a — текущая позиция. Знак минус относится к части минимизации алгоритма. Гамма в середине — это константа скорости обучения, а градиент (дельта (f (a)) — это направление наибольшего спуска.

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

Алгоритм работает следующим образом:

  1. выбрать отправную точку (инициализацию)
  2. вычислить градиент в этой точке
  3. сделать масштабный шаг в направлении, противоположном градиенту
  4. повторяйте шаги 2 и 3 до тех пор, пока не будет выполнено одно из следующих условий: размер шага меньше допуска или не будет достигнуто максимальное количество итераций.

Выполнение

Ниже приведена реализация градиентного спуска на Python с использованием среднеквадратичной ошибки в качестве функции стоимости со скоростью обучения = 0,0001, max_iterations = 1000 и порогом остановки (допуском) = 1e-6.

# Importing Libraries
import numpy as np
import matplotlib.pyplot as plt

def mean_squared_error(y_true, y_predicted):
 
 # Calculating the loss or cost
 cost = np.sum((y_true-y_predicted)**2) / len(y_true)
 return cost

# Gradient Descent Function
# Here iterations, learning_rate, stopping_threshold
# are hyperparameters that can be tuned
def gradient_descent(x, y, iterations = 1000, learning_rate = 0.0001,
     stopping_threshold = 1e-6):
 
 # Initializing weight, bias, learning rate and iterations
 current_weight = 0.1
 current_bias = 0.01
 iterations = iterations
 learning_rate = learning_rate
 n = float(len(x))
 
 costs = []
 weights = []
 previous_cost = None
 
 # Estimation of optimal parameters
 for i in range(iterations):
  
  # Making predictions
  y_predicted = (current_weight * x) + current_bias
  
  # Calculating the current cost
  current_cost = mean_squared_error(y, y_predicted)

  # If the change in cost is less than or equal to
  # stopping_threshold we stop the gradient descent
  if previous_cost and abs(previous_cost-current_cost)<=stopping_threshold:
   break
  
  previous_cost = current_cost

  costs.append(current_cost)
  weights.append(current_weight)
  
  # Calculating the gradients
  weight_derivative = -(2/n) * sum(x * (y-y_predicted))
  bias_derivative = -(2/n) * sum(y-y_predicted)
  
  # Updating weights and bias
  current_weight = current_weight - (learning_rate * weight_derivative)
  current_bias = current_bias - (learning_rate * bias_derivative)
    
  # Printing the parameters for each 1000th iteration
  print(f"Iteration {i+1}: Cost {current_cost}, Weight \
  {current_weight}, Bias {current_bias}")
 
 
 # Visualizing the weights and cost at for all iterations
 plt.figure(figsize = (8,6))
 plt.plot(weights, costs)
 plt.scatter(weights, costs, marker='o', color='red')
 plt.title("Cost vs Weights")
 plt.ylabel("Cost")
 plt.xlabel("Weight")
 plt.show()
 
 return current_weight, current_bias


def main():
 
 # Data
 X = np.array([32.50234527, 53.42680403, 61.53035803, 47.47563963, 59.81320787,
  55.14218841, 52.21179669, 39.29956669, 48.10504169, 52.55001444,
  45.41973014, 54.35163488, 44.1640495 , 58.16847072, 56.72720806,
  48.95588857, 44.68719623, 60.29732685, 45.61864377, 38.81681754])
 Y = np.array([31.70700585, 68.77759598, 62.5623823 , 71.54663223, 87.23092513,
  78.21151827, 79.64197305, 59.17148932, 75.3312423 , 71.30087989,
  55.16567715, 82.47884676, 62.00892325, 75.39287043, 81.43619216,
  60.72360244, 82.89250373, 97.37989686, 48.84715332, 56.87721319])

 # Estimating weight and bias using gradient descent
 estimated_weight, estimated_bias = gradient_descent(X, Y, iterations=2000)
 print(f"Estimated Weight: {estimated_weight}\nEstimated Bias: {estimated_bias}")

 # Making predictions using estimated parameters
 Y_pred = estimated_weight*X + estimated_bias

 # Plotting the regression line
 plt.figure(figsize = (8,6))
 plt.scatter(X, Y, marker='o', color='red')
 plt.plot([min(X), max(X)], [min(Y_pred), max(Y_pred)], color='blue',markerfacecolor='red',
   markersize=10,linestyle='dashed')
 plt.xlabel("X")
 plt.ylabel("Y")
 plt.show()

 
if __name__=="__main__":
 main()

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

Преимущества

  1. Устойчив к шуму в данных, оценке градиента, домене, если настроен правильно.
  2. Находит либо экстремальную точку, либо седловую точку.
  3. Легко вычислить. Нам не нужен точный градиент.
  4. Он решает самые серьезные проблемы с низкими требованиями к памяти.
  5. Это легко реализовать параллельно.

Недостатки

  1. Любая процедура градиентного спуска может застрять в пределах локального минимума. Эта проблема увеличивается пропорционально размеру поверхности ошибок, и универсального решения не существует.
  2. Плоские плато на поверхности ошибки могут привести к замедлению обучения. Например, при прохождении плоского плато уклон также становится пренебрежимо малым, поскольку спуск, требующий многих дальнейших шагов, вряд ли возможен.
  3. Даже если хорошие минимумы достигнуты, впоследствии их можно оставить. На крутом склоне уклон очень велик, поэтому можно делать большие шаги и, следовательно, можно пропустить хороший минимум.
  4. Крутые каньоны на поверхности ошибок могут вызывать колебания.

Типы градиентного спуска

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

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

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

Некоторыми преимуществами пакетного GD являются его вычислительная эффективность: он обеспечивает стабильный градиент ошибок и стабильную сходимость. С другой стороны, стабильный градиент ошибок иногда может привести к состоянию сходимости, которое не является лучшим достижением модели. Также требуется, чтобы весь набор обучающих данных находился в памяти.

Стохастический градиентный спуск

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

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

Мини-пакетный градиентный спуск

Мини-пакетный GD — это наиболее подходящий метод, поскольку он представляет собой комбинацию концепций SGD и BGD. он просто разбивает набор обучающих данных на небольшие пакеты и выполняет обновление для каждого из этих пакетов. Это создает баланс между надежностью ЦУР и эффективностью BGD.

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