Transformatorbaserte modeller er kjent for sin evne til å analysere og tolke kompleks tekst. De er avhengige av å forstå ordens rekkefølge og kontekst – oppgaver der tradisjonelle posisjonskodingsmetoder har vist sine begrensninger. For å løse dette gapet, redefinerer ROFORMER-modellen, drevet av Rotary Position Embedding (RoPE), vår tilnærming til posisjonell koding.
Tradisjonell posisjonskoding
Transformatorer behandler tekst som en serie tokens, og tillater parallell behandling av sekvenser for større effektivitet. Denne styrken ga imidlertid sin utfordring: modellens iboende agnostisisme til symbolsk orden. Posisjonell koding var svaret, ga hvert token en unik signatur som angir sekvensposisjonen.
Absolute Position Embeddings
Opprinnelig brukte modeller som BERT absolutt posisjonsinnbygging, og tilordnet en fast vektor til hver posisjon i en sekvens. Selv om denne metoden er enkel, mangler den iboende evnen til å tilpasse seg sekvenslengdevariasjoner, eller å understreke de relative avstandene mellom tokens**, noe som er avgjørende for å forstå mange språklige konstruksjoner.
Relative Position Embeddings
For å fange språkets dynamiske natur, ble relative posisjonsinnbygginger introdusert, med fokus på avstanden mellom tokens i stedet for deres absolutte posisjoner. Til tross for deres konseptuelle fordel, introduserte disse innbyggingene beregningskompleksitet, og klarte ikke å integreres sømløst i selvoppmerksomhetsmekanismen til Transformers, noe som begrenset deres effektivitet.
ROFORMER og Rotary Position Embedding
Ved å erkjenne begrensningene til eksisterende posisjonskodingsstrategier, introduserer ROFORMER Rotary Position Embedding (RoPE), en tilnærming som kombinerer fordelene med absolutt og relativ posisjonsinformasjon uten deres respektive ulemper.
Innebygging av roterende posisjon
RoPE koder posisjonsinformasjon ved hjelp av rotasjonsmatriser, noe som gjør at modellen ikke bare forstår hvor et token er, men hvordan det forholder seg til alle andre token i en sekvens.
Credit: Original Paper
Den opererer gjennom en geometrisk linse, og behandler tokenposisjoner som punkter i et flerdimensjonalt rom som roteres for å markere deres sekvensielle forhold. Denne rotasjonen lar modellen bevare og utnytte både absolutte og relative posisjonelle signaler innenfor sin selvoppmerksomhetsmekanisme.
Implementering av RoPE
Implementering av RoPE innebærer å kode hver tokens posisjon til en rotasjonsmatrise, og bruke denne matrisen innenfor selvoppmerksomhetsmekanismen til transformatoren. Denne prosessen gir mulighet for en fleksibel, dynamisk tolkning av posisjonsinformasjon, tilpasser seg varierende sekvenslengder og fanger opp essensen av token-relasjoner uten betydelige beregningsmessige overhead.
Først trenger du en funksjon for å generere roterende embeddings, og deretter vil du integrere disse embeddings i modellen din. Eksemplet nedenfor forutsetter at du er kjent med å lage egendefinerte lag i Keras.
Trinn 1: Definer Rotary Embedding-funksjonen
Denne funksjonen genererer roterende embeddings gitt maksimal sekvenslengde, og dimensjonaliteten til embeddingene.
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))
Denne linjen beregner inversen av eksponentielt skalerte frekvenser basert på posisjonsindeksene. Disse frekvensene brukes til å generere sinusformede mønstre for roterende innebygginger, noe som hjelper til med å kode den relative posisjonsinformasjonen i sekvenser. Denne mekanismen er spesielt nyttig i oppgaver der det er avgjørende å forstå rekkefølgen og den relative plasseringen av elementer, for eksempel i naturlig språkbehandling eller tidsserieanalyse.
I detaljer:
-
tf.range(0, dim, 2, dtype=tf.float32)
oppretter et verdiområde som starter fra 0 opp tildim
(eksklusiv), og går med 2. Argumentetdtype=tf.float32
spesifiserer at elementene i denne tensoren er 32-bits flytende tall. Hvis «dim» er 8, for eksempel, vil dette produsere «[0, 2, 4, 6]». -
Tensoren som produseres av
tf.range
blir deretter delt med dimensjonaliteten (dim
) til innebyggingene. Denne operasjonen skalerer disse indeksene ned til et område mellom 0 og 1 (eksklusivt hvis 'dim' er partall, litt inkluderende hvis 'dim' er oddetall, fordi områdetrinnet hopper over annenhver verdi). Hvis du fortsetter eksemplet med «dim» = 8, dividerer med 8, får du «[0,0, 0,25, 0,5, 0,75]». -
Operasjonen
10000 ** (...)
øker 10 000 til styrken til hvert element i den tidligere skalerte tensoren. Grunnlaget på 10.000 er noe vilkårlig, men er valgt for å sikre at frekvensene varierer over et bredt område, noe som hjelper modellen til å skille mellom ulike posisjoner mer effektivt. For[0.0, 0.25, 0.5, 0.75]
vil den bruke kraftoperasjonen på hver, noe som resulterer i mye større verdier for høyere elementer. -
Til slutt oppnås den inverse frekvensen ved å ta den resiproke (1/x) av verdiene fra forrige trinn. De inverse frekvensene er mindre for høyere indekser, noe som betyr at elementer lenger i sekvensen vil ha mindre frekvenser, noe som påvirker hvordan posisjonene deres er kodet inn i modellen. Dette gjør at innbyggingene kan skaleres på en måte der relative posisjoner kan utledes gjennom modellens oppmerksomhetsmekanismer.
Køen:
freqs = tf.einsum('i,j->ij', t, inv_freq)
bruker TensorFlows tf.einsum
-funksjon, et verktøy som muliggjør kortfattet og effektivt uttrykk for tensoroperasjoner ved å bruke Einstein-summeringsnotasjonen.
Denne operasjonen beregner effektivt det ytre produktet av vektorene "t" og "inv_freq", noe som resulterer i en matrise der hvert element "(i, j)" er produktet av det "i"-te elementet til "t" og "t" j-te element av
inv_freq`. Denne matrisen ("freqs") representerer frekvensene som brukes til å generere de sinusformede mønstrene for de roterende innstøpningene.
Trinn 2: Egendefinert Keras-lag for roterende innstøpninger
La oss nå lage et egendefinert Keras-lag som bruker roterende embeddings på inngangstensoren. Dette laget antar at inngangstensoren har formen "(batch_size, sequence_length, embedding_dim)".
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
Linjen embeddings = self.rotary_embeddings[:seq_len]
velger riktig delsett av forhåndsberegnet roterende embeddings basert på gjeldende inngangssekvenslengde. Siden lengden på sekvenser kan variere fra en batch til en annen, sikrer denne skjæringsoperasjonen at bare innbyggingene som tilsvarer den faktiske sekvenslengden brukes.
Variabelen embeddings
har nå en tensor av form (seq_len, embedding_dim)
, der seq_len
er lengden på sekvensene i gjeldende batch, og embedding_dim
er dimensjonaliteten til innebyggingene. Denne tensoren inneholder de roterende posisjonelle innebyggingene for hver posisjon i sekvensen opp til seq_len
.
emb = tf.concat((tf.cos(freqs), tf.sin(freqs)), axis=-1)
kombinerer sinus- og cosinustransformasjoner av posisjonsfrekvenser til en enkelt tensor:
-tf.cos(freqs)
og tf.sin(freqs)
bruker henholdsvis cosinus- og sinustransformasjonene på freqs
-tensoren. Freqs
-tensoren inneholder frekvensverdier for hver posisjon i inngangssekvensen og hver dimensjon av innebyggingsrommet, beregnet basert på sekvensposisjonene og de inverse frekvensene til innebyggingsdimensjonene. Sinus- og cosinusfunksjonene brukes elementvis, noe som resulterer i to tensorer med samme form som "freqs". Disse transformasjonene hjelper til med å kode posisjonen på en måte som fanger den sykliske naturen til posisjonelle relasjoner, og letter modellens evne til å forstå relative posisjoner.
-tf.concat((tf.cos(freqs), tf.sin(freqs)), axis=-1)
kobler de cosinus- og sinustransformerte tensorene sammen langs den siste aksen (angitt med axis=-1
). Sammenknytting av disse tensorene side om side dobler effektivt dimensjonaliteten til "freqs"-tensoren, med den første halvdelen som representerer cosinus-transformerte verdier og den andre halvdelen representerer sinus-transformerte verdier for hver posisjon. Sammenkoblingen sikrer at hver posisjonskoding inneholder både sinus- og cosinusinformasjon, noe som muliggjør bevaring av informasjon om både amplituden og fasen til posisjonssignalene.
- Den sammenkoblede tensor-'emb'en holder nå de komplette roterende embeddings for inngangsposisjonene. Formen til "emb" vil være den samme som "freqs" i de to første dimensjonene (tilsvarer sekvensposisjoner og innebyggingsdimensjoner), men den siste dimensjonen vil være dobbelt så stor, og tar hensyn til både sinus- og cosinusverdier. Disse innebyggingene brukes til å modulere inngangs-innbyggingene ved å legge til posisjonsinformasjon på en rotasjonsmessig ekvivariant måte.
-cos_emb = embeddings[:, Ingen, :self.dim // 2]
:
-
Det første kolon
:
betyr "velg alle elementer i denne dimensjonen," som i dette tilfellet refererer til alle posisjoner i sekvensen. -
Ingen
brukes for å legge til en ekstra dimensjon, noe som gjør tensoren 3-dimensjonal. Dette gjøres ofte for å sikre kompatibilitet med visse operasjoner som forventer innganger av et spesifikt antall dimensjoner. For eksempel, når du utfører elementvis multiplikasjon med en annen tensor som er 3-dimensjonal, må formene justeres i henhold til kringkastingsregler. -
:self.dim // 2
, velger den første halvdelen av dimensjonene i den siste aksen. Siden "embedding_dimension" er doblet for å inkludere både sinus- og cosinusverdier, vil divisjon med 2 effektivt bare velge cosinuskomponentene til innebyggingene.
Trinn 3: Integrasjon med en Keras-modell
Etter å ha definert RotaryEmbeddingLayer
, kan du integrere det i din Keras-modell. Dette laget bør påføres innstøpingene dine før de mates inn i oppmerksomhetslag eller påfølgende modelllag.
Her er et forenklet eksempel på hvordan du integrerer de roterende embeddings i en modell:
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()