Hoppa till innehållet
8/30Kapitel 8 av 30

Next-token prediction: embedding och vad perplexity betyder

Träna en teckenmodell på 32 033 namn och se gradient descent återupptäcka en räknetabell – plus varför perplexity sällan matchar.

På den här sidan

Här är tio namn producerade av ett program som aldrig har sett ett ord:

TEXT
cexze   momakurailezitynn   konimittain   llayn   ka
da      moliellavo          emia          sade    ftlsp

Inget av dem är ett namn. Nästan alla försöker. De går att uttala, de slutar där namn brukar sluta, och ett av dem — emia — är en enda bokstav från ett riktigt namn. Programmet som skapade dem rymmer 729 tal, har ingen föreställning om ord, stavelser eller personer, och anpassades med en enda genomgång där intilliggande bokstavspar räknades.

I slutet av det här kapitlet kommer ett neuralt nätverk att ha sänkt programmets resultat med en tredjedel på samma mått. Det som gör det värt att stanna kvar är vad nätverket gör först: det återskapar räknetabellen med tre decimalers noggrannhet på varje välfylld rad, utan att bli ombett, eftersom de två objekten är svar på samma fråga. Allt därefter är sådant som räknande aldrig hade kunnat göra.

Kapitel 7 lämnade dig med en sekvens av heltal och ingen anledning till att ett skulle följa på ett annat. Här är anledningen, och den är en rad från Kapitel 2.

En språkmodell är en funktion som tar hittillsvarande tokens och returnerar en fördelning över vilken token som kommer härnäst: ett tal per post i vokabulären, icke-negativt, med summa ett. Inget annat. För att gå från det till en sannolikhet för ett helt dokument använder man sannolikhetens kedjeregel:

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})

Det är en identitet, sann för vilken sekvens av vad som helst, utan antaganden. Alltså har en modell som gör det lilla jobbet — nästa token givet de tidigare — redan gjort det stora jobbet att tilldela en sannolikhet till varje möjligt dokument, exakt och gratis. Den populära inramningen av detta som ett billigt trick (”den förutsäger bara nästa ord”) vänder logiken bak och fram: att förutsäga nästa token är att modellera den gemensamma fördelningen. Det fanns aldrig en andra sak att göra.

Förlusten följer lika mekaniskt. Vid varje position producerar modellen en fördelning qq och sanningen är en enda känd token, så Kapitel 4:s korsentropi gäller oförändrad:

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

Det är den genomsnittliga negativa log-likelihooden — Kapitel 2:s recept med en kategorisk fördelning på platsen där gaussfördelningen satt. Och eftersom den sanna fördelningen är one-hot är dess entropi noll, så enligt Kapitel 4:s identitet är korsentropin lika med KL-divergensen: att driva ned detta tal och dra modellens uppfattningar mot datans är samma handling.

En konsekvens förtjänar en egen mening, eftersom den är det ekonomiska faktumet under hela fältet. Etiketterna är datan, förskjuten ett steg. Ingen annoterar någonting. En biljon tokens text är en biljon färdigetiketterade exempel, vilket är varför träningskorpusen för en modern modell är ”internet” och inte ”en dataset som någon byggde”.

Före något nätverk, baslinjen: 32 033 namn, ett per rad, och uppgiften att producera fler av dem en bokstav i taget.1

Vokabulären är 26 bokstäver plus en gränssymbol . som markerar både början och slutet på ett namn, så modellen måste lära sig var namn börjar och var de slutar. Det är 27 symboler, och den minsta möjliga modellen är en tabell över hur ofta varje symbol följde varje annan symbol.

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   

Två rader aritmetik och modellen är anpassad — och den är inte en heuristik: att dela antal med radsummor är maximum-likelihood-skattningen för en kategorisk fördelning, vilket är Kapitel 2:s recept med kalkylen redan gjord.

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

