Erklärung der Funktionsweise von Transformern: Die dahinterliegende Mathematik verstehen
(osanseviero.github.io)- Der Inferenzprozess eines Transformers wird am Übersetzungsbeispiel Hello World → Hola Mundo auf ein kleines Format reduziert, sodass man ihn von der Tokenisierung über Encoder und Decoder bis zur Berechnung der Wahrscheinlichkeiten für das nächste Token von Hand nachvollziehen kann
- Statt der großen Konfiguration aus der Originalarbeit verwendet das Beispiel 4-dimensionale Embeddings, 2 Attention-Headss und eine 8-dimensionale Feedforward-Schicht, um Matrixmultiplikationen und den Ablauf von Softmax klein zu halten
- Der Encoder addiert zum Token-Embedding eine Positionskodierung und erzeugt danach über Multi-Head-Self-Attention und eine Feedforward-Schicht eine kontextuelle Repräsentation der Eingabesequenz
- Der Decoder startet bei
SOS, verwendet die zuvor erzeugten Tokens zusammen mit der Encoder-Ausgabe, und bei der Encoder-Decoder-Attention stammt die Query aus dem Decoder, Key und Value aus der Encoder-Ausgabe - Das letzte Decoder-Embedding wird über eine lineare Schicht und Softmax in die Wahrscheinlichkeiten für das nächste Token umgewandelt, aber da das Beispiel zufällige Gewichte nutzt, ist keine echte Übersetzungsqualität zu erwarten
Ziel und Voraussetzungen
- An einem End-to-End-Beispiel wird nachvollzogen, wie sich die Mathematik während der Inferenz innerhalb eines Transformer-Modells Schritt für Schritt fortsetzt
- Damit man die Berechnungen leicht von Hand verfolgen kann, wird die Modellgröße stark verkleinert
- Statt der Embedding-Dimension 512 aus der Originalarbeit verwendet das Beispiel 4 Dimensionen
- Statt 8 Attention-Heads aus der Originalarbeit verwendet das Beispiel 2
- Statt der Feedforward-Dimension 2048 aus der Originalarbeit werden 8 Dimensionen verwendet
- Benötigt werden grundlegende Kenntnisse in linearer Algebra, und die meisten Berechnungen bestehen aus Matrixmultiplikationen
- Der Fokus liegt weniger darauf, was ein Transformer „ist“, sondern darauf, wie die tatsächlichen Berechnungen ablaufen
- Für eine intuitive Erklärung eignet sich die Lektüre zusammen mit The Illustrated Transformer; die Originalarbeit ist Attention is all you need
Encoder-Eingabe erstellen
-
Tokenisierung
- Da Machine-Learning-Modelle keine Texte, sondern Zahlen verarbeiten, wird der Eingabetext in Token-IDs umgewandelt
- Zur Vereinfachung zerlegt das Beispiel
"Hello World"in die beiden Wort-Tokens"Hello"und"World" - Reale Tokenisierungsverfahren können wortbasiert, zeichenbasiert oder subword-basiert sein
- Ein wortbasierter Ansatz benötigt ein großes Vocabulary und behandelt
"dog"und"dogs"als unterschiedliche Tokens - Ein zeichenbasierter Ansatz hat ein kleines Vocabulary, enthält aber unter Umständen weniger semantische Information
- Die Subword-Tokenisierung liegt zwischen Wort- und Zeichenansatz, wobei der Tokenizer in einem statistischen Verfahren trainiert wird
-
Token-Embeddings
- Da Token-IDs selbst keine Bedeutung tragen, wird jedes Token in ein Embedding fester Größe umgewandelt
- Das Beispiel verwendet Embeddings mit beliebigen Werten
Hello -> [1, 2, 3, 4]World -> [2, 3, 4, 5]
- In echten Transformern wird auch diese Embedding-Zuordnung gelernt, sodass das Modell passende Token-Repräsentationen für die Aufgabe erlernt
- Die beiden Embeddings werden zu einer Matrix zusammengefasst, die anschließend in Matrixmultiplikationen verwendet wird
-
Positionskodierung
- Nur mit Embeddings kennt das Modell die Position eines Wortes im Satz nicht, daher wird eine Positionskodierung addiert
- Die Originalarbeit verwendet eine feste sinus-/cosinusbasierte Positionskodierung, und das Beispiel folgt demselben Ansatz
- Die Positionskodierung des Beispiels wird wie folgt berechnet
Hello -> [0, 1, 0, 1]World -> [0.84, 0.99, 0, 1]
- Durch Addition von Token-Embedding und Positionskodierung entsteht die Encoder-Eingabematrix
Hello -> [1, 3, 3, 5]World -> [2.84, 3.99, 4, 6]
Self-Attention berechnen
-
Q, K, V erzeugen
- Self-Attention berechnet aus den Eingabe-Embeddings Query (Q), Key (K) und Value (V)
- Das Beispiel verwendet 2 Attention-Heads, und jeder Head besitzt eigene Matrizen
WQ,WK,WV - Jede Gewichtsmatrix transformiert das 4-dimensionale Embedding in 3-dimensionale Query-/Key-/Value-Vektoren
- Im ersten Head erhält man
K1,V1undQ1, indem man die Eingabematrix mitWK1,WV1undWQ1multipliziert
-
Attention-Formel
- Die Attention-Scores werden in vier Schritten berechnet
- Berechnung des Skalarprodukts zwischen Query und jedem Key
- Division durch die Quadratwurzel der Key-Dimension
- Umwandlung per Softmax in positive Gewichte mit Summe 1
- Bildung einer gewichteten Summe der Value-Vektoren anhand dieser Gewichte
- Dieser Ablauf wird in der Originalarbeit in folgende Formel verdichtet
- [
- Attention(Q,K,V) = \text{softmax}\left(\frac{QK^\top}{\sqrt{d}}\right)V
- ]
- Wegen der kleinen Dimensionen und zufälligen Initialwerte liegt das Softmax-Ergebnis im Beispiel fast nur bei 0 oder 1
- Große Skalarprodukte können durch Softmax noch stärker verstärkt werden, weshalb die Skalierung durch die Quadratwurzel der Key-Dimension nötig ist
- Zur Veranschaulichung wird vorübergehend auch eine Variante verwendet, die statt durch
sqrt(3)durch 30 teilt, langfristig ist das jedoch keine Lösung
- Die Attention-Scores werden in vier Schritten berechnet
-
Ausgabe der Multi-Head-Attention
- Die Attention-Ergebnisse jedes Heads werden konkateniert und anschließend mit einer lernbaren Gewichtsmatrix multipliziert, um wieder auf die Embedding-Dimension zurückzukehren
- Im Beispiel werden die Ergebnisse beider Heads zu einer 6-dimensionalen Matrix zusammengeführt und anschließend in eine 4-dimensionale Ausgabe transformiert
- Diese Ausgabe wird an die nächste Stufe des Encoder-Blocks, die Feedforward-Schicht, weitergegeben
Feedforward-Schicht und Encoder-Block
-
Feedforward-Schicht
- Auf die Self-Attention folgt ein Feedforward Neural Network (FFN)
- Das FFN besteht aus zwei linearen Transformationen mit einer ReLU-Aktivierung dazwischen
- Die erste lineare Schicht erweitert die Dimension, die zweite reduziert sie wieder auf die ursprüngliche Größe
- ReLU setzt negative Werte auf 0 und lässt positive Werte unverändert, wodurch Nichtlinearität hinzugefügt wird
- Im Beispiel wird die 4-dimensionale Eingabe zunächst auf 8 Dimensionen erweitert und danach wieder auf 4 reduziert
- [
- \text{FFN}(x) = \text{ReLU}(xW_1 + b_1)W_2 + b_2
- ]
-
Encoder-Block
- Ein Encoder-Block besteht aus Multi-Head-Attention und FFN
- Die Originalarbeit stapelt 6 Encoder, und auch der Beispielcode wiederholt den Encoder mit
n=6 - Wenn mehrere Encoder-Blöcke einfach ohne Weiteres durchlaufen werden, können die Werte zu groß werden, sodass beim Softmax Overflow auftritt und
nanentsteht
Residual Connection und Layer Normalization
-
Problem explodierender Werte
- Beim Durchlauf durch 6 Encoder treten im Beispiel die Warnungen
overflow encountered in expundinvalid value encountered in divideauf, und die Ausgabe wird zunan - Dass Werte zu groß werden und in der nächsten Schicht noch weiter wachsen, ist ein häufiges Problem in tiefen neuronalen Netzen
- Wenn beim Backpropagation die Gradienten zu groß werden, spricht man von gradient explosion
- Beim Durchlauf durch 6 Encoder treten im Beispiel die Warnungen
-
Residual Connection
- Eine Residual Connection addiert die Eingabe einer Schicht zu ihrer Ausgabe
- [
- \text{Residual}(x) = x + \text{Layer}(x)
- ]
- Im Beispiel wird sowohl auf die Attention-Ausgabe als auch auf die FFN-Ausgabe jeweils eine Residual Connection angewendet
- Residual Connections werden eingesetzt, um das Problem verschwindender Gradienten abzuschwächen
-
Layer Normalization
- Layer Normalization normalisiert so, dass für jede Embedding-Dimension ein Mittelwert von 0 und eine Standardabweichung von 1 entsteht
- Die Formel lautet wie folgt
- [
- \text{LayerNorm}(x) = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} \times \gamma + \beta
- ]
- (\epsilon) ist ein kleiner Wert, um eine Division durch 0 zu vermeiden, wenn die Standardabweichung 0 ist
- (\gamma) und (\beta) sind lernbare Parameter, die Skalierung und Verschiebung steuern
- Nach dem Hinzufügen von Residual Connection und Layer Normalization liefert der Durchlauf durch 6 Encoder normale Werte ohne
nan
Decoder-Struktur
-
Decoder-Eingabe und Generierungsweise
- Der Decoder erhält als Eingabe die Encoder-Ausgabe und die bisher erzeugte Ausgabesequenz
- Während der Inferenz beginnt er mit dem SOS(start-of-sequence)-Token
- Der Decoder erzeugt autoregressiv jeweils ein Token nach dem anderen
-
- Durchlauf: Eingabe
SOS, Ausgabe"hola"
- Durchlauf: Eingabe
-
- Durchlauf: Eingabe
SOS + hola, Ausgabe"mundo"
- Durchlauf: Eingabe
-
- Durchlauf: Eingabe
SOS + hola + mundo, AusgabeEOS
- Durchlauf: Eingabe
-
- Sobald
EOS(end-of-sequence)erzeugt wird, stoppt das Decoding - Der Encoder kann seine Repräsentation in einem einzigen Forward-Pass erzeugen, der Decoder braucht mehrere Forward-Passes und ist deshalb langsamer
-
Aufbau des Decoder-Blocks
- Ein Decoder-Block ist komplexer als ein Encoder-Block und besteht in dieser Reihenfolge aus
- Masked Self-Attention
- Residual Connection und Layer Normalization
- Encoder-Decoder-Attention
- Residual Connection und Layer Normalization
- Feedforward-Schicht
- Residual Connection und Layer Normalization
- Im Inferenzbeispiel wird für das
SOS-Embedding nach Addition der Positionskodierung[1, 1, 0, 1]verwendet - Während des Trainings wird Masked Self-Attention verwendet, bei der Attention-Scores mit
-infmaskiert werden, damit keine zukünftigen Tokens sichtbar sind
- Ein Decoder-Block ist komplexer als ein Encoder-Block und besteht in dieser Reihenfolge aus
Encoder-Decoder-Attention
- Die Encoder-Decoder-Attention sorgt dafür, dass sich der Decoder auf relevante Teile des Eingabesatzes konzentriert
- Die Berechnung entspricht der Self-Attention, aber die Eingaben zur Erzeugung von Q/K/V sind unterschiedlich
- Die Query wird aus der Ausgabe der vorherigen Decoder-Schicht berechnet
- Key und Value werden aus der Encoder-Ausgabe berechnet
- Dank dieser Struktur kann jede Position im Decoder auf alle Positionen der Eingabesequenz Bezug nehmen
- Das ist nützlich für Aufgaben wie Übersetzung, bei denen die Ausgabetokens von relevanten Positionen im Eingabesatz abhängen
Ausgabetoken erzeugen
-
Lineare Schicht und Softmax
- Die Decoder-Ausgabe ist noch kein Wort, daher wird das letzte Embedding durch eine lineare Schicht in einen Logits-Vektor der Größe des Vocabulary umgewandelt
- Das Beispiel-Vocabulary hat die Größe 10, und die Kandidaten für das nächste Token sind
hello,mundo,world,how,?,EOS,SOS,a,hola,c
- Die Logits werden per Softmax in eine Wahrscheinlichkeitsverteilung über alle Tokens umgewandelt
- In der Beispielverteilung hat
"hola"die höchste Wahrscheinlichkeit und wird deshalb als nächstes Token ausgewählt - Immer das Token mit der höchsten Wahrscheinlichkeit zu wählen, nennt man greedy decoding; das ist nicht immer optimal
- Weitere Details zu Generierungsmethoden finden sich in diesem Hugging Face-Beitrag
-
Vollständige Generierungsschleife
- Der gesamte Generierungsablauf folgt diesem Muster
- Die Eingabesequenz wird in Embeddings umgewandelt
- Der Encoder erzeugt eine kontextuelle Repräsentation der vollständigen Eingabe
- Der Decoder beginnt bei
SOSund verwendet zusammen mit den zuvor erzeugten Tokens die Encoder-Ausgabe - Auf das letzte Decoder-Embedding werden lineare Schicht und Softmax angewendet
- Das wahrscheinlichste nächste Token wird ausgewählt und der Sequenz hinzugefügt
- Dies wird wiederholt, bis
EOSerscheint oder die maximale Länge erreicht ist
- Bei der Ausführung des Beispiels wird für die Eingabe
hello worlddie SequenzSOS hola mundo worlderzeugt - Da alle Gewichte und Embeddings zufällig gewählt wurden, ist das Ergebnis keine gute Übersetzung, und genau das ist zu erwarten
- Der gesamte Generierungsablauf folgt diesem Muster
Fazit und Umfang
- Das Beispiel verbindet die zentralen Bausteine eines Transformers — Embeddings, Positionskodierung, Self-Attention, Multi-Head-Attention, FFN, Residual Connection, Layer Normalization, Encoder-Decoder-Attention und Softmax-Ausgabe — in einem durchgehenden Ablauf
- Moderne Transformer-Architekturen ergänzen viele weitere Techniken, doch die Kernmathematik basiert auf der hier behandelten Struktur
- Je nach Aufgabentyp kann ein anderer Stack verwendet werden
- Für verständnisorientierte Aufgaben wie Klassifikation kann auf dem Encoder-Stack eine lineare Schicht liegen
- Für generierungsorientierte Aufgaben wie Übersetzung können Encoder- und Decoder-Stack gemeinsam verwendet werden
- Für freie Generierungsaufgaben wie ChatGPT oder Mistral kann nur der Decoder-Stack verwendet werden
- Der Trainingsprozess wird nicht behandelt; im Mittelpunkt steht das Verständnis der Inferenzmathematik beim Einsatz eines bestehenden Modells
- Für formaleres mathematisches Material kann dieses PDF herangezogen werden
1 Kommentare
Hacker-News-Kommentare
Das „Mysterium“ des Transformers liegt darin, dass in jeder Schicht nicht statische Gewichte und Werte in linearer Reihenfolge multipliziert werden, sondern dass aus derselben Eingabe durch Multiplikation mit gelernten Gewichten drei Matrizen erzeugt und diese Matrizen miteinander multipliziert werden.
Das funktioniert gut, weil die Parallelität zunimmt, aber die Attention-Formel selbst ist fest vorgegeben und daher sehr eingeschränkt.
Für weitere Fortschritte scheint es nötig, den Rechengraphen selbst als lernbare Parameter zu verallgemeinern. Ob das mit traditionellen Gradientenmethoden möglich ist, weiß ich wegen des chaotischen Effekts nicht, bei dem kleine Änderungen große Leistungsunterschiede bewirken; intern könnte auch eine Form von genetischem Algorithmus oder Partikelschwarmoptimierung nötig sein.
Der große theoretische Vorteil gegenüber RNNs ist, dass er dies verlustfrei unterstützt. Denn jedes Element kann auf alle Informationen aller anderen Elemente der Sequenz zugreifen, beziehungsweise bei zeitlicher Reihenfolge auf die gesamte Information der vorhergehenden Elemente.
RNNs und „lineare Transformer“ dagegen komprimieren vergangene Werte; deshalb ist es für das letzte Element einer langen Sequenz normalerweise schwierig, auf alle Informationen des ersten Elements zuzugreifen, und unmöglich, sofern der interne Zustand nicht so groß ist, dass keine Information verworfen wird.
Das Problem ist, dass man dadurch nicht viel gewinnt. Operationen, die keine Matrixmultiplikationen sind, dürften langsamer oder höchstens ähnlich schnell sein.
Wenn man allerdings Flusskontrolle einbaut, läuft man Gefahr, im Grunde eine Turing machine zu erhalten; dann wird, wie gesagt, das Training zum Problem. Vielleicht ist es dennoch kein völlig unbeherrschbares Problem.
Wer eine trockenere, formellere und knappere Erklärung möchte, findet bei John Thickstun „The Transformer Model in Equations“ [0].
In Standard-Mathe-Notation passt das Ganze auf eine Seite.
[0] https://johnthickstun.com/docs/transformers.pdf
Oft wirkt es so, als hätten Machine-Learning-Forscher überhaupt keine Mathematik studiert.
Die Erklärung „Es entsteht NaN, die Werte sind zu groß, explodieren beim Übergang zum nächsten Encoder, das ist ein explodierender Gradient“ ist nach meinem Verständnis falsch.
Hier wird an keiner Stelle ein Gradient berechnet, also ist es kein explodierender Gradient.
Das Problem scheint eher bei der softmax-Implementierung zu liegen; wie man softmax numerisch stabil implementiert, wird hier [0] erklärt.
[0]: https://jaykmody.com/blog/stable-softmax/
Allerdings ist das gesamte neuronale Netz empfindlich gegenüber großen Werten, daher lässt sich das nicht allein mit numerisch stabilem softmax lösen. Damit das Netzwerk funktioniert, ist Normalisierung entscheidend.
Transformer-Tutorials könnten die neuen Monad-Tutorials werden. Es ist ein schwer verständliches Konzept, aber eines, das man erst versteht, wenn man sich mit Beispielen abmüht und sie übt.
Wie so vieles in der Informatik.
Ich habe erst sechs Absätze gelesen und schon eine Frage.
Bei
Hello -> [1,2,3,4] World -> [2,3,4,5]heißt es zwar, die Vektoren seien zufällig, aber es sieht nach einem Muster aus. Ich frage mich, ob die2, die in beiden Vektoren vorkommt, etwas bedeutet, oder ob die gesamte Menge die Eindeutigkeit herstellt.Hier liegen sie ungefähr 60 Grad auseinander und zeigen zu einem gewissen Grad in dieselbe Richtung; weil im Beispiel keine negativen Zahlen verwendet werden sollten, wirken die Vektoren ähnlicher, als sie in Wirklichkeit wären.
Die bloße Tatsache, dass Zahlen wiederverwendet wurden, hat keine Bedeutung. Die
1an der ersten Position hat kaum etwas mit der1an der zweiten Position zu tun. Schließlich führt man auf diesem Vektor auch keine Faltung aus.Nach dem Training haben ähnliche Wörter bis zu einem gewissen Grad Kosinus-Ähnlichkeit, aber es kommt fast nie vor, dass die Kosinus-Ähnlichkeit so hoch ist wie bei
[1,2,3,4]und[2,3,4,5].Das ist keine ganz direkt verwandte Frage, aber ich suche nach Artikeln oder Papers, die erklären, warum ein Transformer, obwohl er scheinbar einfach wie ein „Next-Token-Predictor“ arbeitet, mit Fragen wie den folgenden umgehen kann:
"sdsfs_ff"und"fsdf_value"als Spalten erstellenDas scheint eine häufige Frage zu sein, aber ich finde nicht die richtigen Suchbegriffe. Links, die Position Embeddings ausführlich behandeln, wären ebenfalls gut; auch dafür, warum Sinus/Cosinus verwendet werden und wie Multiplikation vs. Addition zu verstehen ist, habe ich noch keine zufriedenstellende Antwort bekommen.
Wenn das Modell es für nötig hält, kann es unbekannte Sequenzen reproduzieren, indem es einzelne Zeichen-Tokens kopiert, oder sie neu erzeugen, wenn es im Kontext Sinn ergibt.
P(X_1=x_1, X_2=x_2, X_3=x_3) = P(X_3=x_3 | X_1=X_1, X_2=x_2) • P(X_1=x_1, X_2=x_2)= P(X_3=x_3 | X_1=X_1, X_2=x_2) • P(X_2=x_2 | X_1=x_1) • P(X_1=x_1)Mit anderen Worten: Wenn man für das nächste Token, gegeben die vorherigen Tokens, die richtige bedingte Wahrscheinlichkeitsverteilung hat, erhält man auch die richtige Wahrscheinlichkeitsverteilung für die gesamte Token-Sequenz.
Eine „richtige Wahrscheinlichkeitsverteilung über Token-Sequenzen“ oder die richtige bedingte Wahrscheinlichkeitsverteilung einer Token-Sequenz unter bestimmten Bedingungen kann praktisch jede Art von Ein-/Ausgabeverhalten in solchen Begriffen beschreiben.
Deshalb ist „es funktioniert, indem es das nächste Token vorhersagt“ im Prinzip keine große Einschränkung dafür, welches Ein-/Ausgabeverhalten möglich ist.
Egal wie beeindruckend etwas ist: Es steht nicht im Widerspruch dazu, dass die Ausgabe aus
P(X_{n+1}=x_{n+1} | X_1=x_1, ..., X_n=x_n)stammt, also zu „Next-Token-Prediction“.Next-Token-Prediction ist eine intelligentere Aufgabe, als sie klingt.
Ich stimme der Aussage zu, dass „Komplexität aus der Anzahl der Schritte und der Anzahl der Parameter entsteht“.
Transformer-Modelle, die einfach genug sind, damit wir sie verstehen, können nichts Interessantes; Transformer, die komplex genug sind, um Interessantes zu leisten, wirken zu komplex, als dass wir sie verstehen könnten.
Ich würde gern Modelle mittlerer Größe untersuchen, die einfach genug sind, um verstanden zu werden, aber komplex genug, um Interessantes zu leisten.
Wenn Begriffe verwendet werden, ohne sie zu definieren oder einzuführen, ist das schwer zu verstehen. Der Abschnitt Encoder beginnt direkt, ohne zu erklären, was das ist oder wo es im Gesamtprozess steht.
Ich verstehe, was der Autor vorhat, aber die grundlegende Struktur fehlt: Ideen erst vorstellen, dann erklären und anschließend verwenden.
Wenn man sich nicht ohnehin schon mit dem Thema beschäftigt und es ungefähr zur Hälfte verstanden hat, wirkt der ganze Text verwirrend.
Ich habe schon einmal ein ANN von Grund auf geschrieben und dabei kein TensorFlow verwendet, aber diese Erklärung ist trotzdem verwirrend.
Ich habe ChatGPT gebeten, ohne die Wörter
MatrixoderVectorzu verwenden zu erklären, wie man ein einfaches ANN so verändert, dass es Self-Attention implementiert, und bekam eine ziemlich einfache Erklärung. Implementiert habe ich es allerdings noch nicht.Mir liegt es eher, alles in Begriffen von Knoten, Gewichten und Schichten zu denken. Matrizen und Vektoren machen es schwerer, den Bezug zu dem herzustellen, was innerhalb eines ANN tatsächlich passiert.
In der vertrauten Art, ANNs zu schreiben, ist jeder Eingabeknoten ein Skalar, aber der Forward-Pass-Algorithmus multipliziert alle Eingabeknoten mit Gewichten und addiert sie, sodass es wie eine Vektor-Matrix-Multiplikation aussieht. Ich habe das Gefühl, dass ich mit der falschen Denkhaltung an solche Erklärungen herangehe, und vielleicht fehlt mir auch das nötige Hintergrundwissen.