Werkplaats / De werkplaats
In jouw eigen tempo
← Taalmodellen
MODULE 02 · LES 05

Geef dezelfde token op een andere plek een positie mee

Voeg geleerde of sinusvormige positie-informatie toe en verzamel de gradiënten van gedeelde tokenrijen en posities.

60 min3 checkpoints
DIT GA JE ONTDEKKEN
  • 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.

ybt=Eids[b,t]+Po+t

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.

01
CHECKPOINTTel positievectoren bij tokenvectoren op

2. 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.

a=p100002iC

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.

ACHT KANALEN, VIER FREQUENTIESGeef iedere positie een patroon van golven

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.

-101010203040
Kanaal 00.141
Kanaal 1-0.990
Kanaal 20.296
Kanaal 30.955
Kanaal 40.030
Kanaal 51.000
Kanaal 60.003
Kanaal 71.000
Sinus · groen0.1411Cosinus · oranje-0.9900Frequentiedeler1

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.

02
CHECKPOINTGebruik vaste frequenties als alternatief

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.

03
CHECKPOINTTokens delen rijen, batchvoorbeelden delen posities

4. 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.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
DE VOLGENDE STAPLaat iedere query informatie uit andere posities mengen
JOUW EXPERIMENT

Probeer het zelf

Python
main.py

Python start zodra jij code uitvoert.

UITVOER

Je uitvoer verschijnt hier.
Alles rekent in je eigen browser.

Jouw checkpoints0 / 3
01
Tel positievectoren bij tokenvectoren op

Schrijf token_positions(table,positions,ids,offset=0). Controleer matrixvormen, gelijke breedte, 2D-gehele ID’s binnen [0,V), en een niet-negatieve integeroffset waarvoor het venster past. Anders ValueError.

Een hint

Maak ids een NumPy-array en controleer dtype.kind in "iu" als er elementen zijn. De positiearray broadcast over de batchas.

02
Gebruik vaste frequenties als alternatief

Schrijf sinusoidal(length,width,offset=0) met vorm (length,width). length≥0, width positief en even, offset≥0; alle drie exacte integers. Ongeldig is ValueError.

Een hint

Maak de even kanaalindices met arange(0,width,2). Deel de positiekolom door 10000**(indices/width).

03
Tokens delen rijen, batchvoorbeelden delen posities

Schrijf position_backward(ids,dy,vocab_size,max_positions,offset=0), met dictionary tokens en positions. ids is geldig (B,T), dy is (B,T,C); de indexgrenzen zijn al gecontroleerd.

Een hint

np.add.at verzamelt herhaalde tokenrijen. Voeg dy.sum(axis=0) aan het gekozen positieblok toe.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

def token_positions(table,positions,ids,offset=0):
    table=np.asarray(table,float);positions=np.asarray(positions,float)
    ids=np.asarray(ids)
    if table.ndim!=2 or positions.ndim!=2 or table.shape[1]!=positions.shape[1] or ids.ndim!=2:
        raise ValueError('Ongeldige vormen')
    if ids.size and (ids.dtype.kind not in 'iu' or ids.min()<0 or ids.max()>=len(table)):
        raise ValueError('Ongeldige token-ID')
    if type(offset) is not int or offset<0 or offset+ids.shape[1]>len(positions):
        raise ValueError('Positie valt buiten de tabel')
    return table[ids.astype(int)]+positions[offset:offset+ids.shape[1]]

def sinusoidal(length,width,offset=0):
    if any(type(n) is not int for n in [length,width,offset]) or length<0 or width<=0 or width%2 or offset<0:
        raise ValueError('Gebruik een niet-negatieve lengte/offset en positieve even breedte')
    angles=np.arange(offset,offset+length)[:,None]/10000**(np.arange(0,width,2)/width)
    result=np.empty((length,width))
    result[:,0::2]=np.sin(angles)
    result[:,1::2]=np.cos(angles)
    return result

def position_backward(ids,dy,vocab_size,max_positions,offset=0):
    dy=np.asarray(dy,float)
    tokens=np.zeros((vocab_size,dy.shape[-1]))
    positions=np.zeros((max_positions,dy.shape[-1]))
    np.add.at(tokens,np.asarray(ids).ravel(),dy.reshape(-1,dy.shape[-1]))
    positions[offset:offset+dy.shape[1]]+=dy.sum(axis=0)
    return {'tokens':tokens,'positions':positions}

Voer de code uit en verklaar voor jezelf waarom elke stap werkt.