Partie 2 · En faire des vecteurs
Embeddings
Un numéro n'est pas une mesure. Donné tel quel à un neurone, « toux » (numéro 4) pèserait deux fois plus que « fièvre » (numéro 2), ce qui n'a aucun sens. Il faut une représentation où aucun token n'est plus grand qu'un autre : le one-hot. C'est un vecteur (une liste de nombres) avec une composante par token du vocabulaire (22 704 pour le vrai), toutes à 0 sauf celle du token, à 1.
Additionner les one-hot des tokens d'un résumé donne le sac de mots : chaque composante compte les apparitions de son token. Sa taille ne dépend plus de la longueur du texte. Compare deux phrases :
Deux limites du sac de mots : l'ordre est perdu (« toux sans fièvre » = « fièvre sans toux »), et il est énorme (22 704 composantes) sans rapprocher « patient » de « patients ». Mais d'abord, programmons-le : il suffit de compter, en utilisant chaque numéro comme position dans le vecteur.
À toi de coder
L'embedding. Un vecteur à 2 composantes se dessine comme un point : la 1re composante donne la position horizontale, la 2e la verticale. Un embedding est un vecteur court attribué à un token (64 composantes dans le notebook). Deux embeddings proches sont deux points voisins : le réseau peut ainsi rapprocher des tokens qui jouent le même rôle. Déplace les points pour t'en faire une idée :
La table d'embeddings range les embeddings de tous les tokens : une ligne par token. Ses nombres sont des poids : tirés au hasard au départ, puis ajustés par la descente de gradient. Il n'existe pas de « bon » embedding auquel les comparer : la perte compare les spécialités prédites aux vraies, et la rétropropagation remonte jusqu'à la table.
Un résumé entier : on additionne les embeddings composante par composante, puis on divise par le nombre de tokens. « fièvre sans toux » : ([1;0] + [0;1] + [1;1]) / 3 = [0,67 ; 0,67] ; « toux sans fièvre » donne la même chose : l'ordre est toujours perdu.
Le piège du paquet. Dans un paquet, chaque numéro, <rempl> compris, reçoit un embedding. Si on divise la somme par la longueur du paquet, les remplissages comptent comme de vrais tokens et tirent la moyenne vers 0. Voyons-le :
À toi de coder
En PyTorch, c'est exactement ce calcul, sur tout le paquet à la fois :
emb = nn.Embedding(len(vocab), 64, padding_idx=0) # la table : une ligne de 64 poids par token vecteurs = emb(paquet) # (paquet, longueur) → (paquet, longueur, 64) poids = masque.unsqueeze(2) # (paquet, longueur) → (paquet, longueur, 1) somme = (vecteurs * poids).sum(dim=1) # les remplissages comptent 0 moyenne = somme / poids.sum(dim=1) # divisé par le nombre de vrais tokens
padding_idx=0 laisse à zéro la ligne de <rempl> et ne l'apprend pas. unsqueeze(2) ajoute une dimension de taille 1 pour que chaque 0 ou 1 du masque multiplie les 64 composantes d'un embedding. La table pèse 22 704 × 64 = 1 453 056 poids.