Sampla från den — välj en bokstav från raden för den aktuella bokstaven, gå till den raden, upprepa tills gränssymbolen dyker upp — och du får namnen längst upp i kapitlet. De misslyckas på ett specifikt och informativt sätt: lokalt plausibla, globalt nonsens. Varje intilliggande bokstavspar i momakurailezitynn är ett par som förekommer i riktiga namn; det råkar bara vara sjutton sådana i rad. Modellen har en bokstavs minne, så den kan inte veta att den har hållit på för länge.

Förlusten på undanhållna namn är 2,4546 nats. Det talet betyder ingenting på egen hand, och det är därför perplexity finns:

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}

Utskrivet, utan att något bibliotek gör arbetet:

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())             

Exponentiering gör om logaritmen och återför talet till enheten att räkna saker. Det rena sättet att se vad den räknar är att mäta en modell som inte vet någonting alls — en som tilldelar sannolikheten 1/271/27 till varje symbol oavsett sammanhang:

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

Exakt 27,000, eftersom elog27=27e^{\log 27} = 27. Perplexity är det effektiva antalet lika sannolika alternativ som modellen väljer mellan. En perplexity på 27 betyder ”ingen aning, det kan vara vad som helst”. Räkne­modellens 11,642 betyder att en bokstavs context gör den lika osäker som någon som väljer blint mellan ungefär tolv alternativ i stället för tjugosju — vilket är varför perplexity citeras och den råa förlusten inte gör det.

Två saker går fel med den, och den andra går fel i publicerade artiklar.

Nollsannolikheter är ödesdigra. Av de 729 cellerna i tabellen förekommer 113 aldrig i träning — 15,5 % av den är tom. Det är fint tills den undanhållna uppsättningen hamnar i en sådan cell, och sju bigram i valideringen gör det, bland dem dq, zj och qo två gånger. Sannolikhet noll betyder log -\infty, vilket betyder oändlig förlust och oändlig perplexity: ett namn på tre tusen förstör måttet. Den vanliga lagningslappen är att lägga till 1 till varje antal före normalisering, vilket nästan inte kostar något här (2,4546 i stället för 2,4524). Men lagningslappen är ett erkännande. En räkne­modell kan inte generalisera alls. Den har inget sätt att ana att qo är plausibelt eftersom qu är vanligt och o beter sig som u på andra ställen, eftersom den inte har någon föreställning om att två symboler kan likna varandra. Varje cell lärs in ensam, och att fixa det är vad resten av kapitlet handlar om.

Perplexity är ett pris per token, och token är en fri parameter. Det här är misstaget som ständigt dyker upp när modeller jämförs, och det är lätt att se när man tittar. Ta samma korpus med engelsk prosa från Kapitel 7, samma interpolerade bigrammodell, och ändra bara hur texten skärs upp:

enhetvokabulärtokens i testkorsentropiperplexitybitar per tecken
tecken7614 4692,521712,453,6378
BPE, 512 sammanslagningar3296 8713,854747,212,6407
BPE, 2 048 sammanslagningar1 8204 2335,7468313,202,4254
ord2 9916 2843,562735,262,2322

Perplexity varierar med en faktor 25 mellan de raderna. Inget med modellen ändrades; bara storleken på det som förutsägs. Att förutsäga ett helt ord är svårare än att förutsäga en bokstav, så det kostar mer per förutsägelse — och det finns färre förutsägelser att göra.

Läs nu den sista kolumnen, som i stället delar den totala kostnaden med antalet tecken och konverterar den till bitar. Den ordnar om tabellen. Med perplexity är rangordningen tecken, ord, BPE-512, BPE-2048; med bitar per tecken är den ord, BPE-2048, BPE-512, tecken. Teckenmodellen går från första plats till sista. Modellen med 2 048 sammanslagningar, som med perplexity ser 6,6 gånger sämre ut än modellen med 512 sammanslagningar, är i själva verket den bättre av de två med 2,4254 bitar mot 2,6407.

