Обратное распространение ошибки — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Примечания)
м (rollbackEdits.php mass rollback)
 
(не показано 9 промежуточных версий 2 участников)
Строка 87: Строка 87:
 
== Алгоритм ==
 
== Алгоритм ==
  
Пример реализации алгоритма на Python
+
* <math>\eta</math> - скорость обучения
 +
* <math>\alpha</math> - коэффициент инерциальности для сглаживания резких скачков при перемещении по поверхности целевой функции
 +
* <math>\{x_i^d, t^d\}_{i=1,d=1}^{n,m}</math> {{---}} обучающее множество
 +
* <math>\textrm{steps}</math> {{---}} количество повторений
 +
* <math>network(x)</math> {{---}} функция, подающая x на вход сети и возвращающая выходные значения всех ее узлов
 +
* <math>layers</math> {{---}} количество слоев в сети
 +
* <math>layer_i</math> {{---}} множество нейронов в слое i
 +
* <math>output</math> {{---}} множество нейронов в выходном слое
  
  def backward_propagate_error(network, expected):
+
 
    for i in reversed(range(len(network))):
+
  '''fun''' BackPropagation<math>(\eta, \alpha, \{x_i^d, t^d\}_{i=1,d=1}^{n,m}, \textrm{steps})</math>:
          layer = network[i]
+
    '''init''' <math>\{w_{ij}\}_{i,j} </math>
          errors = list()
+
    '''repeat''' <math>\textrm{steps}</math>:
          if i != len(network)-1:
+
        '''for''' <math>d</math> = <math>1</math> to <math>m</math>:
              for j in range(len(layer)):
+
            <math>o</math> = <math>network(\{x_i^d\}) </math>
                     error = 0.0
+
            '''for''' <math>k \in output</math>:
                    for neuron in network[i + 1]:
+
                <math>\delta _k</math> = <math>\sigma'(o_k)(t_k - o_k)</math>
                        error += (neuron['weights'][j] * neuron['delta'])
+
            '''for''' <math>i</math> = <math>layers - 1</math> to <math>1</math>:
                    errors.append(error)
+
                '''for''' <math>j \in layer_i</math>:
          else:
+
                     <math>\delta _j</math> = <math>\sigma'(o_j)\sum_{k \in layer_{i + 1}} \delta _k w_{j,k}</math>
              for j in range(len(layer)):
+
            '''for''' <math>\forall w_{i,j}</math>:
                    neuron = layer[j]
+
                <math>\Delta w_{i,j}^{n}</math> = <math>\alpha \Delta w_{i,j}^{n-1} + ( 1 - \alpha ) \eta \delta _j o_{i}</math>
                    errors.append(expected[j] - neuron['output'])
+
                <math>w_{i,j}^{n}</math> = <math>w_{i,j}^{n-1} + \Delta w_{i,j}^{n}</math>
          for j in range(len(layer)):
+
    '''return''' <math>w_{ij}</math>
              neuron = layer[j]
 
              neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])
 
  
 
== Недостатки алгоритма ==
 
== Недостатки алгоритма ==
Строка 123: Строка 128:
  
 
==См. также==
 
==См. также==
*[[:Нейронные_сети,_перцептрон|Нейронные_сети,_перцептрон]]
+
*[[:Нейронные_сети,_перцептрон|Нейронные сети, перцептрон]]
 
*[[:Стохастический градиентный спуск|Стохастический градиентный спуск]]
 
*[[:Стохастический градиентный спуск|Стохастический градиентный спуск]]
 +
*[[:Настройка_глубокой_сети|Настройка глубокой сети]]
 +
*[[:Практики_реализации_нейронных_сетей|Практики реализации нейронных сетей]]
  
 
== Источники информации ==
 
== Источники информации ==

Текущая версия на 19:32, 4 сентября 2022

Метод обратного распространения ошибок (англ. backpropagation) — метод вычисления градиента, который используется при обновлении весов в нейронной сети.

Обучение как задача оптимизации

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

Изначально веса задаются случайно. Затем, нейрон обучается с помощью тренировочного множества, которое в этом случае состоит из множества троек [math](x_1, x_2, t)[/math] где [math]x_1[/math] и [math]x_2[/math] — это входные данные сети и [math]t[/math] — правильный ответ. Начальная сеть, приняв на вход [math]x_1[/math] и [math]x_2[/math], вычислит ответ [math]y[/math], который вероятно отличается от [math]t[/math]. Общепринятый метод вычисления несоответствия между ожидаемым [math]t[/math] и получившимся [math]y[/math] ответом — квадратичная функция потерь:

[math]E=(t-y)^2, [/math] где [math]E[/math] ошибка.
В качестве примера, обучим сеть на объекте [math](1, 1, 0)[/math], таким образом, значения [math]x_1[/math] и [math]x_2[/math] равны 1, а [math]t[/math] равно 0. Построим график зависимости ошибки [math]E[/math] от действительного ответа [math]y[/math], его результатом будет парабола. Минимум параболы соответствует ответу [math]y[/math], минимизирующему [math]E[/math]. Если тренировочный объект один, минимум касается горизонтальной оси, следовательно ошибка будет нулевая и сеть может выдать ответ [math]y[/math] равный ожидаемому ответу [math]t[/math]. Следовательно, задача преобразования входных значений в выходные может быть сведена к задаче оптимизации, заключающейся в поиске функции, которая даст минимальную ошибку.
График ошибки для нейрона с линейной функцией активации и одним тренировочным объектом

В таком случае, выходное значение нейрона — взвешенная сумма всех его входных значений:

[math]y=x_1w_1 + x_2w_2,[/math]

где [math]w_1[/math] и [math]w_2[/math] — веса на ребрах, соединяющих входные вершины с выходной. Следовательно, ошибка зависит от весов ребер, входящих в нейрон. И именно это нужно менять в процессе обучения. Распространенный алгоритм для поиска набора весов, минимизирующего ошибку — градиентный спуск. Метод обратного распространения ошибки используется для вычисления самого "крутого" направления для спуска.

Дифференцирование для однослойной сети

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

[math]E = \tfrac 1 2 (t - y)^2,[/math] где [math]E[/math] — квадратичная ошибка, [math]t[/math] — требуемый ответ для обучающего образца, [math]y[/math] — действительный ответ сети.

Множитель [math]\textstyle\frac{1}{2}[/math] добавлен чтобы предотвратить возникновение экспоненты во время дифференцирования. На результат это не повлияет, потому что позже выражение будет умножено на произвольную величину скорости обучения (англ. learning rate).

Для каждого нейрона [math]j[/math], его выходное значение [math]o_j[/math] определено как

[math]o_j = \varphi(\text{net}_j) = \varphi\left(\sum_{k=1}^n w_{kj}o_k\right).[/math]

Входные значения [math]\text{net}_j[/math] нейрона — это взвешенная сумма выходных значений [math]o_k[/math] предыдущих нейронов. Если нейрон в первом слое после входного, то [math]o_k[/math] входного слоя — это просто входные значения [math]x_k[/math] сети. Количество входных значений нейрона [math]n[/math]. Переменная [math]w_{kj}[/math] обозначает вес на ребре между нейроном [math]k[/math] предыдущего слоя и нейроном [math]j[/math] текущего слоя.

Функция активации [math]\varphi[/math] нелинейна и дифференцируема. Одна из распространенных функций активации — сигмоида:

[math] \varphi(z) = \frac 1 {1+e^{-z}}[/math]

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

[math] \frac {d \varphi(z)}{d z} = \varphi(z)(1-\varphi(z)) [/math]

Находим производную ошибки

Вычисление частной производной ошибки по весам [math]w_{ij}[/math] выполняется с помощью цепного правила:

[math]\frac{\partial E}{\partial w_{ij}} = \frac{\partial E}{\partial o_j} \frac{\partial o_j}{\partial\text{net}_j} \frac{\partial \text{net}_j}{\partial w_{ij}}[/math]

Только одно слагаемое в [math]\text{net}_j[/math] зависит от [math]w_{ij}[/math], так что

[math]\frac{\partial \text{net}_j}{\partial w_{ij}} = \frac{\partial}{\partial w_{ij}} \left(\sum_{k=1}^n w_{kj} o_k\right) = \frac{\partial}{\partial w_{ij}} w_{ij} o_i= o_i.[/math]

Если нейрон в первом слое после входного, то [math]o_i[/math] — это просто [math]x_i[/math].

Производная выходного значения нейрона [math]j[/math] по его входному значению — это просто частная производная функции активации (предполагается что в качестве функции активации используется сигмоида):

[math]\frac{\partial o_j}{\partial\text{net}_j} = \frac {\partial}{\partial \text{net}_j} \varphi(\text{net}_j) = \varphi(\text{net}_j)(1-\varphi(\text{net}_j))[/math]

По этой причине данный метод требует дифференцируемой функции активации. (Тем не менее, функция ReLU стала достаточно популярной в последнее время, хоть и не дифференцируема в 0)

Первый множитель легко вычислим, если нейрон находится в выходном слое, ведь в таком случае [math]o_j = y[/math] и

[math]\frac{\partial E}{\partial o_j} = \frac{\partial E}{\partial y} = \frac{\partial}{\partial y} \frac{1}{2}(t - y)^2 = y - t [/math]

Тем не менее, если [math]j[/math] произвольный внутренний слой сети, нахождение производной [math]E[/math] по [math]o_j[/math] менее очевидно.

Если рассмотреть [math]E[/math] как функцию, берущую на вход все нейроны [math]L = {u, v, \dots, w}[/math] получающие на вход значение нейрона [math]j[/math],

