Google Transformer

Inhaltsverzeichnis

Einleitung: Der Transformer von Google

In diesem Blogpost erfahren Sie, wie ein von Google im Jahr 2017 entwickeltes Modell, der Transformer (Research Paper: „Attention is all you need“Google AI Blog), von einer Sprache in eine andere Sprache übersetzt – und das lediglich mit Beispieldaten. Dabei sind Übersetzungen sehr komplex. Das erkennt man, wenn man die folgenden zwei Sätze ins Deutsche übersetzt möchte:

1: „The dog didn’t cross the street, because it was too tired“. ==> Der Hund hat die Straße nicht überquert, weil er zu müde war.

2: „The dog didn’t cross the street, because it was too wide“. ==> Der Hund hat die Straße nicht überquert, weil sie zu breit war.

Wenn wir das Wort „it“ im 1. Satz übersetzen wollen, übersetzten wir es mit „er“, weil sich „it“ auf „dog“ bezieht, und „dog“ ist „der Hund“ im Deutschen. Im 2. Satz hingegen, übersetzten wir „it“ mit „sie“, weil sich „it“ jetzt plötzlich auf „street“ bezieht und „street“ ist „die Straße“ im Deutschen. Für uns Menschen ist dies eine ziemlich leichte Aufgabe, da wir aus dem Kontext heraus erkennen können auf welches Wort sich „it“ bezieht. Denn jeder mit gesundem Menschenverstand weiß, dass Hunde nicht breit und Straßen nicht müde sein können.

Doch wie kann man einen Algorithmus entwickeln, der in der Lage ist, mit Beispieldaten die beiden oberen Sätze zu erlernen und „it“ dem korektem Geschlecht zuzuweisen? Bevor wir zur Antwort kommen, sollte man die Anwendungsmöglichkeiten des Transformers kennen.

Use Cases für den Transformer

Der Transformer wurde für Übersetzungen von einer beliebigen Sprache in eine andere entwickelt und sicher haben Sie ihn über Google Translate schon einmal genutzt. Man kann den angepassten Transformer aber auch für jegliche Art von Textklassifikationen einsetzen, z.B. für eine Sentiment Analyse oder ein POS Tagging.

Sentiment Analysen sind für Sie interessant, wenn Sie auf Ihrer Webseite ein Kommentarforum haben und sogenannte „Trolle“ (kontraproduktive Chatteilnehmer) erkennen wollen oder wenn Sie z.B. Bewertungen der Kunden schnell und automatisch auswerten wollen. Textklassifikationen helfen auch im juristischen Sektor weiter, wo große Mengen an Text ausgewertet werden müssen. Langweilige Aufgaben, die früher noch Praktikanten sehr langsam und mühselig ausgeführt haben, können mittlerweile sehr schnell mithilfe von Algorithmen ausgeführt werden. Und für Menschen stehen die höherwertigen Aufgaben bereit.

Wie funktioniert der Transformer?

Der Transformer ist ein neuronales Netz. Es legt den Grundstein für das momentane State-of-the-Art (SOTA) Modell im Bereich Natural Language Processing (NLP). Die Modellarchitektur des Transformers erlaubt eine deutlich kürzere Trainingszeit als die bisherigen SOTA NLP Modelle, da es teilweise parallelisiert arbeitet. Es setzt dazu keine Convolutional Zellenblöcke mehr ein und nur teilweise rekurrente, bzw. auto-regressive Zellenblöcke.

Grundsätzlich besteht der Transformer aus einem Encoder und einem Decoder, die jeweils in sechs Blöcke unterteilt sind. Der Encoder erzeugt eine Art Representation des Satzes in der Eingangssprache und der Decoder formt diese Representation in die Zielsprache um. Angenommen, dass man einen englischen Satz in einen deutschen Satz übersetzen will, wird der englische Satz in dem Encoder Abschnitt eingelesen und bis ans Ende durchgeschläust. Danach wird das Zwischenergebnis in dem Decoder Abschnitt eingelesen und das erste Wort in dem englischen Eingangssatz wird ins Deutsche übersetzt. Danach wird anhand des Zwischenergebnisses des Encoders und den bereits ins Deutsche übersetzten Wörtern Schritt für Schritt das nächste Wort in dem englischen Eingangssatz übersetzt.