Alltså är en perplexity bara jämförbar mellan två modeller som delar tokenizer, och modeller med olika tokenizers kan bara jämföras i bitar per tecken — den storhet Shannon mätte 1951 genom att låta mänskliga försökspersoner gissa nästa bokstav i engelsk text, och begränsade till ungefär en bit per tecken.2 Vår bästa bigrammodell ligger på 2,23 bitar, vilket är en rättvis sammanfattning av hur långt det här kapitlet fortfarande har kvar.

Bygg nu samma modell som ett nätverk. Det kommer att kräva storleksordningar mer aritmetik för att komma till samma plats, och poängen är just att komma till samma plats.

Ersätt tabellen med en viktmatris WW med formen 27×2727 \times 27. Gör den aktuella bokstaven till en one-hot-vektor, multiplicera och kalla resultatet logits — de onormaliserade poängen från Kapitel 4. Sedan softmax, sedan korsentropi, sedan 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

Den markerade raden innehåller en definition värd att ha. Att multiplicera en one-hot-vektor med en matris väljer en rad i den, så multiplikationen är en uppslagning — och varje implementation hoppar över aritmetiken och gör uppslagningen direkt, vilket är vad W[xs] är.

Det är en embedding-tabell. En matris med en rad per post i vokabulären, indexerad med token-id. Ingen geometri, ingen semantik, ingen separat algoritm: en uppslagstabell vars innehåll råkar läras in av gradient descent tillsammans med allt annat. Varje mystiskt påstående om ”embedding space” bottnar här.

Träna den och se vart den går:

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

Den sista kolumnen är den största absoluta skillnaden mellan någon cell i softmax(W) och motsvarande cell i räknetabellen, och den går mot noll. Efter 3 000 steg är den största oenigheten någonstans i de 729 cellerna 0,004048 och medelvärdet är 0,000224. Den värsta cellen är qi, sedd tolv gånger i hela träningsmängden; bland de 22 raderna med fler än tusen förekomster är den värsta oenigheten 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, med start från slumpmässiga tal och utan att få veta något annat än ”gör log-sannolikheten för nästa bokstav stor”, återupptäckte räknetabellen. Och det var den tvungen att göra: antalen är maximum-likelihood-skattningen, korsentropi är den negativa log-likelihooden, så båda procedurerna optimerar samma mål och det målet har ett optimum. Nätverket lärde sig inte något som liknar räknande. Det konvergerade till räknande, långsamt.

Vilket väcker den rimliga frågan varför någon skulle bry sig. För att räknetabellen inte har någonstans att gå härifrån, och nätverket har det.

Utöka modellen så att den tittar på mer än ett tidigare tecken. Det här är Bengios arkitektur från 2003, den direkta förfadern till varje modell i resten av kursen:4 ta de tre sista tecknen, mappa vart och ett genom en embedding-tabell till en 10-dimensionell rad, sammanfoga raderna till 30 tal, skjut dem genom det dolda lagret från Kapitel 5, och avsluta med ett output-lager som producerar en logit per post i vokabulären.

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)

Notera vad som är nytt och vad som inte är det. Det dolda lagret är Kapitel 5:s, oförändrat; förlusten är Kapitel 4:s, oförändrad. Nyheterna är embedding-tabellen längst fram och ett output-lager lika brett som Kapitel 7:s vokabulär — och den andra delen är den dyra delen i varje språkmodell som någonsin byggts, eftersom en riktig vokabulär har 100 000 poster och denna matrismultiplikation körs vid varje position.

Samma kod, tränad identiskt, med bara storleken på context window ändrad:

contextparametrarvalideringsförlustvalideringsperplexity
räknande, 1 tecken7292,454611,642
neural, 1 tecken7 8972,457711,678
neural, 3 tecken11 8972,11458,285
neural, 8 tecken21 8972,05067,773

Den andra raden är den intressanta. Ett nätverk med ett dolt lager på 200 enheter och elva gånger så många parametrar som räknetabellen presterar exakt lika bra som räknetabellen och inte bättre. Kapaciteten var aldrig begränsningen. Ett teckens context tillåter en viss förlust och ingenting du skruvar på kan komma under den, eftersom informationen inte finns där.

