Les embeddings.
Dans la leçon précédente, nous avons vu comment chaque token est représenté par un identifiant dans le vocabulaire, et comment sa position indique sa place dans la phrase. Mais est-ce que cela suffit à notre modèle pour saisir le sens de la phrase ? Pas encore. Avec ces seuls numéros, le modèle n’a pas grand-chose pour travailler. Dans cette leçon, nous allons voir comment chaque identifiant est remplacé par une description plus riche, composée de plusieurs nombres : son embedding.
Du numéro à la fiche descriptive
Imaginez un vestiaire. Quand vous déposez votre manteau, on vous donne un ticket avec un numéro. Ce numéro ne dit rien de votre manteau : ni sa couleur, ni sa taille, ni sa matière. Il sert seulement à retrouver le bon casier.
Une fois le casier ouvert, vous retrouvez votre manteau, avec tout ce qui le caractérise : sa couleur, sa taille, sa matière. Le ticket ne décrit rien, c’est le contenu du casier qui compte.
Pour un token, c’est la même chose. L’identifiant est le ticket : il sert seulement à ouvrir le bon casier. L’embedding, c’est ce que l’on trouve à l’intérieur. À la place d’un manteau, le casier contient une petite fiche remplie de nombres qui décrit le token. C’est sur cette fiche contenant une série de nombres que le modèle va pouvoir faire ses calculs.
Peu importe où se trouve le casier dans le couloir : son emplacement, c’est-à-dire son numéro d’identifiant, ne dit rien du token. Ce qui compte, ce sont les nombres inscrits sur la fiche. Dans un modèle entraîné, les fiches de mots qui partagent des sens ou des usages proches contiennent des nombres proches, même si leurs casiers sont à l’opposé du couloir.
Le vecteur et ses dimensions
Pourquoi plusieurs nombres plutôt qu’un seul ? Prenons un exemple de la vie courante. Si vous deviez décrire des fruits avec trois notes entre 0 et 1 (sucré, acide, gros), vous pourriez écrire :
fraise → [0,8 ; 0,3 ; 0,1]
cerise → [0,8 ; 0,2 ; 0,1]
citron → [0,1 ; 0,9 ; 0,2]
pastèque → [0,7 ; 0,1 ; 0,9]Avec ces trois nombres, on voit déjà que la fraise et la cerise se ressemblent beaucoup, que la pastèque partage leur goût sucré, alors que le citron est à part. Un seul numéro de catalogue n’aurait jamais permis la granularité de cette comparaison.
Cette liste ordonnée porte un nom à retenir : celle-ci s’appelle un vecteur. Chaque nombre de la liste est une composante, et le nombre de composantes donne le nombre de dimensions du vecteur. Nos fruits sont donc décrits par des vecteurs à 3 dimensions. Une composante peut être positive, négative ou nulle.
Vous pouvez imaginer chaque vecteur comme un point placé sur une carte, où chaque dimension est une direction. Deux vecteurs aux valeurs proches donnent deux points voisins : c’est ce qu’on appelle la proximité vectorielle. Sur notre carte, la fraise et la cerise seraient presque collées, tandis que le citron se retrouverait à l’écart. Plus deux points sont proches, plus ce qu’ils décrivent se ressemble.
Vous comprenez maintenant l’importance des dimensions vectorielles : plus il y a de dimensions, plus la description est fine. En ne gardant que deux de nos notes (le sucré et l’acide), chaque fruit devient un point sur un plan :
fraise → [0,8 ; 0,3]
cerise → [0,8 ; 0,2]
citron → [0,1 ; 0,9]
pastèque → [0,7 ; 0,1]fraise et de la cerise contiennent des nombres très proches : leurs points sont presque collés. Celui du citron est très différent : son point se retrouve loin des autres.Et la pastèque ? Dans le tableau à trois notes, elle se distinguait par sa taille. Mais sur ce dessin, nous avons retiré la note « gros » : il ne reste que le sucré et l’acide, et la pastèque vient se placer juste à côté de la fraise et de la cerise. En perdant une dimension, on a perdu une partie de la description. C’est pour cela que plus un vecteur a de dimensions, plus il peut décrire finement ce qu’il représente.
L’embedding d’un token est un vecteur de ce type. Il y a cependant une grande différence avec nos fruits : dans un vrai modèle, personne ne choisit ce que veut dire chaque dimension. On ne peut pas lire une composante isolée et dire « celle-ci mesure le côté sucré ». En revanche, la proximité fonctionne de la même façon : dans un modèle entraîné, les mots qui partagent un sens ou des usages proches ont des vecteurs voisins.
Une grande table à consulter
Revenons à notre vestiaire. Tous les casiers sont alignés dans une grande table d’embeddings : une ligne par identifiant du vocabulaire, et dans chaque ligne, le vecteur du token. Un tableau de nombres comme celui-ci s’appelle aussi une matrice.
Le modèle reçoit la suite d’identifiants préparée à la leçon précédente. Pour chaque identifiant, il ouvre la ligne correspondante et récupère son vecteur, dans le même ordre que les tokens.
baba → [50, 49, 50, 49]
50 → ligne 50 de la table → vecteur de « b »
49 → ligne 49 de la table → vecteur de « a »Comme les deux b ont le même identifiant, ils ouvrent le même casier et repartent avec le même vecteur. Il n’y a aucun calcul compliqué ici : c’est une simple consultation de la table.
Attention à ne pas tout mélanger : les casiers 49 et 50 sont côte à côte dans le vestiaire, mais cela ne veut rien dire. Ce n’est pas le rangement des casiers qui compte, c’est le contenu des fiches. La proximité vectorielle se mesure entre les vecteurs, pas entre les numéros.
D’où viennent ces nombres ?
Dans un vrai modèle de langage, la table d’embeddings fait partie de ses paramètres, qu’on appelle aussi ses poids : une immense grille de nombres que le modèle va régler lui-même. Au départ, avant tout entraînement, les fiches sont remplies avec des valeurs initiales qui ne veulent rien dire. C’est un peu comme un nouvel élève qui arrive dans une classe : on lui attribue une place au hasard, avant de le connaître.
Pendant l’entraînement, le modèle ajuste petit à petit les nombres de chaque fiche pour mieux réussir sa tâche. Avec le temps, des tokens qui s’utilisent dans des situations proches finissent souvent par avoir des vecteurs proches. Sur la carte, ces tokens se retrouvent alors voisins, exactement comme notre fraise et notre cerise. C’est quelque chose que leurs simples identifiants ne permettaient pas. Nous verrons comment se passe cet apprentissage plus tard dans le cours.
La table du laboratoire, elle, est une table de démonstration non entraînée. Ses valeurs sont produites par une formule fixe, toujours la même, pour que vous puissiez observer le mécanisme. Elles ne bougent jamais et ne contiennent aucune relation de sens : si deux lettres y ont des vecteurs proches, c’est un simple hasard de calcul.
Observez par vous-même
Dans le laboratoire, saisissez baba, puis sélectionnez le premier b et le second. Leurs positions sont différentes, mais la ligne mise en évidence dans la table est la même, tout comme les trois composantes de leur vecteur.
Ajoutez ensuite une autre lettre et regardez une nouvelle ligne apparaître dans la table, avec son propre vecteur. Les barres vous montrent pour chaque dimension si la valeur est positive ou négative, et à quel point.
Les limites de notre exemple
Pour rester lisible, notre table n’utilise que 3 dimensions. Les modèles de langage utilisés au quotidien en utilisent des centaines, voire des milliers : leurs fiches sont beaucoup plus détaillées.
L’embedding d’un token est aussi toujours le même, quelle que soit la phrase. Le chat de « mon chat dort » et celui de « écris sur le chat en ligne » partent donc avec la même fiche. Ce sont les étapes suivantes du modèle qui tiendront compte du contexte : nous les découvrirons dans les prochaines leçons.
L’identifiant sert de ticket pour ouvrir la bonne ligne de la table d’embeddings. Cette ligne contient un vecteur, c’est-à-dire une liste ordonnée de nombres qui décrit le token. Le modèle récupère ces vecteurs dans l’ordre de la séquence. Dans un modèle entraîné, des mots de sens ou d’usage proches ont des vecteurs voisins.
Dans un vrai modèle, les valeurs de la table font partie des paramètres ajustés pendant l’entraînement. Dans le laboratoire, elles sont fixes et non entraînées : elles servent uniquement à observer le mécanisme.
Pour l’instant, chaque token reçoit sa fiche sans se soucier de ses voisins dans la phrase. Dans la prochaine leçon, nous prendrons du recul pour comprendre l’objectif global du modèle : prédire le token suivant. Nous verrons ensuite comment il tient compte de l’ordre des tokens grâce à l’encodage positionnel, puis comment ils échangent des informations sur leur contexte grâce à la matrice d’attention, au cœur des blocs Transformer.
La table d’embeddings et ses poids entraînables · PyTorch ↗
L’espace des embeddings et les mots voisins · Google Machine Learning Crash Course ↗