Zum Inhalt springen

~/teaching · machine learning

Aufmerksamkeitsmechanismus und Transformer, Schritt für Schritt

Ein einfacher Satz, dann die Mathematik, dann die Zahlen von Hand: Einbettungen, Abfragen, Schlüssel, Werte, Köpfe, Positionen, Encoder, Decoder

Zusammenfassung

Ein Sprachmodell braucht die Bedeutung eines Wortes, und die Bedeutung eines Wortes hängt von den Wörtern ab, die es umgeben. Aufmerksamkeit (Attention) ist die Operation, mit der jedes Wort eines Satzes etwas von den anderen übernehmen kann, in Anteilen, die das Modell lernt. Dieser Artikel baut sie von der ersten Idee bis zum vollständigen Transformer auf, in der Reihenfolge, die Studierende brauchen: Einbettungen (Embeddings), der gewichtete Mittelwert, Abfragen, Schlüssel und Werte, mehrere Köpfe, die Position der Wörter, dann Encoder, Decoder und die Vorhersage des nächsten Tokens.

Jeder Abschnitt beginnt mit einem einfachen Satz, bringt dann die Mathematik und rechnet anschließend alles an einem Satz aus vier Wörtern in zwei Dimensionen nach, klein genug, um ihn mit dem Bleistift zu prüfen. Nichts verschwindet hinter einem Bibliotheksaufruf: Die zwölf Zeilen NumPy am Ende reproduzieren jede Zahl im Text.

Lesehinweise

  • Wenn Sie nur zehn Minuten haben, lesen Sie die einfachen Sätze und sehen Sie sich die Abbildungen an: Zusammen erzählen sie die ganze Geschichte.
  • Wenn Sie sich auf eine Prüfung oder ein Vorstellungsgespräch vorbereiten, folgen Sie den Schritt-für-Schritt-Listen und rechnen Sie das Beispiel auf Papier nach; die Tabellen liefern die Zahlen zum Vergleich.
  • Wenn Sie es implementieren wollen, springen Sie zum Code am Ende und kehren Sie dann zu den Abschnitten zurück, um die Gründe hinter jeder Zeile zu verstehen.

Dieser Artikel begleitet den Machine-Learning-Kurs an der Centrale Méditerranée.

Einbettungen: Ein Wort ist ein Punkt im Raum

Eine Einbettung verwandelt ein Wort in eine Liste von Zahlen, so angeordnet, dass Wörter mit ähnlicher Bedeutung nahe beieinander liegen.

Ein Computer kann die Wörter Pferd und Gaul nicht als Text vergleichen: Die beiden Zeichenketten haben keinen Buchstaben gemeinsam. Zwei Listen von Zahlen kann er dagegen vergleichen. Eine Einbettung (Embedding) ist eine Funktion, die jedem Token (einem Wort oder einem Wortteil) einen Vektor in Rd\R^d zuordnet, mit dd typischerweise zwischen einigen Hundert und einigen Tausend, und die so trainiert wird, dass die Geometrie die Bedeutung widerspiegelt: nahe Vektoren für verwandte Wörter, weit entfernte für Wörter ohne Bezug zueinander.

Schritt für Schritt

  1. Tokenisieren. Den Text in Tokens zerlegen. In diesem Artikel ist ein Token ein Wort; echte Modelle zerlegen in Teilwörter, damit auch seltene Wörter abgedeckt sind.
  2. Nachschlagen. Jedes Token ist eine Zeile einer großen Tabelle E∈R∣V∣×dE \in \R^{|V| \times d}, wobei ∣V∣|V| die Größe des Vokabulars ist. Die Einbettung von Token ii ist einfach die Zeile EiE_i.
  3. Vergleichen. Zwei Vektoren vergleicht man über den Kosinus des Winkels zwischen ihnen, der ihre Länge ignoriert: cos⁡θ=a⃗⋅b⃗∥a⃗∥ ∥b⃗∥\cos\theta = \dfrac{\vv{a}\cdot\vv{b}}{\lVert\vv{a}\rVert\,\lVert\vv{b}\rVert}. Ein Wert nahe 1 bedeutet dieselbe Richtung, nahe 0 keinen Zusammenhang, nahe −1-1 die entgegengesetzte Richtung.
cos⁡θab=a⃗⋅b⃗∥a⃗∥ ∥b⃗∥=∑k=1dak bk∑kak2 ∑kbk2\cos\theta_{ab} = \frac{\vv{a}\cdot\vv{b}}{\lVert\vv{a}\rVert\,\lVert\vv{b}\rVert} = \frac{\sum_{k=1}^{d} a_k\, b_k}{\sqrt{\sum_k a_k^2}\,\sqrt{\sum_k b_k^2}}
(1)

Die Tabelle EE wird gelernt, nicht von Hand geschrieben: Man trainiert ein Modell auf einem großen Korpus darauf, Wörter aus ihren Nachbarn vorherzusagen, und die Zeilen von EE pendeln sich dort ein, wo die Vorhersage am besten funktioniert. Die berühmten Regelmäßigkeiten folgen aus diesem Training: In einem guten Raum ist der Vektor von König zu Königin fast derselbe wie der Vektor von Mann zu Frau.

Das Beispiel, das sich durch den ganzen Artikel zieht

Nehmen wir den Satz Apple shares fell sharply („Die Apple-Aktien fielen stark“), vier Tokens, und einen Spielzeugraum der Dimension d=2d = 2. Achse 1 lesen wir als „Nahrung“, Achse 2 als „Finanzen“. Die Einbettungstabelle, auf allgemeinem Text trainiert, hat Apple in die Nähe der Frucht gelegt, weil das Wort meistens genau das bedeutet:

X=[x⃗Applex⃗sharesx⃗fellx⃗sharply]=[1.000.200.101.000.300.600.200.50]X = \begin{bmatrix} \vv{x}_{\text{Apple}} \\ \vv{x}_{\text{shares}} \\ \vv{x}_{\text{fell}} \\ \vv{x}_{\text{sharply}} \end{bmatrix} = \begin{bmatrix}1.00 & 0.20 \\ 0.10 & 1.00 \\ 0.30 & 0.60 \\ 0.20 & 0.50\end{bmatrix}
(2)
0.250.250.50.50.750.7511Achse 1: NahrungAchse 2: FinanzenApplesharesfellsharplyApple nach der Aufmerksamkeit[0.36, 0.62]
Abbildung 1: Die vier Tokens in der Spielzeugebene. Apple zeigt in Richtung Nahrung; shares, fell und sharply zeigen in Richtung Finanzen. Der gestrichelte Pfeil zeigt, wohin die Aufmerksamkeit Apple in Abschnitt 3 verschieben wird.

