Transformeren

De kraftige sprogmodeller, der indgår i de nyeste former for sproglig kunstig intelligens, benytter ikke helt den tilgang, vi hidtil har beskrevet, hvor man først laver Word2Vec og derefter træner et neuralt netværk til at prædiktere ord. I stedet bruges en videreudvikling, kaldet transformeren, der kombinerer de to trin i én algoritme. I Chat-GPT står GPT for eksempel for "Generative Pre-trained Transformer". Nedenfor beskriver vi nogle af de centrale dele af transformeren1. Bemærk, at selv om vi prøver at give en intuition for, hvad transformeren gør, så er der ingen, der helt forstår i detaljer, hvorfor den virker.

1 Den version af transformeneren, der beskrives her er en decoder-only transformer, som er den, der indgår i GPT teknologien.

Opmærksomhed

Vi er ude på at lave en algoritme, der genererer ny tekst ét ord ad gangen. Hvis vi for eksempel har genereret teksten

\[ \text{"Min hund er sulten, så jeg fodrer den med ---"} \]

så skal vi lave en algoritme, der gætter næste ord. Både \(N\)-gram modeller og neurale netværk tager de seneste \(N-1\) ord i betragtning for at gætte det næste. Hvis \(N=4\) skal vi altså gætte næste ord efter "fodrer den med". Det skal nok være en form for dyremad, men der kunne stå mange forskellige ting, for eksempel "hø" eller "fuglefrø". I dette tilfælde er vi nødt til at gå helt tilbage til ordet "hund" for at vide, hvilket dyr der er tale om og dermed hvilken slags mad, den skal have. Det smarte ved transformeren er, at den tager alle de hidtidige ord i betragtning. Dog lægger den mest vægt på de nærmeste ord.

Husk på, at da vi lavede Word2Vec, lod vi vektoren \(\vec{v}_{\text{ord}}\) repræsentere betydningen af ordet. For at gætte næste ord i en sætning blev vektorerne for de \(N-1\) foregående ord brugt som input til et neuralt netværk. Hver vektor \(\vec{v}_{\text{ord}}\) blev brugt som en fast vektor uanset hvilke ord, der stod i nærheden af det. Men betydningen af et ord kan ændre sig alt efter konteksten. Se for eksempel på \[\text{"Orkesteret skal spille ---"}\] Kigger vi udelukkende på ordet "spille", kan der stå mange ting bagefter, for eksempel "ludo", "tennis" eller "koncert". Men når vi ser, at ordet "orkesteret" optræder inden, så ved vi, at der nok er tale om en slags musik. Det kunne derfor give mening at lade ordet "spille" repræsentere ved forskellige vektorer, alt efter om det indgår i en musikkontekst, en sportskontekst eller en brætspilskontekst. Det er netop idéen i transformeren: Vi vil lade vektorrepræsentationen af et ord afhænge af konteksten. Vi er altså opmærksomme på konteksten når vi laver vektorrepræsentationen. Derfor siges transformeren at have opmærksomhed på sig selv2.

2 På engelsk siges transformeren at have self-attention.

Når transformeren skal bruges til at generere tekst, beregner den både nye vektorrepræsentationer og bruger dem til at gætte næste ord. Da vi kun har adgang til de forudgående ord når vi genererer tekst, er det vigtigt at vektorrepræsentationerne også kun dannes ud fra den forudgående tekst. Dette er anderledes end Word2Vec, hvor vektorrepræsentationerne blev beregnet én gang for alle på baggrund af træningsdata, hvor vi havde kendskab til både de ordene før og efter fokusordet.

Vektorregning

Som udgangspunkt lader vi ordet "spille" være repræsenteret af en fast vektor \(\vec{v}_{\text{spille}}\). Vi modificerer derefter vores vektorrepræsentation \(\vec{v}_{\text{spille}}\) til en ny vektor \(\vec{w}_{\text{spille}}\) afhængigt af hvilke ord, der kommer før. Hvis ordet "orkesteret" kommer inden, så kan man forestille sig, at det trækker \(\vec{v}_{\text{spille}}\) i en musikretning, mens hvis ordet havde været "landsholdet", så ville det trække \(\vec{v}_{\text{spille}}\) i en sportsretning.

