Werkplaats / De werkplaats
In jouw eigen tempo
← Deep learning
MODULE 04 · LES 10

Maak van een token-ID een trainbare vector

Bouw embeddinglookup, verzamel gradiënten voor herhaalde tokens en gebruik een masker bij gemiddelde pooling.

60 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je haalt representaties op zonder token-ID als meetwaarde te behandelen.
  • Je telt gedeelde embeddinggradiënten op en houdt padding buiten updates en gemiddelden.

Een netwerk kan niet uit token-ID 8 afleiden dat dit “meer” betekent dan ID 3. Die nummers zijn adressen, zoals rijnummers in een woordenlijst. Een embeddingtabel vervangt ieder adres door een vector die tijdens training kan veranderen.

Denk aan een fiche per token met meerdere instelbare waarden. Hetzelfde token verwijst steeds naar dezelfde fiche, ook wanneer het twee keer in een zin staat. Welke relaties de vectoren gaan uitdrukken hangt af van de trainingsopdracht; willekeurige beginwaarden hebben nog geen aangeleerde betekenis.

1. Haal rijen op in de gevraagde volgorde

Een tabel E van vorm (V,D) bevat V tokenrijen met ieder D getallen. Lookup geeft voor ieder ID de bijbehorende rij terug. Dat is gelijk aan een onehot-selectiematrix maal E, maar je hoeft die grote matrix niet echt te bouwen.

Ht=Eidt

Neem vier rijen: PAD=[9,9], kat=[1,0], hond=[0,2], fiets=[−1,1]. De gekozen PAD-waarden zijn expres niet nul, zodat je straks ziet of het masker echt werkt. Voor IDs [2,1,2] verwacht je hond, kat, hond; een herhaling verdwijnt niet.

import numpy as np

table = np.array([[9.,9.],[1.,0.],
                  [0.,2.],[-1.,1.]])
ids = np.array([2,1,2])
values = table[ids].copy()
print(values.tolist())
# [[0.,2.],[1.,0.],[0.,2.]]

Gebruik een eendimensionale reeks gehele ID's binnen [0,V). Een negatief NumPy-indexnummer kan anders stilzwijgend een rij vanaf het einde kiezen. Een onbekend token vraagt een expliciete UNK-afspraak, niet een willekeurige modulo of afronding naar een bestaand adres.

Maak lookup(table,ids), geef een kopie van de geselecteerde rijen en weiger ongeldige ID's met ValueError. Controleer vóór het casten ook dimensionaliteit en integerdtype; een lege lijst levert een lege (0,D)-array. Behoud de tabel en de volgorde van de input.

01
CHECKPOINTSelecteer rijen, reken niet met ID-groottes

2. Verzamel iedere bijdrage aan een gedeelde rij

Bij backprop krijgt iedere opgezochte positie een upstream-vector dy. Die bijdrage hoort terug bij de rij van zijn token. Omdat meerdere posities dezelfde rij kunnen gebruiken, moet je bijdragen optellen in plaats van de laatste te bewaren.

Noem G de tabelgradiënt. Voor een tokenrij k sommeer je alle dy-posities met dat ID; een paddingrij slaan we over wanneer padding_idx is opgegeven. We delen hier niet door het aantal voorkomens, want eventuele verliesmiddeling zit al in dy.

Gk=∑t:idt=kdyt

Neem IDs [2,1,2,0] en dy=[[1,2],[3,4],[−0.5,1],[9,9]]. Rij 2 ontvangt [1,2]+[−0.5,1]=[0.5,3], rij 1 krijgt [3,4], en bij padding_idx 0 blijft rij 0 nul. De ongebruikte fietsrij krijgt ook nul.

ids = np.array([2,1,2,0])
dy = np.array([[1.,2.],[3.,4.],
               [-.5,1.],[9.,9.]])
gradient = np.zeros_like(table)
for token,signal in zip(ids,dy):
    if token != 0:
        gradient[token] += signal
print(gradient.tolist())
# [[0.,0.],[3.,4.],[.5,3.],[0.,0.]]

Een veelgemaakte fout is gradient[ids]+=dy: geavanceerde indexering kan herhaalde indices via een tijdelijke array verwerken en zo bijdragen verliezen. Een expliciete lus werkt; np.add.at is een tweede correcte manier om dezelfde herhalingen op te tellen.

other = np.zeros_like(table)
mask = ids != 0
np.add.at(other,ids[mask],dy[mask])
print(np.array_equal(other,gradient))
# True

Maak embedding_backward(ids,dy,vocab_size,padding_idx=None) met uitvoervorm (vocab_size,D). De opgegeven ID's zijn geldig; begin op nul, tel alle bijdragen op en behoud ids en dy. Zonder padding_idx sla je geen rij over; padding hoeft bovendien niet altijd ID 0 te zijn.