Der Kosinus zwischen Apple und shares beträgt 0,29: Die bloßen Einbettungen sagen, dass die beiden Wörter wenig miteinander zu tun haben. In diesem Satz haben sie alles miteinander zu tun. Diese Lücke ist das Problem, das die Aufmerksamkeit löst.

Zurück zum Inhaltsverzeichnis.

Das Problem: ein Vektor pro Wort, egal in welchem Satz

Eine feste Einbettung gibt einem Wort in jedem Satz denselben Vektor und kann deshalb eine Frucht nicht von einem Unternehmen unterscheiden.

Die Tabelle EE hat eine Zeile pro Token. Apple erhält denselben Vektor in an apple a day und in Apple shares fell sharply; Bank erhält denselben Vektor, ob sie ein Geldinstitut oder eine Sitzgelegenheit ist; Kurs, ob er ein Börsenkurs, eine Fahrtrichtung oder ein Lehrgang ist. Linguisten nennen solche Wörter polysem; für ein Modell sind sie ein einziger Punkt, der eigentlich zwei sein müsste.

Was wir wollen, ist eine zweite Folge y⃗1,…,y⃗L\vv{y}_1, \dots, \vv{y}_L, ein Vektor pro Token, wobei y⃗i\vv{y}_i der Vektor von Token ii in diesem Satz ist: dasselbe Wort, von seinen Nachbarn neu verortet. Diese Darstellung muss aus dem Satz selbst aufgebaut werden, in dem Moment, in dem der Satz gelesen wird, statt in einer Tabelle nachgeschlagen zu werden.

Bemerkung (Warum ein gewichteter Mittelwert). Der einfachste Weg für ein Token, seinen Kontext zu berücksichtigen, besteht darin, ein wenig von jedem anderen Token beizumischen. Eine Mischung ist ein gewichteter Mittelwert, und die ganze Frage lautet dann: wie viel von jedem Nachbarn, und wer entscheidet das.

Zurück zum Inhaltsverzeichnis.

Sitraka FORLERSystemarchitekt, Dozent für Machine Learning
Aufmerksamkeit, Schritt für Schritt

Aufmerksamkeit als gewichteter Mittelwert

Jedes Wort wird zu einem gewichteten Mittelwert aller Wörter des Satzes, wobei die Wörter, die für dieses Wort zählen, mehr Gewicht erhalten.

Der Satz sei die Folge x⃗1,…,x⃗L\vv{x}_1, \dots, \vv{x}_L von Einbettungen, LL Tokens der Dimension dd. Der kontextuelle Vektor von Token ii ist eine gewichtete Summe aller Tokens:

y⃗i=∑j=1Lwij x⃗j,wij≥0,∑j=1Lwij=1\vv{y}_i = \sum_{j=1}^{L} w_{ij}\,\vv{x}_j, \qquad w_{ij} \ge 0,\quad \sum_{j=1}^{L} w_{ij} = 1
(3)

Das Gewicht wijw_{ij} gibt an, wie stark Token jj in der neuen Fassung von Token ii zählt. Die Nebenbedingungen machen die Gewichte zu einer Wahrscheinlichkeitsverteilung über den Satz, sodass y⃗i\vv{y}_i in derselben Region des Raums bleibt wie die Eingaben: ein echter Mittelwert, keine Explosion.

Schritt für Schritt

  1. Bewerten. Messen, wie relevant jj für ii ist. Das rechnerisch günstigste Maß ist das Skalarprodukt (Dot Product) sij=x⃗i⋅x⃗js_{ij} = \vv{x}_i\cdot\vv{x}_j: groß, wenn die beiden Vektoren in dieselbe Richtung zeigen, null, wenn sie senkrecht aufeinander stehen.
  2. Normieren. Die LL Scores der Zeile ii mit der Softmax-Funktion in Gewichte umwandeln, die positiv sind und sich zu 1 addieren: wij=esij∑ℓ=1Lesiℓw_{ij} = \dfrac{e^{s_{ij}}}{\sum_{\ell=1}^{L} e^{s_{i\ell}}}.
  3. Mischen. y⃗i\vv{y}_i als gewichtete Summe aus Gleichung (3) berechnen.
  4. Für jedes Token wiederholen. Die Zeilen beeinflussen sich nicht gegenseitig: Die LL Berechnungen sind unabhängig und laufen parallel, weshalb diese Operation so gut zu GPUs passt.
wij=softmax⁡j(si1,…,siL)=exp⁡(x⃗i⋅x⃗j)∑ℓ=1Lexp⁡(x⃗i⋅x⃗ℓ)w_{ij} = \softmax_j\big(s_{i1}, \dots, s_{iL}\big) = \frac{\exp(\vv{x}_i\cdot\vv{x}_j)}{\sum_{\ell=1}^{L}\exp(\vv{x}_i\cdot\vv{x}_\ell)}
(4)

Stapelt man die Zeilen, erhält man die Matrixform. Mit X∈RL×dX \in \R^{L\times d} ist die Score-Matrix S=XXTS = XX\T (Eintrag ijij ist x⃗i⋅x⃗j\vv{x}_i\cdot\vv{x}_j), die Softmax wird Zeile für Zeile angewendet, und die neue Folge ist das Produkt der Gewichte mit den Eingaben:

Y=softmax⁡ ⁣(XXT)X,Y∈RL×dY = \softmax\!\left(XX\T\right) X, \qquad Y \in \R^{L\times d}
(5)

Von Hand, am Beispiel

Zeile 1 ist Apple. Die Skalarprodukte von Apple mit den vier Tokens betragen x⃗1⋅x⃗1=1,0×1,0+0,2×0,2=1,04\vv{x}_1\cdot\vv{x}_1 = 1{,}0\times1{,}0 + 0{,}2\times0{,}2 = 1{,}04, dann 0,30 mit shares, 0,42 mit fell und 0,30 mit sharply. Die Exponentialwerte, geteilt durch ihre Summe, ergeben die Gewichte der ersten Zeile von Tabelle 1.

