Dans le
dernier chapitre, nous avons vu comment les réseaux de neurones peuvent apprendre indépendamment les poids et les décalages en utilisant l'algorithme de descente de gradient. Cependant, il y avait une lacune dans notre explication: nous n'avons pas discuté du calcul du gradient de la fonction de coût. Et c'est un écart décent! Dans ce chapitre, je présenterai un algorithme rapide pour calculer de tels gradients, connu sous le nom de rétropropagation.
L'algorithme de rétropropagation a été inventé pour la première fois dans les années 1970, mais son importance n'a pas été pleinement comprise avant le célèbre travail de 1986, écrit par David Rumelhart, Joffrey Hinton et Ronald Williams. L'article décrit plusieurs réseaux de neurones dans lesquels la rétropropagation fonctionne beaucoup plus rapidement que dans les approches antérieures de l'apprentissage, c'est pourquoi il a depuis été possible d'utiliser un réseau de neurones pour résoudre des problèmes auparavant insolubles. Aujourd'hui, l'algorithme de rétropropagation est le cheval de bataille pour l'apprentissage d'un réseau neuronal.
Ce chapitre contient plus de mathématiques que tout le monde dans le livre. Si vous n'aimez pas particulièrement les mathématiques, vous pourriez être tenté de sauter ce chapitre et de simplement traiter la distribution en arrière comme une boîte noire, dont vous êtes prêt à ignorer les détails. Pourquoi perdre du temps à les étudier?
La raison, bien sûr, est la compréhension. La rétropropagation est basée sur l'expression de la dérivée partielle ∂C / ∂w de la fonction de coût C par rapport au poids w (ou biais b) du réseau. L'expression indique la vitesse à laquelle la valeur change lorsque les pondérations et les décalages changent. Et bien que cette expression soit assez complexe, elle a sa propre beauté, car chacun de ses éléments a une interprétation naturelle et intuitive. Par conséquent, la rétropropagation n'est pas seulement un algorithme d'apprentissage rapide. Il nous donne une compréhension détaillée de la façon dont la modification des poids et des décalages modifie tous les comportements du réseau. Et cela vaut la peine d'étudier les détails.
Compte tenu de tout cela, si vous voulez simplement parcourir ce chapitre ou passer au suivant, ça va. J'ai écrit le reste du livre pour qu'il soit compréhensible, même si l'on considère la distribution inverse comme une boîte noire. Bien sûr, plus tard dans le livre, il y aura des moments à partir desquels je ferai référence aux résultats de ce chapitre. Mais à ce moment, vous devez comprendre les conclusions de base, même si vous n'avez pas suivi tous les arguments.
Pour l'échauffement: une approche matricielle rapide pour calculer la sortie d'un réseau neuronal
Avant de discuter de la rétropropagation, obtenons un algorithme matriciel rapide pour calculer la sortie d'un réseau neuronal. Nous avons en fait déjà rencontré cet algorithme à la fin du chapitre précédent, mais je l'ai décrit rapidement, il vaut donc la peine de l'examiner plus en détail. En particulier, ce sera un bon moyen de s'adapter au disque utilisé en back distribution, mais dans un contexte familier.
Commençons par un record qui nous permet d'indiquer clairement les poids sur le net. Nous utiliserons w
l jk pour désigner le poids de connexion du neurone n ° k dans la couche n ° (l-1) avec le neurone n ° j dans la couche n ° l. Ainsi, par exemple, le diagramme ci-dessous montre le poids de connexion du quatrième neurone de la deuxième couche avec le deuxième neurone de la troisième couche:

Au début, un tel enregistrement semble gênant et nécessite une certaine habitude. Cependant, cela vous semblera bientôt simple et naturel. L'une de ses caractéristiques est l'ordre des indices j et k. Vous pourriez décider qu'il serait plus raisonnable d'utiliser j pour désigner le neurone d'entrée, et k - pour le neurone de sortie, et non l'inverse, comme nous l'avons fait. Je vais expliquer la raison de cette fonctionnalité ci-dessous.
Nous utiliserons des notations similaires pour les compensations et les activations de réseau. En particulier, b
l j désignera le déplacement du neurone n ° j dans la couche n ° l. a
l j désignera l'activation du neurone n ° j dans la couche n ° l. Le diagramme suivant montre des exemples d'utilisation de cette entrée:

