Aller au contenu
8/30Chapitre 8 sur 30

Prédiction du token suivant : embeddings et sens de la perplexité

Entraînez un modèle caractère sur 32 033 noms : gradient descent retrouve une table de comptages, puis la perplexité se complique.

Dans cet article

Voici dix noms produits par un programme qui n’a jamais vu un mot :

TEXT
cexze   momakurailezitynn   konimittain   llayn   ka
da      moliellavo          emia          sade    ftlsp

Aucun d’eux n’est un nom. Presque tous essaient de l’être. Ils sont prononçables, ils se terminent comme des noms, et l’un d’eux — emia — n’est qu’à une lettre d’un vrai. Le programme qui les a produits contient 729 nombres, n’a aucune notion de mot, de syllabe ou de personne, et a été ajusté par un simple passage de comptage de paires de lettres adjacentes.

À la fin de ce chapitre, un réseau de neurones aura réduit d’un tiers le score de ce programme sur la même mesure. Ce qui vaut la peine de rester, c’est ce que le réseau fait d’abord : il reproduit la table de comptages à trois décimales près sur chaque ligne bien remplie, sans prompt, parce que les deux objets sont des réponses à la même question. Tout ce qui vient ensuite est ce que le comptage n’aurait jamais pu faire.

L’objectif est une identité, pas un choix de conception

Lien vers la section : L’objectif est une identité, pas un choix de conception

Le chapitre 7 vous a laissé avec une séquence d’entiers et aucune raison pour que l’un suive l’autre. Voici cette raison, en une ligne du chapitre 2.

Un modèle de langage est une fonction qui prend les tokens vus jusque-là et renvoie une distribution sur le token qui vient ensuite : un nombre par entrée du vocabulaire, non négatif, dont la somme vaut un. Rien d’autre. Pour passer de là à une probabilité pour un document entier, appliquez la règle de la chaîne des probabilités :

P(x1,x2,,xT)=t=1TP(xtx1,,xt1)P(x_1, x_2, \ldots, x_T) = \prod_{t=1}^{T} P(x_t \mid x_1, \ldots, x_{t-1})

C’est une identité, vraie pour n’importe quelle séquence de n’importe quoi, sans hypothèse attachée. Donc un modèle qui accomplit la petite tâche — le token suivant étant donnés les précédents — a déjà accompli la grande tâche consistant à attribuer une probabilité à chaque document possible, exactement et gratuitement. Présenter cela comme une astuce bon marché (« il ne fait que prédire le mot suivant ») inverse la logique : prédire le token suivant, c’est modéliser la distribution jointe. Il n’y a jamais eu de deuxième chose à faire.

La loss en découle tout aussi mécaniquement. À chaque position, le modèle produit une distribution qq et la vérité est un token connu unique ; la cross-entropy du chapitre 4 s’applique donc sans modification :

L=1Tt=1Tlogqθ(xtx<t)L = -\frac{1}{T}\sum_{t=1}^{T} \log q_\theta(x_t \mid x_{<t})

C’est la log-vraisemblance négative moyenne — la recette du chapitre 2 avec une distribution catégorielle à l’endroit où se trouvait la gaussienne. Et puisque la vraie distribution est one-hot, son entropie est nulle ; par l’identité du chapitre 4, la cross-entropy égale donc la divergence KL : faire baisser ce nombre et rapprocher les croyances du modèle de celles des données sont un seul et même acte.

Une conséquence mérite sa propre phrase, parce que c’est le fait économique sous-jacent à tout le domaine. Les étiquettes sont les données, décalées d’une position. Personne n’annote quoi que ce soit. Mille milliards de tokens de texte, ce sont mille milliards d’exemples pré-étiquetés, ce qui explique pourquoi le corpus d’entraînement d’un modèle moderne est « internet » et non « un dataset construit par quelqu’un ».

Avant tout réseau, la baseline : 32 033 noms, un par ligne, et la tâche d’en produire d’autres une lettre à la fois.1

Le vocabulaire contient 26 lettres plus un symbole de frontière . qui marque à la fois le début et la fin d’un nom ; le modèle doit donc apprendre où les noms commencent et où ils s’arrêtent. Cela fait 27 symboles, et le plus petit modèle possible est une table indiquant à quelle fréquence chaque symbole suit chaque autre symbole.

bigram.pyPYTHON
N = torch.zeros((27, 27), dtype=torch.int32)
for w in words:
    cs = ["."] + list(w) + ["."]
    for a, b in zip(cs, cs[1:]):
        N[stoi[a], stoi[b]] += 1

P = N.float()
P = P / P.sum(1, keepdim=True)            # one distribution per row   

Deux lignes d’arithmétique et le modèle est ajusté — et ce n’est pas une heuristique : diviser les comptages par les totaux de ligne est l’estimateur du maximum de vraisemblance pour une distribution catégorielle, c’est-à-dire la recette du chapitre 2 avec le calcul différentiel déjà fait.

