- Je combineert token- en positie-embeddings met een expliciete offset.
- Je bouwt sinusvormige posities en telt de embeddinggradiënten op de juiste assen op.
In “de kat ziet de hond” doet de plek van een woord ertoe. De tokenvector uit je embeddingtabel kent alleen het token-ID: dezelfde “de” krijgt aanvankelijk dezelfde vector. We geven elke plek daarom een extra vector mee, zoals een adreskaartje naast het woordkaartje.
Een self-attentionlaag zonder masker en positie-informatie volgt alleen de inhoud: bij herschikken van de invoer herschikt zijn uitvoer mee. Een causaal masker beperkt al welke plekken bereikbaar zijn; dat vervangt niet automatisch onze expliciete positievectoren. Hier vergelijk je een geleerde tabel met vaste sinusgolven, twee alternatieven die je niet beide hoeft op te tellen.
De voorbeelden gebruiken NumPy en de functies die je in deze editor bouwt. Werk eerst met twee kenmerken, zodat je iedere optelling kunt narekenen. Een tabel bevat geen betekenisvolle taal voordat een model zijn getallen heeft geleerd.
1. Hetzelfde token, een andere positie
Noem de tokentabel E en de positietabel P. Voor batchvoorbeeld b en tijdstap t kiezen we tokenrij ids[b,t] en positierij o+t; o is de offset, de absolute startplek. Beide vectoren hebben dezelfde breedte C.
Neem tokenvector [2,3]. Op plek 1 tel je [10,100] erbij: [12,103]; op plek 2 wordt het [22,203]. Het token blijft hetzelfde, maar de gecombineerde invoer verschilt.
import numpy as np
table = np.array([[0,0],[2,3]])
pos = np.array([[0,0],
[10,100],[20,200]])
ids = np.array([[1,1]])
y = token_positions(table,pos,ids,1)
print(y.tolist())
# [[[12.,103.],[22.,203.]]]
Maak token_positions met table van vorm (V,C), positions van vorm (L,C) en ids van vorm (B,T). De gekozen positievectoren worden over de batchas gedeeld. Controleer geldige gehele token-ID’s, gelijke breedtes en een exacte integeroffset vanaf nul; een venster buiten de tabel geeft ValueError.
V is het aantal tokenrijen, L het aantal beschikbare positierijen, B het aantal batchvoorbeelden en T het aantal tijdstappen per voorbeeld. Twee voorbeelden gebruiken op dezelfde tijdstap dus dezelfde positierij, ook wanneer hun token-ID’s verschillen.
012. Vaste golven in plaats van geleerde rijen
Een vaste codering gebruikt voor kanaalpaar i een hoek a. Kanaal 2i krijgt sin(a) en kanaal 2i+1 krijgt cos(a), met i vanaf nul tot C/2−1; p is de absolute positie. Bij breedte C=4 hebben de twee paren hoeken p en p/100.
Op positie 0 zijn de vier waarden [0,1,0,1]. Op positie 1 zijn ze ongeveer [.8415,.5403,.0100,1.0000]. De laatste niet-afgeronde waarde is net kleiner dan één; afronden verandert de onderliggende berekening niet.
waves = sinusoidal(2,4)
print(np.round(waves,4))
# [[0. 1. 0. 1. ]
# [0.8415 0.5403 0.01 1. ]]
shift = sinusoidal(1,4,offset=1)
print(np.allclose(shift,waves[1:]))
# True
Maak sinusoidal met een niet-negatieve lengte en offset en een positieve even breedte; alle drie zijn exacte integers. Gebruik de kanaalindex, niet een vaste frequentie voor breedte vier. Vaste golven kun je verder uitrekenen dan het trainingsvenster, maar dat bewijst geen goede voorspellingen op zulke lange teksten.
Voor breedte C=8 vormen kanalen 2i en 2i+1 een sinus/cosinus-paar. Kies een positie en bekijk de waarden, zonder iets te trainen.
De horizontale as is de positie. Langzamere golven veranderen minder tussen naburige tokens. Dat we een grotere positie kunnen uitrekenen bewijst niet dat een model daar goed generaliseert.
3. Eén optelling, twee gradiëntroutes
Bij y=token+positie ontvangt ieder invoerdeel dezelfde uitgaande gradiënt dy. Herhaalde tokens verzamelen bijdragen bij dezelfde tokenrij; een positierij verzamelt de bijdragen van alle batchvoorbeelden op die tijdstap. Sommeer die bijdragen, want eventuele verliesmiddeling zit al in dy.
Neem ids [[1,1],[1,2]] en overal dy=[1,1]. Token 1 komt drie keer voor en krijgt [3,3]; token 2 krijgt [1,1]. Elke gebruikte positie komt in twee batchvoorbeelden voor en krijgt dus [2,2].
ids = np.array([[1,1],[1,2]])
dy = np.ones((2,2,2))
g = position_backward(ids,dy,3,4,1)
print(g['tokens'].tolist())
# [[0.,0.],[3.,3.],[1.,1.]]
print(g['positions'][1:3])
# [[2. 2.]
# [2. 2.]]
Maak position_backward voor geldige ids en passende dy. De offset verschuift alleen het gebruikte positieblok; ongebruikte rijen blijven nul. Dit is de terugweg voor geleerde posities: de vaste sinuswaarden zijn zelf geen trainbare tabel.
034. Bewaar de afgesproken venstergrenzen
Een leeg tijdvenster mag, ook precies aan het einde van de positietabel. Een niet-leeg venster voorbij de laatste rij mag niet. Bereken met drijvendekommagetallen vóór je optelt of sommeert: beperkte integerdtypes mogen de vectoren en gradiënten niet laten overlopen.
empty = np.empty((1,0),dtype=int)
print(token_positions(
table,pos,empty,offset=3).shape)
# (1,0,2)
Wat neem je mee?
- Tokenrijen delen informatie over hetzelfde token; positierijen voegen een plek toe.
- Offset, batchas en tijdas hebben verschillende rollen.
- Geleerde tabellen en vaste golven zijn alternatieven; hun terugwegen verschillen.
Hierna laat je een query informatie uit toegestane keyposities mengen.