Tabelle 1: Aufmerksamkeitsgewichte wijw_{ij} mit dem bloßen Skalarprodukt (ohne Projektion, ohne Skalierung). Jede Zeile summiert sich zu 1. Apple behält 40 % von sich selbst und gibt shares nicht mehr als sharply.
i\ji \backslash jApplesharesfellsharply
Apple0,4010,1910,2160,191
shares0,1760,3590,2450,220
fell0,2380,2930,2450,224
sharply0,2330,2900,2470,230

Der neue Vektor von Apple ist y⃗1=0.401 x⃗1+0.191 x⃗2+0.216 x⃗3+0.191 x⃗4=[0.523,  0.497]\vv{y}_1 = 0.401\,\vv{x}_1 + 0.191\,\vv{x}_2 + 0.216\,\vv{x}_3 + 0.191\,\vv{x}_4 = [0.523,\;0.497]. Er hat sich von [1.00,  0.20][1.00,\;0.20] in Richtung der Finanzachse bewegt, was die richtige Richtung ist, aber vor allem, weil drei der vier Wörter zufällig dort liegen. Nichts in der Rechnung wusste, dass shares das Wort war, auf das es ankam.

bloßes SkalarproduktApplesharesfellsharply0.400.190.220.190.180.360.250.220.240.290.250.220.230.290.250.23Applesharesfellsharplymit der Projektion W^QApplesharesfellsharply0.210.320.250.230.360.200.230.210.300.240.240.220.290.240.240.23ApplesharesfellsharplyZeilen: das Token, das schaut; Spalten: das Token, auf das geschaut wird
Abbildung 2: Dieselben Gewichte als Heatmap, links mit dem bloßen Skalarprodukt, rechts nach den gelernten Projektionen aus Abschnitt 4. Je dunkler, desto größer das Gewicht. Rechts hat die Zeile von Apple ihr Gewicht auf shares verlagert.

Bemerkung (Wo die Methode an ihre Grenzen stößt). Das Skalarprodukt belohnt Vektoren, die bereits in dieselbe Richtung zeigen. Stünden Apple und shares im Einbettungsraum senkrecht aufeinander, wäre ihr Score 0, und shares bekäme das kleinste Gewicht, genau dann, wenn es das größte bekommen sollte. Die Gewichte müssen in einem Raum berechnet werden, in dem die relevanten Paare gleich ausgerichtet sind, und dieser Raum muss gelernt werden. Das ist die Aufgabe des nächsten Abschnitts.

Zurück zum Inhaltsverzeichnis.

Abfragen, Schlüssel und Werte

Vor dem Vergleich wird jedes Wort dreimal projiziert: auf eine Frage, die es stellt, auf ein Etikett, auf das es hört, und auf den Inhalt, den es weitergibt.

Im gewichteten Mittelwert spielte ein einziger Vektor x⃗i\vv{x}_i drei Rollen zugleich: Er fragte nach Kontext, er diente als Vergleichspunkt, und er wurde eingemischt. Trennt man die drei Rollen, kann das Modell jede einzeln lernen. Stellen Sie sich einen Lesesaal vor: Ein Leser gibt eine Abfrage (Query) ab („wonach ich suche“), jedes Buch trägt auf dem Rücken einen Schlüssel (Key) („wovon ich handle“), und das Buch selbst ist der Wert (Value) („was Sie bekommen, wenn Sie mich aufschlagen“). Die Relevanz wird zwischen der Abfrage und den Schlüsseln gemessen; zurückgegeben wird eine Mischung der Werte.

Schritt für Schritt

  1. Projizieren. Drei gelernte Matrizen verwandeln die Eingabe in Abfragen, Schlüssel und Werte: Q=XWQQ = XW^Q, K=XWKK = XW^K, V=XWVV = XW^V, mit WQ,WK∈Rd×dkW^Q, W^K \in \R^{d\times d_k} und WV∈Rd×dvW^V \in \R^{d\times d_v}.
  2. Bewerten. Der Score von Token jj für Token ii ist das Skalarprodukt der Abfrage von ii mit dem Schlüssel von jj: sij=q⃗i⋅k⃗js_{ij} = \vv{q}_i\cdot\vv{k}_j, also S=QKTS = QK\T.
  3. Skalieren. Die Scores durch dk\sqrt{d_k} teilen, damit sie in dem Bereich bleiben, in dem die Softmax noch einen Gradienten hat (siehe Bemerkung unten).
  4. Normieren und mischen. Softmax auf jede Zeile, dann mit den Werten multiplizieren.
Attention⁡(Q,K,V)=softmax⁡ ⁣(QKTdk)V\Attention(Q, K, V) = \softmax\!\left(\frac{QK\T}{\sqrt{d_k}}\right) V
(6)

Das ist die skalierte Skalarprodukt-Aufmerksamkeit (Scaled Dot-Product Attention) aus dem Paper von 2017, und sie ist der ganze Mechanismus. Alles Weitere (Köpfe, Positionen, Encoder, Decoder) baut auf dieser einen Zeile auf. Die Matrizen WQW^Q, WKW^K, WVW^V sind die Parameter, die das Training anpasst: Der Verlust bei der Vorhersage des nächsten Tokens verschiebt sie so, dass sich die Abfragen eines Tokens an den Schlüsseln der Tokens ausrichten, die helfen, das Folgende vorherzusagen.

für alle Tokens zugleich: softmax(QKᵀ / √dₖ) VxᵢToken iW^QW^KW^VqᵢkᵢvᵢSchlüssel aller TokensWerte aller TokensScores qᵢ·kⱼsoftmax → Gewichtegewichtete SummeΣⱼ wᵢⱼ vⱼyᵢkontextueller Vektor
Abbildung 3: Die drei Projektionen eines Tokens, dann der Score gegen jeden Schlüssel, die Softmax und die Mischung der Werte. Der graue Kasten wird auf alle Tokens des Satzes gleichzeitig angewendet, als ein einziges Matrixprodukt.