TEXT
names: 32033        train/val/test: 25626 / 3203 / 3204
training bigrams: 182583

the six most likely letters after 'a':
    a -> '.'  0.1944   a -> 'n'  0.1600   a -> 'r'  0.0967
    a -> 'l'  0.0749   a -> 'h'  0.0690   a -> 'y'  0.0606

Échantillonnez depuis cette table — choisissez une lettre dans la ligne de la lettre courante, passez à cette ligne, répétez jusqu’à ce que le symbole de frontière apparaisse — et vous obtenez les noms du début de ce chapitre. Ils échouent d’une manière précise et instructive : plausibles localement, absurdes globalement. Chaque paire de lettres adjacentes dans momakurailezitynn est une paire qui apparaît dans de vrais noms ; il y en a simplement dix-sept d’affilée. Le modèle n’a qu’une lettre de mémoire, il ne peut donc pas savoir qu’il dure depuis trop longtemps.

La loss sur les noms de validation est de 2,4546 nats. Ce nombre ne signifie rien à lui seul, d’où l’existence de la perplexité :

PPL=exp ⁣(1Ttlogq(xtx<t))=eL\mathrm{PPL} = \exp\!\left(-\frac{1}{T}\sum_t \log q(x_t \mid x_{<t})\right) = e^{L}

Écrit explicitement, sans bibliothèque pour faire le travail :

perplexity.pyPYTHON
@torch.no_grad()
def perplexity(logits, Y):
    logp = F.log_softmax(logits, dim=1)          # log q for every symbol
    chosen = logp[torch.arange(len(Y)), Y]       # log q of the one that came next   
    return torch.exp(-chosen.mean())             

L’exponentiation défait le logarithme et ramène le nombre dans les unités du comptage de choses. La manière la plus nette de voir ce qu’il compte est de mesurer un modèle qui ne sait rien du tout — un modèle qui attribue la probabilité 1/271/27 à chaque symbole quel que soit le contexte :

TEXT
uniform over 27 symbols            loss 3.2958 nats   ppl  27.000
bigram counts, add-one smoothed    loss 2.4546 nats   ppl  11.642

Exactement 27,000, parce que elog27=27e^{\log 27} = 27. La perplexité est le nombre effectif d’options également probables entre lesquelles le modèle choisit. Une perplexité de 27 signifie « aucune idée, ça pourrait être n’importe quoi ». Les 11,642 du modèle de comptage signifient qu’une lettre de contexte le laisse aussi incertain que quelqu’un qui choisirait à l’aveugle parmi environ douze options au lieu de vingt-sept — c’est pour cela qu’on cite la perplexité, et non la loss brute.

Deux choses tournent mal avec elle, et la seconde tourne mal dans des articles publiés.

Les probabilités nulles sont fatales. Sur les 729 cellules de la table, 113 n’apparaissent jamais à l’entraînement — 15,5 % de la table est vide. Tout va bien jusqu’à ce que l’ensemble de validation tombe dans l’une d’elles, et sept bigrammes de validation le font, notamment dq, zj et qo deux fois. Une probabilité zéro signifie log -\infty, donc une loss infinie et une perplexité infinie : un nom sur trois mille détruit la métrique. Le correctif habituel consiste à ajouter 1 à chaque comptage avant de normaliser, ce qui ne coûte presque rien ici (2,4546 au lieu de 2,4524). Mais ce correctif est un aveu. Un modèle de comptage ne peut pas généraliser du tout. Il n’a aucun moyen de soupçonner que qo est plausible parce que qu est courant et que o se comporte comme u ailleurs, puisqu’il n’a aucune notion que deux symboles puissent se ressembler. Chaque cellule est apprise seule, et corriger cela est l’objet du reste de ce chapitre.

La perplexité est un prix par token, et le token est un paramètre libre. C’est l’erreur qui apparaît sans cesse quand on compare des modèles, et elle devient évidente dès qu’on regarde. Prenez le même corpus de prose anglaise que dans le chapitre 7, le même modèle bigramme interpolé, et changez uniquement la façon dont le texte est découpé :

unitévocabulairetokens dans le testcross-entropyperplexitébits par caractère
caractères7614 4692,521712,453,6378
BPE, 512 fusions3296 8713,854747,212,6407
BPE, 2 048 fusions1 8204 2335,7468313,202,4254
mots2 9916 2843,562735,262,2322

La perplexité varie d’un facteur 25 entre ces lignes. Rien n’a changé dans le modèle ; seule la taille de la chose prédite a changé. Prédire un mot entier est plus difficile que prédire une lettre, cela coûte donc plus par prédiction — et il y a moins de prédictions à faire.

