Formule de rétropropagation du réseau neuronal multicouche (utilisant la descente de gradient stochastique)


Basj

Utilisation des notations du calcul de rétropropagation | Deep learning, chapitre 4 , j'ai ce code de rétro-propagation pour un réseau neuronal à 4 couches (c'est-à-dire 2 couches cachées):

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) 

Cela fonctionne, mais:

  • la précision à la fin (pour mon cas d'utilisation: la reconnaissance de chiffres MNIST) est tout simplement correcte, mais pas très bonne. C'est bien mieux (c'est-à-dire que la convergence est bien meilleure) lorsque la ligne (1) est remplacée par :

    delta = np.dot(self.weights[k].T, delta)  # (2)
    
  • le code de Machine Learning with Python: Training and Testing the Neural Network with MNIST data set suggère également:

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

    au lieu de:

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

    (Avec les notations de cet article, c'est:

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

    )

Ces 2 arguments semblent concordants: le code (2) est meilleur que le code (1).

Cependant, les maths semblent montrer le contraire (voir la vidéo ici ; autre détail: notez que ma fonction de perte est multipliée par 1/2 alors que ce n'est pas sur la vidéo):

entrez la description de l'image ici

Question: lequel est correct: l'implémentation (1) ou (2)?


Dans LaTeX:

$$C = \frac{1}{2} (a^L - y)^2$$
$$a^L = \sigma(\underbrace{w^L a^{L-1} + b^L}_{z^L}) = \sigma(z^L)$$
$$\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)$$
$$\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)$$
Basj

J'ai passé deux jours à analyser ce problème, j'ai rempli quelques pages de cahier de calculs de dérivées partielles ... et je peux confirmer:

  • les mathématiques écrites en LaTeX dans la question sont correctes
  • le code (1) est le bon , et il est en accord avec les calculs mathématiques:

    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) 
    
  • le code (2) est faux:

    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) 
    

    et il y a une légère erreur dans l' apprentissage automatique avec Python: formation et test du réseau neuronal avec l'ensemble de données MNIST :

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

    devrait être

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

Maintenant, la partie difficile qui m'a pris des jours à réaliser:

  • Apparemment, le code (2) a une bien meilleure convergence que le code (1), c'est pourquoi je me trompe en pensant que le code (2) était correct et que le code (1) était faux

  • ... Mais en fait, ce n'est qu'une coïncidence car le learning_rateparamètre a été réglé trop bas . Voici la raison: lors de l'utilisation de code (2), le paramètre deltacroît beaucoup plus vite ( print np.linalg.norm(delta)aide à voir cela) qu'avec le code (1).

  • Ainsi "code incorrect (2)" vient de compenser le "taux d'apprentissage trop lent" en ayant un deltaparamètre plus grand , et cela conduit, dans certains cas, à une convergence apparemment plus rapide.

Maintenant résolu!

Articles connexes