Как учится машина. Революция в области нейронных сетей и глубокого обучения - Ян Лекун
g[0]= (a – h)/dw
g[1]= (b – h)/dw
Вектор g называется градиентом функции стоимости. Он содержит вектор, составленный из наклонов по каждой оси. Данный вектор указывает на направление наибольшего наклона вверх. Используя метафору горного ландшафта, если наклон в направлении восток-запад (w[0]) больше, чем в направлении север-юг (w[1]), направление большего уклона будет ближе от оси восток-запад, чем от оси север-юг.
Вот небольшая функция, которая вычисляет градиент функции с двумя параметрами по возмущению. Эта процедура проста, но неэффективна. Позже мы увидим, как это улучшить:
# функция расчета градиента через помехи
# X: таблица входов набора данных
# Y: таблица желаемых выходов
# W: вектор параметров
# Dw: помеха
def gradient (X, Y, w, dw):
h = L (X, Y, w) # расчет стоимости
wa = [0,0] # создаем вектор wa
wa [0] = w[0] + dw # помеха 1-й координаты
wa [1] = w[1]
a = L (X, Y, wa) # расчет стоимости после помехи
wb = [0,0] # создаем вектор wb
wb [0] = w[0]
wb [1] = w[1] + dw # помеха 2-й координаты
b = L (X, Y, wb) # расчет стоимости после помехи
g = [0,0] # создаем вектор g
g [0] = (a – h) / dw # наклон по 1-й координате
g [1] = (b – h) / dw # наклон по 2-й координате
return g # вернуть вектор градиента
Мы получаем приближение вектора градиента:
g = gradient(X, Y, w, dw)
По определению этот вектор g указывает вверх, но вектор – g, полученный инвертированием знаков его компонентов, указывает вниз в противоположном направлении. Таким образом, мы можем двигаться к дну долины в направлении наибольшего уклона, шагая в направлении, противоположном наклону. Это соответствует шагу размера – e*g[0] в направлении w[0] (восток-запад) и размера -e * g[1] в направлении w[1] (север-юг), где переменная e представляет собой небольшое положительное число, которое определяет размер шага, который необходимо выполнить.
Все это выполняется функцией в нескольких инструкциях:
# сделать шаг градиента
# X: таблица входов набора данных
# Y: таблица желаемых выходов
# w: вектор параметров
# e: без градиента
# dw: помеха
def descend (X, Y, w, e, dw):
g = gradient (X, Y, w, dw) # вычисление вектора
# градиента
w[0] = w[0] – e * g [0] # обновить w[0]
w[1] = w[1] – e * g [1] # обновить w[1]
return w # вернуть новый вектор параметров
Этапы обучения по градиентному спуску состоят из:
1) расчета стоимости;
2) расчета градиента;
3) обновления параметров путем вычитания градиента, умноженного на константу e, шаг градиента.
При повторении данной процедуры и при условии, что шаг градиента достаточно мал, процедура «сходится» ко дну долины.
# процедура обучения
# n: количество итераций градиентного спуска
def learn (X, Y, w, e, dw, n):
for i in range (n): # повторить n раз
w = descend (X, Y, w, e, dw) # сделать шаг
print (L (X, Y, w)) # вывести стоимость
return w # вернуть вектор веса
Можно сделать так, чтобы выполнение функции остановилась само по себе, когда стоимость перестанет уменьшаться.
Градиентный спуск на практике
В действительности «ландшафт» имеет не только два измерения – их могут быть миллионы, а иногда и миллиарды. В нашем примере вектор параметров состоял из двух чисел, но в практических задачах их больше на много порядков. Градиент также имеет миллионы компонентов, каждый из которых указывает наклон функции стоимости обучения в каждой из осей пространства.
В этих условиях вычисление градиента по помехам неэффективно. Изменения каждого параметра и соответствующие вычисления стоимости обучения занимают слишком много времени.
Чтобы продемонстрировать это, я предлагаю вам программу, которая делает градиентный расчет помех в любом измерении:
def градиент (X, Y, w, dw):
h = L(X, Y, w)
for i in range (len(w)): # цикл по размерам
wa = w
wa[i] = w[i] + dw
a = L(X, Y, wa)
g[i] = (a – h)/dw
return g
Данная функция должна пересчитывать значение L после каждой помехи. Если бы у нас было 10 млн параметров, нам пришлось бы пересчитывать L 10 млн раз … Это просто нереально.
Гораздо более эффективный способ вычисления градиента без создания помех – это аналитический метод вычисления градиента. Он сводится к вычислению производных стоимости в направлении каждой из осей без помех.
Возьмем простой многочлен второй степени:
c(x, y, w) = (y – w * x) ** 2
Производная этого многочлена по отношению к w, учитывая, что х и у являются константами, является прямой:
dc_dw(x, y, w) = –2 * (y – w * x) * x = 2 *(x ** 2) * w – 2 * y * x
Не нужно изменять w, чтобы узнать наклон c(x, y, w) в любой точке: он задается его производной.
Теперь давайте возьмем двумерную линейную модель, функция входа-выхода которой:
f(x, w) = w[0] * x[0] + w[1] + x[1]
и рассмотрим квадратичную функцию стоимости (т. е. функцию с квадратом):
C(x, y, w) = (y – f(x, w)) ** 2 = (y – (w[0] * x[0] + w[1] * x[1])) ** 2
Мы можем вычислить производную этой функции по w[0], считая другие символы константами.
Данная производная будет отмечена: dc_dw[0]
dc_dw[0] = –2 * (y – (w[0] * x[0] + w[1] * x[1])) * x[0]
Мы можем сделать то же самое для производной по отношению к w[1], которая является вычислением производной.
dc_dw[1] = –2 * (y – (w[0] * x[0] + w[1] * x[1])) * x[1]
Вектор, компонентами которого являются эти два значения, будет градиентом C(x, y, w) по отношению к w. Это вектор того же размера, что и вектор параметров, каждый компонент которого содержит производную по соответствующему параметру, то есть наклон функции при движении в измерении рассматриваемого параметра.
dc_dw = [dc_dw[0], [dc_dw[1]]
Производная функции нескольких переменных по одной из