Avec un tel enregistrement, l'activation d'un
lj du neurone n °
j dans la couche n ° 1 est associée à l'activation dans la couche n ° (l-1) par l'équation suivante (comparer avec l'équation (4) et sa discussion dans le chapitre précédent):
alj= sigma( sumkwljkal−1k+blj) tag23
où la somme passe par tous les neurones k de la couche (l-1). Pour réécrire cette expression sous forme matricielle, nous définissons une matrice de pondération w
l pour chaque couche l. Les éléments de la matrice de poids sont simplement les poids connectés à la couche n ° l, c'est-à-dire que l'élément de la ligne n ° j et de la colonne n ° k sera w
l jk . De même, pour chaque couche l, nous définissons le vecteur de déplacement b
l . Vous avez probablement deviné comment cela fonctionne - les composantes du vecteur de déplacement seront simplement les valeurs b
l j , une composante pour chaque neurone de la couche n ° l. Et enfin, nous définissons le vecteur d'activation a
l , dont les composants sont l'activation a
l j .
Le dernier ingrédient nécessaire pour réécrire (23) est la forme matricielle de la vectorisation de la fonction σ. Nous avons rencontré par hasard la vectorisation dans le dernier chapitre - l'idée est que nous voulons appliquer la fonction σ à chaque élément du vecteur v. Nous utilisons la notation évidente σ (v) pour désigner l'application élément par élément de la fonction. Autrement dit, les composantes σ (v) sont simplement σ (v)
j = σ (v
j ). Par exemple, si nous avons une fonction f (x) = x
2 , alors la forme vectorisée f nous donne
f( beginbmatrix23 endbmatrix)= beginbmatrixf(2)f(3) endbmatrix= beginbmatrix49 endbmatrix tag24
c'est-à-dire qu'un f vectorisé met simplement au carré chaque élément du vecteur.
Étant donné toutes ces formes d'écriture, l'équation (23) peut être réécrite sous une forme vectorisée belle et compacte:
al= sigma(wlal−1+bl) tag25
Une telle expression nous permet de jeter un regard plus global sur la relation entre les activations d'une couche et les activations de la précédente: nous appliquons simplement la matrice de poids aux activations, ajoutons le vecteur de déplacement, puis appliquons le sigmoïde. Soit dit en passant, c'est cet enregistrement qui nécessite l'utilisation de l'enregistrement w
l jk . Si nous utilisions l'indice j pour désigner le neurone d'entrée, et k pour le neurone de sortie, il nous faudrait remplacer la matrice de pondération dans l'équation (25) par la matrice transposée. Il s'agit d'un changement modeste mais ennuyeux, et nous perdrions la simplicité de l'énoncé (et de la réflexion) sur «l'application de la matrice de poids aux activations». Une telle approche globale est plus simple et plus concise (et utilise moins d'index!) Que celle des poneurones. C'est juste un moyen d'éviter l'enfer des index sans perdre la précision de ce qui se passe. Cette expression est également utile dans la pratique, car la plupart des bibliothèques de matrices offrent des moyens rapides de multiplier les matrices, d'ajouter des vecteurs et de vectoriser. Le code du dernier chapitre utilise directement cette expression pour calculer le comportement du réseau.
En utilisant l'équation (25) pour calculer a
l , nous calculons la valeur intermédiaire z
l ≡ w
l a
l - 1 + b
l . Cette valeur est assez utile pour nommer: nous appelons z
l l' entrée pondérée des neurones de la couche n ° l. Plus tard, nous utiliserons activement cette entrée pondérée. L'équation (25) est parfois écrite par une entrée pondérée, comme a = σ (z
l ). Il convient également de noter que z
l a des composants
zlj= sumkwljkal−1k+blj c'est-à-dire que z
l j n'est qu'une entrée pondérée de la fonction d'activation du neurone j dans la couche l.
Deux hypothèses essentielles sur la fonction de coût
Le but de la rétropropagation est de calculer les dérivées partielles ∂C / ∂w et ∂C / ∂b de la fonction de coût C pour chaque poids w et le biais b du réseau. Pour que la rétropropagation fonctionne, nous devons faire deux hypothèses principales sur la forme de la fonction de coût. Cependant, avant cela, il serait utile d'imaginer un exemple de fonction de coût. Nous utilisons la fonction quadratique du chapitre précédent (équation (6)). Dans l'entrée de la section précédente, il ressemblera à
C= frac12n sumx||y(x)−aL(x)||2 tag26
où: n est le nombre total d'exemples de formation; la somme vaut pour tous les exemples x; y = y (x) est la sortie requise; L désigne le nombre de couches dans le réseau; a
L = a
L (x) est le vecteur de sortie des activations de réseau lorsque x est à l'entrée.
D'accord, de quel type d'hypothèses avons-nous besoin au sujet de la fonction de coût C pour appliquer la rétropropagation? Premièrement, la fonction de coût peut être écrite comme la moyenne C = 1 / n ∑
x C
x de la fonction de coût C
x pour des exemples de formation individuels x. Cela se fait dans le cas d'une fonction de coût quadratique, où le coût d'un exemple de formation est C
x = 1/2 || y - a
L ||
2 . Cette hypothèse sera vraie pour toutes les autres fonctions de coût que nous rencontrerons dans le livre.
Nous avons besoin de cette hypothèse car en fait, la rétropropagation nous permet de calculer les dérivées partielles ∂C / ∂w et ∂C / ∂b, en faisant la moyenne sur les exemples d'apprentissage. En acceptant cette hypothèse, nous supposons que l'exemple d'apprentissage x est fixe, et cessons de spécifier l'index x, en écrivant la valeur de C
x comme C. Ensuite, nous renverrons x, mais pour l'instant, il vaut mieux simplement le dire.
La deuxième hypothèse concernant la fonction de coût - elle peut être écrite en fonction de la sortie d'un réseau neuronal:

Par exemple, la fonction de coût quadratique satisfait cette exigence, car le coût quadratique d'un exemple de formation x peut être écrit comme
C=1/2||y−aL||2=1/2 sumj(yj−aLj)2 tag27
qui sera la fonction des activations de sortie. Bien sûr, cette fonction de coût dépend également de la sortie souhaitée y, et vous vous demandez peut-être pourquoi nous ne considérons pas C comme une fonction également de y. Cependant, rappelez-vous que l'exemple d'apprentissage d'entrée x est fixe, donc la sortie y est également fixe. En particulier, nous ne pouvons pas le changer en changeant les poids et les déplacements, c'est-à-dire que ce n'est pas ce que le réseau neuronal apprend. Par conséquent, il est logique de considérer C comme une fonction des seules activations de sortie a
L , et y comme juste un paramètre qui aide à le déterminer.
L'œuvre de Hadamard s⊙t
L'algorithme de rétropropagation est basé sur les opérations habituelles de l'algèbre linéaire - addition de vecteurs, multiplication d'un vecteur par une matrice, etc. Cependant, l'une des opérations est utilisée moins fréquemment. Supposons que s et t soient deux vecteurs de même dimension. Ensuite, par s⊙t, nous désignons la multiplication élément par élément de deux vecteurs. Alors les composants s⊙t sont simplement (s⊙t)
j = s
j t
j . Par exemple:
beginbmatrix12 endbmatrix odot beginbmatrix34 endbmatrix= beginbmatrix1∗32∗4 endbmatrix= beginbmatrix38 endbmatrix tag28
Un tel travail par morceaux est parfois appelé le
travail de Hadamard ou le travail de Schur. Nous l'appellerons l'œuvre d'Hadamard. De bonnes bibliothèques pour travailler avec des matrices ont généralement une implémentation rapide du produit Hadamard, et cela peut être pratique lors de l'implémentation de la rétropropagation.
Quatre équations fondamentales pour la rétropropagation
La rétropropagation implique de comprendre comment la modification des pondérations et des décalages du réseau modifie la fonction de coût. Essentiellement, cela signifie calculer les dérivées partielles ∂C / ∂w
l jk et ∂C / ∂b
l j . Mais pour leur calcul, nous calculons d'abord la valeur intermédiaire δ
l j , que nous appelons l'erreur du neurone n ° j dans la couche n ° l. La rétropropagation nous donnera une procédure pour calculer l'erreur δ
l j , puis associera δ
l j à ∂C / ∂w
l jk et ∂C / ∂b
l j .
Pour comprendre comment une erreur est déterminée, imaginez qu'un démon a démarré dans notre réseau neuronal:

Il est assis sur le neurone n ° j dans la couche n ° l. Dès réception des données d'entrée, le démon perturbe le neurone. Il ajoute un petit changement de Δz
l j à l'entrée pondérée du neurone, et au lieu de produire σ (z
l j ), le neurone produira σ (z
l j + Δz
l j ). Ce changement se propagera également à travers les couches suivantes du réseau, ce qui modifiera finalement le coût total de (∂C / ∂z
l j ) * Δz
l j .
Mais notre démon est bon, et il essaie de vous aider à améliorer le coût, c'est-à-dire à trouver Δz
l j qui réduit le coût. Supposons que la valeur ∂C / ∂z
l j soit grande (positive ou négative). Le démon peut alors sérieusement réduire le coût en choisissant Δz
l j avec le signe opposé à ∂C / ∂z
l j . Mais si ∂C / ∂z
l j est proche de zéro, le démon ne peut pas améliorer considérablement le coût en modifiant l'entrée pondérée z
l j . Donc, du point de vue du démon, le neurone est déjà proche de l'optimum (ce qui, bien sûr, n'est vrai que pour les petits Δz
l j . Supposons que ce soient les limites des actions du démon). Par conséquent, au sens heuristique, ∂C / ∂z
l j est une mesure de l'erreur neuronale.
Sous la motivation de cette histoire, nous définissons l'erreur δ
l j du neurone j dans la couche l, comme
deltalj equiv frac partialC partialzlj tag29
Par notre convention habituelle, nous utilisons δ
l pour désigner le vecteur d'erreur associé à la couche l. La rétropropagation nous donnera un moyen de calculer δ
l pour n'importe quelle couche, puis de corréler ces erreurs avec les quantités qui nous intéressent vraiment, ∂C / ∂w
l jk et ∂C / ∂b
l j .
Vous vous demandez peut-être pourquoi le démon modifie l'entrée pondérée z
l j . Après tout, il serait plus naturel d'imaginer que le démon modifie l'activation de sortie a
l j de sorte que nous utilisons ∂C / ∂a
l j comme mesure d'erreur. En fait, si vous le faites, alors tout se révèle très similaire à ce que nous discuterons plus loin. Cependant, dans ce cas, la représentation de la rétropropagation sera algébriquement un peu plus compliquée. Par conséquent, nous nous attardons sur la variante δ
l j = ∂C / ∂z
l j comme mesure d'erreur.
Dans les problèmes de classification, le terme «erreur» signifie parfois le nombre de classifications incorrectes. Par exemple, si un réseau de neurones classe correctement 96,0% des chiffres, l'erreur sera de 4,0%. Évidemment, ce n'est pas du tout ce que nous entendons par les vecteurs δ. Mais dans la pratique, vous pouvez généralement facilement comprendre ce que signifie le sens.
Plan d'attaque : la rétropropagation est basée sur quatre équations fondamentales. Ensemble, ils nous permettent de calculer à la fois l'erreur δ
l et le gradient de la fonction de coût. Je les donne ci-dessous. Pas besoin d'attendre leur développement instantané. Vous serez déçu. Les équations de rétropropagation sont si profondes qu'une bonne compréhension demande du temps et de la patience tangibles, et un approfondissement progressif de la question. La bonne nouvelle est que cette patience portera ses fruits. Par conséquent, dans cette section, notre raisonnement ne fait que commencer, vous aidant à suivre le chemin d'une compréhension approfondie des équations.
Voici un schéma de la façon dont nous approfondirons ces équations plus tard: j'en donnerai une brève preuve pour aider à expliquer pourquoi elles sont vraies; nous les réécrirons sous une forme algorithmique sous forme de pseudocode, et verrons comment l'implémenter en vrai code python; dans la dernière partie du chapitre, nous développerons une idée intuitive de la signification des équations de rétropropagation, et comment elles peuvent être trouvées à partir de zéro. Nous reviendrons périodiquement sur les quatre équations fondamentales, et plus vous les comprendrez, plus elles vous sembleront confortables et peut-être belles et naturelles.
L'équation de l'erreur de la couche de sortie, δ L : les composantes de δ
L sont considérées comme
deltaLj= frac partialC partialaLj sigma′(zLj) tagBP1
Expression très naturelle. Le premier terme à droite, ∂C / ∂a
L j , mesure la vitesse à laquelle le coût change en fonction de l'activation de la sortie n ° j. Si, par exemple, C ne dépend pas particulièrement du neurone de sortie particulier j, alors δ
L j sera petit, comme prévu. Le deuxième terme à droite, σ '(z
L j ), mesure la vitesse à laquelle la fonction d'activation σ change dans z
L j .
Notez que tout dans (BP1) est facile à compter. En particulier, nous calculons z
L j lors du calcul du comportement du réseau, et il faudra un peu plus de ressources pour calculer σ '(z
L j ). Bien entendu, la forme exacte ∂C / ∂a
L j dépend de la forme de la fonction de coût. Cependant, si la fonction de coût est connue, le calcul de ∂C / ∂a
L j ne devrait poser aucun problème. Par exemple, si nous utilisons la fonction de coût quadratique, alors C = 1/2 ∑
j (y
j - a
L j )
2 , donc ∂C / ∂a
L j = (a
L j - y
j ), ce qui est facile à calculer.
L'équation (BP1) est une expression éclatée de δ
L. C'est tout à fait normal, mais pas enregistré sous la forme matricielle, dont nous avons besoin pour la rétrodiffusion. Cependant, il est facile de réécrire sous forme matricielle, comme
deltaL= nablaaC odot sigma′(zL) tagBP1a
Ici, ∇
a C est défini comme un vecteur dont les composantes sont les dérivées partielles ∂C / ∂a
L j . Il peut être représenté comme une expression du taux de variation de C par rapport aux activations de sortie. Il est facile de voir que les équations (BP1a) et (BP1) sont équivalentes, donc nous utiliserons (BP1) pour faire référence à l'une d'entre elles ci-dessous. Par exemple, dans le cas d'une valeur quadratique, nous avons ∇
a C = (a
L - y), donc la forme matricielle complète (BP1) sera
deltaL=(aL−y) odot sigma′(zL) tag30
Tout dans cette expression a une forme vectorielle pratique, et il est facile de calculer en utilisant une bibliothèque telle que Numpy.
L'expression de l'erreur δ l à travers l'erreur de la couche suivante, δ l + 1 : en particulier,
deltal=((wl+1)T deltal+1) cdot sigma′(zl) tagBP2
où (w
l + 1 )
T est la transposition de la matrice de poids w
l + 1 pour la couche n ° (l + 1). L'équation semble compliquée, mais chaque élément est facile à interpréter. Supposons que nous connaissions l'erreur δ
l + 1 pour la couche (l + 1). La transposition de la matrice de poids, (w
l + 1 )
T , peut être imaginée comme faisant reculer l'erreur à travers le réseau, ce qui nous donne une certaine mesure d'erreur à la sortie de la couche n ° l. Ensuite, nous considérons le produit de Hadamard ⊙σ '(z
l ). Cela repousse l'erreur à travers la fonction d'activation dans la couche l, nous donnant la valeur d'erreur δl dans l'entrée pondérée pour la couche l.
En combinant (BP2) avec (BP1), nous pouvons calculer l'erreur δ
l pour n'importe quelle couche de réseau.
Nous commençons par utiliser (BP1) pour calculer δ L , puis utilisons l'équation (BP2) pour calculer δ L-1 , puis à nouveau pour calculer δ L-2 , et ainsi de suite, jusqu'à l'arrière du réseau.L'équation du taux de variation des coûts par rapport à toute compensation dans le réseau : en particulier:∂ C∂ b l j =δ l j
En d'autres termes , l'erreur δ l j est exactement égale au taux de variation ∂C / ∂b l j . C'est excellent car (BP1) et (BP2) nous ont déjà dit comment calculer δ l j . On peut réécrire (BP3) plus court comme∂ C∂ b =δ
où δ est estimé pour le même neurone que le biais b.L'équation du taux de variation de valeur par rapport à tout poids dans le réseau : en particulier:∂ C∂ w l j k =a l - 1 k δ l j
De là, nous apprenons à calculer la dérivée partielle ∂C / ∂w l jk à travers les valeurs de δ l et a l-1 , dont nous connaissons déjà la méthode de calcul. Cette équation peut être réécrite sous une forme moins chargée:∂ C∂ w =ainδout
où a in est l'activation de l'entrée neuronale pour le poids w, et δ out est l'erreur de la sortie neuronale du poids w. Si nous regardons plus en détail le poids w et deux neurones connectés par lui, alors nous pouvons le dessiner comme ceci:
Une bonne conséquence de l'équation (32) est que lorsque l'activation a in est petite, a in ≈ 0, le terme terme ∂C / ∂w tend également à à zéro. Dans ce cas, nous disons que le poids est entraîné lentement, c'est-à-dire qu'il ne change pas beaucoup pendant la descente du gradient. En d'autres termes, l'une des conséquences (BP4) est que la sortie pondérée des neurones à faible activation apprend lentement.D'autres idées peuvent être tirées de (BP1) - (BP4). Commençons par la couche de sortie. Considérons le terme σ '(z L j) dans (BP1). Rappelez-vous à partir du graphique du sigmoïde du chapitre précédent qu'il devient plat lorsque σ (z L j ) approche de 0 ou 1. Dans ces cas, σ '(z L j ) ≈ 0. Par conséquent, le poids dans la dernière couche sera entraîné lentement si l'activation le neurone de sortie est petit (≈ 0) ou grand (≈ 1). Dans ce cas, il est généralement dit que le neurone de sortie est saturé et, par conséquent, le poids a cessé d'être entraîné (ou est entraîné lentement). Les mêmes remarques sont valables pour les déplacements du neurone de sortie.Des idées similaires peuvent être obtenues concernant les couches antérieures. En particulier, considérons le terme σ '(z l ) dans (BP2). Cela signifie que δ l jle plus probable sera petit à mesure que le neurone approche de la saturation. Et cela, à son tour, signifie que tout poids à l'entrée d'un neurone saturé sera entraîné lentement (bien que cela ne fonctionnera pas si w l + 1 T δ l + 1 aura des éléments suffisamment grands pour compenser la petite valeur de σ '(z L j )).Pour résumer: nous avons appris que le poids sera entraîné lentement si l'activation du neurone d'entrée est petite ou le neurone de sortie est saturé, c'est-à-dire que son activation est petite ou grande.Ce n'est pas particulièrement surprenant. Et pourtant, ces observations nous aident à mieux comprendre ce qui se passe lorsque nous formons le réseau. De plus, nous pouvons aborder ces arguments de l'autre côté. Les quatre équations fondamentales sont valables pour toute fonction d'activation, et pas seulement pour le sigmoïde standard (car, comme nous le verrons plus loin, les propriétés n'utilisent pas le sigmoïde). Par conséquent, ces équations peuvent être utilisées pour développer des fonctions d'activation avec certaines propriétés d'apprentissage nécessaires. Par exemple, supposons que nous choisissions une fonction d'activation σ différente d'une sigmoïde, telle que σ 'soit toujours positive et n'approche pas de zéro. Cela empêche le ralentissement de l'apprentissage qui se produit lorsque les neurones sigmoïdes normaux sont saturés. Plus loin dans le livre, nous verrons des exemples où la fonction d'activation change de manière similaire.Étant donné les équations (BP1) - (BP4), nous pouvons expliquer pourquoi de telles modifications sont nécessaires et comment elles peuvent affecter la situation.
:δL=Σ′(zL)∇aC
où Σ '(z L ) est une matrice carrée avec des valeurs de σ' (z L j ) situées le long de la diagonale et d'autres éléments égaux à 0. Notez que cette matrice interagit avec ∇ a C par le biais d'une multiplication matricielle ordinaire.Montrer que (BP2) peut être réécrit enδ l = Σ ′ ( z l ) ( w l + 1 ) T δ l + 1
En combinant les tâches précédentes, montrez que:δ l = Σ ′ ( z l ) ( w l + 1 ) T … Σ ′ ( z L - 1 ) ( w L ) T Σ ′ ( z L ) ∇ a C
Pour les lecteurs habitués à la multiplication matricielle, cette équation sera plus facile à comprendre que (BP1) et (BP2). Je me concentre sur (BP1) et (BP2) car cette approche est plus rapide à mettre en œuvre numériquement. [ici Σ n'est pas la somme (∑), mais le capital σ (sigma) / env. perev. ]
Preuve des quatre équations fondamentales (section facultative)
Maintenant, nous prouvons les quatre équations fondamentales (BP1) - (BP4). Tous sont des conséquences de la règle de chaîne (la règle de différenciation d'une fonction complexe) de l'analyse des fonctions de nombreuses variables. Si vous connaissez la règle de la chaîne, je vous recommande fortement d'essayer de compter les dérivés vous-même avant de poursuivre la lecture.Commençons par l'équation (BP1), ce qui nous donne une expression pour le delta d'erreur de sortie de L . Pour le prouver, nous rappelons que, par définition:δ L j = ∂ C∂ z L j
En appliquant la règle de chaîne, nous réécrivons les dérivées partielles par les dérivées partielles des activations de sortie:δ L j = ∑ k ∂ C∂ a L k ∂a L k∂ z L j
où la sommation passe par tous les neurones k dans la couche de sortie. Bien entendu, l'activation de sortie a L k du neurone n ° k ne dépend que de l'entrée pondérée z L j pour le neurone n ° j lorsque k = j. Par conséquent, ∂a L k / ∂z L j disparaît lorsque k ≠ j. En conséquence, nous simplifions l'équation précédente pourδ L j = ∂ C∂ a L j ∂a L j∂ z L j
Rappelant que a L j = σ (z L j ), nous pouvons réécrire le deuxième terme à droite comme σ '(z L j ), et l'équation se transforme enδ L j = ∂ C∂ a L j σ′(z L j )
c'est-à-dire dans (BP1) dans une vue éclatée.Ensuite, nous prouvons (BP2), qui donne l'équation de l'erreur δ l à travers l'erreur dans la couche suivante δ l + 1 . Pour ce faire, nous devons réécrire δ l j = ∂C / ∂z l j à δ l + 1 k = ∂C / ∂z l + 1 k . Cela peut être fait en utilisant la règle de chaîne:δ l j = ∂ C∂ z l j
= ∑ k ∂ C∂ z l + 1 k ∂z l + 1 k∂ z l j
= ∑ k ∂ z l + 1 k∂ z l j δ l + 1 k
où dans la dernière ligne, nous avons échangé les deux termes sur la droite, et substitué la définition de δ l + 1 k . Pour calculer le premier terme sur la dernière ligne, notez quez l + 1 k = ∑ j w l + 1 k j a l j + b l + 1 k = ∑ j w l + 1 k j σ ( z l j ) + b l + 1 k
Différencier, on obtient∂ z l + 1 k∂ z l j =w l + 1 k j σ′(z l j ).
En substituant ceci en (42), nous obtenonsδ l j = ∑ k w l + 1 k j δ l + 1 k σ ′ ( z l j ) .
Autrement dit, (BP2) dans une entrée éclatée.Reste à prouver (BP3) et (BP4). Ils découlent également de la règle de la chaîne, à peu près de la même manière que les deux précédents. Je vous les laisse comme exercice.Exercice
C'est toute la preuve des quatre équations fondamentales de rétropropagation. Cela peut sembler compliqué. Mais en réalité, c'est simplement le résultat d'une application prudente de la règle de la chaîne. Pour parler moins succinctement, la rétropropagation peut être imaginée comme un moyen de calculer le gradient de la fonction de coût grâce à l'application systématique de la règle de chaîne à partir de l'analyse des fonctions de nombreuses variables. Et c'est vraiment tout ce que la distribution arrière est - le reste n'est que des détails.Algorithme de rétropropagation
Les équations de rétropropagation nous donnent une méthode pour calculer le gradient de la fonction de coût. Écrivons cela explicitement sous forme d'algorithme:- Entrée x: attribuez l'activation appropriée à 1 pour la couche d'entrée.
- : l = 2,3,…,L z l = w l a l−1 +b l a l = σ(z l ).
- δ L : δ L = ∇ a C ⊙ σ'(z L ).
- : l = L−1,L−2,…,2 δ l = ((w l+1 ) T δ l+1 ) ⊙ σ'(z l ).
- : ∂C∂wljk=al−1kδlj et ∂C∂blj=δlj .
En regardant l'algorithme, vous comprendrez pourquoi il est appelé rétropropagation. Nous calculons les vecteurs d'erreur δ l à l' envers, à partir de la dernière couche. Il peut sembler étrange que nous retournions à travers le réseau. Mais si vous pensez à la preuve de la propagation arrière, le mouvement inverse est une conséquence du fait que le coût est fonction de la sortie du réseau. Pour comprendre comment le coût varie avec les premiers poids et décalages, nous devons appliquer la règle de chaîne encore et encore, en parcourant les couches pour obtenir des expressions utiles.Exercices
- . , , f(∑ j w j x j +b), f – , . ?
- . , σ(z) = z . .
Comme je l'ai expliqué précédemment, l'algorithme de rétropropagation calcule le gradient de la fonction de coût pour un exemple d'apprentissage, C = C x . En pratique, la rétropropagation est souvent combinée avec un algorithme d'apprentissage, par exemple, avec une descente de gradient stochastique, lorsque nous calculons le gradient pour de nombreux exemples d'apprentissage. En particulier, pour un mini-package donné de m exemples d'apprentissage, l'algorithme suivant applique une descente de gradient basée sur ce mini-package:- Entrée: un ensemble d'exemples de formation.
- Pour chaque exemple de formation x, affectez l'activation d'entrée correspondante a x, 1 et procédez comme suit:
- l=2,3,…,L z x,l = w l a x,l−1 +b l a x,l = σ(z x,l ).
- δ x,L : δ x,L = ∇ a C x ⋅ σ'(z x,L ).
- : l=L−1,L−2,…,2 δ x,l = ((w l+1 ) T δ x,l+1 ) ⋅ σ'(z x,l ).
- : l=L,L−1,…,2 wl rightarrowwl− frac etam sumx deltax,l(ax,l−1)T et décalages selon la règle bl rightarrowbl− frac etam sumx deltax,l .
Bien sûr, pour mettre en œuvre la descente de gradient stochastique dans la pratique, vous aurez également besoin d'un cycle externe qui génère des mini-packages d'exemples de formation et d'un cycle externe qui passe par plusieurs époques de formation. Par souci de simplicité, je les ai omis.
Code de distribution arrière
Ayant compris le côté abstrait de la rétropropagation, nous pouvons maintenant comprendre le code utilisé dans le chapitre précédent qui implémente la rétropropagation. Rappelez-vous à partir de ce chapitre que le code était contenu dans les méthodes update_mini_batch et backprop de la classe Network. Le code de ces méthodes est une traduction directe de l'algorithme décrit ci-dessus. En particulier, la méthode update_mini_batch met à jour les poids et décalages du réseau en calculant le gradient pour les exemples de formation mini_batch actuels:
class Network(object): ... def update_mini_batch(self, mini_batch, eta): """ , -. mini_batch – (x, y), eta – .""" nabla_b = [np.zeros(b.shape) for b in self.biases] nabla_w = [np.zeros(w.shape) for w in self.weights] for x, y in mini_batch: delta_nabla_b, delta_nabla_w = self.backprop(x, y) nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)] nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)] self.weights = [w-(eta/len(mini_batch))*nw for w, nw in zip(self.weights, nabla_w)] self.biases = [b-(eta/len(mini_batch))*nb for b, nb in zip(self.biases, nabla_b)]
La plupart du travail est effectué par les lignes delta_nabla_b, delta_nabla_w = self.backprop (x, y), en utilisant la méthode backprop pour calculer les dérivées partielles ∂C
x / ∂b
l j et ∂C
x / ∂w
l jk . La méthode backprop répète presque l'algorithme de la section précédente. Il y a une petite différence - nous utilisons une approche légèrement différente de l'indexation des couches. Ceci est fait afin de profiter de la fonctionnalité python, des index de tableaux négatifs qui vous permettent de compter les éléments en arrière depuis la fin. l [-3] sera le troisième élément à partir de la fin du tableau l. Le code backprop est donné ci-dessous, ainsi que les fonctions auxiliaires utilisées pour calculer le sigmoïde, sa dérivée et la dérivée de la fonction de coût. Avec eux, le code est complet et compréhensible. Si quelque chose n'est pas clair, reportez-vous à la première description complète du code de liste.
class Network(object): ... def backprop(self, x, y): """ ``(nabla_b, nabla_w)``, C_x. ``nabla_b`` ``nabla_w`` - numpy, ``self.biases`` and ``self.weights``.""" nabla_b = [np.zeros(b.shape) for b in self.biases] nabla_w = [np.zeros(w.shape) for w in self.weights]
Défi
- Une approche de rétropropagation entièrement matricielle sur le mini-pack. Notre implémentation de la descente de gradient stochastique utilise une série d'exemples de formation du mini-package. L'algorithme de rétropropagation peut être modifié de sorte qu'il calcule les gradients pour tous les exemples de formation du mini-package en même temps. Au lieu de commencer par un seul vecteur x, nous pouvons commencer par la matrice X = [x 1 x 2 ... x m ], dont les colonnes sont les vecteurs du mini-pack. La distribution directe se fait par le produit de matrices de poids, l'ajout d'une matrice appropriée pour les déplacements et l'utilisation généralisée de sigmoïde. La propagation arrière suit le même schéma. Écrivez un pseudo-code pour cette approche pour l'algorithme de rétropropagation. Modifiez network.py pour qu'il utilise cette approche matricielle. L'avantage de cette approche sera l'utilisation de tous les avantages des bibliothèques modernes pour l'algèbre linéaire. Par conséquent, il peut s'exécuter plus rapidement que le cycle de mini-paquets (par exemple, sur mon ordinateur, le programme accélère environ 2 fois sur les tâches de classification du MNIST). En pratique, toutes les bibliothèques sérieuses pour la distribution arrière utilisent une telle approche matricielle complète ou une certaine version de celle-ci.
Dans quel sens la rétropropagation est-elle un algorithme rapide?
Dans quel sens la rétropropagation est-elle un algorithme rapide? Pour répondre à cette question, envisagez une autre approche pour calculer le gradient. Imaginez les premiers jours de la recherche sur les réseaux de neurones. Peut-être que ce sont les années 1950 ou 1960, et vous êtes la première personne au monde à avoir eu l'idée d'utiliser la descente en pente pour l'entraînement! Mais pour que cela fonctionne, vous devez calculer le gradient de la fonction de coût. Vous vous souvenez de l'algèbre et décidez de voir si vous pouvez utiliser la règle de chaîne pour calculer le gradient. Après avoir joué un peu, vous voyez que l'algèbre semble difficile, et vous êtes déçu. Vous essayez de trouver une approche différente. Vous décidez de considérer le coût en fonction uniquement des poids C = C (w) (nous reviendrons sur les déplacements un peu plus tard). Vous numérotez les poids w
1 , w
2 , ... et vous voulez calculer ∂C / ∂w
j pour le poids w
j . La manière évidente est d'utiliser l'approximation
frac partialC partialwj approx fracC(w+ epsilonej)−C(w) epsilon tag46
Où ε> 0 est un petit nombre positif et e
j est le vecteur de direction unitaire j. En d'autres termes, nous pouvons approximativement estimer ∂C / ∂w
j en calculant le coût C pour deux valeurs légèrement différentes de w
j , puis appliquer l'équation (46). La même idée nous permet de calculer les dérivées partielles de ∂C / ∂b par rapport aux déplacements.
L'approche semble prometteuse. Conceptuellement simple, facile à implémenter, n'utilise que quelques lignes de code. Cela semble beaucoup plus prometteur que l'idée d'utiliser une règle de chaîne pour calculer le gradient!
Malheureusement, bien que cette approche semble prometteuse, lorsqu'elle est implémentée dans du code, il s'avère qu'elle fonctionne extrêmement lentement. Pour comprendre pourquoi, imaginez que nous avons un million de poids dans le réseau. Ensuite, pour chaque poids w
j, nous devons calculer C (w + εe
j ) pour calculer ∂C / ∂w
j . Et cela signifie que pour calculer la pente, nous devons calculer la fonction de coût un million de fois, ce qui nécessitera un million de passages directs à travers le réseau (pour chaque exemple de formation). Et nous devons également calculer C (w), donc nous obtenons un million et un passage à travers le réseau.
L'astuce de la rétropropagation est qu'elle nous permet de calculer simultanément toutes les dérivées partielles ∂C / ∂w
j en utilisant un seul passage direct à travers le réseau, suivi d'un passage inverse. En gros, le coût de calcul de la passe de retour est à peu près le même que celui de la passe directe.
Par conséquent, le coût total de la propagation arrière est approximativement le même que celui de deux passages directs à travers le réseau. Comparez cela avec le million et une passe directe nécessaires pour implémenter la méthode (46)! Ainsi, bien que la rétropropagation ressemble à une approche plus compliquée, en réalité, elle est beaucoup plus rapide.
Pour la première fois, cette accélération a été pleinement appréciée en 1986, ce qui a considérablement élargi la gamme des tâches résolues à l'aide de réseaux de neurones. À son tour, cela a conduit à une augmentation du nombre de personnes utilisant des réseaux de neurones. Bien sûr, la rétropropagation n'est pas une panacée. Même à la fin des années 1980, les gens ont déjà rencontré ses limites, en particulier lorsqu'ils essayent d'utiliser la rétropropagation pour former des réseaux de neurones profonds, c'est-à-dire des réseaux avec de nombreuses couches cachées. Plus tard, nous verrons comment les ordinateurs modernes et les nouvelles idées délicates permettent d'utiliser la rétropropagation pour former de tels réseaux de neurones profonds.
Distribution inverse: en général
Comme je l'ai déjà expliqué, la rétropropagation nous révèle deux mystères. La première chose que fait réellement l'algorithme? Nous avons développé un schéma de rétropropagation pour l'erreur de la sortie. Est-il possible d'aller plus loin, d'avoir une idée plus intuitive de ce qui se passe lors de toutes ces multiplications de vecteurs et matrices? La deuxième énigme est de savoir comment quelqu'un a-t-il même détecté une propagation arrière? C'est une chose de suivre les étapes de l'algorithme ou la preuve de son fonctionnement. Mais cela ne signifie pas que vous avez si bien compris le problème que vous avez pu inventer cet algorithme. Existe-t-il un raisonnement raisonnable pouvant nous conduire à la découverte de l'algorithme de rétropropagation? Dans cette section, je couvrirai les deux puzzles.
Pour améliorer la compréhension du fonctionnement de l'algorithme, imaginons que nous ayons fait un petit changement Δw
l jk d' un certain poids w
l jk :

