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

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

Здравствуйте и вновь добро пожаловать на занятия по машинному обучению без учителя и скрытым моделям Маркова на языке Python.

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

Суть в следующем. Пусть у вас есть функция, минимум которой вы хотите найти, и пусть вам нужно найти такие входные данные, при которых функция была бы в минимуме. Как правило, мы хотим минимизировать функцию затрат или ошибок. Может потребоваться также и найти максимумы – например, когда мы ищем максимум для функции правдоподобности некоторого распределения вероятностей. Всё, что нужно сделать – это просто поменять местами стороны. Чтобы объяснить это, рассмотрим очень простой одномерный пример. Как правило, в машинном обучении мы используем размерности, гораздо большие единицы, но этот пример позволит наглядно увидеть суть.

Итак, пусть у нас есть простая функция J=w2. Мы знаем, что минимум функции при w=0, но, предположим, мы этого не знаем. Наш весовой коэффициент установим случайным образом. Предположим, w=20. Мы знаем, что производная dJ/dw равна 2w. Установим коэффициент обучения равным 0,1. В первом приближении мы имеем:

w – 0,1*2w = 20 – 0,1*40 = 16.

Поэтому установим w=16. Во втором приближении

w – 0,1*2w = 16 – 0,1*2*16 = 12,8.

Это даёт нам новое значение w=12,8. В третьм приближении

w – 0,1*2w = 12,8 – 0,1*2*12,8 = 10,24.

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

Теперь давайте попробуем реализовать это в коде и посмотрим, сможем ли мы полностью дойти до нуля. Импортируем библиотеку NumPy, установим значение w=20 и будем печатать результат на каждом шаге итераций. Количество приближений установим равным 30.

import numpy as np

w = 20

for i in xrange(30):

w = w – 0.1*2*w

print w

Как видим, w достигает значения 0,02, так что, похоже, 30 приближений недостаточно. Попробуем 100 приближений.

w = 20

for i in xrange(100):

w = w – 0.1*2*w

print w

Теперь результат равен 4,07*10-9 – это очень близко к нулю.

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

Почему этот метод так важен?

По мере продвижения далее в глубокое обучение и машинное обучение функции будут становиться всё более сложными. Для нейронных сетей с softmax нахождение производной может занять у вас несколько часов или даже дней. При переходе к свёрточным и возвратным нейронным сетям градиенты, конечно, можно найти на бумаге, но нет никакого желания тратить на это время. Куда лучше потратить время на проверку различных архитектур и параметров, не заботясь о градиентах. Тем более, что для вычисления градиентов можно использовать специальные библиотеки, такие как Theano и TensorFlow.

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

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

J (w_1, \;w_2) = w_{1}^^{2} + w_{2}^^{4}.

Помощь по производной функции софтмакс

В этой лекции я дам вам подсказку по выводу формул алгоритма обратного распространения ошибок. Самой сложной частью алгоритма является место с функцией софтмакс – нахождение собственно производной функции софтмакс и подстановку её в общую производную согласно цепного правила. Цель этого курса – побудить вас думать самостоятельно, поэтому данное видео вам следует смотреть только после того, как вы потратили как минимум несколько дней, пытаясь решить эту задачу. Заметьте, для данной задачи не нужны никаких новые знания, кроме дифференциального счисления. Единственная проблема, с которой вы, вероятнее всего, столкнётесь, заключается в том, что надо быть очень и очень осторожными, поскольку сделать ошибку крайне легко. Но, вновь-таки, никаких новых знаний тут не требуется, за исключением умения находить производную.

Начнём с определения функции софтмакс:

y(k) = \frac {e^{a(k)}} {\sum^{K}_{j=1} e^{a(j)}}.

Сложность здесь заключается в том, что нам нужно взять производную относительно a(k) и относительно a(k’), где k k. Это даст два различных результата, поскольку если брать производную относительно a(k), то и числитель, и знаменатель являются функциями переменной, тогда как если брать производную относительно a(k’), где k k, то дифференцируемая переменная появляется только в знаменателе.

Сначала найдём производную по a(k). Во-первых, надо использовать правило умножения:

\frac {dy(k)}{da(k)} = \frac {d(e^{a(k)})}{da(k)} \frac {1}{\sum^{K}_{j=1} \;e^{a(j)}} + \frac {d[\sum^{K}_{j=1} \;e^{a(j)}]^{-1}}{da(k)}.

После вычисления отдельных производных мы получим:

\frac {dy(k)}{da(k)} = \frac {e^{a(k)}}{\sum^{K}_{j=1} \;e^{a(j)}} -[\sum^{K}_{j=1} \;e^{a(j)}]^{-2} e^{a(k)} e^{a(k)}.

Таким образом, можно видеть, что

\frac {dy(k)}{da(k)} = y(k) - y(k)^2 = y(k) (1-y(k)).

Теперь давайте найдём производную по a(k’), где k k:

\frac {dy(k)}{da(k')} = e^{a(k)} \frac {d[\sum^{K}_{j=1} \;e^{a(j)}]^{-1}}{da(k')}.

Как видно, нам надо взять производную лишь в знаменателе, поскольку числитель является константой относительно a(k’). Получим:

\frac {dy(k)}{da(k')} = e^{a(k)} (-1) [\sum^{K}_{j=1}e^{a(j)}]^{-2} \;\; e^{a(k')}.

Перепишем выражение следующим образом:

\frac {dy(k)}{da(k')} = \frac {e^{a(k)}}{\sum^{K}_{j=1} e^{a(j)}} \; \frac {e^{a(k)}}{\sum^{K}_{j=1} e^{a(j)}} = -y(k) y(k').

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

\frac {dy(k)}{da(k')}= y(k) (\delta (k, k') - y (k')),

\frac {dy(k)}{da(k')}= y(k') (\delta (k, k') - y (k)).

Обратите внимание, что оба эти выражения эквивалентны, но лишь одно из них может пригодиться в выводе метода обратного распространения ошибок. В методе обратного распространения ошибок мы суммируем по k, но берём производную по k, поэтому получится следующее:

\sum^{K}_{k'=1} t_n (k') \frac {1} {y_n(k')} \; y_n(k') (\delta(k, k') - y(k)).

yn(k’) сокращается, в результате чего получаем:

\sum^{K}_{k'=1} t_n (k') (\delta(k, k') - y_n(k)).

Я всегда приходил в тупик от вопроса, как же мы получаем конечный результат

t_n(k) - y(k).

и куда деваются k и сумма?

Во-первых рассмотрим суммы для обоих членов отдельно. Мы немедленно увидим, что первая сумма сокращается, поскольку дельта-функция равна единице, только когда k = k:

\sum^{K}_{k'=1} t_n (k')(\delta(k, k') - \sum^{K}_{k'=1} t_n (k') y_n (k),

 t_n (k) - \sum^{K}_{k'=1} t_n (k') y_n (k).

Фокус в том, что yn(k) не зависит от k, а потому может быть вынесено за сумму:

t_n(k) - y_n (k) \sum^{K}_{k'=1} t_n (k').

И наконец, поскольку лишь одна целевая переменная из всех классов равна 1, а все остальные нули, то и сумма по всем классам равна 1. Отсюда мы получаем наше окончательное выражение:

t_n(k) - y(k).

Спасибо, что потратили время на изучение курса по Глубокому обучению в Python, часть 1.

Продолжим изучать данную тематику в следующих частях!

Андрей Никитенко
Андрей Никитенко
Задать вопрос эксперту
Понравилась статья? Поделить с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

Share via
Copy link