- Je koppelt iedere inputpositie aan het volgende token zonder splitgrenzen te overschrijden.
- Je middelt cross-entropy over de bedoelde voorspellingen en maskeert padding correct.
Hoe maak je van “de kat slaapt” een leeropdracht? Geef een model steeds een begin van de tekst en vraag welk token daarna komt. Eén zin levert zo meerdere voorspellingen op; het model moet de antwoorden nog steeds uitsluitend uit het toegestane verleden afleiden.
Bij training geven we de echte eerdere tokens mee: teacher forcing. Tijdens vrij genereren kiest het model zelf een token en gebruikt dat als nieuwe invoer. Een lage trainingsloss bewijst daarom niet dat een lange eigen tekst vrij blijft van herhaling of fouten.
De voorbeelden gebruiken NumPy en de drie functies die je in deze editor bouwt. Het losse speelgoedvocabulaire van deze proef is de=1, kat=2, slaapt=3 en eindtoken=0. Een echt taalmodel moet zo'n eindteken expliciet in zijn eigen tokenizercontract opnemen.
1. Koppel ieder venster aan zijn vervolg
Neem data [1,2,3,0] en contextlengte T=2. Vanaf start 0 is x=[1,2] en y=[2,3]; vanaf start 1 is x=[2,3] en y=[3,0]. Het laatste doeltoken moet nog bestaan, dus de laatste geldige start is lengte−T−1: hier 4−2−1=1.
import numpy as np
data = np.array([1,2,3,0])
starts = np.array([0,1])
x,y = windows(data,starts,2)
print(x.tolist()) # [[1,2],[2,3]]
print(y.tolist()) # [[2,3],[3,0]]
Binnen het eerste venster ziet tijdpositie t=0 alleen “de” en voorspelt “kat”. Tijdpositie t=1 ziet “de kat” en voorspelt “slaapt”. Beide berekeningen kunnen tegelijk plaatsvinden, omdat het causale masker voorkomt dat positie 0 de al aanwezige invoer op positie 1 leest.
| t | Toegestaan begin | Doel |
|---|---|---|
| 0 | de | kat |
| 1 | de kat | slaapt |
Maak windows met niet-lege 1D-gehele data en starts en een positieve exacte integercontext. Negatieve ID’s, negatieve starts en vensters buiten de reeks geven ValueError. Bewaar de aangevraagde volgorde en herhalingen: starts zijn opdrachten, geen verzameling unieke indices.
x,_ = windows(data,np.array([1,0,1]),2)
print(x.tolist())
# [[2,3],[1,2],[2,3]]
Splits train, validatie en test vóór het maken van vensters. De functie ontvangt steeds slechts één split; zij kan uit losse ID’s niet zelf herkennen waar een document- of evaluatiegrens ligt. Een verschoven doeltabel is geen vervanging voor die datascheiding.
012. Geef de juiste voorspelling zijn verlies
Logits bevat B vensters, T tijdposities en V mogelijke token-ID’s. Targets bevat voor iedere batch- en tijdpositie het juiste ID. Zonder loss-masker tellen N=B×T voorspellingen mee; het vocabulaire V bepaalt de softmaxbreedte, niet de verliesnoemer.
Hier is L het gemiddelde verlies en indexeert n één voorspelling. Het symbool yₙ is het juiste token-ID; pₙ,ᵧₙ is de voorspelde kans op dat doel.
Gebruik voor beide posities scores log([1,1,4,2]). Softmax maakt daarvan [1/8,1/8,1/2,1/4]. De doelen 2 en 3 krijgen kansen 1/2 en 1/4: hun verliezen zijn log(2) en log(4), gemiddeld ongeveer 1.0397.
logits = np.log(np.array(
[[[1.,1.,4.,2.],[1.,1.,4.,2.]]]))
targets = np.array([[2,3]])
loss,grad = token_loss(logits,targets)
print(round(loss,4)) # 1.0397
print(grad)
# [[[.0625 .0625 -.25 .125]
# [.0625 .0625 .25 -.375]]]
De gradiënt per rij is softmax minus de one-hotdoelvector, gedeeld door N. Bij de eerste rij wordt de doelkolom dus (1/2−1)/2=−1/4. Gebruik natuurlijke logaritmen en trek het rijmaximum af vóór exponentiëren; reken eerst met floats zodat een integeraftrekking niet al overloopt.
Maak token_loss zonder logits te wijzigen. Dezelfde berekening werkt voor (N,V) met targets (N,); invoer is eindig en niet leeg en targets zijn geldige gehele ID’s. Flatten alleen voorbeeld- en tijdassen, en herstel de volledige gradiëntvorm.
023. Kies welke doelen meetellen
Een attentionmasker begrenst informatieoverdracht tussen posities. Een loss-masker kiest voorspellingen voor het trainingsdoel, bijvoorbeeld wanneer een positie alleen padding bevat. Een nulgewicht in de loss geeft een token dus niet automatisch toestemming om andere posities te beïnvloeden.
Laat in ons voorbeeld alleen de eerste voorspelling meetellen. Nu is N=1, loss=log(2) en verdubbelt de eerste gradiëntrij ten opzichte van het gemiddelde over twee doelen. De tweede rij wordt exact nul, ook al stond daar een geldige voorspelling.
mask = np.array([[True,False]])
loss,grad = masked_token_loss(
logits,targets,mask)
print(round(loss,4)) # .6931
print(grad)
# [[[.125 .125 -.5 .25]
# [0. 0. 0. 0.]]]
Maak masked_token_loss met een booleaans masker van exact dezelfde vorm als targets; anders volgt ValueError. Middel alleen over gekozen posities en schrijf hun gradiënten terug in een nularray. Alle target-ID’s blijven geldig: er is geen verborgen −100-conventie.
Als niets meetelt, spreken we loss nul en een volledige nulgradiënt af. Dat voorkomt delen door nul; het bewijst niet dat het model de tekst goed voorspelde. Er was eenvoudig geen trainingsdoel geselecteerd.
loss,grad = masked_token_loss(
logits,targets,np.zeros_like(mask))
print(loss,np.count_nonzero(grad))
# 0.0 0
03Wat neem je mee?
- Het doeltoken staat precies één plek na zijn invoerpositie.
- Attentionmasker en loss-masker hebben verschillende taken.
- De noemer telt voorspellingen die meedoen, niet vocabulairekolommen.
Hierna bouw je het transformerblok dat de context voor zulke voorspellingen berekent.