Formule de rétropropagation du réseau neuronal multicouche (utilisant la descente de gradient stochastique)
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):
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)$$
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_rate
paramètre a été réglé trop bas . Voici la raison: lors de l'utilisation de code (2), le paramètredelta
croî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
delta
paramètre plus grand , et cela conduit, dans certains cas, à une convergence apparemment plus rapide.
Maintenant résolu!