Vi vil modificere \(\vec{v}_{\text{spille}}\) ved hjælp af vektoraddition og skalarmultiplikation. Husk på, at man lægger to 2-dimensionale vektorer sammen koordinatvis:

\[ \begin{pmatrix}a_1\\ a_2\end{pmatrix} + \begin{pmatrix}b_1\\ b_2\end{pmatrix} = \begin{pmatrix}a_1+b_1\\ a_2+b_2\end{pmatrix} \]

Geometrisk lægger man vektorerne \(\vec{a}\) og \(\vec{b}\) sammen ved først at tegne \(\vec{a}\) og derefter tegne \(\vec{b}\) med udgangspunkt, der hvor \(\vec{a}\) slutter. Vektoren \(\vec{a} + \vec{b}\) er så den vektor, der starter samme sted som \(\vec{a}\) og slutter, der hvor \(\vec{b}\) slutter.

Figur 1: Illustration af vektoraddition.

Dette kan vi nu benytte til at opdatere vektoren for "spille", når vi ved, at "orkesteret" står inden. Hvis vi har vektorerne \(\vec{v}_{\text{spille}}\) og \(\vec{v}_{\text{orkesteret}}\), så kunne vi opdatere \(\vec{v}_{\text{spille}}\) til

\[ \vec{w}_{\text{spille}} = \vec{v}_{\text{orkesteret}}+\vec{v}_{\text{spille}} \]

Det er illustreret på figur 2. Det ses, at retningen på \(\vec{w}_{\text{spille}}\) er blevet trukket over mod \(\vec{v}_{\text{orkesteret}}\) og væk fra \(\vec{v}_{\text{landsholdet}}\).

Figur 2: Illustration som viser, at \(\vec{w}_{\text{spille}} = \vec{v}_{\text{orkesteret}}+\vec{v}_{\text{spille}}\).

Der er dog et andet problem: Den nye vektor \(\vec{w}_{\text{spille}}\) er meget lang i forhold til de oprindelige. Det løser vi ved at skalere de to vektorer ned, inden vi lægger dem sammen. Man kan skalere en vektor med et tal \(c\in \mathbb{R}\) ved at gange hver koordinat med \(c\):

\[ c\begin{pmatrix} a_1\\a_2 \end{pmatrix} = \begin{pmatrix} ca_1\\ca_2 \end{pmatrix} \]

Hvis \(c>0\), er \(c\vec{a}\) en vektor, der peger i samme retning som \(\vec{a}\), men er \(c\) gange så lang. Hvis \(c<0\), er vektoren \(|c|\) gange så lang som \(\vec{a}\), men peger i modsat retning.

Figur 3: Illustration af \(\vec v\) ganget med henholdsvis \(2\) og \(-2\).

I vores eksempel kunne man skalere begge vektorer med en faktor \(\frac{1}{2}\), inden man lægger dem sammen, således at

\[ \vec{w}_{\text{spille}} = \frac{1}{2}\cdot \vec{v}_{\text{orkesteret}}+\frac{1}{2}\cdot \vec{v}_{\text{spille}} \]

Dette er skitseret i figur 4.

Figur 4: Illustration af \(\vec{w}_{\text{spille}} = \frac{1}{2}\cdot \vec{v}_{\text{orkesteret}}+\frac{1}{2}\cdot \vec{v}_{\text{spille}}\).

Den nye vektor \(\vec{w}_{\text{spille}}\) er mere sammenlignelig med den oprindelige \(\vec{v}_{\text{spille}}\) i figur 2. Vi kunne have gjort noget tilsvarende hvis der havde stået

\[ \text{Landsholdet skal spille ---} \]

Så ville \(\vec{w}_{\text{spille}}\) blive trukket i retning af \(\vec{v}_{\text{landsholdet}}\) i stedet. Men hvad nu, hvis der havde stået

\[ \text{Manden skal spille ---} \]

Her bliver vi ikke rigtig klogere på, om der er tale om sport eller musik. Vi får altså ikke megen ny viden om betydningen af "spille". Her ville det derfor give mere mening at vælge

\[ \vec{w}_{\text{spille}} = \vec{v}_{\text{spille}} = 0\cdot \vec{v}_{\text{manden}}+1\cdot \vec{v}_{\text{spille}} \]