Ge den tre tecken och perplexity faller från 11,68 till 8,29 — en minskning på 29 %, köpt med 4 000 extra parametrar. Den slår räknande här av precis den anledning som diagnosticerades tidigare: en räkne­modell över treteckens-context behöver 273=19,68327^3 = 19{,}683 rader, de flesta tomma eller med en enda observation, och den lär in var och en för sig. Nätverket delar. Om a, e och i får liknande embedding-rader överförs det den lär sig efter bra till bre utan att den någonsin har sett bre. Den överföringen är hela värdet med embedding-tabellen, och den är gapet mellan rad två och tre.

Samplen förbättras därefter:

TEXT
deliah   nellara   joce     kael      quintis
salayson  reety    khyrmin  mahnen    madiaryxia

Fortfarande inte en lista med riktiga namn. Men deliah, nellara och kael skulle inte se malplacerade ut på en sådan, och de utdragna monstren är borta: det längsta av tjugo sampel från räkne­modellen är nitton bokstäver, det längsta av tjugo från den här är tretton.

Tabellen är 27×1027 \times 10: en rad med tio tal per tecken, alla slumpmässigt initierade och flyttade endast av gradienten från nästa-tecken-förlusten. Ingen lade något där. Så vad hamnade i den?

Verktyget för att fråga är cosinuslikhet, vilket är skalärprodukten från Kapitel 1 med längderna bortdelade:

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

Den mäter vinkeln mellan två vektorer och ignorerar deras längder, vilket är vad du vill när en rads längd speglar hur ofta dess token förekom snarare än vad den betyder. Normalisera varje vektor till längd 1 först — som riktiga system gör, en gång, vid indexering — och cosinuslikhet är helt enkelt skalärprodukten.

Här är de närmaste grannarna till några tecken i den tränade tabellen:

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

En del av det är vad folktron utlovar. c och k är utbytbara i namn, och det är även i och y; j och z är båda sällsynta, mestadels initiala konsonanter som beter sig lika. Gränssymbolen . ligger nära ingenting alls — 0,077 till sin närmaste bokstav — eftersom den är den enda symbolen som markerar en position snarare än ett ljud.

Och en del är det inte. Den närmaste grannen till a är h, inte en annan vokal. I genomsnitt över alla par:

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

Vokalerna liknar varandra mer än de liknar konsonanter, och effekten är verklig men liten. Testat mot 2 000 slumpmässigt valda grupper om fem bokstäver är 58 av dessa grupper separerade minst lika rent — ett gap signifikant vid ungefär p=0.03p = 0.03. Verkligt, alltså, men inget i närheten av den skarpa geometriska ö som populära beskrivningar av embeddings antyder.

Det är den ärliga beskrivningen av en embedding-tabell och den är värd att hålla fast vid under resten av kursen. Den är inte en karta över betydelse. Den är ett koordinatbyte, inlärt snarare än designat, vars enda jobb är att göra nästa lagers jobb lätt — samma mening som Kapitel 5 använde för det dolda lagret som vek planet för att lösa XOR. All struktur du hittar i den finns där för att den sänkte förlusten, och struktur som inte sänker förlusten finns helt enkelt inte där.

Om den användbara delen är tabellen kan du gå direkt på den. Det är word2vec: behåll embedding-uppslagningen, kasta bort språkmodellen.5

Målet skip-gram with negative sampling är en rad. För ett verkligt (centrum, context)-par från korpusen, tryck upp deras skalärprodukt; för kk fejkpar dragna från en brusfördelning, tryck ned den: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})

Det är en binär klassificering — ”förekom de här två orden verkligen tillsammans?” — och den är billig just därför att den aldrig rör hela vokabulären, vilket var det som gjorde träning på miljarder ord praktisk 2013. GloVe kommer fram till liknande vektorer från andra hållet, genom att faktorisera matrisen med globala samförekomsttal i stället för att strömma genom exempel.7 Båda anpassas till exakt den statistik som räknetabellen byggdes av. De är räknande, komprimerat.