Bemerkung (Warum durch die Wurzel der Schlüsseldimension teilen). Sind die Koordinaten von q⃗\vv{q} und k⃗\vv{k} unabhängig mit Mittelwert 0 und Varianz 1, so ist ihr Skalarprodukt eine Summe von dkd_k Termen der Varianz 1; es hat also die Varianz dkd_k und die typische Größe dk\sqrt{d_k}. Bei dk=64d_k = 64 streuen die rohen Scores über Dutzende Einheiten, die Softmax legt ihre gesamte Masse auf ein einziges Token, und ihr Gradient verschwindet. Die Division durch dk\sqrt{d_k} bringt die Varianz unabhängig von der Dimension auf 1 zurück.

Von Hand, am Beispiel

Zuerst nur die Skalierung. Mit dk=2d_k = 2 werden die Scores von Zeile 1 durch 2\sqrt{2} geteilt: 0,735; 0,212; 0,297; 0,212, und die Gewichte werden zu 0,353; 0,209; 0,228; 0,209: flacher, wie erwartet, denn die Softmax kleinerer Zahlen liegt näher an der Gleichverteilung.

Nun eine Projektion. Wir wählen WQW^Q von Hand, um zu zeigen, was das Training entdecken könnte: eine Matrix, die die beiden Achsen vertauscht. Die Abfrage eines Tokens fragt dann nach der anderen Lesart seiner selbst. Apple, das auf der Nahrungsachse liegt, fragt nach Finanzen. WKW^K und WVW^V bleiben die Identität.

WQ=[0110],Q=XWQ=[0.201.001.000.100.600.300.500.20],K=V=XW^Q = \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix}, \qquad Q = XW^Q = \begin{bmatrix}0.20 & 1.00 \\ 1.00 & 0.10 \\ 0.60 & 0.30 \\ 0.50 & 0.20\end{bmatrix}, \qquad K = V = X
(7)

Die Abfrage von Apple ist jetzt [0.20,  1.00][0.20,\;1.00]. Die skalierten Scores dieser Abfrage gegen die vier Schlüssel betragen 0,283; 0,721; 0,467; 0,382: shares passt jetzt am besten, wie Tabelle 2 zeigt.

Tabelle 2: Aufmerksamkeitsgewichte nach der Projektion WQW^Q und der Skalierung mit dk\sqrt{d_k}. Die Zeile von Apple legt nun ihr größtes Gewicht auf shares; die Zeile von shares blickt zurück auf Apple.
i\ji \backslash jApplesharesfellsharply
Apple0,2060,3190,2470,227
shares0,3610,2020,2270,210
fell0,2960,2400,2400,225
sharply0,2920,2380,2410,229

Die Ausgabe für Apple wird zu y⃗1=[0.358,  0.623]\vv{y}_1 = [0.358,\;0.623], weiter entlang der Finanzachse als zuvor, und diesmal, weil das richtige Wort sie gezogen hat. Eine einzige, absichtlich gewählte Matrix der Größe 2×22\times2 hat geschafft, was kein Skalarprodukt auf den rohen Einbettungen konnte. Ein trainiertes Modell hat Tausende solcher Matrizen und wählt sie anhand der Daten.

Zurück zum Inhaltsverzeichnis.

Mehrkopf-Aufmerksamkeit

Mehrere Aufmerksamkeitsberechnungen laufen nebeneinander, jede mit eigenen Projektionen, sodass eine die Bedeutung verfolgen kann, während eine andere die Grammatik verfolgt.

Ein einzelnes Tripel von Projektionen erfasst eine einzige Art von Beziehung. Die Vertauschungsmatrix oben klärt den Fall Apple; sie sagt nichts darüber, welches Verb zu welchem Subjekt gehört oder welches Wort zwei Stellen weiter links steht. Statt eine einzige Projektion alles erledigen zu lassen, führt der Transformer in der Mehrkopf-Aufmerksamkeit (Multi-Head Attention) hh Aufmerksamkeitsberechnungen parallel aus, die Köpfe (Heads), jede mit eigenen WiQW^Q_i, WiKW^K_i, WiVW^V_i der reduzierten Dimension dk=dv=d/hd_k = d_v = d / h, und fügt ihre Ausgaben anschließend zusammen.

Schritt für Schritt

  1. Aufteilen. Kopf ii projiziert die Eingabe in seinen eigenen kleinen Raum: headi=Attention⁡(XWiQ,  XWiK,  XWiV)\text{head}_i = \Attention(XW^Q_i,\; XW^K_i,\; XW^V_i), jede Ausgabe in RL×dv\R^{L\times d_v}.
  2. Verketten. Die hh Ausgaben werden nebeneinandergelegt, was eine Matrix in RL×hdv=RL×d\R^{L\times h d_v} = \R^{L\times d} ergibt.
  3. Zurückprojizieren. Eine letzte Matrix WO∈Rhdv×dW^O \in \R^{h d_v\times d} mischt die Köpfe zu einer Darstellung pro Token.
MultiHead⁡(X)=Concat⁡(head1,…,headh) WOheadi=Attention⁡ ⁣(XWiQ,  XWiK,  XWiV)\begin{aligned} \MultiHead(X) &= \Concat(\text{head}_1, \dots, \text{head}_h)\,W^O \\ \text{head}_i &= \Attention\!\left(XW^Q_i,\; XW^K_i,\; XW^V_i\right) \end{aligned}
(8)

Die Kosten sind dieselben wie bei einem einzigen Kopf voller Dimension: hh Köpfe der Dimension d/hd/h erfordern genauso viel Rechenarbeit wie ein Kopf der Dimension dd. Gewonnen wird Vielfalt. In trainierten Modellen finden Sondierungsanalysen (Probes) Köpfe, die der Syntax folgen (ein Verb achtet auf sein Subjekt), Köpfe, die auf das vorherige Token achten, Köpfe, die ein seltenes Wort von weiter vorne im Text kopieren, und Köpfe, deren Rolle noch niemand benannt hat.

X (4 Tokens, d = 2)Kopf 1: Achse FinanzenGewichte von Apple:0.23 0.27 0.25 0.25Kopf 2: Achse NahrungGewichte von Apple:0.43 0.17 0.21 0.19verkettenW^OApple, 2 Köpfe:[0.591, 0.544]
Abbildung 4: Zwei Köpfe am Beispiel. Jeder Kopf sieht eine Achse des Spielzeugraums; ihre Ausgaben werden verkettet und mit WOW^O projiziert. In einem echten Modell sieht jeder Kopf einen gelernten Ausschnitt der Dimension d/hd/h.

