Op Transformer gebaseerde modellen staan bekend om hun vermogen om complexe tekst te ontleden en te interpreteren. Ze vertrouwen op het begrijpen van de volgorde en context van woorden – taken waarbij traditionele positionele coderingsmethoden hun grenzen hebben laten zien. Door deze leemte aan te pakken, herdefinieert het ROFORMER-model, mogelijk gemaakt door de Rotary Position Embedding (RoPE), onze benadering van positionele codering.
Traditionele positionele codering
Transformers behandelen tekst als een reeks tokens en maken parallelle verwerking van reeksen mogelijk voor grotere efficiëntie. Deze kracht bracht echter ook een uitdaging met zich mee: het model's inherente agnosticisme tegenover symbolische orde. Positionele codering was het antwoord, waardoor elk token een unieke handtekening kreeg die de positie van de reeks aangeeft.
Absolute positie-inbedding
Aanvankelijk gebruikten modellen zoals BERT absolute positie-inbedding, waarbij aan elke positie in een reeks een vaste vector werd toegewezen. Deze methode, hoewel eenvoudig, mist inherent het vermogen om zich aan te passen aan variaties in de lengte van de reeks, of om de relatieve afstanden tussen tokens te benadrukken**, wat van cruciaal belang is voor het begrijpen van veel taalkundige constructies.
Relatieve positie-inbedding
Om de dynamische aard van taal vast te leggen, werden relatieve positie-inbedding geïntroduceerd, waarbij de nadruk lag op de afstand tussen tokens in plaats van op hun absolute posities. Ondanks hun conceptuele voordeel introduceerden deze inbeddingen computationele complexiteit, en slaagden ze er niet in naadloos te integreren in het zelfaandachtsmechanisme van Transformers, waardoor hun doeltreffendheid werd beperkt.
ROFORMER en roterende positie-inbedding
ROFORMER erkent de beperkingen van bestaande positionele coderingsstrategieën en introduceert Rotary Position Embedding (RoPE), een benadering die de voordelen van absolute en relatieve positie-informatie combineert zonder de respectieve nadelen ervan.
Roterende positie inbedden
RoPE codeert positionele informatie met behulp van rotatiematrices, waardoor het model niet alleen begrijpt waar een token zich bevindt, maar ook hoe deze zich verhoudt tot elk ander token in een reeks.
Credit: Original Paper
Het werkt via een geometrische lens en behandelt symbolische posities als punten in een multidimensionale ruimte die worden geroteerd om hun opeenvolgende relaties te markeren. Door deze rotatie kan het model zowel absolute als relatieve positionele signalen behouden en exploiteren binnen zijn zelfaandachtsmechanisme.
RoPE implementeren
Het implementeren van RoPE omvat het coderen van de positie van elk token in een rotatiematrix, en het toepassen van deze matrix binnen het zelfaandachtsmechanisme van de Transformer. Dit proces maakt een flexibele, dynamische interpretatie van positionele informatie mogelijk, waarbij verschillende sequentielengtes worden geaccommodeerd en de essentie van token-interrelaties wordt vastgelegd zonder noemenswaardige computeroverhead.
Eerst hebt u een functie nodig om de roterende insluitingen te genereren, en vervolgens integreert u deze insluitingen in uw model. In het onderstaande voorbeeld wordt ervan uitgegaan dat u bekend bent met het maken van aangepaste lagen in Keras.
Stap 1: Definieer de roterende inbeddingsfunctie
Deze functie genereert de roterende inbedding op basis van de maximale sequentielengte en de dimensionaliteit van de inbedding.
from tensorflow.keras.layers import Layer
import numpy as np
def get_rotary_embedding(dim, max_seq_len):
inv_freq = 1.0 / (10000 ** (tf.range(0, dim, 2, dtype=tf.float32) / dim))
t = tf.range(max_seq_len, dtype=tf.float32)
freqs = tf.einsum('i,j->ij', t, inv_freq)
emb = tf.concat((tf.cos(freqs), tf.sin(freqs)), axis=-1)
return emb
inv_freq = 1.0 / (10000 ** (tf.range(0, dim, 2, dtype=tf.float32) / dim))
Deze lijn berekent de inverse van exponentieel geschaalde frequenties op basis van de positie-indices. Deze frequenties worden gebruikt bij het genereren van sinusoïdale patronen voor roterende inbedding, wat helpt bij het coderen van de relatieve positionele informatie in reeksen. Dit mechanisme is vooral nuttig bij taken waarbij het begrijpen van de volgorde en de relatieve positionering van elementen cruciaal is, zoals bij natuurlijke taalverwerking of tijdreeksanalyse.
In detail:
-
tf.range(0, dim, 2, dtype=tf.float32)
creëert een bereik van waarden beginnend bij 0 totdim
(exclusief), stapsgewijs met 2. Het argumentdtype=tf.float32
specificeert dat de elementen van deze tensor 32-bit drijvende-kommagetallen zijn. Alsdim
bijvoorbeeld 8 is, zou dit[0, 2, 4, 6]
opleveren. -
De door
tf.range
geproduceerde tensor wordt vervolgens gedeeld door de dimensionaliteit (dim
) van de inbedding. Deze bewerking schaalt deze indices terug naar een bereik tussen 0 en 1 (exclusief als 'dim' even is, enigszins inclusief als 'dim' oneven is, omdat de bereikstap elke andere waarde overslaat). Als we het voorbeeld voortzetten metdim
= 8, levert delen door 8[0,0, 0,25, 0,5, 0,75]
op. -
De bewerking
10000 ** (...)
verhoogt 10.000 tot de macht van elk element in de eerder geschaalde tensor. De basis van 10.000 is enigszins willekeurig, maar is gekozen om ervoor te zorgen dat de frequenties over een groot bereik variëren, waardoor het model effectiever onderscheid kan maken tussen verschillende posities. Voor '[0,0, 0,25, 0,5, 0,75]' zou het de machtsbewerking op elk toepassen, wat resulteert in waarden die veel groter zijn voor hogere elementen. -
Ten slotte wordt de inverse frequentie verkregen door het omgekeerde (1/x) van de waarden uit de vorige stap te nemen. De inverse frequenties zijn kleiner voor hogere indices, wat betekent dat elementen verder in de reeks kleinere frequenties zullen hebben, wat van invloed is op de manier waarop hun posities in het model worden gecodeerd. Hierdoor kunnen de inbeddingen worden geschaald op een manier waarbij relatieve posities kunnen worden afgeleid via de aandachtsmechanismen van het model.
De lijn:
freqs = tf.einsum('i,j->ij', t, inv_freq)
maakt gebruik van de tf.einsum
-functie van TensorFlow, een hulpmiddel dat een beknopte en efficiënte uitdrukking van tensorbewerkingen mogelijk maakt met behulp van de Einstein-sommatienotatie.
Deze bewerking berekent effectief het buitenste product van de vectoren t
en inv_freq
, resulterend in een matrix waarin elk element (i, j)
het product is van het i
-de element van t
en de j
-de element van inv_freq
. Deze matrix ("freqs") vertegenwoordigt de frequenties die worden gebruikt om de sinusoïdale patronen voor de roterende inbedding te genereren.
Stap 2: Aangepaste Keras-laag voor roterende insluitingen
Laten we nu een aangepaste Keras-laag maken die roterende inbedding toepast op de invoertensor. Deze laag gaat ervan uit dat de invoertensor de vorm '(batch_size, sequence_length, embedding_dim)' heeft.
class RotaryEmbeddingLayer(Layer):
def __init__(self, dim, max_seq_len, **kwargs):
super().__init__(**kwargs)
self.dim = dim
self.max_seq_len = max_seq_len
self.rotary_embeddings = get_rotary_embedding(dim, max_seq_len)
def call(self, inputs):
seq_len = tf.shape(inputs)[1]
embeddings = self.rotary_embeddings[:seq_len]
cos_emb = embeddings[:, None, :self.dim // 2]
sin_emb = embeddings[:, None, self.dim // 2:]
# Decompose inputs into sine and cosine components
inputs_cos = inputs[..., :self.dim // 2]
inputs_sin = inputs[..., self.dim // 2:]
# Apply rotary embeddings
rotated_cos = inputs_cos * cos_emb - inputs_sin * sin_emb
rotated_sin = inputs_sin * cos_emb + inputs_cos * sin_emb
return tf.concat([rotated_cos, rotated_sin], axis=-1)
def get_config(self):
config = super().get_config()
config.update({
"dim": self.dim,
"max_seq_len": self.max_seq_len
})
return config
De regel `embeddings = self.rotary_embeddings[:seq_len]' selecteert de juiste subset van vooraf berekende roterende insluitingen op basis van de huidige lengte van de invoerreeks. Omdat de lengte van sequenties van batch tot batch kan variëren, zorgt deze snijbewerking ervoor dat alleen de inbedding die overeenkomt met de feitelijke sequentielengte worden gebruikt.
De variabele 'embeddings' bevat nu een tensor van vorm '(seq_len, embedding_dim)', waarbij 'seq_len' de lengte is van de reeksen in de huidige batch, en 'embedding_dim' de dimensionaliteit van de inbedding is. Deze tensor bevat de roterende positionele inbedding voor elke positie in de reeks tot aan seq_len
.
emb = tf.concat((tf.cos(freqs), tf.sin(freqs)), axis=-1)
combineert sinus- en cosinustransformaties van positionele frequenties in een enkele tensor:
-tf.cos(freqs)
en tf.sin(freqs)
passen respectievelijk de cosinus- en sinustransformaties toe op de freqs
-tensor. De 'freqs'-tensor bevat frequentiewaarden voor elke positie in de invoerreeks en elke dimensie van de inbeddingsruimte, berekend op basis van de reeksposities en de inverse frequenties van de inbeddingsdimensies. De sinus- en cosinusfuncties worden elementair toegepast, wat resulteert in twee tensoren met dezelfde vorm als freqs
. Deze transformaties helpen bij het coderen van de positie op een manier die de cyclische aard van positionele relaties vastlegt, waardoor het vermogen van het model om relatieve posities te begrijpen wordt vergemakkelijkt.
-tf.concat((tf.cos(freqs), tf.sin(freqs)), axis=-1)
voegt de cosinus- en sinus-getransformeerde tensoren aan elkaar langs de laatste as (aangegeven met axis=-1
). Het naast elkaar aaneenschakelen van deze tensoren verdubbelt effectief de dimensionaliteit van de 'freqs'-tensor, waarbij de eerste helft cosinus-getransformeerde waarden vertegenwoordigt en de tweede helft sinus-getransformeerde waarden voor elke positie vertegenwoordigt. De aaneenschakeling zorgt ervoor dat elke positionele codering zowel sinus- als cosinusinformatie bevat, waardoor informatie over zowel de amplitude als de fase van de positionele signalen behouden blijft.
- De aaneengeschakelde tensor
emb
bevat nu de volledige roterende inbedding voor de invoerposities. De vorm van 'emb' zal hetzelfde zijn als 'freqs' in de eerste twee dimensies (overeenkomend met reeksposities en inbeddingsdimensies), maar de laatste dimensie zal twee keer zo groot zijn, rekening houdend met zowel sinus- als cosinuswaarden. Deze inbedding wordt gebruikt om de invoerinbedding te moduleren door positionele informatie op een rotatie-equivariante manier toe te voegen.
-cos_emb = embeddings[:, Geen, :self.dim // 2]
:
-
De eerste dubbele punt
:
betekent 'selecteer alle elementen in deze dimensie', wat in dit geval verwijst naar alle posities in de reeks. -
'Geen' wordt gebruikt om een extra dimensie toe te voegen, waardoor de tensor driedimensionaal wordt. Dit wordt vaak gedaan om compatibiliteit te garanderen met bepaalde bewerkingen waarbij invoer van een specifiek aantal dimensies wordt verwacht. Wanneer u bijvoorbeeld elementgewijze vermenigvuldiging uitvoert met een andere tensor die driedimensionaal is, moeten de vormen worden uitgelijnd volgens de uitzendregels.
-
:self.dim // 2
, selecteert de eerste helft van de afmetingen in de laatste as. Omdat de 'inbeddingsdimensie' wordt verdubbeld om zowel sinus- als cosinuswaarden op te nemen, selecteert delen door 2 effectief alleen de cosinuscomponenten van de inbedding.
Stap 3: Integratie met een Keras-model
Nadat u de RotaryEmbeddingLayer
hebt gedefinieerd, kunt u deze in uw Keras-model integreren. Deze laag moet op uw inbedding worden toegepast voordat u deze in aandachtslagen of eventuele volgende modellagen invoert.
Hier is een vereenvoudigd voorbeeld van hoe u de roterende inbedding in een model kunt integreren:
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Embedding, Dense
max_seq_len = 512
embedding_dim = 64
inp = Input(shape=(max_seq_len,))
x = Embedding(input_dim=10000, output_dim=embedding_dim)(inp)
x = RotaryEmbeddingLayer(dim=embedding_dim, max_seq_len=max_seq_len)(x)
# Add your model's layers here, e.g., Transformer blocks
x = Dense(1, activation='sigmoid')(x)
model = Model(inputs=inp, outputs=x)
model.summary()