Tränade på text8 — 17 005 207 ord från engelska Wikipedia, 71 290 av dem med minst fem förekomster, 100 dimensioner, tre genomgångar — kommer vektorerna ut med egenskapen som gjorde dem berömda:

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

Ingen tillhandahöll en kategori för instrument eller för räkneord. Nu den berömda delen: ta king, subtrahera man, addera woman, och hitta den närmaste vektorn till resultatet.

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)

Den närmaste vektorn till king - man + woman är king. Det är inte en egenhet hos ett exempel. Mikolovs utvärderingsmängd ställer frågor av formen a : b :: c : ? — 8 869 semantiska (paris : france :: rome : italy) och 10 675 syntaktiska (walking : walked :: swimming : swam) — och över de 4 103 semantiska frågor som den här vokabulären kan besvara är vinnaren ett av de tre input-orden 99,8 % av tiden. De publicerade demonstrationerna nämner det inte, eftersom standardregeln för poängsättning tar bort a, b och c innan den tittar. Det är en legitim regel, och den gör mer arbete än aritmetiken:

hur svaret väljssemantisksyntaktisk
offset, med input-orden uteslutna (standard)17,0 %11,9 %
offset, utan att något utesluts0,1 %0,4 %
närmaste granne till c ensam, input uteslutna13,1 %9,3 %
närmaste granne till b ensam, input uteslutna2,3 %0,4 %

Den tredje raden är den att sitta med. Kasta bort a och b, gör ingen aritmetik alls, returnera vad som än ligger närmast c — och du behåller 77 % av den semantiska poängen. Det mesta av det som ser ut som analogiskt resonerande är närhet plus en regel som förbjuder de uppenbara svaren, vilket är vad Linzen mätte på korrekt tränade vektorer och vad baslinjerna ovan replikerar.8 Just dessa vektorer är små — 17 miljoner ord mot miljarderna bakom de publicerade modellerna — så läs procenttalen som en form, inte som state of the art. Formen är det som överlever i varje skala: aritmetiken är verklig, och mycket svagare än den enda demonstration som alla citerar.

Statisk och kontextuell: en vektor per ord, eller en per förekomst

Länk till avsnittet: Statisk och kontextuell: en vektor per ord, eller en per förekomst

Allt hittills har en hård gräns inbyggd i datastrukturen. En tabell har en rad per token. Ordet bank får en vektor, samma i en mening om en flod och en mening om ett bolån — med nödvändighet, eftersom en uppslagning via id inte kan bero på något annat.

Lösningen är att sluta läsa vektorn ur tabellen och börja beräkna den från meningen. Det är en contextual embedding, introducerad av ELMo 2018 och standardiserad av BERT samma år.910 Mätt på den riktiga modellen är siffrorna skarpare än förklaringen:

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

Den första raden är exakt, inte ungefärlig: den statiska vektorn för bank är samma 768 tal i båda meningarna, så cosinus är 1 per konstruktion. Nio lager senare ligger de två förekomsterna på 0,43, medan bank i två olika flodmeningar stannar på 0,87. Ingen etiketterade någon betydelse någonstans i processen; betydelserna separerades därför att separationen gör träningsmålet — att gissa en dold token från dess grannar — lättare att uppfylla.

Två detaljer är värda uppmärksamhet. Lager 0 är redan 0,9512 snarare än 1,0, eftersom positions-embeddings har lagts till och ordet sitter på olika plats i varje mening. Och likheten stiger igen i lager 11 och 12: de sista lagren i en förtränad modell är specialiserade på dess träningsmål, och är ofta inte den bästa platsen att ta en representation från.

Visa detaljer

Valfritt: weight tying.