Hierunter haben wir einmal eine Visualisierung des Datenflusses innerhalb des Transformers mit zwei Encoder und zwei Decoder Blöcken erstellt (aus Platzgründen konnten wir nicht alle sechs Blöcke visualisieren). Was die einzelnen Blöcke leisten, erfahren Sie weiter unten.

In [ ]:
# Abbildung 1
Image(os.path.join("images", "transformer.gif.png"), width=WIDTH, height=HEIGHT)
Output hidden; open in https://colab.research.google.com to view.

Wort-Embeddings

Die Wörter in der Eingangsprache (Englisch in unserem Fall) werden anhand eines Wort-Embeddings in Vektoren umgewandelt. Zur Erinnerung: Ein Wort-Embedding ist ein Mapping zwischen einer Kategorie, z.B. einem Wort, und einem Vektor von einer gewissen Länge. Die Wort-Embeddings von Abbilung 1 werden von 𝑥1,𝑥2x1,x2 und 𝑦̂ 1,𝑦̂ 2y^1,y^2 representiert und haben eine Länge von vier, während der eigentliche Transformer eine Embeddinglänge von 512 hat. Wort-Embeddings können erlernt werden, oder es können vortrainierte Wort-Embeddings benutzt werden, wie wir bereits in diesem und diesem Blogpost erläutert haben.

Positional Encoding (PE)

Weil der Transformer keine rekurrenten oder Convolutional Zellenblöcke im Encoder benutzt, muss er eine andere Methode benutzen, um die Reihenfolge der Wörter innerhalb eines Satzes zu modellieren. Die Entwickler des Transformers benutzen eine Methode, die sich „Positional Encoding“ (PE) nennt, welche von Sinus und Cosinus Wellen unterschiedlicher Frequenzen Gebrauch macht. 𝑃𝐸𝑡PEt, also der PE Vektor von Wort 𝑤𝑡wt in der Inputsequenz, kann anhand der folgenden zwei Gleichungen definiert werden:

𝑃𝐸𝑝𝑜𝑠,2𝑖=𝑠𝑖𝑛(𝑝𝑜𝑠100002𝑖/512)𝑃𝐸𝑝𝑜𝑠,2𝑖+1=𝑐𝑜𝑠(𝑝𝑜𝑠100002𝑖/512),PEpos,2i=sin(pos100002i/512)PEpos,2i+1=cos(pos100002i/512),

𝑝𝑜𝑠pos stellt die Position von 𝑤𝑡wt in dem Eingangssatz/Inputsequenz dar und 𝑖i die Dimenstion von 𝑃𝐸𝑡PEt. Also wird der PE Vektor von dem ersten Wort in der Inputequenz, nämlich 𝑃𝐸1PE1, wie folgt aussehen (Quelle):

𝑃𝐸1=[𝑠𝑖𝑛(11000020/512),𝑐𝑜𝑠(11000020/512),𝑠𝑖𝑛(11000021/512),𝑐𝑜𝑠(11000021/512),...,𝑠𝑖𝑛(1100002255/512),𝑐𝑜𝑠(1100002255/512)]PE1=[sin(1100002∗0/512),cos(1100002∗0/512),sin(1100002∗1/512),cos(1100002∗1/512),…,sin(1100002∗255/512),cos(1100002∗255/512)]

Wie man sehen kann, hat 𝑃𝐸1PE1 256 Elemente, die anhand von Sinus Wellen errechnet wurden und 256 Elemente, die anhand von Cosinus Wellen errechnet wurden. 𝑖i kann Werte zwischen inklusive 0 und inklusive 255 annehmen, also 256 verschiedene Werte insgesammt. Da jedes zweite Element von 𝑃𝐸1PE1 anhand von Sinus Wellen beschrieben ist und jedes andere zweite Element von Cosinus Wellen, ist auch sichergestellt, dass 𝑃𝐸1PE1 insgesammt 2×256=5122×256=512 Elemente hat.