Von Hand, am Beispiel

Nehmen wir h=2h = 2 Köpfe der Dimension dk=dv=1d_k = d_v = 1. Kopf 1 behält nur die Finanzkoordinate jedes Tokens (W1Q=W1K=W1V=[0,1]TW^Q_1 = W^K_1 = W^V_1 = [0, 1]\T), Kopf 2 nur die Nahrungskoordinate ([1,0]T[1, 0]\T). In Kopf 2 lautet die Zeile von Apple 0,425; 0,173; 0,211; 0,191: Apple, das einzige Wort aus dem Bereich Nahrung, achtet vor allem auf sich selbst. In Kopf 1 lautet sie 0,232; 0,272; 0,251; 0,246: Auf der Finanzachse ähneln sich die vier Tokens, und die Gewichte sind nahezu gleich verteilt. Die Verkettung der beiden Ausgaben für Apple ergibt [0.591,  0.544][0.591,\;0.544]; mit WOW^O als Identität ist das die Ausgabe der Mehrkopf-Aufmerksamkeit.

Bemerkung (Was die beiden Köpfe sagen). Kopf 2 beantwortet die Frage Wer bin ich?, Kopf 1 die Frage Wie sieht der Markt um mich herum aus? Ein einziger Kopf der Dimension 2 hätte beide Fragen zu einem einzigen Score verschmelzen müssen. Genau darum geht es bei den Köpfen: getrennte Fragen, getrennte Gewichte, eine gemeinsame Antwort.

Zurück zum Inhaltsverzeichnis.

Positionskodierung: den Wörtern eine Reihenfolge geben

Die Aufmerksamkeit behandelt den Satz als ungeordnete Wortmenge (Bag of Words), deshalb wird zu jedem Token ein Vektor addiert, der seine Position beschreibt, bevor irgendetwas anderes geschieht.

Sehen Sie sich noch einmal Gleichung (6) an: Nichts darin hängt vom Index jj ab, außer über x⃗j\vv{x}_j. Vertauscht man die Tokens, werden die Ausgaben auf dieselbe Weise vertauscht, sonst aber nicht verändert. Die Bank kaufte die Firma und Die Firma kaufte die Bank ergeben dieselbe Menge von Vektoren. Die Reihenfolge trägt Bedeutung, und der Mechanismus wirft sie weg.

Die Abhilfe, die Positionskodierung (Positional Encoding), besteht darin, zu jeder Einbettung vor der ersten Aufmerksamkeitsschicht einen Vektor zu addieren, der nur von der Position pp des Tokens in der Folge abhängt:

x⃗p←x⃗p+PE⁡(p),PE⁡(p)∈Rd\vv{x}_p \leftarrow \vv{x}_p + \PE(p), \qquad \PE(p) \in \R^d
(9)

Schritt für Schritt

  1. Für jede Position einen Code wählen. Das Paper von 2017 verwendet Sinus und Kosinus mit geometrisch gestaffelten Frequenzen: Koordinate 2i2i von PE⁡(p)\PE(p) ist sin⁡(p/100002i/d)\sin(p / 10000^{2i/d}), Koordinate 2i+12i+1 ist cos⁡(p/100002i/d)\cos(p / 10000^{2i/d}). Niedrige Koordinaten schwingen schnell und unterscheiden benachbarte Positionen; hohe Koordinaten schwingen langsam und ordnen die ganze Folge.
  2. Addieren, nicht verketten. Die Addition behält die Dimension dd bei und lässt die Projektionen WQW^Q, WKW^K Position und Inhalt gemeinsam lesen; so kann ein Kopf lernen, auf das Token direkt vor mir zu achten.
  3. Die Schichten davon Gebrauch machen lassen. Von da an tragen jede Abfrage und jeder Schlüssel die Position, sodass die Scores vom Abstand ebenso abhängen können wie von der Bedeutung.
PE⁡(p)2i=sin⁡ ⁣(p100002i/d),PE⁡(p)2i+1=cos⁡ ⁣(p100002i/d)\PE(p)_{2i} = \sin\!\left(\frac{p}{10000^{2i/d}}\right), \qquad \PE(p)_{2i+1} = \cos\!\left(\frac{p}{10000^{2i/d}}\right)
(10)

Bemerkung (Warum Sinus und Kosinus). Für einen festen Versatz kk ist das Paar (sin⁡ωp,cos⁡ωp)(\sin\omega p, \cos\omega p) an Position p+kp+k eine Drehung des Paars an Position pp um den Winkel ωk\omega k: eine lineare Funktion, unabhängig von pp. Ein Kopf kann daher k Positionen nach links als feste lineare Abbildung lernen, unabhängig von der absoluten Position. Die Kodierungen sind außerdem beschränkt, und sie lassen sich auf Längen ausdehnen, die im Training nie vorkamen. Viele neuere Modelle verwenden eine rotationsbasierte Variante, die dieselbe Drehidee innerhalb der Abfragen und Schlüssel anwendet, statt sie zur Eingabe zu addieren.

sin pcos pp = 0Applep = 1sharesp = 2fellp = 3sharplyPE(p) = [sin p, cos p]; ein Schritt = Drehung um einen Radiant
Abbildung 5: Die sinusförmige Kodierung für d=2d = 2: Die beiden Koordinaten von PE⁡(p)\PE(p) für die Positionen 0 bis 3 sind Punkte auf dem Einheitskreis bei den Winkeln 0, 1, 2 und 3 Radiant. Der Schritt zur nächsten Position ist immer dieselbe Drehung.

Von Hand, am Beispiel

Bei d=2d = 2 gibt es nur eine Frequenz, 1/100000=11/10000^0 = 1, also PE⁡(p)=[sin⁡p,cos⁡p]\PE(p) = [\sin p, \cos p] im Bogenmaß. Tabelle 3 addiert sie zu den vier Einbettungen. Die beiden Sätze Apple shares fell sharply und Shares fell sharply, Apple geben Apple jetzt zwei verschiedene Eingaben, [1.00,  1.20][1.00,\;1.20] an Position 0 und [1.14,  −0.79][1.14,\;-0.79] an Position 3, und alle Aufmerksamkeitsgewichte ändern sich mit ihnen.

