Backpropagation - Descente de gradient
Un rĂ©seau de neurone peut ĂȘtre vu comme une expression mathĂ©matique :
- On a des donnĂ©es dâentrĂ©e (inputs) sous forme de vecteurs (appelĂ©s Tensor)
- une matrice dont on essaie de calculer les valeurs (poids ou weights) durant la phase dâentrainement
- et on calcule un rĂ©sultat : cela peut ĂȘtre la prĂ©diction d'une valeur numĂ©rique par notre rĂ©seau
Dans le cas oĂč notre rĂ©seau essaie de prĂ©dire une valeur, il nous faut une mĂ©trique qu'on peut suivre et qui va nous permettre d'Ă©valuer la pertinence de notre modĂšle. Cette mĂ©trique est mesurĂ©e par la fonction de perte.
Pour calculer les poids (weigths) on essaie de minimiser une fonction "loss function", car plus on minimise, et moins on a dâĂ©cart entre le rĂ©sultat du rĂ©seau et le rĂ©sultat attendu. Le calcul se fait sur des donnĂ©es dâentrĂ©es (inputs) qui servent Ă lâentrainement du modĂšle.
Lâalgorithme qui permet itĂ©rativement de converger vers ce minimum sâappelle "la descente de gradient".
On a 4 étapes:
- Passage avant (forward pass)
- Ăvaluation de la fonction de perte (loss function)
- Rétropropagation (backward pass)
- Mise à jour du réseau
Forward pass
Lors du passage avant, les données d'entrée traversent le réseau pour générer des prédictions. Chaque neurone calcule une sortie en appliquant une fonction d'activation à la somme pondérée de ses entrées. La sortie de la derniÚre couche est ensuite comparée aux valeurs cibles à l'aide de la fonction de perte.
Ătant donnĂ© des poids (weights) et des donnĂ©s dâentrĂ©es (inputs), le cacul de la sortie (output) se fait en faisant une forward pass.
On part des donnĂ©es dâentrĂ©es, et on calcule Ă chaque Ă©tape du rĂ©seau de neurones les valeurs intermĂ©diaires jusquâĂ arriver au rĂ©sultat final.
On obtient pour chaque jeu de données en entrée [X1,...,Xi] la prédiction Ʒi
Loss function
On cherche un indicateur unique nous donnant la prĂ©cision de notre modĂšle. La fonction de perte mesure lâĂ©cart entre les prĂ©dictions du modĂšle et les rĂ©sultats rĂ©els.
On prend comme fonction de perte l'écart entre les valeurs prédites et les valeurs constatées : MSE Mean Square Error
Rétropropagation (backpropagation)
La rĂ©tropropagation permet de calculer le gradient de la fonction de perte par rapport Ă chaque poids du rĂ©seau. L'idĂ©e clĂ© est de propager lâerreur, Ă partir de la couche de sortie, vers l'entrĂ©e, en ajustant les poids Ă chaque Ă©tape.
Backpropagation vs Gradient descent
- Gradient descent :âŻalgorithme dâoptimisation gĂ©nĂ©ral pour calculer les poids du modĂšle.
- Backpropagation :âŻcâest une Ă©tape de lâalgorithme gradient descent, oĂč on met Ă jour les poids du modĂšle en calculant des derivĂ©es partielles (le gradient) qui donne lâajustement quâon donne au poids pour converger vers un minimun.
On part de lâoutput node, et on remonte le graphe jusquâaux inputs node. DâoĂč le terme de backpropagation
à chaque itération (backpass), on a un ajustement des poids.
- Soit [W]^n les poids Ă lâĂ©tape n
- Lr : learning rate - un paramĂštre qui dit Ă quelle vitesse on veut aller (si câest trop petit, on converge tout doucement, si câest trop grand on peut osciller et ne pas trouver le min)
- [G]^n : le gradient - câest Ă dire le petit ajusteemnt local des poids qui va permettre au modĂšle de se rapprocher de lâoptimal
[W]^n+1 = [W]^n - Lr * [G]^n
Exemple manuel:
- Inputs :âŻa , b ,c,f
- Opérations : e = a * b ; d = e + c ; d * f = L
-
RĂ©sultat :âŻL
a --|
| () --> e --|
b --| | (+) --> d --|
c --| | () --> L
f --|
On veut connaitre lâeffet dâune variation de a sur L. Si on augmente un peu a, est-ce que L augmente ou diminue.
Pour cela on doit calculer la dérivée de L par rapport à a, soit dL/da. Pour y arriver on va calculer les dérivées intermédiaires
- dL/dd
- puis dL/de
- puis dL/da
Effectuons la backpropagation manuelle
1
dL/dL = 1 câest le gradient local.
Notons la valeur du gradient sous la forme [gradient]
a --|
| (*) --> e --|
b --| | (+) --> d --|
c --| | (*) --> L [1]
f --|
2
Comme L = d*f
dL/dd = f
dL/df = d
a --|
| (*) --> e --|
b --| | (+) --> d --| [f]
c --| | (*) --> L [1]
f --| [d]
3
dL/de = dL/dd * dd/de
d = e + c
Sur une addition, cela correspond à faire une fois le gradient précédent
dd/de = 1 * grad[d]
a --|
| (*) --> e --| [f]
b --| | (+) --> d --| [f]
c --| [f] | (*) --> L [1]
f --| [d]
4
a --| [f*b]
| (*) --> e --| [f]
b --| [f*a] | (+) --> d --| [f]
c --| [f] | (*) --> L [1]
f --| [d]
Mini-batch
Le mécanisme général est
- On utilise 1 jeu de donnée [X],[Y], on fait la forward pass, calcul de la fonction de perte puis backward pass et mise à jour des poids
Pour optimiser on peut aussi, travailler sur un batch de donnée -
On a par exemple 2 données [X1] [X2]
-
On fait en parallÚle la forward pass (grùce aux GPU, on parallélise les calculs),
- on obtient notre fonction de perte qui est fonction des deux jeux de donnĂ©es, pour une MSE : Loss = (o1-y0)ÂČ + (o2 -y1)ÂČ. Avec o1, o2 les outputs du rĂ©seaux, c'est Ă dire les valeurs prĂ©dites.
- on fait la backward pass, en mettant Ă jour [W] et b.
- upgrade des valeurs de [W] et b
La différence entre les méthodes 1 et 2, c'est que la mise à jour du gradient est mutualisée sur un ensemble de jeux de données. On prend un batch de donnée, on calcule le gradient et on mets à jours les poids
Exemple numérique
- Soit un réseau avec un neurone, 2 entrées (et donc un vecteur poids W[w1,w2]) et une fonction d'activation tanh.
- Soit X[x1,x2] une vecteur d'input, et y la valeur réelle attendue
- Soit o1 = tanh(X@W+b)
-
Note :Â n1 = X@W+b -> c'est un scalaire.
L = (Ć· -y)ÂČ = Ć·ÂČ + yÂČ -2Ć·y
L = (o1 -y)ÂČ = o1ÂČ + yÂČ -2o1y
dL/do1 = 2o1 + 0 -2y
dLdo1 = 2o1 - 2yCalculons do1 / db
g(x): x->tanh(x) // g'(x) = 1 - tanh(x)tanh(x)
o1 = tanh(n1)
do1db = 1- torch.tanh(n1)*2
print(f"{fo1b=}")dL/db = dL/o1 * do1/db
dLdb = dLdo1*do1db
print(f"Computed gradient: {dLdbitem()=}")
Nouvel exemple avec :
- 2 données d'entrée
- 1 seul neurone
-
on va calculer le gradient de W[]
dL/dw1 = dL/do1 * do1/dw1
- dL/do1 = 2o1- 2y[0]
- do1/dw1 = d(tanh(x1w1+b))/dw1
g(x) = tanh(ax+b) // g'(x) = a * tanh'(ax+b) = a * (1 - tanh(ax+b)tanh(ax+b))
a -> x1 = x1[0]
dLdw11 = (2o1- 2y[0])x1[0](1-torch.tanh(n1)**2)
print(f"{dLdw11=}")dLdw12 = (2o1- 2y[0])x1[1](1-torch.tanh(n1)**2)
print(f"{dLdw12=}")dLdw21 = (2o2- 2y[1])x2[0](1-torch.tanh(n2)**2)
print(f"{dLdw21=}")dLdw22 = (2o2- 2y[1])x2[1](1-torch.tanh(n2)**2)
print(f"{dLdw22=}")
print(dLdw11+dLdw21)
print(dLdw12+dLdw22)
print(f"{w1.grad=}")
Mise Ă jour des poids
Questions
- Scalar vs Tensor
On pourrait travailler sur des scalaires (des floats), mais on ne le fait pas en production pour des raisons de performances. On parallélise les calculs et les bibliothÚques telles que pytorch ne prennent pas en entrée des scalaires mais des Tensors. Un Tensor est un vecteur.
Dans Pytorch, un Tensor est un objet qui a des mĂ©thodes, une reprĂ©sentation interne et qui a donc un ensemble dâopĂ©rations disponibles de façons optimisĂ©se. - diffĂ©rence entre un vecteur et un tensor : pourquoi deux noms diffĂ©rents ?
Un Tensor est un objet de la bibliothĂšque Pytorch. Il permet de rĂ©aliser plus dâopĂ©rations quâun simple vecteur. On peut le convertir, changer sa taille, le multiplier, appliquer une fonction dessus. - dans un rĂ©seau de neurone, on a les poids qui reprĂ©sentent les valeurs des opĂ©rations Ă effectuer mais oĂč sont stockĂ©s les opĂ©rations. Ex je veux passer du layer 3 Ă 4. Et je dois faire 3x-7. OĂč est stockĂ© cette Ă©quation ?
Si on utilise une bibliothĂšque comme pytorch, et quâon passe par des tensors, ceux-ci modĂ©lisent la structure complĂšte du graph des opĂ©rations.
Si T1 = log(T2T3 + T4), alors quand on utilise la fonction
T1.backward(), pytorch "sait" que pour cela il faut calculer les dérivées de log (T2T3 + T4) - à quoi sert la "forward pass" vs la "backward pass" dans le gradient descent
Le forward pass sert Ă caculer la valeur de sortie du rĂ©seau et en particulier Ă calculer la fonction loss quâon cherche Ă optimiser
La backward pass sert Ă ajuster la valeur des poids du rĂ©seau en se servant du gradient. Via le gradiant on sait quels poids doivent ĂȘtre augmentĂ©s et lesquels doivent ĂȘtre diminuer afin de faire diminuer la fonction de perte (loss)