- Je rekent attention uit met een masker vóór de softmax.
- Je berekent de terugweg door de gewogen som, softmax en inproducten.
In “de kat slaapt omdat hij moe is” moet een positie informatie uit andere plekken kunnen gebruiken. Attention maakt daarvoor een gewogen mengsel. Denk aan een query die verschillende kaartjes vergelijkt: keys bepalen de vergelijkingsscore en values leveren de inhoud die je uiteindelijk mengt.
De namen query, key en value beschrijven rekenrollen, geen menselijk begrip. Een hoog attentiongewicht is geen volledige verklaring van een modelantwoord. We kiezen hieronder kleine getallen, zodat de gewichten én de terugweg met de hand te controleren zijn.
De voorbeelden gebruiken NumPy en jouw functies uit deze editor. Q bevat T queries met elk d kenmerken, K bevat N keys met dezelfde breedte en V bevat N valuevectoren. Eventuele voorloopassen, zoals batch en heads, moeten in dit lescontract gelijk zijn.
1. Een masker vóór de verdeling
Stel dat twee toegestane scores log(2) en 0 zijn. Na exponentiëren krijg je 2 en 1; delen door de som geeft gewichten 2/3 en 1/3. Een verboden key krijgt vóór deze normalisatie score min oneindig, zodat zijn exponent en gewicht nul zijn.
scores = np.array([[np.log(2),0.]])
allowed = np.array([[True,True]])
print(masked_softmax(scores,allowed))
# [[0.66666667 0.33333333]]
allowed[0,1] = False
print(masked_softmax(scores,allowed))
# [[1. 0.]]
Trek het hoogste toegestane getal per rij af vóór exponentiëren; de gewichtsverhouding blijft gelijk, terwijl enorme exponenten worden voorkomen. Nul zetten ná softmax is een misvatting: de overgebleven gewichten tellen dan doorgaans niet meer op tot één. Hier staat True voor toegestaan.
Maak masked_softmax voor eindige scores met minstens twee assen en een niet-lege laatste as, plus een booleaans broadcastbaar masker. Een volledig verboden rij wordt volgens ons expliciete lescontract een nulrij, zonder NaN; die rij vormt geen kansverdeling. Bij gewone causale attention blijft de diagonaal toegestaan.
012. Van overeenkomsten naar een mengsel
Het matrixproduct QKᵀ berekent iedere query-keyovereenkomst. Deel door de wortel van d, normaliseer over keys tot P en bereken Y=PV. De schaling tempert grote scores bij bredere heads; het is geen harde bovengrens of deling door het aantal tokens.
Neem d=1, query 1, keys log(2) en 0 en values 2 en 8. De gewichten zijn opnieuw 2/3 en 1/3; de uitvoer wordt 2×2/3+8×1/3=4. Andere valuebreedtes zijn toegestaan: ieder valuekenmerk gebruikt dezelfde gewichten.
q = np.array([[1.]])
k = np.array([[np.log(2)],[0.]])
v = np.array([[2.],[8.]])
allowed = np.ones((1,2),dtype=bool)
y,cache = attention(q,k,v,allowed)
print(y) # [[4.]]
Maak attention met uitvoer en cache voor q,k,v,weights. Controleer passende vormen en d>0. Reken met floats vóór dot-products, zodat bijvoorbeeld int16-invoer niet eerst overloopt.
Handgemaakte Q, K en V voor drie woorden; geen aangeleerde betekenis. Verander de query van één rij. De score is q·k/√2, daarna volgt softmax.
| Query | de | kat | slaapt |
|---|---|---|---|
| de | 1.000 | — | — |
| kat | 0.362 | 0.638 | — |
| slaapt | 0.472 | 0.381 | 0.147 |
Keys én values in dit rekenvoorbeeld: de=(1,0), kat=(0,1), slaapt=(−1,0.5). De streepjes zijn exact nul door het masker.
Deze uitvoer is de gewogen som van V. Een hoog attentiongewicht is op zichzelf geen verklaring van het hele model.
3. Softmax koppelt de keys aan elkaar
Uit Y=PV volgen dV=PᵀdY en dP=dYVᵀ. De speciale p−y-terugweg uit cross-entropy kun je hier niet gebruiken: attention krijgt een algemene gradiënt dP. Het verhogen van één score verandert immers ook de andere genormaliseerde gewichten.
Schrijf per rij g=dP en c als de som van p×g over alle keys. Dan is de scoregradiënt p×(g−c); c wordt naar iedere kolom van dezelfde rij gebroadcast. Dit volgt uit de softmax-afgeleide hieronder, waarbij δ alleen bij gelijke indices één is.
Voor ons voorbeeld is dY=1, dus g=[2,8] en c=4. De scoregradiënten zijn [−4/3,4/3]: meer gewicht naar de tweede value verhoogt de uitvoer. De som van deze twee gradiënten is nul, passend bij de onveranderde softmax als alle scores evenveel verschuiven.
p = cache['weights']
dp = np.array([[2.,8.]])
ds = p*(dp-(p*dp).sum(
axis=-1,keepdims=True))
print(np.round(ds,4))
# [[-1.3333 1.3333]]
Daarna zijn dQ=dSK/√d en dK=dSᵀQ/√d. Dezelfde schaalfactor hoort in beide routes; dV krijgt die factor niet opnieuw. Verboden keys hebben P=0 en daardoor dS=0, ook voor een volledig gemaskeerde rij.
g = attention_backward(
cache,np.ones_like(y))
print(np.round(g['v'],4))
# [[0.6667]
# [0.3333]]
eps = 1e-5
plus = attention(q+eps,k,v,allowed)[0]
minus = attention(q-eps,k,v,allowed)[0]
numeric = (plus-minus)/(2*eps)
print(np.allclose(g['q'],numeric))
# True
Maak attention_backward zonder een extra batch- of tijdmiddeling. Deze kleine differentieproef controleert één querygetal; de checkpoints onderzoeken ook meerdere batches, breedtes en maskers.
03Wat neem je mee?
- Masker vóór softmax en normaliseer over de toegestane keys.
- Queries en keys bepalen gewichten; values leveren het mengsel.
- De softmax-terugweg koppelt alle keys en gebruikt de gecachete gewichten.
Hierna verdeel je de modelbreedte over meerdere heads en leer je de projecties eromheen.