I bert-base-uncased är embedding-tabellen 30,522×76830{,}522 \times 768 — 23 440 896 tal, 21,4 % av modellens 109 482 240 parametrar. I en liten språkmodell är andelen ännu större, vilket är varför ett trick är nästan universellt: input-tabellen och output-lagret som producerar logits är samma matris, använd en gång genom raduppslagning och en gång transponerad.11 Output-lagret tilldelar redan varje post i vokabulären en vektor — det tar en skalärprodukt mot var och en — och tying säger att vektorn som används för att läsa en token och vektorn som används för att skriva den ska vara samma objekt. Det minskar parametrar och förbättrar perplexity samtidigt, vilket är tillräckligt ovanligt för att noteras.

För att söka i en korpus efter betydelse behöver du en vektor per mening. Givet dem är sökningen trivial — det här är hela semantisk retrieval, och Kapitel 19 handlar om allt runt omkring den:

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]

Så den enda verkliga frågan är var embed kommer ifrån. Det uppenbara draget är att ta en förtränad språkmodell, köra varje mening genom den och ta medelvärdet av token-vektorerna. Här är den metoden mot fyra alternativ, poängsatta på två sätt: rangkorrelationen mellan cosinus och mänskliga likhetsbedömningar över de 1 379 paren i STS-benchmarken, och top-1-retrieval på ett index byggt av de 200 starkast parafraserade av dessa par — ena sidan av varje par indexerad, den andra använd som query.

hur meningen embed:asrangkorrelationtop-1 på ett index med 200 meningar
binärt ordöverlapp (ingen modell alls)0,550089,0 %
medelvärde av de statiska vektorerna tränade ovan0,526385,5 %
BERT, [CLS] token0,203067,0 %
BERT, medelvärde av token-vektorer0,472984,0 %
MiniLM, tränad kontrastivt0,820392,0 %

Läs de tre mittersta raderna mot de två första. En förtränad transformer med 109 miljoner parametrar, använd på det uppenbara sättet, är sämre på att bedöma meningslikhet än att räkna hur många ord två meningar delar — och sämre än att ta medelvärdet av de 100-dimensionella text8-vektorerna som tränades nyss. [CLS] token, som tutorials fortfarande rekommenderar eftersom BERT förtränades med ett meningsnivåmål kopplat till den, är sämre än hälften av det.

Det är inte en defekt i BERT. Det är målet. En språkmodell tränas så att dess dolda tillstånd förutsäger en token; inget där ber två parafraser hamna nära varandra, och inget belönar en geometri där cosinus betyder ”samma betydelse”. Den sista raden är en modell som är en femtedel så stor (22 713 216 parametrar) tränad på en helt annan förlust: contrastive learning, där exemplen är par — en fråga och dess svar, en mening och dess parafras — och målet drar sanna par samman medan samplade negativa exempel trycks isär. Det är Sentence-BERT:s bidrag och ursprunget till hela embedding model-industrin.12 Dense Passage Retrieval använder samma recept direkt för sök, med en encoder för queries och en för passages.13

Alltså, den praktiska regeln:

En embedding model är inte en språkmodell med sista lagret borttaget. Det är en annan modell med ett annat mål, oftast mycket mindre, vars cosinus betyder det du vill att det ska betyda eftersom den tränades på par där det var målet. Tabellen ovan är kostnaden för att ersätta den ena med den andra.

Och familjen misslyckas med ordföljd. ”Hunden bet mannen” och ”mannen bet hunden” har identiska påsar av ord, så ordöverlapp och medelvärdet av statiska vektorer ger dem cosinus exakt 1,000000, och mean-pooled BERT, som faktiskt ser position, landar fortfarande nästan där — och den kontrastivt tränade MiniLM placerar dem fortfarande på 0,979. Om din retrieval-uppgift avgörs av vem som gjorde vad mot vem kommer ingen cosinuströskel att rädda dig.

Kapitel 19 bygger ett produktionssystem för retrieval på denna grund och landar i en konkret cosinusgräns. Den sista mätningen i det här kapitlet är vad som gör ett sådant tal försvarbart snarare än magiskt.