Nachden alle PE Vektoren berechnet wurden, werden diese elementweise mit den Wort-Embedding Vektoren addiert und dann in den nächsten Zellenblock, nämlich dem Multi Head Self Attention Zellenblock, eingefüttert. Um zu verstehen was ein Multi Head Self Attention Zellenblock genau ist, muss man erst verstehen, was ein einzelner Self Attention Zellenblock macht.

Self Attention

Ein einzelner Self Attention Zellenblock bestimmt, wie viel „Aufmerksamkeit“ man jedem Wort in der Inputsequenz schenken soll, wenn man ein bestimmtes Wort in der Inputsequenz, z.B. 𝑤𝑡wt, übersetzen will. Wenn man z.B. „it“ übersetzen will, muss man bestimmen, wie viel Aufmerksamkeit man jedem einzelnen Wort in „The dog didn’t cross the street, because it was too tired“ schenken soll (siehe Beispiel in der Einleitung). Wie dies genau funktioniert versuchen wir anhand der folgenden Grafik zu erklären:

In [ ]:
# Abbildung 2
Image(os.path.join("images", "transformer_slides", "Transformer Modell-38.png"), width=WIDTH, height=HEIGHT)
Out[ ]:

Zuerst wird der Embedding Vektor jedes Wortes (𝑥𝑡xt) elementweise mit dem dazugehörigen 𝑃𝐸𝑡PEt Vektor addiert wird. Das Ergebnis dieser Addition ist ein Vektor, den wir hier 𝑒𝑛𝑐𝑡enct genannt haben.

Danach wird für jedes Wort ein Query (𝑞𝑡qt), ein Key (𝑘𝑡kt) und ein Value (𝑣𝑡vt) Vektor berechnet. Um diese Vektoren zu errechnen, wird 𝑒𝑛𝑐𝑡enct mit drei verschiedenen, trainierbaren Gewichtsmatrizen multipliziert, nämlich jeweils mit 𝑊𝑄,𝑊𝐾WQ,WK und 𝑊𝑉WV.

Wenn man dann diese Query, Key und Value Vektoren auf eine ganz bestimmte Art und Weise miteinander kombiniert, kann man bestimmen, wie viel Aufmerksamkeit der Übersetzer jedem Wort in der Inputsequenz schenken soll, wenn man 𝑤𝑡wt übersetzt. Wie genau man diese Vektoren miteinander kombinieren muss wird in der folgenden Abbildung dargestellt.

In [ ]:
# Abbildung 3
Image(os.path.join("images", "transformer_slides", "Transformer Modell-39.png"), width=WIDTH, height=HEIGHT)
Out[ ]:

Um die Erklärung so einfach wie möglich zu halten, nehmen wir an, dass wir „Thinking Machines“ ins Deutsche übersetzen wollen, und dass wir gerade herausfinden wollen, wie viel Aufmerksamkeit wir dem Wort „Thinking“ schenken sollen, wenn wir „Thinking“ selbst übersetzen müssen. Falls dieses Beispiel zu banal für Sie klingt, stellen Sie sich einfach vor, wir wollen gerade herausfinden wie viel Aufmerksamkeit man dem Wort „Street“ schenken soll, wenn man das Wort „it“ übersetzt (siehe Einleitung).

Um das herauszufinden, werden im ersten Schritt die Skalarprodukte zwischen dem Query Vektor von „Thinking“ (𝑞1q1) und den Key Vektoren aller Wörter in der Inputsequenz (𝑘1,𝑘2k1,k2) ausgerechnet. D.h. in unserem Fall rechnen wir 𝑞1𝑘1q1⋅k1 und 𝑞1𝑘2q1⋅k2 aus. Das Ergebnis jedes dieser Skalarprodukte ist dann eine sogenannte Score, die in etwa aussagt wie viel Aufmerksamkeit man demjenigen Wort in der Inputsequenz schenken soll, wenn man „Thinking“ übersetzten will. Da die Score für „Thinking“ (112) größer ist als die Score für „Machines“ (96), können wir folgern, dass wir dem Wort „Thinking“ mehr Aufmerksamkeit als „Machines“ schenken sollen, wenn wir „Thinking“ übersetzen.