Lisez maintenant la dernière colonne, qui divise plutôt le coût total par le nombre de caractères et le convertit en bits. Elle réordonne la table. Selon la perplexité, le classement est caractères, mots, BPE-512, BPE-2048 ; selon les bits par caractère, il est mots, BPE-2048, BPE-512, caractères. Le modèle caractère passe de la première à la dernière place. Le modèle à 2 048 fusions, qui semble 6,6 fois pire que celui à 512 fusions selon la perplexité, est en réalité le meilleur des deux avec 2,4254 bits contre 2,6407.

Une perplexité n’est donc comparable qu’entre deux modèles qui partagent un tokenizer, et les modèles avec des tokenizers différents ne peuvent être comparés qu’en bits par caractère — la grandeur que Shannon a mesurée en 1951 en demandant à des sujets humains de deviner la lettre suivante d’un texte anglais, et qu’il a bornée à environ un bit par caractère.2 Notre meilleur bigramme est à 2,23 bits, ce qui résume honnêtement le chemin que ce chapitre doit encore parcourir.

Construisons maintenant le même modèle sous forme de réseau. Il faudra des ordres de grandeur d’arithmétique en plus pour arriver au même endroit, et arriver au même endroit est justement le point.

Remplacez la table par une matrice de poids WW de forme 27×2727 \times 27. Transformez la lettre courante en vecteur one-hot, multipliez, et appelez le résultat logits — les scores non normalisés du chapitre 4. Puis softmax, puis cross-entropy, puis gradient descent.

neural_bigram.pyPYTHON
W = torch.randn((27, 27), requires_grad=True)

for step in range(3000):
    logits = W[xs]                            
    loss = F.cross_entropy(logits, ys)
    W.grad = None
    loss.backward()
    W.data -= 50.0 * W.grad

La ligne mise en évidence contient une définition qui vaut la peine d’être gardée. Multiplier un vecteur one-hot par une matrice sélectionne une ligne de celle-ci ; la multiplication est donc un lookup — et toute implémentation saute l’arithmétique pour faire directement le lookup, ce qui correspond à W[xs].

C’est une embedding table. Une matrice avec une ligne par entrée du vocabulaire, indexée par id de token. Pas de géométrie, pas de sémantique, pas d’algorithme séparé : une table de lookup dont le contenu se trouve être appris par gradient descent avec tout le reste. Toute affirmation mystique sur l’« embedding space » aboutit ici.

Entraînez-la et observez où elle va :

TEXT
  step     1   train 3.7550   val 3.3882   max gap to the count table 0.757269
  step   100   train 2.4732   val 2.4726   max gap to the count table 0.388354
  step  1000   train 2.4557   val 2.4549   max gap to the count table 0.041862
  step  3000   train 2.4547   val 2.4544   max gap to the count table 0.004048

La dernière colonne est la plus grande différence absolue entre une cellule de softmax(W) et la cellule correspondante de la table de comptages, et elle tend vers zéro. Après 3 000 steps, le plus grand désaccord parmi les 729 cellules est de 0,004048 et la moyenne est de 0,000224. La pire cellule est qi, observée douze fois dans tout l’ensemble d’entraînement ; parmi les 22 lignes comptant plus de mille occurrences, le pire désaccord est de 0,000562.

TEXT
                 count table   network
    a -> '.'        0.1945     0.1945
    a -> 'n'        0.1601     0.1601
    a -> 'r'        0.0967     0.0967

Gradient descent, en partant de nombres aléatoires et sans autre instruction que « rendez grande la log-probabilité de la lettre suivante », a redécouvert la table de comptages. Et il le devait : les comptages sont l’estimateur du maximum de vraisemblance, la cross-entropy est la log-vraisemblance négative, donc les deux procédures optimisent le même objectif et cet objectif a un unique optimum. Le réseau n’a pas appris quelque chose comme le comptage. Il a convergé vers le comptage, lentement.

Ce qui soulève la question légitime de savoir pourquoi quelqu’un s’en donnerait la peine. Parce que la table de comptages n’a plus nulle part où aller, alors que le réseau, si.

Le contexte est le goulot d’étranglement, pas la capacité

Lien vers la section : Le contexte est le goulot d’étranglement, pas la capacité

Étendez le modèle pour regarder plus d’un caractère précédent. C’est l’architecture de Bengio de 2003, l’ancêtre direct de tous les modèles du reste de ce cours :4 prenez les trois derniers caractères, mappez chacun via une embedding table vers une ligne à 10 dimensions, concaténez les lignes en 30 nombres, poussez-les à travers la couche cachée du chapitre 5, puis terminez par une couche de sortie produisant un logit par entrée du vocabulaire.

mlp.pyPYTHON
C  = torch.randn((27, 10))          # the embedding table
W1 = torch.randn((3 * 10, 200))     # the hidden layer from Chapter 5
W2 = torch.randn((200, 27))         # one output per vocabulary entry