Tabelle 3: Sinusförmige Positionskodierung für d=2d = 2 und die positionsbewusste Eingabe X+PE⁡X + \PE. Die Kodierung hängt nur von der Position ab, nie vom Wort.
Tokenppsin⁡p\sin pcos⁡p\cos px1+sin⁡px_1 + \sin px2+cos⁡px_2 + \cos p
Apple00,0001,0001,0001,200
shares10,8410,5400,9411,540
fell20,909−0,4161,2090,184
sharply30,141−0,9900,341−0,490

Zurück zum Inhaltsverzeichnis.

Sitraka FORLERSystemarchitekt, Dozent für Machine Learning
Aufmerksamkeit, Schritt für Schritt

Der Encoder-Block

Ein Encoder-Block lässt die Wörter miteinander reden (Aufmerksamkeit) und dann jedes Wort für sich nachdenken (ein kleines neuronales Netz), mit Umgehungspfaden, damit das Training stabil bleibt.

Der Transformer von Vaswani et al. (2017) wurde gebaut, um eine Folge in eine andere zu überführen, einen französischen Satz in einen englischen. Sein Encoder liest die gesamte Eingabe und erzeugt pro Eingabetoken einen kontextreichen Vektor. Er ist ein Stapel aus NN identischen Blöcken (6 im Paper), und jeder Block besteht aus zwei Teilschichten, die beide mit denselben zwei Kniffen versehen sind.