[math]\frac{\partial E(o_j)}{\partial o_j} = \frac{\partial E(\mathrm{net}_u, \text{net}_v, \dots, \mathrm{net}_w)}{\partial o_j}[/math]

и взять полную производную по [math]o_j[/math], то получим рекурсивное выражение для производной:

[math]\frac{\partial E}{\partial o_j} = \sum_{\ell \in L} \left(\frac{\partial E}{\partial \text{net}_\ell}\frac{\partial \text{net}_\ell}{\partial o_j}\right) = \sum_{\ell \in L} \left(\frac{\partial E}{\partial o_\ell}\frac{\partial o_\ell}{\partial \text{net}_\ell}w_{j\ell}\right)[/math]

Следовательно, производная по [math]o_j[/math] может быть вычислена если все производные по выходным значениям [math]o_\ell[/math] следующего слоя известны.

Если собрать все месте:

[math] \frac{\partial E}{\partial w_{ij}} = \delta_j o_i [/math]

и

[math]\delta_j = \frac{\partial E}{\partial o_j} \frac{\partial o_j}{\partial\text{net}_j} = \begin{cases} (o_j-t_j)o_j(1-o_{j}) & \text{если } j \text{ является нейроном выходного слоя,}\\ (\sum_{\ell\in L} w_{j\ell} \delta_\ell)o_j(1-o_j) & \text{если } j \text{ является нейроном внутреннего слоя.} \end{cases}[/math]

Чтобы обновить вес [math]w_{ij}[/math] используя градиентный спуск, нужно выбрать скорость обучения, [math]\eta \gt 0[/math]. Изменение в весах должно отражать влияние [math]E[/math] на увеличение или уменьшение в [math]w_{ij}[/math]. Если [math]\frac{\partial E}{\partial w_{ij}} \gt 0[/math], увеличение [math]w_{ij}[/math] увеличивает [math]E[/math]; наоборот, если [math]\frac{\partial E}{\partial w_{ij}} \lt 0[/math], увеличение [math]w_{ij}[/math] уменьшает [math]E[/math]. Новый [math]\Delta w_{ij}[/math] добавлен к старым весам, и произведение скорости обучения на градиент, умноженный на [math]-1[/math], гарантирует, что [math]w_{ij}[/math] изменения будут всегда уменьшать [math]E[/math]. Другими словами, в следующем уравнении, [math]- \eta \frac{\partial E}{\partial w_{ij}}[/math] всегда изменяет [math]w_{ij}[/math] в такую сторону, что [math]E[/math] уменьшается:

[math] \Delta w_{ij} = - \eta \frac{\partial E}{\partial w_{ij}} = - \eta \delta_j o_i[/math]

Алгоритм

  • [math]\eta[/math] - скорость обучения
  • [math]\alpha[/math] - коэффициент инерциальности для сглаживания резких скачков при перемещении по поверхности целевой функции
  • [math]\{x_i^d, t^d\}_{i=1,d=1}^{n,m}[/math] — обучающее множество
  • [math]\textrm{steps}[/math] — количество повторений
  • [math]network(x)[/math] — функция, подающая x на вход сети и возвращающая выходные значения всех ее узлов
  • [math]layers[/math] — количество слоев в сети
  • [math]layer_i[/math] — множество нейронов в слое i
  • [math]output[/math] — множество нейронов в выходном слое


fun BackPropagation[math](\eta, \alpha, \{x_i^d, t^d\}_{i=1,d=1}^{n,m}, \textrm{steps})[/math]:
   init [math]\{w_{ij}\}_{i,j} [/math]
   repeat [math]\textrm{steps}[/math]:
       for [math]d[/math] = [math]1[/math] to [math]m[/math]:
           [math]o[/math] = [math]network(\{x_i^d\}) [/math] 
           for [math]k \in output[/math]:
               [math]\delta _k[/math] = [math]\sigma'(o_k)(t_k - o_k)[/math]
           for [math]i[/math] = [math]layers - 1[/math] to [math]1[/math]:
               for [math]j \in layer_i[/math]:
                   [math]\delta _j[/math] = [math]\sigma'(o_j)\sum_{k \in layer_{i + 1}} \delta _k w_{j,k}[/math]
           for [math]\forall w_{i,j}[/math]:
               [math]\Delta w_{i,j}^{n}[/math] = [math]\alpha \Delta w_{i,j}^{n-1} + ( 1 - \alpha ) \eta \delta _j o_{i}[/math]
               [math]w_{i,j}^{n}[/math] = [math]w_{i,j}^{n-1} + \Delta w_{i,j}^{n}[/math]
   return [math]w_{ij}[/math]

Недостатки алгоритма

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

Градиентный спуск может найти локальный минимум вместо глобального

Паралич сети

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

Локальные минимумы

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

Примечания

См. также

Источники информации