Формула обратного распространения многослойной нейронной сети (с использованием стохастического градиентного спуска) - PullRequest
0 голосов
/ 13 ноября 2018

Использование обозначений Исчисление обратного распространения |Глубокое изучение, глава 4 , у меня есть этот код обратного распространения для 4-уровневой (т.е. 2 скрытых слоя) нейронной сети:

def sigmoid_prime(z): 
    return z * (1-z)  # because σ'(x) = σ(x) (1 - σ(x))

def train(self, input_vector, target_vector):
    a = np.array(input_vector, ndmin=2).T
    y = np.array(target_vector, ndmin=2).T

    # forward
    A = [a]  
    for k in range(3):
        a = sigmoid(np.dot(self.weights[k], a))  # zero bias here just for simplicity
        A.append(a)

    # Now A has 4 elements: the input vector + the 3 outputs vectors

    # back-propagation
    delta = a - y
    for k in [2, 1, 0]:
        tmp = delta * sigmoid_prime(A[k+1])
        delta = np.dot(self.weights[k].T, tmp)  # (1)  <---- HERE
        self.weights[k] -= self.learning_rate * np.dot(tmp, A[k].T) 

Это работает, но:

  • точность в конце (для моего случая использования: распознавание цифр MNIST) просто в порядке, но не очень хорошая. Намного лучше (т.е. сходимость намного лучше), когда строка (1) заменяется на :

    delta = np.dot(self.weights[k].T, delta)  # (2)
    
  • код из MachineОбучение на Python: обучение и тестирование нейронной сети с набором данных MNIST также предлагает:

    delta = np.dot(self.weights[k].T, delta)
    

    вместо:

    delta = np.dot(self.weights[k].T, tmp)
    

    (С помощью обозначений этой статьи этоis:

    output_errors = np.dot(self.weights_matrices[layer_index-1].T, output_errors)
    

    )

Эти 2 аргумента кажутся согласованными: код (2) лучше, чем код (1).

Однако математика, кажется, показывает обратное (см. видео здесь ; еще одна деталь: обратите внимание, что моя функция потерь умножается на 1/2, тогда как ее нет на видео):

enter image description here

Вопрос: какой из них правильный: реализация (1) или (2)?


В LaTeX:

$$\frac{\partial{C}}{\partial{w^{L-1}}} = \frac{\partial{z^{L-1}}}{\partial{w^{L-1}}} \frac{\partial{a^{L-1}}}{\partial{z^{L-1}}} \frac{\partial{C}}{\partial{a^{L-1}}}=a^{L-2} \sigma'(z^{L-1}) \times w^L \sigma'(z^L)(a^L-y) $$
$$\frac{\partial{C}}{\partial{w^L}} = \frac{\partial{z^L}}{\partial{w^L}} \frac{\partial{a^L}}{\partial{z^L}} \frac{\partial{C}}{\partial{a^L}}=a^{L-1} \sigma'(z^L)(a^L-y)$$
$$\frac{\partial{C}}{\partial{a^{L-1}}} = \frac{\partial{z^L}}{\partial{a^{L-1}}} \frac{\partial{a^L}}{\partial{z^L}} \frac{\partial{C}}{\partial{a^L}}=w^L \sigma'(z^L)(a^L-y)$$

1 Ответ

0 голосов
/ 15 ноября 2018

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

  • математика, написанная на LaTeX в вопросе верны
  • правильный код (1) , и он согласуется с математическими вычислениями:

    delta = a - y
    for k in [2, 1, 0]:
        tmp = delta * sigmoid_prime(A[k+1])
        delta = np.dot(self.weights[k].T, tmp)
        self.weights[k] -= self.learning_rate * np.dot(tmp, A[k].T) 
    
  • код (2) неверен:

    delta = a - y
    for k in [2, 1, 0]:
        tmp = delta * sigmoid_prime(A[k+1])
        delta = np.dot(self.weights[k].T, delta)  # WRONG HERE
        self.weights[k] -= self.learning_rate * np.dot(tmp, A[k].T) 
    

    и есть небольшая ошибка в Машинное обучение с Python: обучение и тестирование нейронной сети с набором данных MNIST:

    output_errors = np.dot(self.weights_matrices[layer_index-1].T, output_errors)
    

    должно быть

    output_errors = np.dot(self.weights_matrices[layer_index-1].T, output_errors * out_vector * (1.0 - out_vector))
    

Теперь сложная часть, на которую у меня ушло несколько дней, чтобы осознать:

  • Очевидно, что код (2) имеет гораздо лучшую сходимость, чем код (1), поэтому я ввел в заблуждение, что код (2) был верным, а код (1) неправильным

  • ... Но на самом деле это просто совпадение , потому что learning_rate было установлено слишком низко .Вот причина: при использовании кода (2) параметр delta растет намного быстрее (это помогает увидеть print np.linalg.norm(delta)), чем с кодом (1).

  • Таким образом, «неправильный код (2)» просто компенсирует «слишком медленную скорость обучения», имея больший параметр delta, и в некоторых случаях это приводит к явно более быстромуконвергенция.

Теперь решено!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...