Helt generelt, hvis der i stedet for "orkesteret" havde stået et ord med vektorrepræsentation \(\vec{v}_{\text{ord}}\) ville vi sætte

\[ \vec{w}_{\text{spille}} = c_1 \cdot \vec{v}_{\text{ord}}+c_2 \cdot \vec{v}_{\text{spille}} \]

hvor \(c_1\) og \(c_2\) er ikke-negative konstanter med \(c_1+c_2=1\). Hvis kontekstordet er meget relevant for at forstå betydningen af "spille", vælges \(c_1\) stor, mens \(c_1\) vælges tæt på 0, hvis kontekstordet ikke giver megen ny information om betydningen. Hvordan vi mere præcist vælger \(c_1\) og \(c_2\), vender vi tilbage til i næste afsnit, hvor vi også skal se på, hvordan man kan inddrage information fra mere end et af de foregående ord.

Inden vi afslutter dette afsnit, minder vi om, at det normalt er for lidt at repræsentere ord ved 2-dimensionale vektorer. I stedet repræsenterer vi dem ved \(m\)-dimensionale vektorer. Man kan lægge \(m\)-dimensionale vektorer sammen og skalere dem med en konstant ligesom i to dimensioner:

\[ \begin{pmatrix}a_1\\ a_2 \\ \vdots\\a_m\end{pmatrix} + \begin{pmatrix}b_1\\ b_2\\ \vdots\\b_m\end{pmatrix} = \begin{pmatrix}a_1+b_1\\ a_2+b_2\\ \vdots\\a_m+b_m\end{pmatrix} \]

og

\[ c\begin{pmatrix} a_1\\a_2\\ \vdots\\a_m \end{pmatrix} = \begin{pmatrix} ca_1\\ca_2\\ \vdots\\ca_m \end{pmatrix} \]

I tre dimensioner kan vektoraddition visualiseres på samme måde som i det 2-dimensionale tilfælde, men i højere dimensioner er det ikke muligt at visualisere hvad der foregår.

Opgave 1

Lad \(\vec{v}_1 = \begin{pmatrix} 1\\ 3\end{pmatrix}\) og \(\vec{v}_2 = \begin{pmatrix} 3\\1\end{pmatrix}\).

  • Beregn vektorerne \[ \begin{aligned} \vec{w}_1 = \frac{1}{3} \vec{v}_1 + \frac{2}{3} \vec{v}_2 \\ \\ \vec{w}_2 = \frac{1}{2} \vec{v}_1 + \frac{1}{2} \vec{v}_2 \\ \\ \vec{w}_3 = \frac{2}{3} \vec{v}_1 + \frac{1}{3} \vec{v}_2 \\ \end{aligned} \]

  • Indtegn \(\vec{w}_1\), \(\vec{w}_2\) og \(\vec{w}_3\) i et koordinatsystem sammen med \(\vec{v}_1\) og \(\vec{v}_2\).

  • Passer resultatet med, at vektoren \[ c_1\vec{v}_1 + c_2\vec{v}_2 \] med \(c_1,c_2\geq 0\) ligger tættere på \(\vec{v}_1\) jo større \(c_1\) er?

Lad \(\vec{v}_1 = \begin{pmatrix} 1\\ 2\\ 0\\ -3\end{pmatrix}\) og \(\vec{v}_2 = \begin{pmatrix} 1\\-1\\2\\ 4\end{pmatrix}\).

  • Beregn \[ 2\vec{v}_1+\vec{v}_2 \]

Et opmærksomhedslag

Lad os sige, at vi har et stykke tekst, og at det \(i\)te ord i teksten er vores fokusord, som vi gerne vil finde en vektorrepræsentation for. Som udgangspunkt er betydningen af det \(i\)te ord repræsenteret af vektoren \(\vec{v}_i\). Transformeren opdaterer vektoren \(\vec{v}_{i}\) til \(\vec{w}_{i}\) på baggrund af alle de ord, der går forud. Man kan forstille sig, at vi trækker vektoren \(\vec{v}_{i}\) lidt i retning af alle vektorerne \(\vec{v}_{j}\) med \(j<i\) svarende til alle de ord, der går forud i teksten. Hvor meget hvert ord trækker, afhænger af, hvor meget det siger om betydningen af det \(i\)te ord. Vi opdaterer således \(\vec{v}_i\) til

\[ \vec{w}_{i} = c_{1,i}\vec{v}_1 +c_{2,i}\vec{v}_2 + \dotsm + c_{i,i} \vec{v}_i \]

hvor \(\vec{v}_1,\ldots,\vec{v}_{i-1}\) er vektorrepræsentationerne for de \(i-1\) ord, der går forud for det \(i\)te ord. Desuden indgår nogle konstanter \(c_{j,i}\geq 0\), som er valgt, således at

\[ c_{1,i} + c_{2,i} + \dotsm + c_{i,i}=1 \tag{1}\]

Denne betingelse sikrer, at vi ikke risikerer at få meget lange vektorer ud. Konstanten \(c_{j,i}\) bestemmer, hvor meget \(\vec{v}_i\) bliver trukket i retning af \(\vec{v}_j\). Hvis det \(j\)te ord har stor indflydelse på betydningen af det \(i\)te ord, skal \(c_{j,i}\) være stor, så \(\vec{v}_i\) bliver trukket forholdsvis langt over mod \(\vec{v}_j\). Hvis omvendt det \(j\)te ord ikke indeholder nogen information om betydningen af det \(i\)te ord, skal \(c_{j,i}\) være 0. Dermed bliver \(\vec{v}_i\) ikke trukket i retning af \(\vec{v}_j\). I sætningen

\[ \text{"Orkesteret skal spille ---"} \]

ville \(c_{1,3}\) være stor, fordi ordet "orkesteret" er vigtigt for at forstå betydningen af "spille", mens \(c_{2,3}\) ville være mindre, fordi ordet "skal" ikke fortæller så meget om betydningen af "spille". Desuden vil \(c_{3,3}\) være stor, da ordet "spille" selvfølgelig indeholder megen information om sin egen betydning. Hvordan bestemmer vi så konstanterne \(c_{j,i}\)? Jo, i stedet for at lade hvert ord repræsentere ved to vektorer som i Word2Vec3, lader transformeren hvert ord svare til hele tre vektorer4 \(\vec{v}_{\text{ord}}\), \(\vec{k}_{\text{ord}}\) og \(\vec{q}_{\text{ord}}\). Som i Word2Vec repræsenterer \(\vec{v}_{\text{ord}}\) fokusordets betydning, og \(\vec{k}_{\text{ord}}\) repræsenterer ordet, når det optræder som kontekst (da vi er ude på at gætte næste ord, består konteksten til et ord af alle de ord, der står før i teksten). Den ekstra vektor \(\vec{q}_{\text{ord}}\) repræsenterer også fokusordet, men bruges til at afgøre, hvilke ord der er vigtige for at forstå fokusordets betydning. Vektorerne \(\vec{k}_{\text{ord}}\) og \(\vec{q}_{\text{ord}}\) skal have samme dimension, mens \(\vec{v}_{\text{ord}}\) godt kan have en anden dimension. Alle vektorerne \(\vec{v}_{\text{ord}}\), \(\vec{k}_{\text{ord}}\) og \(\vec{q}_{\text{ord}}\) bliver bestemt, når vi træner transformeren.

3 Hvor hvert ord blev repræsenteret ved en fokus- og en kontekstvektor.

4 I litteraturen kaldes de tre vektorer \(\vec{k}_{\text{ord}}\), \(\vec{q}_{\text{ord}}\) og \(\vec{v}_{\text{ord}}\) for key, query og value.

Lad os igen fokusere på det \(i\)te ord. Vi bruger vektorerne \(\vec{k}_{j}\) og \(\vec{q}_{i}\) til at måle, hvor meget det \(j\)te kontekstord fortæller om betydningen af det \(i\)te ord. Mere præcist bruger vi skalarproduktet

\[ \vec{k}_j \cdot \vec{q}_i \]

Store værdier af \(\vec{k}_j \cdot \vec{q}_i\) svarer til, at det \(j\)te ord indeholder megen information om betydningen af det \(i\)te ord, mens meget negative værdier betyder, at det \(j\)te kontekstord ikke fortæller ret meget om betydningen af det \(i\)te ord. For at sikre at betingelsen i (1) er overholdt, transformerer vi skalarprodukterne med en softmax funktion (se noten om Word2Vec). Vi laver derfor en vektor

\[ \vec{c}_i = \text{Softmax}({\vec{k}_1 \cdot \vec{q}_i}, {\vec{k}_2 \cdot \vec{q}_i},\ldots, {\vec{k}_i \cdot \vec{q}_i} ) \]

hvis \(j\)te koordinat er

\[ c_{j,i} = \frac{e^{\vec{k}_j \cdot \vec{q}_i}}{ e^{\vec{k}_1 \cdot \vec{q}_i}+e^{\vec{k}_2 \cdot \vec{q}_i}+\dotsm +e^{\vec{k}_i \cdot \vec{q}_i}} \] Dette sikrer, at \(c_{j,i}\)’erne er tal mellem 0 og 1, som opfylder ligning (1). Hvis \(\vec{k}_j \cdot \vec{q}_i\) er meget stor bliver \(c_{j,i}\) tæt på 1, mens meget negative \(\vec{k}_j \cdot \vec{q}_i\) svarer til \(c_{j,i}\) tæt på 0.

Processen, der opdaterer vektorrepræsentationen af et ord på baggrund af de foregående ord, kaldes et opmærksomhedslag5 og udgør den centrale del af transformeren. I figur 5 ses skematisk, hvordan de forskellige vektorer bruges i beregningen af \(\vec{w}_i\). Bemærk at vi ikke har taget højde for, at betydningen af det \(i\)te ord afhænger mest af de ord, der står lige i nærheden af det, og i mindre grad af ord langt væk. Derfor modificerer transformeren vektorerne for hvert ord afhængigt af ordets position i sætningen. Vi vil ikke gå i dybden med, hvordan det gøres.

5 På engelsk attention head.

Figur 5: Opdatering af vektorrepræsentationen for \(\text{ord}_{i}\) ved hjælp af opmærksomhed på forudgående ord. Bemærk, at kun \(\vec{q}_i\) benyttes når \(\vec{v}_i\) opdateres, mens \(\vec{q}_1,\ldots,\vec{q}_{i-1}\) ikke benyttes.

Lad os se på et eksempel.

Eksempel 1 Betragt igen sætningen "Orkesteret skal spille". Lad os sige, at de tre ord har følgende vektorrepræsentationer.

\[ \begin{aligned} &\text{orkesteret:} &&\vec{q}_1 = \begin{pmatrix}4\\-1\end{pmatrix}, &&\vec{k}_1=\begin{pmatrix}0\\1\end{pmatrix}, &&\vec{v}_1= \begin{pmatrix} -1\\ 1\end{pmatrix}\\ &\text{skal:}&&\vec{q}_2 = \begin{pmatrix}0\\3\end{pmatrix}, &&\vec{k}_2=\begin{pmatrix}2\\-1\end{pmatrix}, &&\vec{v}_2= \begin{pmatrix} 0.5\\ -1\end{pmatrix}\\ &\text{spille:}&&\vec{q}_3 = \begin{pmatrix}1\\2\end{pmatrix}, &&\vec{k}_3=\begin{pmatrix}1\\1\end{pmatrix}, &&\vec{v}_3= \begin{pmatrix} 1\\ 1\end{pmatrix}\\ \end{aligned} \]

Lad os nu beregne den opdaterede vektor \(\vec{w}_3\) for "spille". Først regnes skalarprodukterne

\[ \begin{aligned} &\vec{k}_1\cdot \vec{q}_3=\begin{pmatrix}0\\1\end{pmatrix} \cdot \begin{pmatrix}1\\2\end{pmatrix} =0\cdot 1 + 1\cdot 2 = 2 \\ &\vec{k}_2\cdot \vec{q}_3=\begin{pmatrix}2\\-1\end{pmatrix}\cdot \begin{pmatrix}1\\2\end{pmatrix} = 2\cdot 1 + (-1)\cdot 2 =0\\ &\vec{k}_3\cdot \vec{q}_3= \begin{pmatrix}1 \\1\end{pmatrix} \cdot \begin{pmatrix}1\\2\end{pmatrix} = 1\cdot 1 + 1 \cdot 2=3 \end{aligned} \]

figur 6 er vektorerne \(\vec{k}_{\text{orkesteret}}\), \(\vec{k}_{\text{skal}}\), \(\vec{k}_{\text{spille}}\) og \(\vec{q}_{\text{spille}}\) indtegnet i et koordinatsystem. Det ses, at \(\vec{k}_{\text{spille}}\) er den vektor, der peger mest i retning af \(\vec{q}_{\text{spille}}\) svarende til største skalarprodukt, mens \(\vec{k}_{\text{skal}}\) peger mindst i retning af \(\vec{q}_{\text{spille}}\) svarende til det mindste skalarprodukt. Det svarer til, at "spille" har meget at sige om sin egen betydning, mens "skal" ikke har meget at sige om betydningen af "spille". Vektorerne \(\vec{q}_{\text{orkesteret}}\) og \(\vec{q}_{\text{skal}}\) bliver ikke brugt.

Figur 6: Vektorerne \(\vec{k}_{\text{orkesteret}}\), \(\vec{k}_{\text{skal}}\), \(\vec{k}_{\text{spille}}\) og \(\vec{q}_{\text{spille}}\).

Vi bruger softmax på skalarprodukterne for at finde konstanterne \(c_{1,3}\), \(c_{2,3}\) og \(c_{3,3}\).

\[ \begin{aligned} c_{1,3} = \frac{e^{\vec{k}_1\cdot \vec{q}_3}}{e^{\vec{k}_1\cdot \vec{q}_3}+e^{\vec{k}_2\cdot \vec{q}_3}+e^{\vec{k}_3\cdot \vec{q}_3}} = \frac{e^{2}}{e^{2}+e^{0}+e^{3} }\approx 0.259\\ c_{2,3} = \frac{e^{\vec{k}_2\cdot \vec{q}_3}}{e^{\vec{k}_1\cdot \vec{q}_3}+e^{\vec{k}_2\cdot \vec{q}_3}+e^{\vec{k}_3\cdot \vec{q}_3}} = \frac{e^{0}}{e^{2}+e^{0}+e^{3} }\approx 0.035\\ c_{3,3} = \frac{e^{\vec{k}_3\cdot \vec{q}_3}}{e^{\vec{k}_1\cdot \vec{q}_3}+e^{\vec{k}_2\cdot \vec{q}_3}+e^{\vec{k}_3\cdot \vec{q}_3}} = \frac{e^{3}}{e^{2}+e^{0}+e^{3} }\approx 0.705\\ \end{aligned} \] Bemærk (bortset fra afrunding), at

\[ c_{1,3} + c_{2,3} + c_{3,3} = 1 \]

Nu kan vi beregne

\[ \begin{aligned} \vec{w}_3 & =c_{1,3}\vec{v}_1 + c_{2,3}\vec{v}_2 + c_{3,3}\vec{v}_3 \\ & = 0.259\begin{pmatrix} -1\\ 1\end{pmatrix} + 0.035\begin{pmatrix} 0.5\\ -1\end{pmatrix} + 0.705\begin{pmatrix} 1\\ 1\end{pmatrix} \\ & =\begin{pmatrix} -0.259\\ 0.259\end{pmatrix} + \begin{pmatrix} 0.0175\\ -0.035\end{pmatrix} + \begin{pmatrix} 0.705\\ 0.705\end{pmatrix} \\ & = \begin{pmatrix} -0.259 + 0.0175 + 0.705\\ 0.259-0.035+0.705 \end{pmatrix}\\ &=\begin{pmatrix} 0.464\\ 0.929 \end{pmatrix} \end{aligned} \]

figur 7 er vektorerne \(\vec{v}_{\text{orkesteret}}\), \(\vec{v}_{\text{skal}}\), \(\vec{v}_{\text{spille}}\) og \(\vec{w}_{\text{spille}}\) indtegnet i et koordinatsystem. Det ses, at \(\vec{w}_{\text{spille}}\) er blevet trukket i retning af \(\vec{v}_{\text{orkesteret}}\) i forhold til \(\vec{v}_{\text{spille}}\).

Figur 7: Vektorerne \(\vec{v}_{\text{orkesteret}}\), \(\vec{v}_{\text{skal}}\), \(\vec{v}_{\text{spille}}\) og \(\vec{w}_{\text{spille}}\) indtegnet i et koordinatsystem.
Opgave 2

Betragt sætningen "Der svømmer marsvinet". Her har "svømmer" stor betydning for vores opfattelse af ordet "marsvinet", mens "der" ikke giver os ret meget ny information. Lad os sige, at de tre ord har vektorrepræsentationerne

\[ \begin{aligned} &\text{der}&& \vec{q}_1 = \begin{pmatrix}0\\-5\end{pmatrix},&& \vec{k}_1=\begin{pmatrix}4\\-1\end{pmatrix},&&\vec{v}_1= \begin{pmatrix} -1\\ -1\end{pmatrix}\\ &\text{svømmer}&& \vec{q}_2 = \begin{pmatrix}3\\-7\end{pmatrix},&& \vec{k}_2=\begin{pmatrix}1\\2\end{pmatrix},&&\vec{v}_2= \begin{pmatrix} -2\\ 2\end{pmatrix}\\ &\text{marsvinet}&& \vec{q}_3 = \begin{pmatrix}0\\1\end{pmatrix}, &&\vec{k}_3=\begin{pmatrix}-1\\2\end{pmatrix},&&\vec{v}_3= \begin{pmatrix} 1\\ 2\end{pmatrix}\\ \end{aligned} \]

Vi vil nu se, at den opdaterede vektor \(\vec{w}_3\) for "marsvinet" bliver trukket meget i retning af "svømmer", men i mindre grad i retning af "der".

  • Beregn \(\vec{w}_3\) ved at følge nedenstående skridt:

    • Beregn skalarprodukterne \(\vec{k}_1\cdot \vec{q}_3\), \(\vec{k}_2\cdot \vec{q}_3\) og \(\vec{k}_3\cdot \vec{q}_3\).

    • Brug softmax til at beregne konstanterne \(c_{1,3}\), \(c_{2,3}\) og \(c_{3,3}\).

    • Beregn \[\vec{w}_3 = c_{1,3}\vec{v}_1 + c_{2,3}\vec{v}_2 + c_{3,3}\vec{v}_3\]

  • Tegn vektorerne \(\vec{v}_1\), \(\vec{v}_2\), \(\vec{v}_3\) og \(\vec{w}_3\) ind i et koordinatsystem og overvej, hvordan \(\vec{w}_3\) ligger i forhold til \(\vec{v}_1\), \(\vec{v}_2\) og \(\vec{v}_3\).

  • Tegn vektorerne \(\vec{k}_1\), \(\vec{k}_2\), \(\vec{k}_3\) og \(\vec{q}_3\) ind i et koordinatsystem. Passer det med, at kontekstvektoren for de ord, der har mest at sige om betydningen af ordet "marsvinet", peger mest i retning af \(\vec{q}_3\)?

En transformerblok

Opmærksomhedslaget er det centrale element i tranformeren. Vi skitserer nu, hvordan resten af transformernetværket er opbygget for at give et indtryk af algoritmens kompleksitet, men en del detaljer er udeladt.

Man kan lave flere opmærksomhedslag parallelt. Hvis der er \(K\) parallelle opmærksomhedslag, så oversætter det \(k\)te lag det \(i\)te ord til tre vektorer \(\vec{q}^{k}_{i}\), \(\vec{k}^{k}_{i}\) og \(\vec{v}^{k}_{i}\) og beregner en ny vektor \(\vec{w}^{k}_{i}\), hvor \(k=1,\ldots,K\). Man kan forestille sig, at de forskellige opmærksomhedslag indfanger forskellige aspekter ved sætningen. Et holder måske styr på grammatikken, mens et andet fokuserer på betydningen af ordene. Men i stedet for at lægge os fast på, hvad hvert lag skal gøre, lader vi det være op til algoritmen selv at vælge nogle passende vektorer. I praksis er det derfor umuligt at gå ind og se præcis, hvad hvert lag gør.

Indtil videre er \(\vec{w}_i^k\) dannet ud fra de oprindelige \(\vec{v}_i^k\) ved hjælp af simple operationer som addition og skalering. Det viser sig dog at være for simpelt til at fungere godt i praksis. Derfor transformeres outputet fra opmærksomhedslagene med et neuralt netværk. Mere præcist sættes outputvektorerne \(\vec{w}^{k}_i\) fra de forskellige lag i forlængelse af hinanden til en samlet outputvektor \(\vec{x}_i\) af længde \(Km\). Koordinaterne i den lange vektor \(\vec{x}_i\) bruges som input til et neuralt netværk, ligesom vi så i noten om neurale netværk til tekstgenerering, bortset fra at vi ikke bruger softmax til sidst. Outputtet bliver en ny vektor \(\vec{y}_i\).