Riktiga embeddings har hundratals eller tusentals komponenter, och avstånd beter sig konstigt där uppe. Ta 1 000 slumpmässiga punkter i enhetskuben med dd dimensioner och titta på kvoten mellan det största och det minsta avståndet mellan två av dem:

dimensionernärmaste parfjärmaste parkvot
20,00071,36121921,66
100,23612,33979,91
1003,00475,17521,72
1 00011,780914,03061,19
10 00039,615242,01251,06

I tiotusen dimensioner är det fjärmaste punktparet bara 6 % längre ifrån varandra än det närmaste paret. Allt är ungefär lika långt från allt annat, ”närmaste granne” slutar bära särskilt mycket information, och det är dimensionalitetens förbannelse — liksom en anledning till att stora vektordatabaser inte gör exakt närmaste-granne-sökning. Den andra sidan av samma mynt är det som gör cosinuströsklar användbara: mätt över tusen par av slumpmässiga enhetsvektorer ligger medelcosinus på 0.0052-0.0052 i 100 dimensioner och +0.0003+0.0003 i 768, med standardavvikelser på 0,0968 och 0,0357 — och i 768 dimensioner överskrider bara 0,2 % av slumpmässiga par 0,1 i absolutvärde. En uppmätt likhet på 0,4 är därför inte ”40 % lika”; den ligger långt utanför allt slumpen producerar, vilket är varför trösklar mellan 0,3 och 0,7 separerar signal från brus i stället för att ligga mitt i det.

Modellen i det här kapitlet läser ett fast antal tidigare tecken, slår upp vart och ett och limmar ihop resultaten i ordning. Den designen har två problem, och de är samma problem.

Titta igen på context-tabellen: att gå från tre tecken till åtta nästan fördubblade parametrarna och köpte 0,06 nats. Kostnaden växer linjärt med context — varje extra position behöver sin egen platta i den första viktmatrisen — och nyttan gör det inte. Tryck upp den till tusen tokens och det första lagret ensamt väger mer än resten av modellen, det mesta spenderat på positioner som inte spelar roll för någon given förutsägelse.

Vilket är det andra problemet: modellen har inget sätt att avgöra vilka av de tidigare tokens som spelar roll. Position två får sina egna vikter och position sju får sina egna, permanent, oavsett vad som finns i dem. När modellen stavar nell är det avgörande tecknet det direkt föregående. När en mening innehåller ett pronomen kan ordet som fixerar dess referent ligga fyrtio tokens bak — och ingen fast slot kan tilldelas ”fyrtio bak”, eftersom nästa gång blir det sex.

Det vi vill ha är en modell som beräknar, för varje förutsägelse, hur mycket varje tidigare token ska räknas — vikter över context som produceras av innehållet snarare än fixeras av layouten. Skriv ned det noggrant och det börjar som något helt vardagligt: ett medelvärde över tidigare tokens. Låt sedan vikterna i det medelvärdet läras in, och låt dem bero på vilken token som frågar.

Det är attention, och det är Kapitel 9.