HERHAALDE ID’S DELEN DEZELFDE RIJTel embeddinggradiënten bij hun token op

De beginvectoren zijn handgemaakte rekenvoorbeelden. Iedere kat-positie ontvangt dy=(1,−0.5), hond (−0.2,0.3), PAD (4,4). Bekijk één SGD-update.

katID 1katID 1hondID 2PADID 0
Nieuwe vector = beginvector − η × som van bijdragen
TokenBeginvectorGradiëntsomNieuwe vector
PADID 00.00, 0.000.00, 0.000.00, 0.00
katID 11.00, 0.002.00, -1.000.80, 0.10
hondID 20.00, 1.00-0.20, 0.300.02, 0.97
fietsID 3-1.00, 1.000.00, 0.00-1.00, 1.00
Kat-gradiënt2.00, -1.00PAD-gradiënt0.00, 0.00

De niet-gebruikte fietsrij krijgt geen bijdrage. Delen door het aantal voorkomens is een andere afspraak; hier tellen we op.

02
CHECKPOINTEen herhaald token krijgt meerdere bijdragen

3. Middel alleen echte tokens

Een eenvoudige reeksrepresentatie is het gemiddelde van de echte tokenvectoren. Laat padding zowel uit de som als uit de noemer weg. Voor [1,0,2,0] blijven kat=[1,0] en hond=[0,2] over: hun som [1,2] gedeeld door 2 geeft [0.5,1].

ids = np.array([1,0,2,0])
mask = ids != 0
kept = table[ids][mask]
average = kept.mean(axis=0)
print(average.tolist())
# [.5,1.]
print(np.zeros(table.shape[1]).tolist())
# [0.,0.]

Zelfs wanneer de PAD-vector nul was, zou delen door alle vier posities hier [0.25,0.5] geven. Dat maakt een korte reeks kunstmatig kleiner. Zet de paddingrij niet tijdelijk op nul: daarmee verander je de gedeelde invoertabel en los je de verkeerde noemer niet op.

Maak masked_mean(table,ids,padding_idx=0), gebruik lookup voor geldige ID's en geef een vector van breedte D terug. Voor een lege reeks of alleen padding kies je expliciet de nulvector. Herhaalde echte tokens blijven meerdere keren meetellen.

Een gemiddelde verliest volgorde; een RNN verwerkt die juist stap voor stap. Ook een nulinput kan een recurrente toestand veranderen via gewichten en bias. Daar vraagt padding om een eigen lengtemasker, niet alleen om een nulvector.

03
CHECKPOINTPadding hoort niet in de noemer

Wat neem je mee?

  • Token-ID's selecteren rijen; hun numerieke grootte draagt geen betekenis.
  • Herhaalde tokens delen parameters en tellen hun gradiënten op.
  • Padding vraagt een expliciete afspraak bij updates én gemiddelden.

Hierna onderzoek je hoe tekst wordt opgedeeld in de tokens die deze tabel gaan gebruiken.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
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
Selecteer rijen, reken niet met ID-groottes

Schrijf lookup(table,ids), met ValueError voor niet-gehele, negatieve of te grote ID’s. Retourneer een kopie van de geselecteerde rijen.

Een hint

Controleer ids.ndim en, wanneer er elementen zijn, dtype.kind in "iu". Een lege reeks mag vóór het casten apart worden behandeld.

02
Een herhaald token krijgt meerdere bijdragen

Schrijf embedding_backward(ids,dy,vocab_size,padding_idx=None), met uitvoervorm (vocab_size,dy.shape[1]). De opgegeven ID’s zijn geldig.

Een hint

Begin met een nulmatrix. Voeg iedere dy-rij toe aan zijn ID, behalve aan padding_idx.

03
Padding hoort niet in de noemer

Schrijf masked_mean(table,ids,padding_idx=0). Gebruik lookup voor geldige ID’s en geef een vector van de embeddingbreedte terug.

Een hint

Filter de posities op ids!=padding_idx vóór mean(axis=0). Als er geen echte tokens zijn, retourneer zeros(table.shape[1]).

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

def lookup(table,ids):
    table=np.asarray(table,float);ids=np.asarray(ids)
    if ids.ndim!=1 or (ids.size and (ids.dtype.kind not in "iu" or ids.min()<0 or ids.max()>=len(table))):
        raise ValueError("Ongeldige token-ID’s")
    return table[ids.astype(int)].copy()

def embedding_backward(ids,dy,vocab_size,padding_idx=None):
    result=np.zeros((vocab_size,dy.shape[1]),dtype=float)
    for index,gradient in zip(ids,dy):
        if index!=padding_idx:
            result[index]+=gradient
    return result

def masked_mean(table,ids,padding_idx=0):
    values=lookup(table,ids)
    mask=np.asarray(ids)!=padding_idx
    return values[mask].mean(axis=0) if np.any(mask) else np.zeros(table.shape[1])

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