Ce changement de poids entraînera une modification de l'activation de sortie du neurone correspondant:

Cela entraînera un changement dans toutes les activations de la couche suivante:

Ces modifications entraîneront des modifications dans la couche suivante, et ainsi de suite, jusqu'à la dernière, puis des modifications dans la fonction de coût:

La variation de ΔC est liée à la variation de Δw
l jk par l' équation
DeltaC approx frac partialC partialwljk Deltawljk tag47
Il s'ensuit qu'une approche probable du calcul de ∂C / ∂w
l jk consiste à surveiller attentivement la propagation d'un petit changement w
l jk , conduisant à un petit changement de C. Si nous pouvons le faire, en exprimant soigneusement en cours de route tout en quantités faciles à calculer , alors nous pouvons calculer ∂C / ∂w
l jk .
Essayons. Un changement de Δw
l jk entraîne un léger changement de Δa
l j dans l'activation du neurone j dans la couche l. Cette modification est définie.
Deltaalj approx frac partialalj partialwljk Deltawljk tag48
Le changement d'activation Δa
l j entraîne des changements dans toutes les activations de la couche suivante, (l + 1). Nous nous concentrerons uniquement sur l'une de ces activations modifiées, par exemple, un
l + 1 q ,

Cela entraînera les modifications suivantes:
Deltaal+1q approx frac partialal+1q partialalj Deltaalj tag49
En substituant l'équation (48), on obtient:
Deltaal+1q approx frac partialal+1q partialalj frac partialalj partialwljk Deltawljk tag50
Bien entendu, un changement de Δa
l + 1 q changera également l'activation dans la couche suivante. On peut même imaginer un chemin le long de l'ensemble du réseau de w
l jk à C, où chaque changement d'activation conduit à un changement de la prochaine activation, et, enfin, à un changement de coût en sortie. Si le chemin passe par des activations a
l j , a
l + 1 q , ..., a
L - 1 n , a
L m , alors l'expression finale sera
DeltaC approx frac partialC partialaLm frac partialaLm partialaL−1n frac partialaL−1n partialaL−2p ldots frac partialal+1q partialalj frac partialalj partialwljk Deltawljk tag51
Autrement dit, nous choisissons un membre de la forme ∂a / ∂a pour chacun des neurones suivants que nous passons, ainsi que pour le terme ∂C / ∂a
L m à la fin. Il s'agit d'une représentation des changements de C dus aux changements d'activations sur ce chemin particulier à travers le réseau. Bien sûr, il existe de nombreuses façons dont un changement dans w
l jk peut se produire et affecter le coût, et nous n'en avons considéré qu'une. Pour calculer la variation totale de C, il est raisonnable de supposer que nous devons résumer tous les chemins possibles du poids au coût final:
DeltaC approx summnp ldotsq frac partialC partialaLm frac partialaLm partialaL−1n frac partialaL−1n partialaL−2p ldots frac partialal+1q partialalj frac partialalj partialwljk Deltawljk tag52
où nous avons résumé tous les choix possibles pour les neurones intermédiaires en cours de route. En comparant cela avec (47), nous voyons que:
frac partialC partialwljk= summnp ldotsq frac partialC partialaLm frac partialaLm partialaL−1n frac partialaL−1n partialaL−2p ldots frac partialal+1q partialalj frac partialalj partialwljk. tag53
L'équation (53) semble compliquée. Cependant, il a une belle interprétation intuitive. Nous comptons la variation de C par rapport aux poids du réseau. Il nous dit que chaque front entre deux neurones du réseau est associé à un facteur de rapport, qui n'est qu'une dérivée partielle de l'activation d'un neurone par rapport à l'activation d'un autre neurone. Pour une côte du premier poids au premier neurone, le facteur de rapport est ∂a
l j / ∂w
l jk . Le coefficient de rapport pour le chemin est simplement le produit des coefficients le long du chemin. Et le coefficient de variation total ∂C / ∂w
l jk est la somme des coefficients dans tous les sens, du poids initial au coût final. Cette procédure est illustrée ci-dessous pour un chemin:

Jusqu'à présent, nous avons donné un argument heuristique, un moyen de représenter ce qui se passe lorsque le poids du réseau change. Permettez-moi d'esquisser une autre façon de penser sur ce sujet pour le développement de cet argument. Premièrement, nous pouvons dériver l'expression exacte de toutes les dérivées partielles individuelles dans l'équation (53). C'est facile à faire en utilisant une algèbre simple. Après cela, vous pouvez essayer de comprendre comment écrire toutes les sommes par indices sous forme de produits matriciels. Cela s'avère être une tâche fastidieuse nécessitant de la patience, mais pas quelque chose d'extraordinaire. Après tout cela et une simplification maximale, vous verrez que le même algorithme de rétropropagation est obtenu! Par conséquent, l'algorithme de rétropropagation peut être imaginé comme un moyen de calculer la somme des coefficients pour tous les chemins. Ou, pour reformuler, l'algorithme de rétropropagation est un moyen délicat de suivre les petits changements de poids (et décalages) lorsqu'ils se propagent sur un réseau, atteignent une sortie et affectent les coûts.
Ici, je ne ferai pas tout cela. Cette entreprise est peu attrayante, nécessitant une étude minutieuse des détails. Si vous êtes prêt pour cela, vous pouvez le faire. Sinon, j'espère que de telles réflexions vous donneront quelques idées concernant les objectifs de la rétropropagation.
Qu'en est-il d'une autre énigme - comment pourrait-on découvrir la propagation arrière? En fait, si vous suivez le chemin que j'ai tracé, vous recevrez des preuves de propagation arrière. Malheureusement, la preuve sera plus longue et plus compliquée que ce que j'ai décrit plus tôt. Alors, comment cette preuve courte (mais encore plus mystérieuse) a-t-elle été découverte? Si vous notez tous les détails d'une longue épreuve, vous remarquerez immédiatement quelques simplifications évidentes. Vous appliquez des simplifications, obtenez une preuve plus simple, notez-la. Et puis vous rencontrez à nouveau quelques simplifications évidentes. Et vous répétez le processus. Après plusieurs répétitions, la preuve que nous avons vue plus tôt sera courte, mais un peu incompréhensible, puisque tous les jalons en ont été supprimés! Bien sûr, je vous suggère de me croire sur parole, mais en fait il n'y a pas de mystère sur l'origine des preuves. Juste beaucoup de travail acharné pour simplifier la preuve que j'ai décrite dans cette section.
Cependant, il existe une astuce astucieuse dans ce processus. Dans l'équation (53), les variables intermédiaires sont des activations de type a
l + 1 q . L'astuce consiste à utiliser des entrées pondérées, telles que z
l + 1 q , comme variables intermédiaires. Si vous ne l'utilisez pas et continuez à utiliser les activations, les preuves obtenues seront un peu plus compliquées que celles données plus haut dans ce chapitre.