11.2 Dėmesio sutelkimas pagal panašumą
Dabar, kai pristatėme pagrindinius dėmesio sutelkimo mechanizmo komponentus, panaudokime juos gana klasikinėje nuostatoje, būtent regresijai ir klasifikavimui per branduolinį tankio vertinimą (Nadaraya, 1964; Watson, 1964). Ši atkarpa tiesiog suteikia papildomo fono: ji visiškai neprivaloma ir, jei reikia, gali būti praleista. Iš esmės Nadaraya–Watsono įverčiai remiasi tam tikru panašumo branduoliu , susiejančiu užklausas su raktais . Keli įprasti branduoliai yra
Galėtume pasirinkti dar daug kitų variantų. Plačiau apie tai žr. Vikipedijos straipsnyje ir kaip branduolių pasirinkimas susijęs su branduoliniu tankio vertinimu, kartais dar vadinamu Parzeno langais (Parzen, 1957). Visi branduoliai yra euristiniai ir juos galima derinti. Pavyzdžiui, galime keisti plotį ne tik globaliai, bet net kiekvienai koordinatei atskirai. Bet kuriuo atveju visi jie veda prie šios lygties, tinkamos ir regresijai, ir klasifikavimui:
(Skaliarinės) regresijos atveju su stebėjimais atitinkamai požymiams ir žymenims, yra skaliarai, yra vektoriai, o užklausa žymi naują vietą, kurioje reikia įvertinti . (Daugelio klasių) klasifikavimo atveju naudojame vienkartinį kodavimą, kad gautume . Viena iš patogių šio įverčio savybių yra ta, kad jam nereikia apmokymo. Dar daugiau, jei tinkamai siaurintume branduolį didėjant duomenų kiekiui, metodas yra suderintas (angl. consistent) (Mack ir Silverman, 1982), t. y. jis konverguos į tam tikrą statistiškai optimalų sprendinį. Pradėkime nuo kai kurių branduolių tyrimo.
from d2l import mxnet as d2l
from mxnet import autograd, gluon, np, npx
from mxnet.gluon import nn
npx.set_np()
d2l.use_svg_display()
from d2l import torch as d2l
import torch
from torch import nn
from torch.nn import functional as F
import numpy as np
d2l.use_svg_display()
from d2l import tensorflow as d2l
import tensorflow as tf
import numpy as np
d2l.use_svg_display()
from d2l import jax as d2l
import jax
from jax import numpy as jnp
from flax import linen as nn
11.2.1 Branduoliai ir duomenys
Visi šiame skyrelyje apibrėžti branduoliai yra postūmio ir sukimo invariantiški; t. y. jei postumiame ir pasukame ir vienodai, reikšmė nepasikeičia. Todėl paprastumo dėlei pasirenkame skaliarinius argumentus ir raktą laikome atskaitos tašku. Tai duoda:
# Define some kernels
def gaussian(x):
return d2l.exp(-x**2 / 2)
def boxcar(x):
return d2l.abs(x) < 1.0
def constant(x):
return 1.0 + 0 * x
if tab.selected('pytorch'):
def epanechikov(x):
return torch.max(1 - d2l.abs(x), torch.zeros_like(x))
if tab.selected('mxnet'):
def epanechikov(x):
return np.maximum(1 - d2l.abs(x), 0)
if tab.selected('tensorflow'):
def epanechikov(x):
return tf.maximum(1 - d2l.abs(x), 0)
if tab.selected('jax'):
def epanechikov(x):
return jnp.maximum(1 - d2l.abs(x), 0)
fig, axes = d2l.plt.subplots(1, 4, sharey=True, figsize=(12, 3))
kernels = (gaussian, boxcar, constant, epanechikov)
names = ('Gaussian', 'Boxcar', 'Constant', 'Epanechikov')
x = d2l.arange(-2.5, 2.5, 0.1)
for kernel, name, ax in zip(kernels, names, axes):
if tab.selected('pytorch', 'mxnet', 'tensorflow'):
ax.plot(d2l.numpy(x), d2l.numpy(kernel(x)))
if tab.selected('jax'):
ax.plot(x, kernel(x))
ax.set_xlabel(name)
d2l.plt.show()
Skirtingi branduoliai atitinka skirtingas ribos (siekio) ir glotnumo sampratas. Pavyzdžiui, stačiakampis branduolys (angl. boxcar) atsižvelgia tik į stebėjimus, esančius ne toliau kaip atstumu (ar kitaip apibrėžtu hiperparametru), ir tai daro nesirinkdamas.
Kad pamatytume Nadaraya–Watsono vertinimą veikiant, apibrėžkime kai kuriuos apmokymo duomenis. Toliau naudojame priklausomybę
kur imamas iš normaliojo skirstinio su nuliniu vidurkiu ir vienetine dispersija. Ištraukiame 40 apmokymo pavyzdžių.
def f(x):
return 2 * d2l.sin(x) + x
n = 40
if tab.selected('pytorch'):
x_train, _ = torch.sort(d2l.rand(n) * 5)
y_train = f(x_train) + d2l.randn(n)
if tab.selected('mxnet'):
x_train = np.sort(d2l.rand(n) * 5, axis=None)
y_train = f(x_train) + d2l.randn(n)
if tab.selected('tensorflow'):
x_train = tf.sort(d2l.rand((n,1)) * 5, 0)
y_train = f(x_train) + d2l.normal((n, 1))
if tab.selected('jax'):
x_train = jnp.sort(jax.random.uniform(d2l.get_key(), (n,)) * 5)
y_train = f(x_train) + jax.random.normal(d2l.get_key(), (n,))
x_val = d2l.arange(0, 5, 0.1)
y_val = f(x_val)
11.2.2 Dėmesio sutelkimas per Nadaraya–Watsono regresiją
Turėdami duomenis ir branduolius, mums reikia tik funkcijos, apskaičiuojančios branduolinės regresijos įverčius. Atkreipkite dėmesį, kad taip pat norime gauti santykinius branduolio svorius, kad galėtume atlikti nedidelę diagnostiką. Todėl pirmiausia apskaičiuojame branduolį tarp visų apmokymo požymių (kovariančių) x_train ir visų validavimo požymių x_val. Gauname matricą, kurią vėliau normalizuojame. Padauginę ją iš apmokymo žymenų y_train, gauname įverčius.
Prisiminkite dėmesio sutelkimą (11.1.1). Tegul kiekvienas validavimo požymis yra užklausa, o kiekviena apmokymo požymio ir žymens pora yra rakto ir reikšmės pora. Todėl normalizuoti santykiniai branduolio svoriai (toliau attention_w) yra dėmesio svoriai.
def nadaraya_watson(x_train, y_train, x_val, kernel):
dists = d2l.reshape(x_train, (-1, 1)) - d2l.reshape(x_val, (1, -1))
# Each column/row corresponds to each query/key
k = d2l.astype(kernel(dists), d2l.float32)
# Normalization over keys for each query
attention_w = k / d2l.reduce_sum(k, 0)
if tab.selected('pytorch'):
y_hat = y_train@attention_w
if tab.selected('mxnet'):
y_hat = np.dot(y_train, attention_w)
if tab.selected('tensorflow'):
y_hat = d2l.transpose(d2l.transpose(y_train)@attention_w)
if tab.selected('jax'):
y_hat = y_train@attention_w
return y_hat, attention_w
Pažiūrėkime, kokius įverčius sukuria skirtingi branduoliai.
def plot(x_train, y_train, x_val, y_val, kernels, names, attention=False):
fig, axes = d2l.plt.subplots(1, 4, sharey=True, figsize=(12, 3))
for kernel, name, ax in zip(kernels, names, axes):
y_hat, attention_w = nadaraya_watson(x_train, y_train, x_val, kernel)
if attention:
if tab.selected('pytorch', 'mxnet', 'tensorflow'):
pcm = ax.imshow(d2l.numpy(attention_w), cmap='Reds')
if tab.selected('jax'):
pcm = ax.imshow(attention_w, cmap='Reds')
else:
ax.plot(x_val, y_hat)
ax.plot(x_val, y_val, 'm--')
ax.plot(x_train, y_train, 'o', alpha=0.5);
ax.set_xlabel(name)
if not attention:
ax.legend(['y_hat', 'y'])
if attention:
fig.colorbar(pcm, ax=axes, shrink=0.7)
plot(x_train, y_train, x_val, y_val, kernels, names)
Pirmiausia krenta į akis tai, kad visi trys netrivialūs branduoliai (Gauso, stačiakampis ir Epanechnikovo) sukuria gana tinkamus įverčius, nelabai nutolusius nuo tikrosios funkcijos. Tik pastovusis branduolys, vedantis prie trivialaus įverčio , duoda gana nerealų rezultatą. Atidžiau patikrinkime dėmesio svorius:
plot(x_train, y_train, x_val, y_val, kernels, names, attention=True)
Vizualizacija aiškiai parodo, kodėl Gauso, stačiakampio ir Epanechnikovo įverčiai labai panašūs: juk jie gauti iš labai panašių dėmesio svorių, nepaisant skirtingos branduolio funkcinės formos. Tai kelia klausimą, ar taip yra visada.
11.2.3 Dėmesio sutelkimo pritaikymas
Gauso branduolį galėtume pakeisti kitokio pločio branduoliu. T. y. galėtume naudoti , kur nustato branduolio plotį. Pažiūrėkime, ar tai paveikia rezultatus.
sigmas = (0.1, 0.2, 0.5, 1)
names = ['Sigma ' + str(sigma) for sigma in sigmas]
def gaussian_with_width(sigma):
return (lambda x: d2l.exp(-x**2 / (2*sigma**2)))
kernels = [gaussian_with_width(sigma) for sigma in sigmas]
plot(x_train, y_train, x_val, y_val, kernels, names)
Akivaizdu, kad kuo siauresnis branduolys, tuo mažiau glotnus įvertis. Tuo pat metu jis geriau prisitaiko prie lokalių svyravimų. Pažiūrėkime į atitinkamus dėmesio svorius.
plot(x_train, y_train, x_val, y_val, kernels, names, attention=True)
Kaip ir galima tikėtis, kuo siauresnis branduolys, tuo siauresnis didelių dėmesio svorių diapazonas. Taip pat akivaizdu, kad to paties pločio pasirinkimas gali būti neidealus. Iš tiesų (B.~W. Silverman, ) pasiūlė euristiką, priklausančią nuo lokalaus tankio. Pasiūlyta dar daug tokių „gudrybių“. Pavyzdžiui, (Norelli ir kt., 2022) panaudojo panašią artimiausių kaimynų interpoliacijos techniką tarpmodaliniams vaizdų ir teksto vaizdams projektuoti.
Įžvalgus skaitytojas gali stebėtis, kodėl pateikiame šią gilią analizę metodui, kuriam daugiau nei pusė amžiaus. Pirma, tai vienas ankstyviausių šiuolaikinių dėmesio sutelkimo mechanizmų pirmtakų. Antra, jis puikiai tinka vizualizavimui. Trečia, ir tiek pat svarbu, jis parodo rankomis kurtų dėmesio sutelkimo mechanizmų ribas. Daug geresnė strategija yra išmokti mechanizmą, mokantis užklausų ir raktų vaizdus. Būtent tuo ir užsiimsime kituose skyreliuose.
11.2.4 Santrauka
Nadaraya–Watsono branduolinė regresija yra ankstyvas dabartinių dėmesio sutelkimo mechanizmų pirmtakas. Ją galima naudoti tiesiogiai, beveik be apmokymo ar derinimo, tiek klasifikavimui, tiek regresijai. Dėmesio svoris priskiriamas pagal užklausos ir rakto panašumą (arba atstumą) ir pagal tai, kiek panašių stebėjimų yra turima.
11.2.5 Pratimai
- Parzeno langų tankio įverčiai yra . Įrodykite, kad dvejetainiam klasifikavimui funkcija , gauta Parzeno langais, yra ekvivalenti Nadaraya–Watsono klasifikavimui.
- Įgyvendinkite stochastinį gradientinį nusileidimą, kad išmoktumėte gerą branduolių pločių reikšmę Nadaraya–Watsono regresijoje.
- Kas nutinka, jei tiesiog naudojate aukščiau pateiktus įverčius, kad tiesiogiai minimizuotumėte ? Užuomina: yra viena iš narių, naudojamų apskaičiuoti.
- Pašalinkite iš įverčio ir optimizuokite branduolių pločius. Ar vis dar stebite persimokymą?
- Tarkime, kad visi guli ant vienetinės sferos, t. y. visi tenkina . Ar galite supaprastinti narį eksponentėje? Užuomina: vėliau pamatysime, kad tai labai glaudžiai susiję su skaliarinės sandaugos dėmesio sutelkimu.
- Prisiminkite, kad (Mack ir Silverman, 1982) įrodė, jog Nadaraya–Watsono vertinimas yra suderintas. Kaip greitai turėtumėte mažinti dėmesio sutelkimo mechanizmo mastelį, gaunant daugiau duomenų? Pateikite šiek tiek intuicijos savo atsakymui. Ar tai priklauso nuo duomenų dimensijos? Kaip?
