Partie 5
Lire tout d'un coup (attention)
Et si chaque token combinait directement les vecteurs de tous les autres ? Pour « toux » dans « fièvre sans toux » (embeddings [1 ; 0], [0 ; 1], [1 ; 1]) :
- Un score par token (lui compris) : le produit scalaire des deux vecteurs. Ici 1, 1, 2.
- Des poids : la softmax de J1 transforme les scores en nombres positifs de somme 1 : 0,21 ; 0,21 ; 0,58.
- Le nouveau vecteur : moyenne des vecteurs pondérée par ces poids = [0,79 ; 0,79], au lieu de [1 ; 1].
L'embedding de « toux » ne change pas d'une phrase à l'autre ; le nouveau vecteur, lui, dépend de la phrase. Aucune étape ne dépend de la distance entre les tokens.
À toi de refaire le calcul de « toux » sans bibliothèque. Il enchaîne trois choses dans cet ordre : scores (un produit scalaire par token), softmax (exponentielles divisées par leur somme, pour que les poids fassent 1), puis moyenne pondérée (somme des poids × vecteurs, composante par composante). On enchaîne dans cet ordre parce que chaque étape consomme le résultat de la précédente.
À toi de coder
Loin ou près, c'est le même calcul
Dans « Pas de fièvre ni de frissons ni de toux », la négation « Pas » est à 8 mots de « toux ». Un RNN a multiplié son apport 8 fois par 0,5 (il en reste 1/256). L'attention calcule le score de « toux » avec « Pas » exactement comme avec « de » : la distance n'entre pas dans le calcul. Dans un résumé de 241 tokens, le premier et le dernier sont reliés par un calcul, pas par 240 mises à jour.
Remettre l'ordre : le vecteur de place
L'attention ignore l'ordre : dans « toux sans fièvre », « toux » obtient le même [0,79 ; 0,79]. On donne donc à chaque place un vecteur (appris comme un embedding) et on l'ajoute au vecteur du token : places [1 ; 0], [0 ; 0], [0 ; 1]. Dans « fièvre sans toux », « toux » devient [1 ; 1] + [0 ; 1] = [1 ; 2] ; dans « toux sans fièvre » il devient [2 ; 1]. Le token garde un seul vecteur : la somme des deux.
Tu vas ajouter les vecteurs de place puis réutiliser la fonction attention déjà écrite. Le but est de voir que la même phrase, dans deux ordres, donne maintenant deux vecteurs différents pour « toux » : [1 ; 1,86] à la place 3, [1,85 ; 1] à la place 1. Il faut faire l'addition avant l'attention : les scores doivent déjà « connaître » la place.
À toi de coder
Le masque de remplissage
Pour traiter des textes de longueurs différentes, on complète les plus courts avec des <rempl> (embedding [0 ; 0]). Ils ne doivent pas recevoir de poids. Avant la softmax, on remplace leur score par −∞ : exp(−∞) = 0, donc le poids vaut exactement 0 et les autres poids se partagent le total. Mettre 0 à la place ne marcherait pas : exp(0) = 1, le remplissage garderait un gros poids.
Exemple : « fièvre » dans « fièvre sans <rempl> » a des scores 1, 0, 0 ; avec le masque 1, 1, 0 on obtient [0,73 ; 0,27 ; 0].
À toi de coder
√d, puis requêtes, clés, valeurs
Avec des vecteurs de d composantes, les produits scalaires grandissent avec d et la softmax devient trop « pointue » (un poids proche de 1, des pentes presque nulles). On divise donc les scores par √d avant la softmax.
Les vrais modèles ne comparent pas directement les embeddings : trois couches denses sans biais calculent, pour chaque token, une requête q (« que cherche-je ? »), une clé k (« que propose-je ? ») et une valeur v (« que transmets-je ? »). Le score est q · k, la moyenne pondérée porte sur les v : softmax(Q Kᵀ / √d) V. Avec x de 2 composantes et q, k, v de 4 composantes, cela fait 3 × 2 × 4 = 24 poids appris.