Tilsammen udgør de parallelle opmærksomhedslag og det efterfølgende neurale netværk en transformerblok. Strukturen er skitseret på figur 8. Undervejs sker der forskellige transformationer af vektorerne, som viser sig at gøre de numeriske beregninger mere stabile. Vi vil ikke beskrive disse i detaljer, da de ikke er centrale for forståelsen.

Figur 8: En transformerblok.

Transformernetværket

Når vi så har beregnet outputvektorerne \(\vec{y}_i\) fra en transformerblok for alle positioner \(i\), kan vi bruge dem som input til en ny transformerblok. I hvert opmærksomhedslag inden for den næste transformerblok oversættes vektoren \(\vec{y}_i\) (ikke det oprindelige ord) til tre nye vektorer. Så beregner transformerblokken en ny \(\vec{y}_i\), som kan bruges som input til endnu en transformerblok og så videre. Tilsammen udgør disse transformerblokke et transformernetværk, som er skitseret på figur 9.

Figur 9: Transformernetværket.

Egentlig var vi jo ude på at lave en algoritme, der kan gætte det næste ord, altså det \((i+1)\)te ord. Det sker i den sidste prædiktionsblok i transformernetværket, se figur 9. Vektoren \(\vec{y}_i\), der kommer ud fra den sidste transformerblok, indeholder information om betydningen af \(i\)te ord samt alle de ord, der går forud. I prædiktionslaget omregnes \(\vec{y}_i\) til sandsynligheder for næste ord.

For hvert ord i ordforrådet udregner vi først en score \(s_{\text{ord}}\), der måler, hvor sandsynligt det er, at ordet er det næste. Store værdier betyder, at ordet er sandsynligt som næste ord. Til at beregne scoren bruger vi en fast vektor \(\vec{a}_{\text{ord}}\) for hvert ord og omregner \(\vec{y}_i\) til scoren

\[ s_{\text{ord}} = \vec{a}_{\text{ord}}\cdot \vec{y}_i \]

Disse scores samles i en vektor \(\vec{s}\) med \(V\) koordinater, hvor \(V\) var antallet af ord i ordforrådet. Endelig omdannes \(\vec{s}\) til en vektor af sandsynligheder ved endnu engang at bruge softmax-funktionen (se noten om neurale netværk til tekstgenerering):

\[ \vec{p} = \text{Softmax}(\vec{s}) \]

Når man har disse sandsynligheder, kan man begynde at generere tekst, for eksempel ved hele tiden at vælge blandt de mest sandsynlige næste ord.

Træning af netværket

I alt indeholder transformernetværket rigtig mange vægte, der skal bestemmes, før vi kan begynde at generere tekst. Hvert opmærksomhedslag skal bruge vægte til at lave koordinaterne i de tre forskellige vektorer. Hver transformerblok indeholder desuden et neuralt netværk, som skal bruge et sæt af vægte. Endelig skal der i det sidste trin bruges \(V\) vektorer på formen \(\vec{a}_{\text{ord}}\).

Den præcise struktur af GPT-4 netværket, som er fundamentet for Chat-GPT, er en forretningshemmelighed. En tidligere version, GPT-2, brugte 12 transformerblokke med 12 parallelle opmærksomhedslag i hvert, altså 144 opmærksomhedslag i alt. I alt havde GPT-2 omkring 1,5 milliarder vægte. Det er dog intet mod GPT-4, der vurderes at have omkring 1 billion vægte.

For at bestemme så mange vægte skal der bruges enorme mængder træningsdata. Til at træne GPT-2 blev der for eksempel brugt 8 millioner dokumenter. I træningsdata kender vi hele tiden det næste ord. For at måle hvor godt det passer med de sandsynligheder for næste ord, som transformeren giver, beregnes en cross-entropy tabsfunktion (se noten om tabsfunktioner). Transformeren trænes til at minimere denne cross-entropy, således at den er god til at forudsige næste ord i træningsdata. Når transformeren er trænet, bliver den som regel fintunet til den opgave, den skal udføre, for eksempel at oversætte en tekst eller besvare et spørgsmål.