Också värt att läsa parallellt: kapitel 3 i Jurafsky och Martins Speech and Language Processing, som behandlar n-grammodeller, smoothing och perplexity mycket noggrannare än det finns plats för här, inklusive varför interpolation och back-off slår att lägga till ett; Stanford CS229-anteckningarna §17.1–17.2 för språkmodellering från den probabilistiska sidan; och Linzens artikel ovan, som är kort och värd att läsa i sin helhet.

  1. Namngenereringsexemplet, datasetet och progressionen från en räknetabell till ett Bengio-liknande nätverk följer Andrej Karpathys building makemore-serie, vars två första delar är det bästa komplementet till det här kapitlet.

  2. Shannon, C. E. Prediction and Entropy of Printed English. Bell System Technical Journal 30(1), s. 50–64 (1951). Mänskliga försökspersoner som gissar nästa bokstav i engelska, och den ursprungliga mätningen i bitar per tecken.

  3. Shannon, C. E. A Mathematical Theory of Communication. Bell System Technical Journal 27 (1948). Källkodningssatsen, och identifieringen av prediktion med komprimering.

  4. Bengio, Y., Ducharme, R., Vincent, P. och Jauvin, C. A Neural Probabilistic Language Model. Journal of Machine Learning Research 3, s. 1137–1155 (2003). Arkitekturen som används ovan: en embedding per ord, sammanfogad över ett fast fönster, genom ett dolt lager, till en softmax över vokabulären.

  5. Mikolov, T., Chen, K., Corrado, G. och Dean, J. Efficient Estimation of Word Representations in Vector Space. arXiv:1301.3781 (2013). CBOW och skip-gram, och analogimängden som används ovan.

  6. Mikolov, T., Sutskever, I., Chen, K., Corrado, G. och Dean, J. Distributed Representations of Words and Phrases and their Compositionality. arXiv:1310.4546 (2013). Negative sampling, subsampling av frekventa ord, och brusfördelningen upphöjd till 3/4 som används ovan.

  7. Pennington, J., Socher, R. och Manning, C. GloVe: Global Vectors for Word Representation. EMNLP 2014. Ordvektorer från en faktorisering av den globala samförekomstmatrisen i stället för strömmade lokala fönster.

  8. Linzen, T. Issues in evaluating semantic spaces using word analogies. RepEval 2016, arXiv:1606.07736. Källan till de offsetfria baslinjer som replikerats ovan.

  9. Peters, M. et al. Deep contextualized word representations. arXiv:1802.05365 (2018). ELMo: en vektor per förekomst, beräknad av en dubbelriktad språkmodell.

  10. Devlin, J., Chang, M.-W., Lee, K. och Toutanova, K. BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. arXiv:1810.04805 (2018). Modellen som mättes i bank-experimentet.

  11. Press, O. och Wolf, L. Using the Output Embedding to Improve Language Models. arXiv:1608.05859 (2016), och Inan, H., Khosravi, K. och Socher, R. Tying Word Vectors and Word Classifiers. arXiv:1611.01462 (2016). Två oberoende argument för samma trick.

  12. Reimers, N. och Gurevych, I. Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks. arXiv:1908.10084 (2019). Dess inledande mätning — mean-pooled BERT som underpresterar mot genomsnittliga statiska vektorer på meningslikhet — är vad tabellen ovan reproducerar.

  13. Karpukhin, V. et al. Dense Passage Retrieval for Open-Domain Question Answering. arXiv:2004.04906 (2020). Kontrastiv träning av en två-encoder-retriever; den direkta förfadern till Kapitel 19:s retrieval-stack.


Skapad av

David Vicente Campos

Grundare av NeuraLIA Labs och medgrundare av MyRealFood

Jag är dataingenjör från Universitetet i León. Jag var med och grundade MyRealFood, där jag som CTO byggde appen som miljontals människor har använt för att äta bättre, och jag grundade NeuraLIA Labs, där jag bygger AI-produkter. Här skriver jag om det jag har behövt förstå längs vägen, så som jag önskar att någon hade förklarat det för mig.

Mer om författaren

Publicerad av NeuraLIA Labs.

Få nya inlägg i din inkorg

AI-nyheter, guider och produktuppdateringar — ett kort mejl när vi publicerar något som är värt din tid.

Kursindex

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jevLästid 11 min

Jevs AI-modell är byggd för beslut, inte prosa

TypeSafe AI:s Jev väcker uppmärksamhet eftersom den behandlar mjukvaruintelligens som ett sannolikhetsproblem: välj rätt gren, lägg till konfidens och undvik att betala en LLM för att skriva text när koden behöver ett beslut.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineeringLästid 11 min

Kontextteknik för AI-agenter med lång horisont

Långkörande agenter misslyckas inte bara för att fönstret är litet. De misslyckas när filer, verktygsutdata och gammal historik tränger undan uppgiften agenten skulle slutföra.

Redo att låta LIA välja åt dig?

Bygg med alla AI-modeller på ett ställe – kom igång gratis i dag.