emb = C[X].view(-1, 30)             # three lookups, concatenated   
h = torch.tanh(emb @ W1 + b1)
logits = h @ W2 + b2                
loss = F.cross_entropy(logits, Y)

Notez ce qui est nouveau et ce qui ne l’est pas. La couche cachée est celle du chapitre 5, inchangée ; la loss est celle du chapitre 4, inchangée. Les nouveautés sont l’embedding table à l’avant et une couche de sortie aussi large que le vocabulaire du chapitre 7 — et cette seconde partie est le composant coûteux de tout modèle de langage jamais construit, parce qu’un vrai vocabulaire contient 100 000 entrées et que cette multiplication matricielle s’exécute à chaque position.

Le même code, entraîné de manière identique, en ne changeant que la taille du context window :

contexteparamètresloss de validationperplexité de validation
comptage, 1 caractère7292,454611,642
neural, 1 caractère7 8972,457711,678
neural, 3 caractères11 8972,11458,285
neural, 8 caractères21 8972,05067,773

La deuxième ligne est la plus intéressante. Un réseau avec une couche cachée de 200 unités et onze fois plus de paramètres que la table de comptages se comporte exactement aussi bien que la table de comptages, et pas mieux. La capacité n’a jamais été la limitation. Un caractère de contexte permet une certaine loss, et rien de ce que vous greffez dessus ne peut descendre en dessous, parce que l’information n’est pas là.

Donnez-lui trois caractères et la perplexité passe de 11,68 à 8,29 — une baisse de 29 %, achetée avec 4 000 paramètres supplémentaires. Il bat le comptage ici pour précisément la raison diagnostiquée plus tôt : un modèle de comptage sur des contextes de trois caractères nécessite 273=19,68327^3 = 19{,}683 lignes, dont la plupart sont vides ou ne contiennent qu’une seule observation, et il apprend chacune isolément. Le réseau partage. Si a, e et i finissent avec des lignes d’embedding similaires, ce qu’il apprend après bra se transfère à bre sans qu’il ait jamais vu bre. Ce transfert est toute la valeur de l’embedding table, et c’est l’écart entre les lignes deux et trois.

Les échantillons s’améliorent en conséquence :

TEXT
deliah   nellara   joce     kael      quintis
salayson  reety    khyrmin  mahnen    madiaryxia

Ce n’est toujours pas une liste de vrais noms. Mais deliah, nellara et kael n’y détonneraient pas, et les monstres interminables ont disparu : le plus long des vingt échantillons du modèle de comptage compte dix-neuf lettres, le plus long des vingt de celui-ci en compte treize.

Ce qu’il y a réellement dans l’embedding table

Lien vers la section : Ce qu’il y a réellement dans l’embedding table

La table est 27×1027 \times 10 : une ligne de dix nombres par caractère, toutes initialisées aléatoirement et déplacées uniquement par le gradient de la loss du caractère suivant. Personne n’y a rien mis. Alors qu’y a-t-il fini ?

L’outil pour poser la question est la similarité cosinus, qui est le produit scalaire du chapitre 1 avec les longueurs divisées :

cos(a,b)=abab\cos(\mathbf{a}, \mathbf{b}) = \frac{\mathbf{a} \cdot \mathbf{b}}{\lVert \mathbf{a} \rVert \, \lVert \mathbf{b} \rVert}

Elle mesure l’angle entre deux vecteurs et ignore leurs longueurs, ce que vous voulez quand la longueur d’une ligne reflète la fréquence d’apparition de son token plutôt que ce qu’il signifie. Normalisez d’abord chaque vecteur à une longueur de 1 — comme le font les systèmes réels, une fois, au moment de l’indexation — et la similarité cosinus est simplement le produit scalaire.

Voici les plus proches voisins de quelques caractères dans la table entraînée :

TEXT
  'c' -> 'k':+0.598      'j' -> 'z':+0.650      'i' -> 'y':+0.541
  'u' -> 'e':+0.482      'a' -> 'h':+0.367      '.' -> 'q':+0.077

Une partie correspond à ce que promet le folklore. c et k sont interchangeables dans les noms, tout comme i et y ; j et z sont deux consonnes rares, surtout initiales, qui se comportent de façon similaire. Le symbole de frontière . n’est proche de rien du tout — 0,077 de sa lettre la plus proche — parce qu’il est le seul symbole qui marque une position plutôt qu’un son.

Et une partie non. Le plus proche voisin de a est h, pas une autre voyelle. En moyenne sur toutes les paires :

TEXT
mean cosine, vowel to vowel         : +0.1889
mean cosine, consonant to consonant : +0.0765
mean cosine, vowel to consonant     : -0.0042

Les voyelles se ressemblent davantage entre elles qu’elles ne ressemblent aux consonnes, et l’effet est réel mais faible. Testé contre 2 000 groupes de cinq lettres choisis aléatoirement, 58 de ces groupes se séparent au moins aussi nettement — un écart significatif à environ p=0.03p = 0.03. Réel, donc, mais très loin de l’île géométrique nette que les récits populaires sur les embeddings suggèrent.

C’est la description honnête d’une embedding table, et elle mérite d’être gardée pour le reste du cours. Ce n’est pas une carte du sens. C’est un changement de coordonnées, appris plutôt que conçu, dont le seul rôle est de faciliter la tâche de la couche suivante — la même phrase que le chapitre 5 utilisait pour la couche cachée qui pliait le plan afin de résoudre XOR. Toute structure que vous y trouvez est là parce qu’elle a abaissé la loss, et toute structure qui n’abaisse pas la loss n’y est tout simplement pas.

word2vec, GloVe et l’arithmétique que tout le monde cite

Lien vers la section : word2vec, GloVe et l’arithmétique que tout le monde cite

Si la partie utile est la table, vous pouvez la viser directement. C’est word2vec : garder le lookup d’embedding, jeter le modèle de langage.5

L’objectif skip-gram with negative sampling tient en une ligne. Pour une vraie paire (centre, contexte) tirée du corpus, poussez leur produit scalaire vers le haut ; pour kk fausses paires tirées d’une distribution de bruit, poussez-le vers le bas :6

logσ(vcvo)+i=1klogσ(vcvni)\log \sigma(\mathbf{v}_c \cdot \mathbf{v}_o) + \sum_{i=1}^{k} \log \sigma(-\mathbf{v}_c \cdot \mathbf{v}_{n_i})

C’est une classification binaire — « ces deux mots sont-ils vraiment apparus ensemble ? » — et elle est bon marché précisément parce qu’elle ne touche jamais tout le vocabulaire, ce qui a rendu pratique l’entraînement sur des milliards de mots en 2013. GloVe arrive à des vecteurs similaires depuis l’autre direction, en factorisant la matrice des comptages de cooccurrence globale au lieu de parcourir des exemples en flux.7 Les deux sont ajustés exactement sur la statistique dont la table de comptages était faite. Ce sont du comptage, compressé.

Entraînés sur text8 — 17 005 207 mots de Wikipédia en anglais, dont 71 290 apparaissent au moins cinq fois, 100 dimensions, trois passes — les vecteurs sortent avec la propriété qui les a rendus célèbres :

TEXT
king     -> charles 0.700, son 0.693, queen 0.686, henry 0.669, throne 0.667
physics  -> chemistry 0.672, electromagnetism 0.661, quantum 0.654, theoretical 0.624
guitar   -> bass 0.733, vocals 0.732, acoustic 0.728, guitars 0.703, drums 0.685
three    -> seven 0.892, two 0.877, one 0.875, five 0.871, four 0.870

Personne n’a fourni de catégorie pour les instruments ou pour les numéraux. Maintenant la partie célèbre : prenez king, soustrayez man, ajoutez woman, puis trouvez le vecteur le plus proche du résultat.

TEXT
king - man + woman
   nothing excluded : king 0.693, elizabeth 0.657, wife 0.629, woman 0.607
   a, b, c excluded : elizabeth 0.657, wife 0.629, mary 0.607   (queen is 4th, 0.604)

Le vecteur le plus proche de king - man + woman est king. Ce n’est pas une bizarrerie d’un exemple. L’ensemble d’évaluation de Mikolov pose des questions de la forme a : b :: c : ? — 8 869 sémantiques (paris : france :: rome : italy) et 10 675 syntaxiques (walking : walked :: swimming : swam) — et, sur les 4 103 questions sémantiques auxquelles ce vocabulaire peut répondre, le gagnant est l’un des trois mots d’entrée 99,8 % du temps. Les démonstrations publiées ne le mentionnent pas, parce que la règle de score standard supprime a, b et c avant de regarder. C’est une règle légitime, et elle fait plus de travail que l’arithmétique :

mode de choix de la réponsesémantiquesyntaxique
offset, avec les entrées exclues (standard)17,0 %11,9 %
offset, sans rien exclure0,1 %0,4 %
plus proche voisin de c seul, entrées exclues13,1 %9,3 %
plus proche voisin de b seul, entrées exclues2,3 %0,4 %

La troisième ligne est celle sur laquelle il faut s’arrêter. Jetez a et b, ne faites aucune arithmétique, renvoyez simplement ce qui est le plus proche de c — et vous conservez 77 % du score sémantique. La majeure partie de ce qui ressemble à du raisonnement analogique est de la proximité plus une règle qui interdit les réponses évidentes, ce que Linzen a mesuré sur des vecteurs correctement entraînés et ce que les baselines ci-dessus répliquent.8 Ces vecteurs précis sont petits — 17 millions de mots contre les milliards derrière les modèles publiés — donc lisez les pourcentages comme une forme, pas comme l’état de l’art. Cette forme survit à toutes les échelles : l’arithmétique est réelle, et bien plus faible que la démonstration que tout le monde cite.

Statique et contextuel : un vecteur par mot, ou un par occurrence

Lien vers la section : Statique et contextuel : un vecteur par mot, ou un par occurrence

Tout ce qui précède a une limite dure intégrée à la structure de données. Une table a une ligne par token. Le mot bank reçoit un vecteur, le même dans une phrase sur une rivière et dans une phrase sur un prêt immobilier — nécessairement, puisqu’un lookup par id ne peut dépendre de rien d’autre.

La solution consiste à arrêter de lire le vecteur dans la table et à commencer à le calculer depuis la phrase. C’est un contextual embedding, introduit par ELMo en 2018 et rendu standard par BERT la même année.910 Mesurés sur le vrai modèle, les nombres sont plus nets que l’explication :

TEXT
sentence A: "He sat on the bank of the river and watched the water go by."
sentence B: "She deposited the cheque at the bank on the corner of the street."

static vector for 'bank' (a row of the input embedding table)
    cosine A vs B ........................ 1.000000

contextual vector for 'bank', layer by layer
    layer  |  A vs B  |  A vs another river sentence  |  B vs another money sentence
        0  |  0.9512  |            0.9512             |            0.9359
        4  |  0.5647  |            0.8987             |            0.7716
        9  |  0.4284  |            0.8699             |            0.7568
       12  |  0.5278  |            0.8702             |            0.7335

La première ligne est exacte, pas approximative : le vecteur statique de bank est les mêmes 768 nombres dans les deux phrases, donc le cosinus vaut 1 par construction. Neuf couches plus tard, les deux occurrences sont à 0,43, tandis que bank dans deux phrases différentes sur des rivières reste à 0,87. Personne n’a étiqueté un sens à aucun moment de ce processus ; les sens se sont séparés parce que les séparer facilite l’objectif d’entraînement — deviner un token masqué à partir de ses voisins.

Deux détails méritent attention. La couche 0 est déjà à 0,9512 plutôt qu’à 1,0, parce que les embeddings de position ont été ajoutés et que le mot occupe une place différente dans chaque phrase. Et la similarité remonte aux couches 11 et 12 : les dernières couches d’un modèle préentraîné sont spécialisées pour son objectif d’entraînement, et ne sont souvent pas le meilleur endroit où prendre une représentation.

Afficher les détails

Optionnel : weight tying.

Dans bert-base-uncased, l’embedding table est 30,522×76830{,}522 \times 768 — 23 440 896 nombres, 21,4 % des 109 482 240 paramètres du modèle. Dans un petit modèle de langage, la fraction est encore plus grande, ce qui explique pourquoi une astuce est presque universelle : la table d’entrée et la couche de sortie qui produit les logits sont la même matrice, utilisée une fois par lookup de ligne et une fois transposée.11 La couche de sortie attribue déjà un vecteur à chaque entrée du vocabulaire — elle prend un produit scalaire avec chacune — et le tying dit que le vecteur utilisé pour lire un token et le vecteur utilisé pour l’écrire doivent être le même objet. Cela réduit les paramètres et améliore la perplexité en même temps, ce qui est assez rare pour être remarqué.

Un embedding model n’est pas un modèle de langage

Lien vers la section : Un embedding model n’est pas un modèle de langage

Pour chercher dans un corpus par sens, il vous faut un vecteur par phrase. Une fois que vous les avez, la recherche est triviale — c’est tout le cœur de la récupération sémantique, et le chapitre 19 porte sur tout ce qui l’entoure :

search.pyPYTHON
E = normalise(embed(sentences))       # (200, d), every row of length 1
q = normalise(embed([query]))         # (1, d)
scores = q @ E.T                      # one matrix multiply   
top5 = scores[0].argsort()[::-1][:5]

La seule vraie question est donc de savoir d’où vient embed. Le mouvement évident consiste à prendre un modèle de langage préentraîné, à faire passer chaque phrase dedans et à moyenner les vecteurs de tokens. Voici cette méthode face à quatre alternatives, évaluées de deux façons : la corrélation de rang entre le cosinus et les jugements humains de similarité sur les 1 379 paires du benchmark STS, et la récupération top-1 sur un index construit à partir des 200 paires les plus fortement paraphrasées — un côté de chaque paire étant indexé, l’autre utilisé comme requête.

méthode d’embedding de la phrasecorrélation de rangtop-1 sur un index de 200 phrases
recouvrement binaire des mots (aucun modèle)0,550089,0 %
moyenne des vecteurs statiques entraînés ci-dessus0,526385,5 %
BERT, le token [CLS]0,203067,0 %
BERT, moyenne des vecteurs de tokens0,472984,0 %
MiniLM, entraîné de manière contrastive0,820392,0 %

Lisez les trois lignes du milieu à la lumière des deux premières. Un transformer préentraîné de 109 millions de paramètres, utilisé de la manière évidente, est moins bon pour juger la similarité de phrases que compter combien de mots deux phrases partagent — et moins bon que moyenner les vecteurs text8 à 100 dimensions entraînés il y a un instant. Le token [CLS], que des tutoriels recommandent encore parce que BERT a été préentraîné avec un objectif au niveau de la phrase attaché à lui, est pire que la moitié de cela.

Ce n’est pas un défaut de BERT. C’est l’objectif. Un modèle de langage est entraîné pour que ses états cachés prédisent un token ; rien là-dedans ne demande à deux paraphrases de se retrouver proches, et rien ne récompense une géométrie où le cosinus signifie « même sens ». La dernière ligne est un modèle cinq fois plus petit (22 713 216 paramètres) entraîné sur une loss entièrement différente : l’apprentissage contrastif, où les exemples sont des paires — une question et sa réponse, une phrase et sa paraphrase — et où l’objectif rapproche les vraies paires tout en repoussant les négatifs échantillonnés. C’est la contribution de Sentence-BERT et l’origine de toute l’industrie des embedding models.12 Dense Passage Retrieval applique la même recette directement à la recherche, avec un encodeur pour les requêtes et un autre pour les passages.13

Donc, la règle pratique :

Un embedding model n’est pas un modèle de langage dont on a retiré la dernière couche. C’est un modèle différent, avec un objectif différent, généralement beaucoup plus petit, dont le cosinus signifie ce que vous voulez qu’il signifie parce qu’il a été entraîné sur des paires où c’était la cible. Le tableau ci-dessus est le coût de la substitution de l’un à l’autre.

Et cette famille échoue sur l’ordre des mots. « The dog bit the man » et « the man bit the dog » ont des sacs de mots identiques, donc le recouvrement de mots et la moyenne des vecteurs statiques leur donnent un cosinus exactement égal à 1,000000, et BERT avec mean pooling, qui voit pourtant la position, arrive presque au même résultat — et MiniLM entraîné de manière contrastive les place encore à 0,979. Si votre tâche de récupération dépend de qui a fait quoi à qui, aucun seuil de cosinus ne vous sauvera.

Le chapitre 19 construit un système de récupération de production sur cette base et aboutit à un seuil de cosinus concret. La dernière mesure de ce chapitre est ce qui rend un tel nombre défendable plutôt que magique.

La malédiction de la dimensionnalité, en un tableau

Lien vers la section : La malédiction de la dimensionnalité, en un tableau

Les embeddings réels ont des centaines ou des milliers de composantes, et les distances se comportent étrangement là-haut. Prenez 1 000 points aléatoires dans le cube unité de dd dimensions et regardez le ratio entre la plus grande et la plus petite distance entre deux d’entre eux :

dimensionspaire la plus prochepaire la plus éloignéeratio
20,00071,36121921,66
100,23612,33979,91
1003,00475,17521,72
1 00011,780914,03061,19
10 00039,615242,01251,06

En dix mille dimensions, la paire de points la plus éloignée n’est que 6 % plus distante que la paire la plus proche. Tout est à peu près équidistant de tout le reste, « plus proche voisin » cesse de porter beaucoup d’information, et c’est cela, la malédiction de la dimensionnalité — ainsi qu’une des raisons pour lesquelles les grandes bases de données vectorielles ne font pas de recherche exacte du plus proche voisin. L’autre face de la même pièce est ce qui rend les seuils de cosinus exploitables : mesurée sur mille paires de vecteurs unitaires aléatoires, la moyenne du cosinus se situe à 0.0052-0.0052 en 100 dimensions et +0.0003+0.0003 en 768, avec des écarts-types de 0,0968 et 0,0357 — et en 768 dimensions, seules 0,2 % des paires aléatoires dépassent 0,1 en valeur absolue. Une similarité mesurée de 0,4 ne signifie donc pas « semblables à 40 % » ; elle est très au-delà de ce que le hasard produit, ce qui explique pourquoi des seuils entre 0,3 et 0,7 séparent le signal du bruit au lieu de se situer au milieu de celui-ci.

Le modèle de ce chapitre lit un nombre fixe de caractères précédents, lookup chacun d’eux et colle les résultats dans l’ordre. Cette conception a deux problèmes, et ce sont le même problème.

Regardez à nouveau le tableau de contexte : passer de trois caractères à huit a presque doublé les paramètres et acheté 0,06 nat. Le coût croît linéairement avec le contexte — chaque position supplémentaire a besoin de sa propre tranche de la première matrice de poids — et le bénéfice non. Poussez cela à mille tokens et la première couche à elle seule pèse plus lourd que le reste du modèle, en grande partie dépensée sur des positions qui ne comptent pas pour une prédiction donnée.

Ce qui est le second problème : le modèle n’a aucun moyen de décider lesquels des tokens précédents comptent. La position deux reçoit ses propres poids et la position sept les siens, en permanence, quel que soit leur contenu. Quand le modèle épelle nell, le caractère décisif est celui qui précède immédiatement. Quand une phrase contient un pronom, le mot qui fixe son référent peut être quarante tokens en arrière — et aucun emplacement fixe ne peut être assigné à « quarante en arrière », parce que la fois suivante ce sera six.

Ce que nous voulons, c’est un modèle qui calcule, pour chaque prédiction, combien chaque token antérieur doit compter — des poids sur le contexte produits par le contenu plutôt que fixés par la disposition. Écrivez cela soigneusement et cela commence comme quelque chose de tout à fait banal : une moyenne sur les tokens précédents. Puis laissez les poids de cette moyenne être appris, et laissez-les dépendre du token qui pose la question.

C’est attention, et c’est le chapitre 9.


À lire aussi en parallèle : le chapitre 3 de Speech and Language Processing de Jurafsky et Martin, qui traite les modèles n-grammes, le smoothing et la perplexité bien plus soigneusement que l’espace ne le permet ici, y compris pourquoi l’interpolation et le back-off battent l’ajout de un ; les notes Stanford CS229 §17.1–17.2 pour la modélisation du langage côté probabiliste ; et l’article de Linzen ci-dessus, court et à lire en entier.

  1. L’exemple de génération de noms, le dataset et la progression d’une table de comptages vers un réseau de style Bengio suivent la série building makemore d’Andrej Karpathy, dont les deux premières parties sont le meilleur compagnon de ce chapitre.

  2. Shannon, C. E. Prediction and Entropy of Printed English. Bell System Technical Journal 30(1), p. 50–64 (1951). Des sujets humains devinant la lettre suivante d’un texte anglais, et la mesure originale en bits par caractère.

  3. Shannon, C. E. A Mathematical Theory of Communication. Bell System Technical Journal 27 (1948). Le théorème de codage de source, et l’identification de la prédiction à la compression.

  4. Bengio, Y., Ducharme, R., Vincent, P. et Jauvin, C. A Neural Probabilistic Language Model. Journal of Machine Learning Research 3, p. 1137–1155 (2003). L’architecture utilisée ci-dessus : un embedding par mot, concaténé sur une fenêtre fixe, à travers une couche cachée, vers un softmax sur le vocabulaire.

  5. Mikolov, T., Chen, K., Corrado, G. et Dean, J. Efficient Estimation of Word Representations in Vector Space. arXiv:1301.3781 (2013). CBOW et skip-gram, ainsi que l’ensemble d’analogies utilisé ci-dessus.

  6. Mikolov, T., Sutskever, I., Chen, K., Corrado, G. et Dean, J. Distributed Representations of Words and Phrases and their Compositionality. arXiv:1310.4546 (2013). Negative sampling, sous-échantillonnage des mots fréquents, et distribution de bruit élevée à la puissance 3/4 utilisée ci-dessus.

  7. Pennington, J., Socher, R. et Manning, C. GloVe: Global Vectors for Word Representation. EMNLP 2014. Des vecteurs de mots issus d’une factorisation de la matrice globale de cooccurrence plutôt que de fenêtres locales parcourues en flux.

  8. Linzen, T. Issues in evaluating semantic spaces using word analogies. RepEval 2016, arXiv:1606.07736. La source des baselines sans offset répliquées ci-dessus.

  9. Peters, M. et al. Deep contextualized word representations. arXiv:1802.05365 (2018). ELMo : un vecteur par occurrence, calculé par un modèle de langage bidirectionnel.

  10. Devlin, J., Chang, M.-W., Lee, K. et Toutanova, K. BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. arXiv:1810.04805 (2018). Le modèle mesuré dans l’expérience bank.

  11. Press, O. et Wolf, L. Using the Output Embedding to Improve Language Models. arXiv:1608.05859 (2016), et Inan, H., Khosravi, K. et Socher, R. Tying Word Vectors and Word Classifiers. arXiv:1611.01462 (2016). Deux arguments indépendants pour la même astuce.

  12. Reimers, N. et Gurevych, I. Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks. arXiv:1908.10084 (2019). Sa mesure d’ouverture — BERT avec mean pooling sous-performant les vecteurs statiques moyennés sur la similarité de phrases — est ce que le tableau ci-dessus reproduit.

  13. Karpukhin, V. et al. Dense Passage Retrieval for Open-Domain Question Answering. arXiv:2004.04906 (2020). Entraînement contrastif d’un récupérateur à deux encodeurs ; l’ancêtre direct de la pile de récupération du chapitre 19.

Prêt à laisser LIA choisir à votre place ?

Créez avec tous les modèles d'IA au même endroit — commencez gratuitement dès aujourd'hui.