Danach werden beide Scores durch acht geteilt/skaliert, um den Trainingsdurchlauf des Transformers besser kontrollieren zu können. Warum jetzt genau die Scores durch acht geteilt wurden ist momentan irrelevant, es ist nur wichtig, dass alle Scores durch acht geteilt werden, um diese miteinander vergleichbar zu halten.

Anschließend werden alle skalierten Score Werte durch eine Softmax Funktion geschläust, um aus den Scores sogenannte Pseudo-Wahrscheinlichkeiten zu generieren, die alle zusammen addiert 1 ergeben. Damit kann das Modell besser trainieren.

Nun wird für jedes Wort in der Inputsequenz der dazugehörende Value Vektor mit der dazugehörigen Pseudo-Wahrscheinlichkeit multipliziert. Also für das Wort „Thinking“ berechnen wir 𝑛1=𝑣1×0.88n1=v1×0.88 und für das Wort „Machines“ berechnen wir 𝑛2=𝑣2×0.12n2=v2×0.12. Falls das Wort „Thinking“ viel wichtiger ist, als das Wort „Machines“, sollten die Elemente von 𝑛1n1 auch viel größere Werte haben als die Elemente von 𝑛2n2. Man hebt damit wichtige Wörter hervor und blendet unwichtige Wörter aus. Am Schluss werden die 𝑛n Vektoren addiert, um einen Vektor 𝑧z zu erhalten (𝑧1=𝑛1+𝑛2z1=n1+n2). Wenn dann z.B. 𝑧1z1 sehr ähnlich wie 𝑣1v1 ist, dann wird das Wort „Thinking“ sehr wichtig in der Übersetzung sein.

Bis jetzt haben wir nur berechnet, wie viel Aufmerksamkeit man dem Wort „Thinking“ bei der Übersetzung schenken sollte. Allerdings haben wir zwei Wörter in der Inputsequenz und deshalb müssen wir auch ausrechnen, wie viel Aufmerksamkeit man jedem Wort in der Inputsequenz schenken soll, wenn man „Machines“ übersetzt. Im Prinzip folgen wir dem gleichen Muster wie oben. Da es aber eine wichtige Kleiningkeit zu beachten gibt, haben wir die obenstehende Grafik noch einmal für diesen Fall angepasst:

In [ ]:
# Abbildung 4
Image(os.path.join("images", "transformer_slides", "Transformer Modell-40.png"), width=WIDTH, height=HEIGHT)
Out[ ]:

Wie vorher, werden für das Wort „Machines“ ein Wort-Encoding (𝑒𝑛𝑐2enc2), ein query (𝑞2q2), ein key (𝑘2k2) und ein value (𝑞2q2) Vektor ausgerechnet. Zu beachten ist nun, dass die Score Werte mit den Skalarprodukten von 𝑞2𝑘1q2⋅k1 und 𝑞2𝑘2q2⋅k2 ausgerechnet werden (siehe roter Pfeil) und nicht mehr mit 𝑞1𝑘1q1⋅k1 und 𝑞1𝑘2q1⋅k2. Angenommen dass die Score für „Thinking“ nun 24 ist und die Score für „Machines“ nun 72, kann man schließen, dass man „Machines“ mehr Aufmerksamkeit als „Thinking“ schenken soll, wenn man „Machines“ übersetzt. Die restlichen Operationen beliben genau die gleichen wie bereits in Abbildung 3 erklärt.

Self Attention mit Matrizen

In der Praxis wird die Self Attention Operation nicht für jedes Wort einzeln durchgeführt, sondern für alle Wörter in der Inputsequenz auf einmal, indem man Matrizenmultiplikation einsetzt (moderne Grafikkarten können dies nämlich verdammt schnell). Wir verdeutlichen dies anhand der untenstehenden Grafik:

In [ ]:
# Abbildung 5
Image(os.path.join("images", "transformer_slides", "Transformer Modell-42.png"), width=WIDTH, height=HEIGHT)
Out[ ]:

Der Input von einem bestimmtem Zellenblock ist immer entweder eine Matrix, die die enkodierten Vektoren der Inputsequenz beinhaltet (𝑋𝑒𝑛𝑐Xenc) oder eine Matrix, die die Zwischenergebnisse des vorherigen Zellenblocks beinhaltet (𝑍𝑖𝑛Zin). Beide dieser Matrizen haben die gleichen Dimensionen, die Anzahl der Zeilen ist nämlich gleich der Inputsequenzlänge und die Anzahl der Spalten ist gleich der Wort-Embedding Größe. In Realität gibt es noch eine dritte Dimension (batch_size), aber um es einfach zu halten, haben wir diese Dimension auf den Abbildungen weggelassen.

Um Self Attention mithilfe von Matrizen auszurechnen, wird zuerst entweder 𝑋𝑒𝑛𝑐Xenc oder 𝑍𝑖𝑛Zin mit den jeweiligen Gewichtsmatrizen (𝑊𝑄WQ𝑊𝐾WK, und 𝑊𝑉WV), multipliziert, welches in den Query (𝑄Q), Key (𝐾K) und Value (𝑉V) Matrizen resultiert. Nachdem man 𝑄,𝐾Q,K und 𝑉V ausgerechnet hat, befinden sich die Query Vektoren (𝑞1,𝑞2q1,q2), Key Vektoren (𝑘1,𝑘2k1,k2) und Value Vektoren (𝑣1,𝑣2v1,v2) nun in jeder Zeile von jeweils 𝑄,𝐾Q,K und 𝑉V.

Dann wird 𝑄Q mit 𝐾𝑇KT multipliziert. 𝑇T bedeutet Transpose, also die Key Vektoren, die vorher in jeder Zeile von 𝐾K waren, sind nun in jeder Spalte von 𝐾K. D.h. durch die Matrizenmultiplikation von 𝑄×𝐾𝑇Q×KT rechnet man die Scores von allen Wörtern in der Inputsequenz zueinander aus, nämlich 𝑞1𝑘1, 𝑞1𝑘2q1⋅k1, q1⋅k2 und 𝑞2𝑘1, 𝑞2𝑘2q2⋅k1, q2⋅k2.

Nehmen wir für den Moment einfach mal an, dass 𝑃=𝑄×𝐾𝑇8P=Q×KT8. Nun wird 𝑃P in die Softmax Funktion eingefüttert, d.h. dass alle Zeilen von Softmax(𝑃)Softmax(P) aufaddiert 11 ergeben müssen. Am Schluss wird 𝑃P mit 𝑉V multipliziert, was dann 𝑍𝑜𝑢𝑡Zout ergibt. 𝑍𝑜𝑢𝑡Zout enthält dann 𝑧1z1 in der ersten Zeile und 𝑧2z2 in der zweiten Zeile.

Multi Head Self Attention

Was wir gerade unter Punkt 3.4. beschrieben haben geschieht in dem Transformer nicht nur einmal, sondern acht mal gleichzeitig. Würde man es nur einmal machen, könnte man dem zu übersetzenden Wort zu viel Aufmerksamkeit schenken. Also in unserem ersten Beisplielsatz in der Einleitung, könnte es sein, dass man „it“ zu viel Aufmerksamkeit schenkt, wenn man „it“ selbst übersetzen will, während man eigentlich „dog“ mehr Aufmerksamkeit hätte schenken sollen.

Wenn man die Schritte aus Sektion 3.4. acht mal ausführt, dann hat man allerdings auch 8 𝑍𝑜𝑢𝑡Zout Matrizen, wie man in der folgenden Grafik sehen kann:

In [ ]:
# Abbildung 6
Image(os.path.join("images", "transformer_slides", "Transformer Modell-44.png"), width=WIDTH, height=HEIGHT)
Out[ ]: