11.6 Saviraiška dėmesio sutelkimo transformacija ir pozicinis kodavimas
Giliajame mokymesi sekoms koduoti dažnai naudojame CNN arba RNN. Dabar, turėdami omenyje dėmesio sutelkimo mechanizmus, įsivaizduokime, kad teksto vienetų seką paduodame į dėmesio sutelkimo mechanizmą taip, kad kiekviename žingsnyje kiekvienas teksto vienetas turi savo užklausą, raktus ir reikšmes. Čia, apskaičiuodamas teksto vieneto vaizdo reikšmę kitame sluoksnyje, teksto vienetas gali sutelkti dėmesį (per savo užklausos vektorių) į bet kurį kitą teksto vienetą (sutapatinant pagal jų raktų vektorius). Naudodami visą užklausos ir rakto suderinamumo įverčių aibę, galime apskaičiuoti kiekvieno teksto vieneto vaizdą, sudarydami atitinkamą svertinę sumą per kitus teksto vienetus. Kadangi kiekvienas teksto vienetas sutelkia dėmesį į kiekvieną kitą teksto vienetą (priešingai nei atveju, kai dekodavimo žingsniai sutelkia dėmesį į kodavimo žingsnius), tokios architektūros paprastai apibūdinamos kaip saviraiškės dėmesio sutelkimo transformacijos (angl. self-attention) modeliai (Lin ir kt., 2017; Vaswani ir kt., 2017), o kitur apibūdinamos kaip vidinio dėmesio (angl. intra-attention) modelis (Cheng ir kt., 2016; Parikh ir kt., 2016; Paulus ir kt., 2017). Šiame skyrelyje aptarsime sekų kodavimą naudojant saviraiškę dėmesio sutelkimo transformaciją, įskaitant papildomos informacijos apie sekos tvarką naudojimą.
from d2l import mxnet as d2l
import math
from mxnet import autograd, np, npx
from mxnet.gluon import nn
npx.set_np()
from d2l import torch as d2l
import math
import torch
from torch import nn
from d2l import tensorflow as d2l
import numpy as np
import tensorflow as tf
from d2l import jax as d2l
from flax import linen as nn
from jax import numpy as jnp
import jax
11.6.1 Saviraiška dėmesio sutelkimo transformacija
Duotai įvesties teksto vienetų sekai , kur bet kuris (), jos saviraiška dėmesio sutelkimo transformacija išveda to paties ilgio seką , kur
pagal dėmesio sutelkimo apibrėžimą (11.1.1). Naudodamas lygiagrečią dėmesio sutelkimo transformaciją, toliau pateiktas kodo fragmentas apskaičiuoja tenzoriaus saviraišką dėmesio sutelkimo transformaciją, kurio forma (rinkinio dydis, laiko žingsnių skaičius arba sekos ilgis teksto vienetais, ). Išvesties tenzorius turi tą pačią formą.
num_hiddens, num_heads = 100, 5
attention = d2l.MultiHeadAttention(num_hiddens, num_heads, 0.5)
batch_size, num_queries, valid_lens = 2, 4, d2l.tensor([3, 2])
X = d2l.ones((batch_size, num_queries, num_hiddens))
d2l.check_shape(attention(X, X, X, valid_lens),
(batch_size, num_queries, num_hiddens))
num_hiddens, num_heads = 100, 5
attention = d2l.MultiHeadAttention(num_hiddens, num_heads, 0.5)
attention.initialize()
num_hiddens, num_heads = 100, 5
attention = d2l.MultiHeadAttention(num_hiddens, num_heads, 0.5)
num_hiddens, num_heads = 100, 5
attention = d2l.MultiHeadAttention(num_hiddens, num_hiddens, num_hiddens,
num_hiddens, num_heads, 0.5)
batch_size, num_queries, valid_lens = 2, 4, d2l.tensor([3, 2])
X = d2l.ones((batch_size, num_queries, num_hiddens))
d2l.check_shape(attention(X, X, X, valid_lens),
(batch_size, num_queries, num_hiddens))
batch_size, num_queries, valid_lens = 2, 4, tf.constant([3, 2])
X = tf.ones((batch_size, num_queries, num_hiddens))
d2l.check_shape(attention(X, X, X, valid_lens, training=False),
(batch_size, num_queries, num_hiddens))
batch_size, num_queries, valid_lens = 2, 4, d2l.tensor([3, 2])
X = d2l.ones((batch_size, num_queries, num_hiddens))
d2l.check_shape(attention.init_with_output(d2l.get_key(), X, X, X, valid_lens,
training=False)[0][0],
(batch_size, num_queries, num_hiddens))
11.6.2 CNN, RNN ir saviraiškės dėmesio sutelkimo transformacijos palyginimas
Palyginkime architektūras, skirtas teksto vienetų sekai atvaizduoti į kitą tokio paties ilgio seką, kur kiekvienas įvesties ar išvesties teksto vienetas vaizduojamas matmenų vektoriumi. Konkrečiai, nagrinėsime CNN, RNN ir saviraišką dėmesio sutelkimo transformaciją. Palyginsime jų skaičiavimo sudėtingumą, nuoseklias operacijas ir didžiausius kelio ilgius. Atkreipkite dėmesį, kad nuoseklios operacijos neleidžia lygiagretinti skaičiavimų, o trumpesnis kelias tarp bet kurio sekos pozicijų derinio palengvina ilgo nuotolio priklausomybių sekoje mokymąsi (Hochreiter ir kt., 2001).
11.6.1 pav. CNN (užpildymo teksto vienetai praleisti), RNN ir saviraiškės dėmesio sutelkimo transformacijos architektūrų palyginimas.
Bet kurią teksto seką laikykime „vienmačiu vaizdu“. Panašiai vienmačiai CNN gali apdoroti lokalius požymius, tokius kaip -gramos tekste. Duotai ilgio sekai apsvarstykime konvoliucinį sluoksnį, kurio branduolio dydis yra , o įvesties ir išvesties kanalų skaičius abu yra . Konvoliucinio sluoksnio skaičiavimo sudėtingumas yra . Kaip rodo 11.6.1 pav., CNN yra hierarchiniai, todėl yra nuoseklių operacijų, o didžiausias kelio ilgis yra . Pavyzdžiui, 11.6.1 pav. ir patenka į dviejų sluoksnių CNN su 3 dydžio branduoliu lokalią matymo sritį.
Atnaujinant RNN paslėptąją būseną, svorių matricos ir matmenų paslėptosios būsenos daugyba turi skaičiavimo sudėtingumą. Kadangi sekos ilgis yra , rekurentinio sluoksnio skaičiavimo sudėtingumas yra . Pagal 11.6.1 pav. yra nuoseklių operacijų, kurių negalima lygiagretinti, o didžiausias kelio ilgis taip pat yra .
Saviraiškėje dėmesio sutelkimo transformacijoje užklausos, raktai ir reikšmės yra visos matricos. Apsvarstykime mastelio keičiamos skaliarinės sandaugos dėmesį (11.3.4), kur matrica dauginama iš matricos, o tada gauta išvesties matrica dauginama iš matricos. Dėl to saviraiškė dėmesio sutelkimo transformacija turi skaičiavimo sudėtingumą. Kaip matome iš 11.6.1 pav., kiekvienas teksto vienetas tiesiogiai sujungtas su bet kuriuo kitu teksto vienetu per saviraišką dėmesio sutelkimo transformaciją. Todėl skaičiavimas gali būti lygiagretus su nuoseklių operacijų, o didžiausias kelio ilgis taip pat yra .
Apskritai ir CNN, ir saviraiškė dėmesio sutelkimo transformacija leidžia lygiagretų skaičiavimą, o saviraiškė dėmesio sutelkimo transformacija turi trumpiausią didžiausią kelio ilgį. Tačiau kvadratinis skaičiavimo sudėtingumas sekos ilgio atžvilgiu daro saviraišką dėmesio sutelkimo transformaciją nepriimtinai lėtą labai ilgoms sekoms.
11.6.3 Pozicinis kodavimas
Skirtingai nei RNN, rekurentiškai apdorojantys sekos teksto vienetus po vieną, saviraiškė dėmesio sutelkimo transformacija atsisako nuosekliųjų operacijų lygiagrečiojo skaičiavimo naudai. Atkreipkite dėmesį, kad saviraiškė dėmesio sutelkimo transformacija pati savaime nesaugo sekos tvarkos. Ką daryti, jei tikrai svarbu, kad modelis žinotų, kokia tvarka atėjo įvesties seka?
Dominuojantis metodas informacijai apie teksto vienetų tvarką išsaugoti yra tai pateikti modeliui kaip papildomą įvestį, susietą su kiekvienu teksto vienetu. Šios įvestys vadinamos poziciniais kodavimais, ir jie gali būti arba išmokti, arba nustatyti a priori. Dabar aprašome paprastą fiksuotų pozicinių kodavimų schemą, pagrįstą sinuso ir kosinuso funkcijomis (Vaswani ir kt., 2017).
Tarkime, kad įvesties vaizdas turi matmenų įterpinius sekos teksto vienetų. Pozicinis kodavimas išveda , naudodamas tos pačios formos pozicinių įterpinių matricą , kurios elementas -ojoje eilutėje ir -ajame arba -ajame stulpelyje yra
Iš pirmo žvilgsnio
šis trigonometrinių funkcijų
dizainas atrodo keistas.
Prieš paaiškindami šį dizainą,
pirmiausia įgyvendinkime jį toliau pateiktoje klasėje PositionalEncoding.
class PositionalEncoding(nn.Block): #@save
"""Positional encoding."""
def __init__(self, num_hiddens, dropout, max_len=1000):
super().__init__()
self.dropout = nn.Dropout(dropout)
# Create a long enough P
self.P = d2l.zeros((1, max_len, num_hiddens))
X = d2l.arange(max_len).reshape(-1, 1) / np.power(
10000, np.arange(0, num_hiddens, 2) / num_hiddens)
self.P[:, :, 0::2] = np.sin(X)
self.P[:, :, 1::2] = np.cos(X)
def forward(self, X):
X = X + self.P[:, :X.shape[1], :].as_in_ctx(X.ctx)
return self.dropout(X)
class PositionalEncoding(nn.Module): #@save
"""Positional encoding."""
def __init__(self, num_hiddens, dropout, max_len=1000):
super().__init__()
self.dropout = nn.Dropout(dropout)
# Create a long enough P
self.P = d2l.zeros((1, max_len, num_hiddens))
X = d2l.arange(max_len, dtype=torch.float32).reshape(
-1, 1) / torch.pow(10000, torch.arange(
0, num_hiddens, 2, dtype=torch.float32) / num_hiddens)
self.P[:, :, 0::2] = torch.sin(X)
self.P[:, :, 1::2] = torch.cos(X)
def forward(self, X):
X = X + self.P[:, :X.shape[1], :].to(X.device)
return self.dropout(X)
class PositionalEncoding(tf.keras.layers.Layer): #@save
"""Positional encoding."""
def __init__(self, num_hiddens, dropout, max_len=1000):
super().__init__()
self.dropout = tf.keras.layers.Dropout(dropout)
# Create a long enough P
self.P = np.zeros((1, max_len, num_hiddens))
X = np.arange(max_len, dtype=np.float32).reshape(
-1,1)/np.power(10000, np.arange(
0, num_hiddens, 2, dtype=np.float32) / num_hiddens)
self.P[:, :, 0::2] = np.sin(X)
self.P[:, :, 1::2] = np.cos(X)
def call(self, X, **kwargs):
X = X + self.P[:, :X.shape[1], :]
return self.dropout(X, **kwargs)
class PositionalEncoding(nn.Module): #@save
"""Positional encoding."""
num_hiddens: int
dropout: float
max_len: int = 1000
def setup(self):
# Create a long enough P
self.P = d2l.zeros((1, self.max_len, self.num_hiddens))
X = d2l.arange(self.max_len, dtype=jnp.float32).reshape(
-1, 1) / jnp.power(10000, jnp.arange(
0, self.num_hiddens, 2, dtype=jnp.float32) / self.num_hiddens)
self.P = self.P.at[:, :, 0::2].set(jnp.sin(X))
self.P = self.P.at[:, :, 1::2].set(jnp.cos(X))
@nn.compact
def __call__(self, X, training=False):
# Flax sow API is used to capture intermediate variables
self.sow('intermediates', 'P', self.P)
X = X + self.P[:, :X.shape[1], :]
return nn.Dropout(self.dropout)(X, deterministic=not training)
Pozicinių įterpinių matricoje eilutės atitinka pozicijas sekoje, o stulpeliai vaizduoja skirtingas pozicinio kodavimo dimensijas. Toliau pateiktame pavyzdyje matome, kad pozicinių įterpinių matricos -asis ir -asis stulpeliai turi aukštesnį dažnį nei -asis ir -asis stulpeliai. Poslinkis tarp -ojo ir -ojo (taip pat -ojo ir -ojo) stulpelių atsiranda dėl sinuso ir kosinuso funkcijų kaitaliojimosi.
encoding_dim, num_steps = 32, 60
pos_encoding = PositionalEncoding(encoding_dim, 0)
pos_encoding.initialize()
X = pos_encoding(np.zeros((1, num_steps, encoding_dim)))
P = pos_encoding.P[:, :X.shape[1], :]
d2l.plot(d2l.arange(num_steps), P[0, :, 6:10].T, xlabel='Row (position)',
figsize=(6, 2.5), legend=["Col %d" % d for d in d2l.arange(6, 10)])
encoding_dim, num_steps = 32, 60
pos_encoding = PositionalEncoding(encoding_dim, 0)
X = pos_encoding(d2l.zeros((1, num_steps, encoding_dim)))
P = pos_encoding.P[:, :X.shape[1], :]
d2l.plot(d2l.arange(num_steps), P[0, :, 6:10].T, xlabel='Row (position)',
figsize=(6, 2.5), legend=["Col %d" % d for d in d2l.arange(6, 10)])
encoding_dim, num_steps = 32, 60
pos_encoding = PositionalEncoding(encoding_dim, 0)
X = pos_encoding(tf.zeros((1, num_steps, encoding_dim)), training=False)
P = pos_encoding.P[:, :X.shape[1], :]
d2l.plot(np.arange(num_steps), P[0, :, 6:10].T, xlabel='Row (position)',
figsize=(6, 2.5), legend=["Col %d" % d for d in np.arange(6, 10)])
encoding_dim, num_steps = 32, 60
pos_encoding = PositionalEncoding(encoding_dim, 0)
params = pos_encoding.init(d2l.get_key(), d2l.zeros((1, num_steps, encoding_dim)))
X, inter_vars = pos_encoding.apply(params, d2l.zeros((1, num_steps, encoding_dim)),
mutable='intermediates')
P = inter_vars['intermediates']['P'][0] # retrieve intermediate value P
P = P[:, :X.shape[1], :]
d2l.plot(d2l.arange(num_steps), P[0, :, 6:10].T, xlabel='Row (position)',
figsize=(6, 2.5), legend=["Col %d" % d for d in d2l.arange(6, 10)])
11.6.3.1 Absoliuti pozicinė informacija
Kad pamatytume, kaip monotoniškai mažėjantis dažnis išilgai kodavimo dimensijos susijęs su absoliučia pozicine informacija, išspausdinkime dvejetainius vaizdus skaičių . Kaip matome, žemiausias bitas, antras žemiausias bitas ir trečias žemiausias bitas keičiasi atitinkamai kas skaičių, kas du skaičius ir kas keturis skaičius.
for i in range(8):
print(f'{i} in binary is {i:>03b}')
Dvejetainiuose vaizduose aukštesnis bitas turi žemesnį dažnį nei žemesnis bitas. Panašiai, kaip parodyta toliau pateiktame šilumos žemėlapyje, pozicinis kodavimas mažina dažnius išilgai kodavimo dimensijos, naudodamas trigonometrines funkcijas. Kadangi išvestys yra slankiojo kablelio skaičiai, tokie tolydūs vaizdai yra efektyvesni vietos požiūriu nei dvejetainiai vaizdai.
P = np.expand_dims(np.expand_dims(P[0, :, :], 0), 0)
d2l.show_heatmaps(P, xlabel='Column (encoding dimension)',
ylabel='Row (position)', figsize=(3.5, 4), cmap='Blues')
P = P[0, :, :].unsqueeze(0).unsqueeze(0)
d2l.show_heatmaps(P, xlabel='Column (encoding dimension)',
ylabel='Row (position)', figsize=(3.5, 4), cmap='Blues')
P = tf.expand_dims(tf.expand_dims(P[0, :, :], axis=0), axis=0)
d2l.show_heatmaps(P, xlabel='Column (encoding dimension)',
ylabel='Row (position)', figsize=(3.5, 4), cmap='Blues')
P = jnp.expand_dims(jnp.expand_dims(P[0, :, :], axis=0), axis=0)
d2l.show_heatmaps(P, xlabel='Column (encoding dimension)',
ylabel='Row (position)', figsize=(3.5, 4), cmap='Blues')
11.6.3.2 Santykinė pozicinė informacija
Be absoliučios pozicinės informacijos užfiksavimo, aukščiau pateiktas pozicinis kodavimas taip pat leidžia modeliui lengvai išmokti sutelkti dėmesį pagal santykines pozicijas. Taip yra todėl, kad bet kuriam fiksuotam pozicijos poslinkiui pozicinis kodavimas pozicijoje gali būti pavaizduotas tiesine to paties kodavimo pozicijoje projekcija.
Šią projekciją galima paaiškinti matematiškai. Pažymėjus , bet kuri pora (11.6.1) gali būti tiesiškai suprojektuota į bet kuriam fiksuotam poslinkiui :
kur projekcijos matrica nepriklauso nuo jokio pozicijos indekso .
11.6.4 Santrauka
Saviraiškėje dėmesio sutelkimo transformacijoje užklausos, raktai ir reikšmės visos ateina iš to paties šaltinio. Ir CNN, ir saviraiškė dėmesio sutelkimo transformacija leidžia lygiagretų skaičiavimą, o saviraiškė dėmesio sutelkimo transformacija turi trumpiausią didžiausią kelio ilgį. Tačiau kvadratinis skaičiavimo sudėtingumas sekos ilgio atžvilgiu daro saviraišką dėmesio sutelkimo transformaciją nepriimtinai lėtą labai ilgoms sekoms. Kad naudotume sekos tvarkos informaciją, galime įterpti absoliučią arba santykinę pozicinę informaciją, pridėdami pozicinį kodavimą prie įvesties vaizdų.
11.6.5 Pratimai
- Tarkime, kad projektuojame gilią architektūrą sekai pavaizduoti, sukraudami saviraiškės dėmesio sutelkimo transformacijos sluoksnius su poziciniu kodavimu. Kokios gali būti galimos problemos?
- Ar galite suprojektuoti išmokstamą pozicinio kodavimo metodą?
- Ar galime priskirti skirtingus išmoktus įterpinius pagal skirtingus poslinkius tarp užklausų ir raktų, lyginamų saviraiškėje dėmesio sutelkimo transformacijoje? Užuomina: galite remtis santykinių pozicijų įterpiniais (Shaw ir kt., 2018; Huang ir kt., 2018).