Ein Encoder-Block, Eingabe X∈RL×dX \in \R^{L\times d}

  1. Mehrkopf-Selbstaufmerksamkeit. A=MultiHead⁡(X)A = \MultiHead(X): Abfragen, Schlüssel und Werte stammen alle aus XX, deshalb heißt sie Selbstaufmerksamkeit (Self-Attention). Hier, und nur hier, fließt Information zwischen den Tokens.
  2. Residualverbindung und Normalisierung. X′=LayerNorm⁡(X+A)X' = \LayerNorm(X + A). Die Eingabe wieder zu addieren (das Residuum) bedeutet, dass ein Block nur eine Korrektur lernen muss und die Gradienten geradewegs durch den Stapel fließen; jeden Token-Vektor auf Mittelwert 0 und Varianz 1 zu normalisieren, hält die Skala von Block zu Block stabil.
  3. Feedforward-Netz. F=FFN⁡(X′)F = \FFN(X'), dasselbe zweischichtige Netz (Feed-Forward Network), unabhängig auf jedes Token angewendet: FFN⁡(x⃗)=W2 max⁡(0,W1x⃗+b⃗1)+b⃗2\FFN(\vv{x}) = W_2\,\max(0, W_1\vv{x} + \vv{b}_1) + \vv{b}_2, mit einer verborgenen Schicht der Breite 4d4d im Paper. Hier fließt keine Information zwischen den Tokens; jeder Vektor wird für sich transformiert.
  4. Noch einmal Residuum und Normalisierung. Z=LayerNorm⁡(X′+F)Z = \LayerNorm(X' + F) ist die Ausgabe des Blocks und die Eingabe des nächsten.
X′=LayerNorm⁡(X+MultiHead⁡(X))Z=LayerNorm⁡(X′+FFN⁡(X′))\begin{aligned} X' &= \LayerNorm\big(X + \MultiHead(X)\big) \\ Z &= \LayerNorm\big(X' + \FFN(X')\big) \end{aligned}
(11)
LayerNorm⁡(x⃗)=γ⊙x⃗−μσ2+ϵ+βμ=1d∑k=1dxk,σ2=1d∑k=1d(xk−μ)2\begin{aligned} \LayerNorm(\vv{x}) &= \gamma \odot \frac{\vv{x} - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta \\ \mu &= \frac{1}{d}\sum_{k=1}^{d} x_k, \qquad \sigma^2 = \frac{1}{d}\sum_{k=1}^{d} (x_k - \mu)^2 \end{aligned}
(12)

Die Aufmerksamkeit mischt über den Satz hinweg; das Feedforward-Netz arbeitet innerhalb jedes Tokens. Diese Arbeitsteilung ist gewollt: Die Aufmerksamkeit entscheidet, wohin geschaut wird, das Feedforward-Netz entscheidet, was daraus gemacht wird. Durch das Stapeln von NN Blöcken kann ein Token Kontext über den Kontext sammeln: Nach zwei Blöcken kann Apple nutzen, was shares selbst von fell gelernt hat.

Encoder-Block (× N)Eingabe + PositionenMehrkopf-SelbstaufmerksamkeitResiduum & Norm.Feedforward-NetzResiduum & Norm.Z: ein Vektor pro EingabetokenDecoder-Block (× N)bisherige Ausgabe + Positionenmaskierte SelbstaufmerksamkeitResiduum & Norm.KreuzaufmerksamkeitResiduum & Norm.Feedforward-NetzResiduum & Norm.Schlüssel,Wertelinear + Softmax → nächstes Token
Abbildung 6: Der Encoder-Block (links) und der Decoder-Block (rechts). Jeder Pfeil, der um eine Teilschicht herumführt, ist eine Residualverbindung mit anschließender Schichtnormalisierung. Die mittlere Teilschicht des Decoders bezieht ihre Schlüssel und Werte aus der Ausgabe des Encoders.

Zurück zum Inhaltsverzeichnis.

Der Decoder-Block

Der Decoder schreibt die Ausgabe Token für Token und schaut dabei auf das, was er schon geschrieben hat, und auf die Lesart der Eingabe, die der Encoder geliefert hat.

Der Decoder erzeugt die Ausgabefolge Token für Token. In Schritt tt hat er bereits t−1t-1 Tokens erzeugt, die eingebettet, mit Positionskodierung versehen und in Y<t∈R(t−1)×dY_{<t} \in \R^{(t-1)\times d} zusammengefasst sind, und er verfügt über die Encoder-Ausgabe Z∈RL×dZ \in \R^{L\times d}. Jeder seiner NN Blöcke hat drei Teilschichten, jede wie im Encoder mit Residualverbindung und Normalisierung versehen.

Ein Decoder-Block

  1. Maskierte Selbstaufmerksamkeit. Mehrkopf-Aufmerksamkeit über die bereits geschriebenen Tokens, mit einer Änderung: Ein Token darf nur auf die Positionen vor ihm achten. In der Score-Matrix wird jeder Eintrag sijs_{ij} mit j>ij > i vor der Softmax auf −∞-\infty gesetzt, sodass sein Gewicht genau 0 wird. Ohne die Maske ließe das Training das Modell die Antwort lesen, die es vorhersagen soll.
  2. Kreuzaufmerksamkeit (Cross-Attention). Die Abfragen stammen aus den eigenen Vektoren des Decoders; die Schlüssel und Werte stammen aus der Encoder-Ausgabe ZZ. Hier schaut die Ausgabe auf die Eingabe: Beim Schreiben des dritten englischen Wortes fragt der Decoder, welche französischen Wörter dafür relevant sind. Es ist die einzige Stelle, an der sich die beiden Hälften des Modells treffen.
  3. Feedforward-Netz. Dasselbe Netz pro Token wie im Encoder.
Attention⁡maskiert(Q,K,V)=softmax⁡ ⁣(QKTdk+M)VMij={0j≤i−∞j>i\begin{aligned} \Attention_{\text{maskiert}}(Q, K, V) &= \softmax\!\left(\frac{QK\T}{\sqrt{d_k}} + M\right) V \\ M_{ij} &= \begin{cases} 0 & j \le i \\ -\infty & j > i \end{cases} \end{aligned}
(13)
Cross(Y,Z)=Attention⁡ ⁣(YWQ,  ZWK,  ZWV)\text{Cross}(Y, Z) = \Attention\!\left(YW^Q,\; ZW^K,\; ZW^V\right)
(14)

Bemerkung (Encoder-Decoder, nur Encoder, nur Decoder). Das Modell von 2017 hat beide Hälften, weil es übersetzt. Modelle, die Text klassifizieren oder einbetten, behalten nur den Encoder (BERT ist eines davon). Die großen Sprachmodelle, die Text schreiben, behalten nur den Decoder: Es gibt keine separate Eingabe, auf die geachtet werden müsste, Prompt und Antwort bilden eine einzige Folge, und die Maske sorgt dafür, dass die Generierung von links nach rechts verläuft.

Zurück zum Inhaltsverzeichnis.

Das nächste Token vorhersagen

Der letzte Vektor des Decoders wird in einen Score für jedes Wort des Vokabulars umgewandelt, die Scores werden zu Wahrscheinlichkeiten, und ein Wort wird ausgewählt.

Nach dem letzten Decoder-Block fasst der Vektor an der letzten Position, h⃗t∈Rd\vv{h}_t \in \R^d, alles zusammen, was bisher geschrieben und was in der Eingabe gelesen wurde. Eine lineare Schicht bildet ihn auf einen Score pro Eintrag des Vokabulars ab, und eine Softmax verwandelt die ∣V∣|V| Scores in eine Wahrscheinlichkeitsverteilung:

P(na¨chstes Token=v∣Kontext)=softmax⁡ ⁣(Wout h⃗t+b⃗)vWout∈R∣V∣×d\begin{aligned} P(\text{nächstes Token} = v \mid \text{Kontext}) &= \softmax\!\left(W^{\text{out}}\,\vv{h}_t + \vv{b}\right)_v \\ W^{\text{out}} &\in \R^{|V|\times d} \end{aligned}
(15)

Die Generierungsschleife

  1. Das Vokabular bewerten. Die obige Verteilung für das aktuelle Präfix berechnen.
  2. Ein Token wählen. Das wahrscheinlichste (gierige Dekodierung, Greedy Decoding) oder eine Stichprobe aus der Verteilung, oft nachdem sie mit einer Temperatur geschärft oder abgeflacht wurde, oder unter Beschränkung auf die besten Kandidaten.
  3. Anhängen und wiederholen. Das gewählte Token wird an das Präfix angehängt, erhält seine Einbettung und seine Position, und der Decoder läuft erneut. Die Schleife endet beim Sequenzende-Token oder bei einer Maximallänge.

Beim Training gibt es keine Schleife: Der gesamte Zielsatz wird dem Decoder auf einmal zugeführt, die Maske verbirgt die Zukunft, und der Verlust ist die Kreuzentropie zwischen der vorhergesagten Verteilung an jeder Position und dem tatsächlichen nächsten Token, summiert über den Satz. Das nennt man Teacher Forcing, und deshalb lässt sich das Training eines Transformers über die Sequenz parallelisieren, die Generierung dagegen nicht.

Bemerkung (Kosten). Die Selbstaufmerksamkeit vergleicht jedes Token mit jedem anderen: L2L^2 Scores aus je dkd_k Multiplikationen, also O(L2d)O(L^2 d) Zeit und O(L2)O(L^2) Speicher pro Kopf und pro Schicht. Dieser quadratische Term ist der Grund, warum lange Kontexte teuer sind, und der Grund, warum es eine ganze Fachliteratur zu dünn besetzter oder approximativer Aufmerksamkeit gibt.

Zurück zum Inhaltsverzeichnis.

Sitraka FORLERSystemarchitekt, Dozent für Machine Learning
Aufmerksamkeit, Schritt für Schritt

Der gesamte Vorwärtsdurchlauf, der Reihe nach

  1. Tokenisieren und einbetten. Den Text in Tokens zerlegen; jedes in der Tabelle EE nachschlagen, um X∈RL×dX \in \R^{L\times d} zu erhalten.
  2. Positionen addieren. X←X+PE⁡X \leftarrow X + \PE, damit die Reihenfolge der Wörter den nächsten Schritt übersteht.
  3. Projizieren. Für jeden Kopf Q=XWQQ = XW^Q, K=XWKK = XW^K, V=XWVV = XW^V.
  4. Bewerten, skalieren, maskieren. S=QKT/dkS = QK\T / \sqrt{d_k}, plus −∞-\infty oberhalb der Diagonale in einem Decoder.
  5. Normieren. Softmax auf jede Zeile: Jedes Token erhält eine Verteilung über den Satz.
  6. Mischen. Die Gewichte mit VV multiplizieren; die Köpfe verketten; mit WOW^O projizieren.
  7. Residuum, Normalisierung, Feedforward, Residuum, Normalisierung. Ein Block ist fertig; NN-mal wiederholen.
  8. Den letzten Vektor lesen. Lineare Schicht auf das Vokabular, Softmax, ein Token wählen, anhängen, für das neue Präfix zurück zu Schritt 1.

Dieselbe Rechnung in zwölf Zeilen NumPy

Zwölf Zeilen, die Abschnitt 4 an der Beispielmatrix XX nachrechnen, mit der Projektion WQW^Q, die die Achsen vertauscht. Die beiden ausgegebenen Zeilen sind die erste Zeile von Tabelle 2 und die Ausgabe für Apple.

import numpy as np

def attention(Q, K, V, mask=None):
    """Scaled dot-product attention, one line per step of section 4."""
    d_k = K.shape[-1]
    S = Q @ K.T / np.sqrt(d_k)            # scores, scaled
    if mask is not None:
        S = S + mask                      # -inf above the diagonal
    W = np.exp(S - S.max(axis=-1, keepdims=True))
    W = W / W.sum(axis=-1, keepdims=True) # softmax, row by row
    return W @ V, W                       # mix of the values, and the weights

# Apple, shares, fell, sharply
X = np.array([[1.0, 0.2], [0.1, 1.0], [0.3, 0.6], [0.2, 0.5]])
W_Q = np.array([[0.0, 1.0], [1.0, 0.0]])   # the axis swap of section 4
Y, W = attention(X @ W_Q, X, X)
print(W[0].round(3))   # [0.206 0.319 0.247 0.227]: Apple looks at shares
print(Y[0].round(3))   # [0.358 0.623]

Glossar

Einbettung (Embedding)
Eine gelernte Abbildung von Tokens auf Vektoren, so trainiert, dass der Abstand die Bedeutung widerspiegelt. Eine Zeile pro Token in einer Tabelle EE.
Token
Die Einheit, die das Modell liest: ein Wort, ein Teilwort oder ein Zeichen, je nach Tokenizer.
Aufmerksamkeit (Attention)
Ein gewichteter Mittelwert der Werte einer Folge, mit Gewichten, die aus der Übereinstimmung zwischen einer Abfrage und allen Schlüsseln berechnet werden.
Abfrage, Schlüssel, Wert (Query, Key, Value)
Drei Projektionen desselben Tokens: wonach es sucht, worauf es hört, was es weitergibt.
Kopf (Head)
Eine Aufmerksamkeitsberechnung mit eigenen Projektionen; mehrere laufen parallel und werden verkettet.
Positionskodierung (Positional Encoding)
Ein zu jeder Einbettung addierter Vektor, der die Position des Tokens kodiert, damit die Reihenfolge nicht verloren geht.
Residualverbindung (Residual Connection)
Die Eingabe einer Teilschicht wird zu ihrer Ausgabe addiert, sodass die Teilschicht eine Korrektur lernt und die Gradienten durchfließen.
Schichtnormalisierung (Layer Normalization)
Jeden Token-Vektor auf Mittelwert 0 und Varianz 1 bringen, danach eine gelernte Skalierung und Verschiebung anwenden.
Encoder
Der Stapel von Blöcken, der die Eingabefolge liest und pro Token einen Kontextvektor ausgibt.
Decoder
Der Stapel von Blöcken, der die Ausgabe Token für Token schreibt, mit einer kausalen Maske und Kreuzaufmerksamkeit auf den Encoder.
Softmax
Die Funktion, die eine Liste von Scores in positive Gewichte mit der Summe 1 verwandelt, indem sie die Exponentialfunktion anwendet und durch die Gesamtsumme teilt.

Literatur

  1. A. Vaswani, N. Shazeer, N. Parmar, J. Uszkoreit, L. Jones, A. N. Gomez, Ł. Kaiser, I. Polosukhin, Attention Is All You Need, NeurIPS 2017. arXiv:1706.03762.
  2. D. Bahdanau, K. Cho, Y. Bengio, Neural Machine Translation by Jointly Learning to Align and Translate, ICLR 2015. arXiv:1409.0473. Der erste Aufmerksamkeitsmechanismus, additiv statt über das Skalarprodukt.
  3. J. L. Ba, J. R. Kiros, G. E. Hinton, Layer Normalization, 2016. arXiv:1607.06450.
  4. J. Su, Y. Lu, S. Pan, A. Murtadha, B. Wen, Y. Liu, RoFormer: Enhanced Transformer with Rotary Position Embedding, 2021. arXiv:2104.09864. Die in Abschnitt 6 erwähnte rotationsbasierte Variante.
  5. J. Alammar, The Illustrated Transformer, 2018. jalammar.github.io.
  6. P. Moskwa, Attention, please! Demystifying the math behind the attention mechanism and the transformer model, 2025. paulinamoskwa.github.io. Der Artikel, dessen Weg dieser hier folgt, mit einer Implementierung in PyTorch.
  7. A. Rush et al., The Annotated Transformer, Harvard NLP, 2018, überarbeitet 2022. nlp.seas.harvard.edu. Das Paper Zeile für Zeile neu implementiert.

Die Notizen des Machine-Learning-Kurses

Dieser Artikel erweitert einen Kurs. Zu jedem Foliensatz des Kurses gibt es außerdem öffentliche Notizen mit den Kernideen, den Formeln und einem durchgerechneten Beispiel:

Über diesen Artikel

Geschrieben für die Studierenden des Schwerpunkts Machine Learning an der Centrale Méditerranée und für alle, die ein Transformer-Tutorial gelesen haben und die Zahlen trotzdem nicht nachrechnen konnten. Der Weg von den Einbettungen zum vollständigen Modell ist der klassische; der Satz, das zweidimensionale Beispiel, die von Hand gewählte Projektion und der Code sind eigens für diesen Artikel entstanden. Die Mathematik ist die von Vaswani et al. (2017), mit der Schichtnormalisierung von Ba et al. (2016).

Mit Unterstützung von Claude Code verfasst, einem Produkt von Anthropic; jede Formel wurde anhand der Originalarbeiten geprüft, und jede Zahl wird bei jedem Build vom Code der Seite selbst neu berechnet.

Stand: . Erstveröffentlichung am selben Tag.