22.4 Daugelio kintamųjų matematinė analizė

Dabar, kai gana gerai suprantame vieno kintamojo funkcijos išvestines, grįžkime prie pradinio klausimo, kuriame nagrinėjome nuostolių funkciją, priklausančią nuo galbūt milijardų parametrų.

22.4.1 Aukštesnių dimensijų diferencijavimas

22.3 skyrelis mums sako, kad jei pakeisime vieną iš šių milijardų parametrų, visų kitų nekeisdami, žinome, kas nutiks! Tai yra ne kas kita, kaip vieno kintamojo funkcija, todėl galime užrašyti

L(w1+ϵ1,w2,…,wN)≈L(w1,w2,…,wN)+ϵ1ddw1L(w1,w2,…,wN).(22.4.1)L(w_1+\epsilon_1, w_2, \ldots, w_N) \approx L(w_1, w_2, \ldots, w_N) + \epsilon_1 \frac{d}{dw_1} L(w_1, w_2, \ldots, w_N). \tag{22.4.1}

Išvestinę pagal vieną kintamąjį, kitus kintamuosius fiksuodami, vadinsime daline išvestine, o išvestinei (22.4.1) žymėti naudosime žymenį ∂∂w1\frac{\partial}{\partial w_1}.

Dabar paimkime tai ir šiek tiek pakeiskime w2w_2 į w2+ϵ2w_2 + \epsilon_2:

L(w1+ϵ1,w2+ϵ2,…,wN)≈L(w1,w2+ϵ2,…,wN)+ϵ1∂∂w1L(w1,w2+ϵ2,…,wN+ϵN)≈L(w1,w2,…,wN)+ϵ2∂∂w2L(w1,w2,…,wN)+ϵ1∂∂w1L(w1,w2,…,wN)+ϵ1ϵ2∂∂w2∂∂w1L(w1,w2,…,wN)≈L(w1,w2,…,wN)+ϵ2∂∂w2L(w1,w2,…,wN)+ϵ1∂∂w1L(w1,w2,…,wN).\begin{aligned} L(w_1+\epsilon_1, w_2+\epsilon_2, \ldots, w_N) & \approx L(w_1, w_2+\epsilon_2, \ldots, w_N) + \epsilon_1 \frac{\partial}{\partial w_1} L(w_1, w_2+\epsilon_2, \ldots, w_N+\epsilon_N) \\ & \approx L(w_1, w_2, \ldots, w_N) \\ & \quad + \epsilon_2\frac{\partial}{\partial w_2} L(w_1, w_2, \ldots, w_N) \\ & \quad + \epsilon_1 \frac{\partial}{\partial w_1} L(w_1, w_2, \ldots, w_N) \\ & \quad + \epsilon_1\epsilon_2\frac{\partial}{\partial w_2}\frac{\partial}{\partial w_1} L(w_1, w_2, \ldots, w_N) \\ & \approx L(w_1, w_2, \ldots, w_N) \\ & \quad + \epsilon_2\frac{\partial}{\partial w_2} L(w_1, w_2, \ldots, w_N) \\ & \quad + \epsilon_1 \frac{\partial}{\partial w_1} L(w_1, w_2, \ldots, w_N). \end{aligned}

Vėl pasinaudojome idėja, kad ϵ1ϵ2\epsilon_1\epsilon_2 yra aukštesnės eilės narys, kurį galime atmesti taip pat, kaip atmetėme ϵ2\epsilon^{2} ankstesniame skyriuje, kartu su tuo, ką matėme (22.4.1). Tęsdami šitaip, galime užrašyti, kad

L(w1+ϵ1,w2+ϵ2,…,wN+ϵN)≈L(w1,w2,…,wN)+∑iϵi∂∂wiL(w1,w2,…,wN).L(w_1+\epsilon_1, w_2+\epsilon_2, \ldots, w_N+\epsilon_N) \approx L(w_1, w_2, \ldots, w_N) + \sum_i \epsilon_i \frac{\partial}{\partial w_i} L(w_1, w_2, \ldots, w_N).

Tai gali atrodyti kaip netvarka, bet galime tai padaryti pažįstamesnį pastebėję, kad dešinėje esanti suma atrodo lygiai kaip skaliarinė sandauga, taigi jei pažymėsime

ϵ=[ϵ1,…,ϵN]⊤  and  ∇xL=[∂L∂x1,…,∂L∂xN]⊤,\boldsymbol{\epsilon} = [\epsilon_1, \ldots, \epsilon_N]^\top \; \textrm{and} \; \nabla_{\mathbf{x}} L = \left[\frac{\partial L}{\partial x_1}, \ldots, \frac{\partial L}{\partial x_N}\right]^\top,

tada

L(w+ϵ)≈L(w)+ϵ⋅∇wL(w).(22.4.2)L(\mathbf{w} + \boldsymbol{\epsilon}) \approx L(\mathbf{w}) + \boldsymbol{\epsilon}\cdot \nabla_{\mathbf{w}} L(\mathbf{w}). \tag{22.4.2}

Vektorių ∇wL\nabla_{\mathbf{w}} L vadinsime LL gradientu.

Lygtį (22.4.2) verta akimirką apmąstyti. Ji turi būtent tokį formatą, su kokiu susidūrėme vienoje dimensijoje, tik viską pavertėme vektoriais ir skaliarinėmis sandaugomis. Ji leidžia apytiksliai pasakyti, kaip pasikeis funkcija LL esant bet kokiam įvesties trikdžiui. Kaip pamatysime kitame skyrelyje, tai suteiks svarbią priemonę geometriškai suprasti, kaip galime mokytis naudodami gradiente esančią informaciją.

Bet pirmiausia pažiūrėkime šią aproksimaciją veikiant su pavyzdžiu. Tarkime, dirbame su funkcija

f(x,y)=log⁡(ex+ey) with gradient ∇f(x,y)=[exex+ey,eyex+ey].f(x, y) = \log(e^x + e^y) \textrm{ with gradient } \nabla f (x, y) = \left[\frac{e^x}{e^x+e^y}, \frac{e^y}{e^x+e^y}\right].

Jei pažiūrėsime į tašką, pavyzdžiui, (0,log⁡(2))(0, \log(2)), matome, kad

f(x,y)=log⁡(3) with gradient ∇f(x,y)=[13,23].f(x, y) = \log(3) \textrm{ with gradient } \nabla f (x, y) = \left[\frac{1}{3}, \frac{2}{3}\right].

Taigi, jei norime aproksimuoti ff taške (ϵ1,log⁡(2)+ϵ2)(\epsilon_1, \log(2) + \epsilon_2), matome, kad turėtume turėti tokį konkretų (22.4.2) atvejį:

f(ϵ1,log⁡(2)+ϵ2)≈log⁡(3)+13ϵ1+23ϵ2.f(\epsilon_1, \log(2) + \epsilon_2) \approx \log(3) + \frac{1}{3}\epsilon_1 + \frac{2}{3}\epsilon_2.

Tai galime patikrinti kode ir pamatyti, kiek gera aproksimacija.

%matplotlib inline
from d2l import mxnet as d2l
from IPython import display
from mpl_toolkits import mplot3d
from mxnet import autograd, np, npx
npx.set_np()

def f(x, y):
    return np.log(np.exp(x) + np.exp(y))
def grad_f(x, y):
    return np.array([np.exp(x) / (np.exp(x) + np.exp(y)),
                     np.exp(y) / (np.exp(x) + np.exp(y))])

epsilon = np.array([0.01, -0.03])
grad_approx = f(0, np.log(2)) + epsilon.dot(grad_f(0, np.log(2)))
true_value = f(0 + epsilon[0], np.log(2) + epsilon[1])
f'approximation: {grad_approx}, true Value: {true_value}'
%matplotlib inline
from d2l import torch as d2l
from IPython import display
from mpl_toolkits import mplot3d
import torch
import numpy as np

def f(x, y):
    return torch.log(torch.exp(x) + torch.exp(y))
def grad_f(x, y):
    return torch.tensor([torch.exp(x) / (torch.exp(x) + torch.exp(y)),
                     torch.exp(y) / (torch.exp(x) + torch.exp(y))])

epsilon = torch.tensor([0.01, -0.03])
grad_approx = f(torch.tensor([0.]), torch.log(
    torch.tensor([2.]))) + epsilon.dot(
    grad_f(torch.tensor([0.]), torch.log(torch.tensor(2.))))
true_value = f(torch.tensor([0.]) + epsilon[0], torch.log(
    torch.tensor([2.])) + epsilon[1])
f'approximation: {grad_approx}, true Value: {true_value}'
%matplotlib inline
from d2l import tensorflow as d2l
from IPython import display
from mpl_toolkits import mplot3d
import tensorflow as tf
import numpy as np

def f(x, y):
    return tf.math.log(tf.exp(x) + tf.exp(y))
def grad_f(x, y):
    return tf.constant([(tf.exp(x) / (tf.exp(x) + tf.exp(y))).numpy(),
                        (tf.exp(y) / (tf.exp(x) + tf.exp(y))).numpy()])

epsilon = tf.constant([0.01, -0.03])
grad_approx = f(tf.constant([0.]), tf.math.log(
    tf.constant([2.]))) + tf.tensordot(
    epsilon, grad_f(tf.constant([0.]), tf.math.log(tf.constant(2.))), axes=1)
true_value = f(tf.constant([0.]) + epsilon[0], tf.math.log(
    tf.constant([2.])) + epsilon[1])
f'approximation: {grad_approx}, true Value: {true_value}'

22.4.2 Gradientų geometrija ir gradientinis nusileidimas

Dar kartą panagrinėkime išraišką iš (22.4.2):

L(w+ϵ)≈L(w)+ϵ⋅∇wL(w).L(\mathbf{w} + \boldsymbol{\epsilon}) \approx L(\mathbf{w}) + \boldsymbol{\epsilon}\cdot \nabla_{\mathbf{w}} L(\mathbf{w}).

Tarkime, noriu tai panaudoti, kad padėčiau minimizuoti savo nuostolius LL. Pirmiausia geometriškai suprasime gradientinio nusileidimo algoritmą, pirmą kartą aprašytą 2.5 skyrelis. Darysime štai ką:

  1. Pradėti nuo atsitiktinai pasirinktų pradinių parametrų w\mathbf{w}.
  2. Rasti kryptį v\mathbf{v}, kuria LL taške w\mathbf{w} mažėja greičiausiai.
  3. Žengti mažą žingsnį ta kryptimi: w→w+ϵv\mathbf{w} \rightarrow \mathbf{w} + \epsilon\mathbf{v}.
  4. Kartoti.

Vienintelis dalykas, kurį tiksliai nežinome, kaip padaryti, yra vektoriaus v\mathbf{v} apskaičiavimas antrame žingsnyje. Tokią kryptį vadinsime stačiausio nusileidimo kryptimi. Naudodami geometrinį skaliarinių sandaugų supratimą iš 22.1 skyrelis, matome, kad (22.4.2) galime perrašyti kaip

L(w+v)≈L(w)+v⋅∇wL(w)=L(w)+∥∇wL(w)∥cos⁡(θ).L(\mathbf{w} + \mathbf{v}) \approx L(\mathbf{w}) + \mathbf{v}\cdot \nabla_{\mathbf{w}} L(\mathbf{w}) = L(\mathbf{w}) + \|\nabla_{\mathbf{w}} L(\mathbf{w})\|\cos(\theta).

Atkreipkite dėmesį, kad patogumo dėlei kryptį paėmėme vienetinio ilgio ir θ\theta naudojome kampui tarp v\mathbf{v} ir ∇wL(w)\nabla_{\mathbf{w}} L(\mathbf{w}) žymėti. Jei norime rasti kryptį, kuria LL mažėja kuo greičiau, norime, kad ši išraiška būtų kuo neigiamesnė. Vienintelis būdas, kuriuo mūsų pasirinkta kryptis patenka į šią lygtį, yra per cos⁡(θ)\cos(\theta), todėl norime, kad šis kosinusas būtų kuo neigiamesnis. Dabar, prisiminę kosinuso formą, galime tai padaryti kuo neigiamesnį padarę cos⁡(θ)=−1\cos(\theta) = -1, arba, ekvivalenčiai, kad kampas tarp gradiento ir mūsų pasirinktos krypties būtų π\pi radianų, arba 180180 laipsnių. Vienintelis būdas tai pasiekti – judėti tiksliai priešinga kryptimi: pasirinkti v\mathbf{v}, nukreiptą tiksliai priešinga kryptimi nei ∇wL(w)\nabla_{\mathbf{w}} L(\mathbf{w})!

Tai mus atveda prie vienos svarbiausių mašininio mokymo matematinių sąvokų: stačiausio nusileidimo kryptis yra −∇wL(w)-\nabla_{\mathbf{w}}L(\mathbf{w}) kryptis. Taigi mūsų neformalų algoritmą galima perrašyti taip.

  1. Pradėti nuo atsitiktinai pasirinktų pradinių parametrų w\mathbf{w}.
  2. Apskaičiuoti ∇wL(w)\nabla_{\mathbf{w}} L(\mathbf{w}).
  3. Žengti mažą žingsnį priešinga tai krypčiai: w←w−ϵ∇wL(w)\mathbf{w} \leftarrow \mathbf{w} - \epsilon\nabla_{\mathbf{w}} L(\mathbf{w}).
  4. Kartoti.

Šį pagrindinį algoritmą daugelis tyrėjų įvairiais būdais modifikavo ir pritaikė, tačiau visų jų pagrindinė idėja išlieka ta pati. Naudoti gradientą, kad rastume kryptį, kuria nuostoliai mažėja kuo greičiau, ir atnaujinti parametrus žengiant žingsnį ta kryptimi.

22.4.3 Pastaba apie matematinį optimizavimą

Visoje šioje knygoje daugiausia dėmesio skiriame skaitiniams optimizavimo metodams dėl praktinės priežasties: visos giliojo mokymosi aplinkoje sutinkamos funkcijos yra per sudėtingos, kad jas būtų galima minimizuoti aiškiai.

Vis dėlto naudinga pagalvoti, ką aukščiau gautas geometrinis supratimas mums sako apie funkcijų optimizavimą tiesiogiai.

Tarkime, norime rasti reikšmę x0\mathbf{x}_0, kuri minimizuoja kokią nors funkciją L(x)L(\mathbf{x}). Tarkime, be to, kad kažkas mums pateikia reikšmę ir sako, kad tai reikšmė, minimizuojanti LL. Ar galime ką nors patikrinti, kad pamatytume, ar jų atsakymas bent jau įtikėtinas?

Vėl panagrinėkime (22.4.2):

L(x0+ϵ)≈L(x0)+ϵ⋅∇xL(x0).L(\mathbf{x}_0 + \boldsymbol{\epsilon}) \approx L(\mathbf{x}_0) + \boldsymbol{\epsilon}\cdot \nabla_{\mathbf{x}} L(\mathbf{x}_0).

Jei gradientas nėra nulinis, žinome, kad galime žengti žingsnį kryptimi −ϵ∇xL(x0)-\epsilon \nabla_{\mathbf{x}} L(\mathbf{x}_0) ir rasti mažesnę LL reikšmę. Taigi, jei tikrai esame minimume, to būti negali! Galime daryti išvadą, kad jei x0\mathbf{x}_0 yra minimumas, tai ∇xL(x0)=0\nabla_{\mathbf{x}} L(\mathbf{x}_0) = 0. Taškus, kuriuose ∇xL(x0)=0\nabla_{\mathbf{x}} L(\mathbf{x}_0) = 0, vadiname kritiniais taškais.

Tai gražu, nes kai kuriais retais atvejais galime aiškiai rasti visus taškus, kuriuose gradientas lygus nuliui, ir išsirinkti tą, kurio reikšmė mažiausia.

Konkrečiam pavyzdžiui panagrinėkime funkciją

f(x)=3x4−4x3−12x2.f(x) = 3x^4 - 4x^3 -12x^2.

Ši funkcija turi išvestinę

dfdx=12x3−12x2−24x=12x(x−2)(x+1).\frac{df}{dx} = 12x^3 - 12x^2 -24x = 12x(x-2)(x+1).

Vienintelės galimos minimumų vietos yra x=−1,0,2x = -1, 0, 2, kuriose funkcija įgyja atitinkamai reikšmes −5,0,−32-5,0, -32, todėl galime daryti išvadą, kad funkciją minimizuojame, kai x=2x = 2. Tai patvirtina greitas grafikas.

x = np.arange(-2, 3, 0.01)
f = (3 * x**4) - (4 * x**3) - (12 * x**2)

d2l.plot(x, f, 'x', 'f(x)')
x = torch.arange(-2, 3, 0.01)
f = (3 * x**4) - (4 * x**3) - (12 * x**2)

d2l.plot(x, f, 'x', 'f(x)')
x = tf.range(-2, 3, 0.01)
f = (3 * x**4) - (4 * x**3) - (12 * x**2)

d2l.plot(x, f, 'x', 'f(x)')

Tai pabrėžia svarbų faktą, kurį verta žinoti dirbant tiek teoriškai, tiek skaitiškai: vieninteliai galimi taškai, kuriuose galime minimizuoti (ar maksimizuoti) funkciją, turės nulinį gradientą, tačiau ne kiekvienas taškas su nuliniu gradientu yra tikrasis globalusis minimumas (ar maksimumas).

22.4.4 Daugelio kintamųjų grandinės taisyklė

Tarkime, turime keturių kintamųjų (w,x,yw, x, y ir zz) funkciją, kurią galime gauti sukomponavę daug narių:

f(u,v)=(u+v)2u(a,b)=(a+b)2,v(a,b)=(a−b)2,a(w,x,y,z)=(w+x+y+z)2,b(w,x,y,z)=(w+x−y−z)2.(22.4.3)\begin{aligned}f(u, v) & = (u+v)^{2} \\u(a, b) & = (a+b)^{2}, \qquad v(a, b) = (a-b)^{2}, \\a(w, x, y, z) & = (w+x+y+z)^{2}, \qquad b(w, x, y, z) = (w+x-y-z)^2.\end{aligned} \tag{22.4.3}

Tokios lygčių grandinės yra įprastos dirbant su neuroniniais tinklais, todėl esminis dalykas yra suprasti, kaip apskaičiuoti tokių funkcijų gradientus. Vizualių užuominų apie šį ryšį galime pradėti matyti 22.4.1 pav., jei pažiūrėsime, kurie kintamieji tiesiogiai susiję vienas su kitu.

Aukščiau pateikti funkciniai ryšiai, kur mazgai žymi reikšmes, o briaunos rodo funkcinę priklausomybę.

22.4.1 pav. Aukščiau pateikti funkciniai ryšiai, kur mazgai žymi reikšmes, o briaunos rodo funkcinę priklausomybę.

Niekas netrukdo tiesiog sukomponuoti viską iš (22.4.3) ir užrašyti, kad

f(w,x,y,z)=(((w+x+y+z)2+(w+x−y−z)2)2+((w+x+y+z)2−(w+x−y−z)2)2)2.f(w, x, y, z) = \left(\left((w+x+y+z)^2+(w+x-y-z)^2\right)^2+\left((w+x+y+z)^2-(w+x-y-z)^2\right)^2\right)^2.

Tada išvestinę galėtume paimti tiesiog naudodami vieno kintamojo išvestines, bet tai daryti greitai atsidurtume paskendę narių, iš kurių daugelis kartojasi! Iš tiesų galima pamatyti, kad, pavyzdžiui:

∂f∂w=2(2(2(w+x+y+z)−2(w+x−y−z))((w+x+y+z)2−(w+x−y−z)2)+2(2(w+x−y−z)+2(w+x+y+z))((w+x−y−z)2+(w+x+y+z)2))×(((w+x+y+z)2−(w+x−y−z)2)2+((w+x−y−z)2+(w+x+y+z)2)2).\begin{aligned} \frac{\partial f}{\partial w} & = 2 \left(2 \left(2 (w + x + y + z) - 2 (w + x - y - z)\right) \left((w + x + y + z)^{2}- (w + x - y - z)^{2}\right) + \right.\\ & \left. \quad 2 \left(2 (w + x - y - z) + 2 (w + x + y + z)\right) \left((w + x - y - z)^{2}+ (w + x + y + z)^{2}\right)\right) \times \\ & \quad \left(\left((w + x + y + z)^{2}- (w + x - y - z)^2\right)^{2}+ \left((w + x - y - z)^{2}+ (w + x + y + z)^{2}\right)^{2}\right). \end{aligned}

Jei po to taip pat norėtume apskaičiuoti ∂f∂x\frac{\partial f}{\partial x}, vėl gautume panašią lygtį su daug pasikartojančių narių ir daug bendrų pasikartojančių narių tarp šių dviejų išvestinių. Tai reiškia didžiulį švaistomo darbo kiekį, ir jei išvestines reikėtų skaičiuoti šitaip, visa giliojo mokymosi revoliucija būtų sustojusi dar neprasidėjusi!

Išskaidykime uždavinį. Pradėsime bandydami suprasti, kaip ff pasikeičia, kai keičiame aa, iš esmės darydami prielaidą, kad w,x,yw, x, y ir zz visai neegzistuoja. Samprotausime taip pat, kaip tada, kai pirmą kartą dirbome su gradientu. Paimkime aa ir pridėkime prie jo mažą dydį ϵ\epsilon.

f(u(a+ϵ,b),v(a+ϵ,b))≈f(u(a,b)+ϵ∂u∂a(a,b),v(a,b)+ϵ∂v∂a(a,b))≈f(u(a,b),v(a,b))+ϵ[∂f∂u(u(a,b),v(a,b))∂u∂a(a,b)+∂f∂v(u(a,b),v(a,b))∂v∂a(a,b)].\begin{aligned} & f(u(a+\epsilon, b), v(a+\epsilon, b)) \\ \approx & f\left(u(a, b) + \epsilon\frac{\partial u}{\partial a}(a, b), v(a, b) + \epsilon\frac{\partial v}{\partial a}(a, b)\right) \\ \approx & f(u(a, b), v(a, b)) + \epsilon\left[\frac{\partial f}{\partial u}(u(a, b), v(a, b))\frac{\partial u}{\partial a}(a, b) + \frac{\partial f}{\partial v}(u(a, b), v(a, b))\frac{\partial v}{\partial a}(a, b)\right]. \end{aligned}

Pirma eilutė išplaukia iš dalinės išvestinės apibrėžimo, o antra – iš gradiento apibrėžimo. Žymėjimo požiūriu sunku sekti, kur tiksliai įvertiname kiekvieną išvestinę, kaip išraiškoje ∂f∂u(u(a,b),v(a,b))\frac{\partial f}{\partial u}(u(a, b), v(a, b)), todėl dažnai tai sutrumpiname iki daug lengviau įsimenamos

∂f∂a=∂f∂u∂u∂a+∂f∂v∂v∂a.\frac{\partial f}{\partial a} = \frac{\partial f}{\partial u}\frac{\partial u}{\partial a}+\frac{\partial f}{\partial v}\frac{\partial v}{\partial a}.

Naudinga pagalvoti apie proceso prasmę. Bandome suprasti, kaip formos f(u(a,b),v(a,b))f(u(a, b), v(a, b)) funkcija keičia savo reikšmę keičiantis aa. Tai gali vykti dviem keliais: yra kelias a→u→fa \rightarrow u \rightarrow f ir kelias a→v→fa \rightarrow v \rightarrow f. Abu šiuos indėlius galime apskaičiuoti pagal grandinės taisyklę: atitinkamai ∂w∂u⋅∂u∂x\frac{\partial w}{\partial u} \cdot \frac{\partial u}{\partial x} ir ∂w∂v⋅∂v∂x\frac{\partial w}{\partial v} \cdot \frac{\partial v}{\partial x}, ir sudėti.

Įsivaizduokite, kad turime kitą funkcijų tinklą, kuriame dešinėje esančios funkcijos priklauso nuo kairėje esančių, su kuriomis jos sujungtos, kaip parodyta 22.4.2 pav..

Dar vienas subtilesnis grandinės taisyklės pavyzdys.

22.4.2 pav. Dar vienas subtilesnis grandinės taisyklės pavyzdys.

Norėdami apskaičiuoti ką nors panašaus į ∂f∂y\frac{\partial f}{\partial y}, turime susumuoti per visus (šiuo atveju 33) kelius nuo yy iki ff, gaudami

∂f∂y=∂f∂a∂a∂u∂u∂y+∂f∂u∂u∂y+∂f∂b∂b∂v∂v∂y.\frac{\partial f}{\partial y} = \frac{\partial f}{\partial a} \frac{\partial a}{\partial u} \frac{\partial u}{\partial y} + \frac{\partial f}{\partial u} \frac{\partial u}{\partial y} + \frac{\partial f}{\partial b} \frac{\partial b}{\partial v} \frac{\partial v}{\partial y}.

Šitaip suprasta grandinės taisyklė atneš didelę naudą bandant suprasti, kaip gradientai teka per tinklus ir kodėl įvairūs architektūros sprendimai, tokie kaip LSTM (10.1 skyrelis) ar liekanų sluoksniai (8.6 skyrelis), gali padėti formuoti mokymosi procesą valdydami gradientų tekėjimą.

22.4.5 Atgalinio sklidimo algoritmas

Grįžkime prie ankstesnio skyrelio pavyzdžio (22.4.3), kuriame

f(u,v)=(u+v)2u(a,b)=(a+b)2,v(a,b)=(a−b)2,a(w,x,y,z)=(w+x+y+z)2,b(w,x,y,z)=(w+x−y−z)2.\begin{aligned} f(u, v) & = (u+v)^{2} \\ u(a, b) & = (a+b)^{2}, \qquad v(a, b) = (a-b)^{2}, \\ a(w, x, y, z) & = (w+x+y+z)^{2}, \qquad b(w, x, y, z) = (w+x-y-z)^2. \end{aligned}

Jei norime apskaičiuoti, tarkime, ∂f∂w\frac{\partial f}{\partial w}, galime pritaikyti daugelio kintamųjų grandinės taisyklę ir gauti:

∂f∂w=∂f∂u∂u∂w+∂f∂v∂v∂w,∂u∂w=∂u∂a∂a∂w+∂u∂b∂b∂w,∂v∂w=∂v∂a∂a∂w+∂v∂b∂b∂w.\begin{aligned} \frac{\partial f}{\partial w} & = \frac{\partial f}{\partial u}\frac{\partial u}{\partial w} + \frac{\partial f}{\partial v}\frac{\partial v}{\partial w}, \\ \frac{\partial u}{\partial w} & = \frac{\partial u}{\partial a}\frac{\partial a}{\partial w}+\frac{\partial u}{\partial b}\frac{\partial b}{\partial w}, \\ \frac{\partial v}{\partial w} & = \frac{\partial v}{\partial a}\frac{\partial a}{\partial w}+\frac{\partial v}{\partial b}\frac{\partial b}{\partial w}. \end{aligned}

Pabandykime naudoti šią skaidą ∂f∂w\frac{\partial f}{\partial w} apskaičiuoti. Atkreipkite dėmesį, kad čia mums reikia tik įvairių vieno žingsnio dalinių išvestinių:

∂f∂u=2(u+v),∂f∂v=2(u+v),∂u∂a=2(a+b),∂u∂b=2(a+b),∂v∂a=2(a−b),∂v∂b=−2(a−b),∂a∂w=2(w+x+y+z),∂b∂w=2(w+x−y−z).\begin{aligned} \frac{\partial f}{\partial u} = 2(u+v), & \quad\frac{\partial f}{\partial v} = 2(u+v), \\ \frac{\partial u}{\partial a} = 2(a+b), & \quad\frac{\partial u}{\partial b} = 2(a+b), \\ \frac{\partial v}{\partial a} = 2(a-b), & \quad\frac{\partial v}{\partial b} = -2(a-b), \\ \frac{\partial a}{\partial w} = 2(w+x+y+z), & \quad\frac{\partial b}{\partial w} = 2(w+x-y-z). \end{aligned}

Jei tai užrašysime kodu, ši išraiška tampa gana valdoma.

# Compute the value of the function from inputs to outputs
w, x, y, z = -1, 0, -2, 1
a, b = (w + x + y + z)**2, (w + x - y - z)**2
u, v = (a + b)**2, (a - b)**2
f = (u + v)**2
print(f'    f at {w}, {x}, {y}, {z} is {f}')

# Compute the single step partials
df_du, df_dv = 2*(u + v), 2*(u + v)
du_da, du_db, dv_da, dv_db = 2*(a + b), 2*(a + b), 2*(a - b), -2*(a - b)
da_dw, db_dw = 2*(w + x + y + z), 2*(w + x - y - z)

# Compute the final result from inputs to outputs
du_dw, dv_dw = du_da*da_dw + du_db*db_dw, dv_da*da_dw + dv_db*db_dw
df_dw = df_du*du_dw + df_dv*dv_dw
print(f'df/dw at {w}, {x}, {y}, {z} is {df_dw}')

Tačiau atkreipkite dėmesį, kad tai vis tiek nepadaro lengvu apskaičiuoti ko nors panašaus į ∂f∂x\frac{\partial f}{\partial x}. To priežastis yra būdas, kuriuo pasirinkome taikyti grandinės taisyklę. Jei pažiūrėsime, ką darėme aukščiau, kai tik galėjome, visada palikdavome ∂w\partial w vardiklyje. Šitaip pasirinkome taikyti grandinės taisyklę žiūrėdami, kaip ww keičia kiekvieną kitą kintamąjį. Jei to ir norėtume, tai būtų gera idėja. Tačiau prisiminkite savo motyvaciją iš giliojo mokymosi: norime pamatyti, kaip kiekvienas parametras keičia nuostolius. Iš esmės norime taikyti grandinės taisyklę, kai tik galime, palikdami ∂f\partial f skaitiklyje!

Aiškiau tariant, atkreipkite dėmesį, kad galime užrašyti

∂f∂w=∂f∂a∂a∂w+∂f∂b∂b∂w,∂f∂a=∂f∂u∂u∂a+∂f∂v∂v∂a,∂f∂b=∂f∂u∂u∂b+∂f∂v∂v∂b.\begin{aligned} \frac{\partial f}{\partial w} & = \frac{\partial f}{\partial a}\frac{\partial a}{\partial w} + \frac{\partial f}{\partial b}\frac{\partial b}{\partial w}, \\ \frac{\partial f}{\partial a} & = \frac{\partial f}{\partial u}\frac{\partial u}{\partial a}+\frac{\partial f}{\partial v}\frac{\partial v}{\partial a}, \\ \frac{\partial f}{\partial b} & = \frac{\partial f}{\partial u}\frac{\partial u}{\partial b}+\frac{\partial f}{\partial v}\frac{\partial v}{\partial b}. \end{aligned}

Atkreipkite dėmesį, kad taikant grandinės taisyklę šitaip, aiškiai apskaičiuojame ∂f∂u,∂f∂v,∂f∂a,∂f∂b,  ir  ∂f∂w\frac{\partial f}{\partial u}, \frac{\partial f}{\partial v}, \frac{\partial f}{\partial a}, \frac{\partial f}{\partial b}, \; \textrm{ir} \; \frac{\partial f}{\partial w}. Niekas netrukdo įtraukti ir lygčių:

∂f∂x=∂f∂a∂a∂x+∂f∂b∂b∂x,∂f∂y=∂f∂a∂a∂y+∂f∂b∂b∂y,∂f∂z=∂f∂a∂a∂z+∂f∂b∂b∂z.\begin{aligned} \frac{\partial f}{\partial x} & = \frac{\partial f}{\partial a}\frac{\partial a}{\partial x} + \frac{\partial f}{\partial b}\frac{\partial b}{\partial x}, \\ \frac{\partial f}{\partial y} & = \frac{\partial f}{\partial a}\frac{\partial a}{\partial y}+\frac{\partial f}{\partial b}\frac{\partial b}{\partial y}, \\ \frac{\partial f}{\partial z} & = \frac{\partial f}{\partial a}\frac{\partial a}{\partial z}+\frac{\partial f}{\partial b}\frac{\partial b}{\partial z}. \end{aligned}

ir tada sekti, kaip ff keičiasi, keičiant bet kurį viso tinklo mazgą. Realizuokime tai.

# Compute the value of the function from inputs to outputs
w, x, y, z = -1, 0, -2, 1
a, b = (w + x + y + z)**2, (w + x - y - z)**2
u, v = (a + b)**2, (a - b)**2
f = (u + v)**2
print(f'f at {w}, {x}, {y}, {z} is {f}')

# Compute the derivative using the decomposition above
# First compute the single step partials
df_du, df_dv = 2*(u + v), 2*(u + v)
du_da, du_db, dv_da, dv_db = 2*(a + b), 2*(a + b), 2*(a - b), -2*(a - b)
da_dw, db_dw = 2*(w + x + y + z), 2*(w + x - y - z)
da_dx, db_dx = 2*(w + x + y + z), 2*(w + x - y - z)
da_dy, db_dy = 2*(w + x + y + z), -2*(w + x - y - z)
da_dz, db_dz = 2*(w + x + y + z), -2*(w + x - y - z)

# Now compute how f changes when we change any value from output to input
df_da, df_db = df_du*du_da + df_dv*dv_da, df_du*du_db + df_dv*dv_db
df_dw, df_dx = df_da*da_dw + df_db*db_dw, df_da*da_dx + df_db*db_dx
df_dy, df_dz = df_da*da_dy + df_db*db_dy, df_da*da_dz + df_db*db_dz

print(f'df/dw at {w}, {x}, {y}, {z} is {df_dw}')
print(f'df/dx at {w}, {x}, {y}, {z} is {df_dx}')
print(f'df/dy at {w}, {x}, {y}, {z} is {df_dy}')
print(f'df/dz at {w}, {x}, {y}, {z} is {df_dz}')

Tai, kad išvestines skaičiuojame nuo ff atgal link įvesčių, o ne nuo įvesčių pirmyn link išvesčių (kaip darėme pirmajame aukščiau pateiktame kodo fragmente), ir suteikia šiam algoritmui pavadinimą: atgalinis sklidimas. Atkreipkite dėmesį, kad yra du žingsniai:

  1. Apskaičiuoti funkcijos reikšmę ir vieno žingsnio daliniais išvestines iš priekio į galą. Nors aukščiau to nedarėme, tai galima sujungti į vieną tiesioginį perėjimą (angl. forward pass).
  2. Apskaičiuoti ff gradientą iš galo į priekį. Tai vadiname atgaliniu perėjimu (angl. backwards pass).

Būtent tai įgyvendina kiekvienas giliojo mokymosi algoritmas, kad vienu perėjimu būtų galima apskaičiuoti nuostolių gradientą pagal kiekvieną tinklo parametrą. Stulbinantis faktas, kad turime tokią skaidą.

Kad pamatytume, kaip tai įkapsuliuoti, greitai pažvelkime į šį pavyzdį.

# Initialize as ndarrays, then attach gradients
w, x, y, z = np.array(-1), np.array(0), np.array(-2), np.array(1)

w.attach_grad()
x.attach_grad()
y.attach_grad()
z.attach_grad()

# Do the computation like usual, tracking gradients
with autograd.record():
    a, b = (w + x + y + z)**2, (w + x - y - z)**2
    u, v = (a + b)**2, (a - b)**2
    f = (u + v)**2

# Execute backward pass
f.backward()

print(f'df/dw at {w}, {x}, {y}, {z} is {w.grad}')
print(f'df/dx at {w}, {x}, {y}, {z} is {x.grad}')
print(f'df/dy at {w}, {x}, {y}, {z} is {y.grad}')
print(f'df/dz at {w}, {x}, {y}, {z} is {z.grad}')
# Initialize as ndarrays, then attach gradients
w = torch.tensor([-1.], requires_grad=True)
x = torch.tensor([0.], requires_grad=True)
y = torch.tensor([-2.], requires_grad=True)
z = torch.tensor([1.], requires_grad=True)
# Do the computation like usual, tracking gradients
a, b = (w + x + y + z)**2, (w + x - y - z)**2
u, v = (a + b)**2, (a - b)**2
f = (u + v)**2

# Execute backward pass
f.backward()

print(f'df/dw at {w.data.item()}, {x.data.item()}, {y.data.item()}, '
      f'{z.data.item()} is {w.grad.data.item()}')
print(f'df/dx at {w.data.item()}, {x.data.item()}, {y.data.item()}, '
      f'{z.data.item()} is {x.grad.data.item()}')
print(f'df/dy at {w.data.item()}, {x.data.item()}, {y.data.item()}, '
      f'{z.data.item()} is {y.grad.data.item()}')
print(f'df/dz at {w.data.item()}, {x.data.item()}, {y.data.item()}, '
      f'{z.data.item()} is {z.grad.data.item()}')
# Initialize as ndarrays, then attach gradients
w = tf.Variable(tf.constant([-1.]))
x = tf.Variable(tf.constant([0.]))
y = tf.Variable(tf.constant([-2.]))
z = tf.Variable(tf.constant([1.]))
# Do the computation like usual, tracking gradients
with tf.GradientTape(persistent=True) as t:
    a, b = (w + x + y + z)**2, (w + x - y - z)**2
    u, v = (a + b)**2, (a - b)**2
    f = (u + v)**2

# Execute backward pass
w_grad = t.gradient(f, w).numpy()
x_grad = t.gradient(f, x).numpy()
y_grad = t.gradient(f, y).numpy()
z_grad = t.gradient(f, z).numpy()

print(f'df/dw at {w.numpy()}, {x.numpy()}, {y.numpy()}, '
      f'{z.numpy()} is {w_grad}')
print(f'df/dx at {w.numpy()}, {x.numpy()}, {y.numpy()}, '
      f'{z.numpy()} is {x_grad}')
print(f'df/dy at {w.numpy()}, {x.numpy()}, {y.numpy()}, '
      f'{z.numpy()} is {y_grad}')
print(f'df/dz at {w.numpy()}, {x.numpy()}, {y.numpy()}, '
      f'{z.numpy()} is {z_grad}')

Viską, ką darėme aukščiau, galima atlikti automatiškai iškvietus f.backwards().

22.4.6 Heso matricos

Kaip ir vieno kintamojo matematinėje analizėje, naudinga nagrinėti aukštesnės eilės išvestines, kad suprastume, kaip galime gauti geresnę funkcijos aproksimaciją nei vien naudojant gradientą.

Dirbant su kelių kintamųjų funkcijų aukštesnės eilės išvestinėmis iškart susiduriama su viena problema: jų yra labai daug. Jei turime nn kintamųjų funkciją f(x1,…,xn)f(x_1, \ldots, x_n), galime imti n2n^{2} antrųjų išvestinių, būtent bet kuriam ii ir jj pasirinkimui:

d2fdxidxj=ddxi(ddxjf).\frac{d^2f}{dx_idx_j} = \frac{d}{dx_i}\left(\frac{d}{dx_j}f\right).

Tradiciškai jos surenkamos į matricą, vadinamą Heso matrica:

Hf=[d2fdx1dx1⋯d2fdx1dxn⋮⋱⋮d2fdxndx1⋯d2fdxndxn].(22.4.4)\mathbf{H}_f = \begin{bmatrix} \frac{d^2f}{dx_1dx_1} & \cdots & \frac{d^2f}{dx_1dx_n} \\ \vdots & \ddots & \vdots \\ \frac{d^2f}{dx_ndx_1} & \cdots & \frac{d^2f}{dx_ndx_n} \\ \end{bmatrix}. \tag{22.4.4}

Ne kiekvienas šios matricos elementas yra nepriklausomas. Iš tiesų galime parodyti, kad kol egzistuoja ir yra tolydžios abi mišriosios išvestinės (dalinės išvestinės pagal daugiau nei vieną kintamąjį), galime sakyti, kad bet kuriems ii ir jj

d2fdxidxj=d2fdxjdxi.\frac{d^2f}{dx_idx_j} = \frac{d^2f}{dx_jdx_i}.

Tai išplaukia iš to, kad pirmiausia sutrikdome funkciją xix_i kryptimi, o tada xjx_j kryptimi, ir palyginame rezultatą su tuo, kas nutinka, kai pirmiausia sutrikdome xjx_j, o tada xix_i, žinodami, kad abi šios tvarkos lemia tą patį galutinį ff išvesties pokytį.

Kaip ir su vienu kintamuoju, šias išvestines galime naudoti, kad gautume gerokai geresnį supratimą apie tai, kaip funkcija elgiasi šalia taško. Visų pirma galime jas naudoti geriausiai tinkančiai kvadratinei funkcijai šalia taško x0\mathbf{x}_0 rasti, kaip matėme vieno kintamojo atveju.

Pažiūrėkime pavyzdį. Tarkime, f(x1,x2)=a+b1x1+b2x2+c11x12+c12x1x2+c22x22f(x_1, x_2) = a + b_1x_1 + b_2x_2 + c_{11}x_1^{2} + c_{12}x_1x_2 + c_{22}x_2^{2}. Tai bendroji dviejų kintamųjų kvadratinės funkcijos forma. Jei pažiūrėsime į funkcijos reikšmę, jos gradientą ir Heso matricą (22.4.4), visus taške nulis:

f(0,0)=a,∇f(0,0)=[b1b2],Hf(0,0)=[2c11c12c122c22],\begin{aligned} f(0,0) & = a, \\ \nabla f (0,0) & = \begin{bmatrix}b_1 \\ b_2\end{bmatrix}, \\ \mathbf{H} f (0,0) & = \begin{bmatrix}2 c_{11} & c_{12} \\ c_{12} & 2c_{22}\end{bmatrix}, \end{aligned}

galime atgauti savo pradinį daugianarį pasakę, kad

f(x)=f(0)+∇f(0)⋅x+12x⊤Hf(0)x.f(\mathbf{x}) = f(0) + \nabla f (0) \cdot \mathbf{x} + \frac{1}{2}\mathbf{x}^\top \mathbf{H} f (0) \mathbf{x}.

Bendru atveju, jei šį skleidinį apskaičiuotume bet kuriame taške x0\mathbf{x}_0, matome, kad

f(x)=f(x0)+∇f(x0)⋅(x−x0)+12(x−x0)⊤Hf(x0)(x−x0).f(\mathbf{x}) = f(\mathbf{x}_0) + \nabla f (\mathbf{x}_0) \cdot (\mathbf{x}-\mathbf{x}_0) + \frac{1}{2}(\mathbf{x}-\mathbf{x}_0)^\top \mathbf{H} f (\mathbf{x}_0) (\mathbf{x}-\mathbf{x}_0).

Tai veikia bet kokios dimensijos įvesčiai ir suteikia geriausią aproksimuojančią kvadratinę funkciją bet kuriai funkcijai taške. Kad pateiktume pavyzdį, nubraižykime funkciją

f(x,y)=xe−x2−y2.f(x, y) = xe^{-x^2-y^2}.

Galima apskaičiuoti, kad gradientas ir Heso matrica yra

∇f(x,y)=e−x2−y2(1−2x2−2xy)  and  Hf(x,y)=e−x2−y2(4x3−6x4x2y−2y4x2y−2y4xy2−2x).\nabla f(x, y) = e^{-x^2-y^2}\begin{pmatrix}1-2x^2 \\ -2xy\end{pmatrix} \; \textrm{and} \; \mathbf{H}f(x, y) = e^{-x^2-y^2}\begin{pmatrix} 4x^3 - 6x & 4x^2y - 2y \\ 4x^2y-2y &4xy^2-2x\end{pmatrix}.

Taigi, pritaikę šiek tiek algebros, matome, kad aproksimuojanti kvadratinė funkcija taške [−1,0]⊤[-1,0]^\top yra

f(x,y)≈e−1(−1−(x+1)+(x+1)2+y2).f(x, y) \approx e^{-1}\left(-1 - (x+1) +(x+1)^2+y^2\right).
# Construct grid and compute function
x, y = np.meshgrid(np.linspace(-2, 2, 101),
                   np.linspace(-2, 2, 101), indexing='ij')
z = x*np.exp(- x**2 - y**2)

# Compute approximating quadratic with gradient and Hessian at (1, 0)
w = np.exp(-1)*(-1 - (x + 1) + (x + 1)**2 + y**2)

# Plot function
ax = d2l.plt.figure().add_subplot(111, projection='3d')
ax.plot_wireframe(x.asnumpy(), y.asnumpy(), z.asnumpy(),
                  **{'rstride': 10, 'cstride': 10})
ax.plot_wireframe(x.asnumpy(), y.asnumpy(), w.asnumpy(),
                  **{'rstride': 10, 'cstride': 10}, color='purple')
d2l.plt.xlabel('x')
d2l.plt.ylabel('y')
d2l.set_figsize()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
ax.set_zlim(-1, 1)
ax.dist = 12
# Construct grid and compute function
x, y = torch.meshgrid(torch.linspace(-2, 2, 101),
                   torch.linspace(-2, 2, 101))

z = x*torch.exp(- x**2 - y**2)

# Compute approximating quadratic with gradient and Hessian at (1, 0)
w = torch.exp(torch.tensor([-1.]))*(-1 - (x + 1) + 2 * (x + 1)**2 + 2 * y**2)

# Plot function
ax = d2l.plt.figure().add_subplot(111, projection='3d')
ax.plot_wireframe(x.numpy(), y.numpy(), z.numpy(),
                  **{'rstride': 10, 'cstride': 10})
ax.plot_wireframe(x.numpy(), y.numpy(), w.numpy(),
                  **{'rstride': 10, 'cstride': 10}, color='purple')
d2l.plt.xlabel('x')
d2l.plt.ylabel('y')
d2l.set_figsize()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
ax.set_zlim(-1, 1)
ax.dist = 12
# Construct grid and compute function
x, y = tf.meshgrid(tf.linspace(-2., 2., 101),
                   tf.linspace(-2., 2., 101))

z = x*tf.exp(- x**2 - y**2)

# Compute approximating quadratic with gradient and Hessian at (1, 0)
w = tf.exp(tf.constant([-1.]))*(-1 - (x + 1) + 2 * (x + 1)**2 + 2 * y**2)

# Plot function
ax = d2l.plt.figure().add_subplot(111, projection='3d')
ax.plot_wireframe(x.numpy(), y.numpy(), z.numpy(),
                  **{'rstride': 10, 'cstride': 10})
ax.plot_wireframe(x.numpy(), y.numpy(), w.numpy(),
                  **{'rstride': 10, 'cstride': 10}, color='purple')
d2l.plt.xlabel('x')
d2l.plt.ylabel('y')
d2l.set_figsize()
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
ax.set_zlim(-1, 1)
ax.dist = 12

Tai sudaro pagrindą Niutono algoritmui, aptartam 12.3 skyrelis, kur atliekame skaitinį optimizavimą iteratyviai randame geriausiai tinkančią kvadratinę funkciją, o tada tiksliai minimizuojame tą kvadratinę funkciją.

22.4.7 Šiek tiek matricų matematinės analizės

Funkcijų, apimančių matricas, išvestinės pasirodo esančios ypač gražios. Šis skyrelis gali tapti žymėjimo požiūriu sunkus, todėl pirmą kartą skaitant gali būti praleistas, tačiau naudinga žinoti, kad funkcijų, apimančių įprastas matricų operacijas, išvestinės dažnai yra gerokai tvarkingesnės, nei galima iš pradžių tikėtis, ypač turint omenyje, kokios svarbios matricų operacijos yra giliojo mokymosi taikymuose.

Pradėkime nuo pavyzdžio. Tarkime, turime kokį nors fiksuotą stulpelio vektorių β\boldsymbol{\beta} ir norime paimti sandaugos funkciją f(x)=β⊤xf(\mathbf{x}) = \boldsymbol{\beta}^\top\mathbf{x} ir suprasti, kaip pasikeičia skaliarinė sandauga, kai keičiame x\mathbf{x}.

Šiek tiek žymėjimo, kuris bus naudingas dirbant su matricų išvestinėmis MM srityje, vadinama vardiklio išdėstymo matricos išvestine (angl. denominator layout), kai savo dalines išvestines surenkame į tokios formos vektorių, matricą ar tenzorių, koks yra diferencialo vardiklyje. Šiuo atveju rašysime

dfdx=[dfdx1⋮dfdxn],\frac{df}{d\mathbf{x}} = \begin{bmatrix} \frac{df}{dx_1} \\ \vdots \\ \frac{df}{dx_n} \end{bmatrix},

kur atitikome stulpelio vektoriaus x\mathbf{x} formą.

Jei savo funkciją išskleisime komponentėmis, tai yra

f(x)=∑i=1nβixi=β1x1+⋯+βnxn.f(\mathbf{x}) = \sum_{i = 1}^{n} \beta_ix_i = \beta_1x_1 + \cdots + \beta_nx_n.

Jei dabar imsime dalinę išvestinę pagal, tarkime, β1\beta_1, atkreipkite dėmesį, kad viskas lygu nuliui, išskyrus pirmąjį narį, kuris yra tiesiog x1x_1, padaugintas iš β1\beta_1, taigi gauname, kad

dfdx1=β1,\frac{df}{dx_1} = \beta_1,

arba bendriau, kad

dfdxi=βi.\frac{df}{dx_i} = \beta_i.

Dabar tai galime vėl surinkti į matricą ir pamatyti

dfdx=[dfdx1⋮dfdxn]=[β1⋮βn]=β.\frac{df}{d\mathbf{x}} = \begin{bmatrix} \frac{df}{dx_1} \\ \vdots \\ \frac{df}{dx_n} \end{bmatrix} = \begin{bmatrix} \beta_1 \\ \vdots \\ \beta_n \end{bmatrix} = \boldsymbol{\beta}.

Tai iliustruoja keletą dalykų apie matricų matematinę analizę, su kuriais dažnai susidursime visame šiame skyrelyje:

  • Pirma, skaičiavimai taps gana sudėtingi.
  • Antra, galutiniai rezultatai yra daug tvarkingesni nei tarpinis procesas ir visada atrodys panašiai kaip vieno kintamojo atveju. Šiuo atveju atkreipkite dėmesį, kad ddx(bx)=b\frac{d}{dx}(bx) = b ir ddx(β⊤x)=β\frac{d}{d\mathbf{x}} (\boldsymbol{\beta}^\top\mathbf{x}) = \boldsymbol{\beta} yra panašūs.
  • Trečia, transponavimai dažnai gali atsirasti tarsi iš niekur. Pagrindinė to priežastis yra susitarimas, kad atitinkame vardiklio formą, todėl dauginant matricas, reikės transponuoti, kad grįžtume prie pradinio nario formos.

Kad toliau kurtume intuiciją, pabandykime šiek tiek sudėtingesnį skaičiavimą. Tarkime, turime stulpelio vektorių x\mathbf{x} ir kvadratinę matricą AA ir norime apskaičiuoti

ddx(x⊤Ax).(22.4.5)\frac{d}{d\mathbf{x}}(\mathbf{x}^\top A \mathbf{x}). \tag{22.4.5}

Kad judėtume link lengviau manipuliuojamo žymėjimo, panagrinėkime šį uždavinį naudodami Einšteino žymėjimą. Šiuo atveju funkciją galime užrašyti kaip

x⊤Ax=xiaijxj.\mathbf{x}^\top A \mathbf{x} = x_ia_{ij}x_j.

Kad apskaičiuotume išvestinę, turime suprasti, kam kiekvienam kk lygi

ddxk(x⊤Ax)=ddxkxiaijxj.\frac{d}{dx_k}(\mathbf{x}^\top A \mathbf{x}) = \frac{d}{dx_k}x_ia_{ij}x_j.

Pagal sandaugos taisyklę tai yra

ddxkxiaijxj=dxidxkaijxj+xiaijdxjdxk.\frac{d}{dx_k}x_ia_{ij}x_j = \frac{dx_i}{dx_k}a_{ij}x_j + x_ia_{ij}\frac{dx_j}{dx_k}.

Nariui, pavyzdžiui, dxidxk\frac{dx_i}{dx_k}, nesunku pamatyti, kad jis lygus vienam, kai i=ki=k, ir nuliui kitais atvejais. Tai reiškia, kad kiekvienas narys, kuriame ii ir kk skiriasi, šioje sumoje išnyksta, todėl toje pirmoje sumoje lieka tik nariai, kur i=ki=k. Tas pats samprotavimas galioja antram nariui, kuriame reikia j=kj=k. Tai duoda

ddxkxiaijxj=akjxj+xiaik.\frac{d}{dx_k}x_ia_{ij}x_j = a_{kj}x_j + x_ia_{ik}.

Dabar indeksų pavadinimai Einšteino žymėjime yra savavališki – tai, kad ii ir jj skiriasi, šiuo metu šiam skaičiavimui nesvarbu, todėl galime iš naujo sužymėti indeksus, kad abu naudotų ii, ir pamatyti, kad

ddxkxiaijxj=akixi+xiaik=(aki+aik)xi.\frac{d}{dx_k}x_ia_{ij}x_j = a_{ki}x_i + x_ia_{ik} = (a_{ki} + a_{ik})x_i.

Dabar čia pradeda reikėti šiek tiek praktikos, kad pasistūmėtume toliau. Pabandykime atpažinti šį rezultatą matricų operacijų terminais. aki+aika_{ki} + a_{ik} yra k,ik, i-oji matricos A+A⊤\mathbf{A} + \mathbf{A}^\top komponentė. Tai duoda

ddxkxiaijxj=[A+A⊤]kixi.\frac{d}{dx_k}x_ia_{ij}x_j = [\mathbf{A} + \mathbf{A}^\top]_{ki}x_i.

Panašiai šis narys dabar yra matricos A+A⊤\mathbf{A} + \mathbf{A}^\top ir vektoriaus x\mathbf{x} sandauga, taigi matome, kad

[ddx(x⊤Ax)]k=ddxkxiaijxj=[(A+A⊤)x]k.\left[\frac{d}{d\mathbf{x}}(\mathbf{x}^\top A \mathbf{x})\right]_k = \frac{d}{dx_k}x_ia_{ij}x_j = [(\mathbf{A} + \mathbf{A}^\top)\mathbf{x}]_k.

Taigi matome, kad kk-asis ieškomos išvestinės iš (22.4.5) elementas yra tiesiog kk-asis dešinėje esančio vektoriaus elementas, todėl abu yra tie patys. Taigi gauname

ddx(x⊤Ax)=(A+A⊤)x.\frac{d}{d\mathbf{x}}(\mathbf{x}^\top A \mathbf{x}) = (\mathbf{A} + \mathbf{A}^\top)\mathbf{x}.

Tam prireikė gerokai daugiau darbo nei ankstesniam, bet galutinis rezultatas trumpas. Be to, panagrinėkime tokį tradicinių vieno kintamojo išvestinių skaičiavimą:

ddx(xax)=dxdxax+xadxdx=(a+a)x.\frac{d}{dx}(xax) = \frac{dx}{dx}ax + xa\frac{dx}{dx} = (a+a)x.

Ekvivalenčiai ddx(ax2)=2ax=(a+a)x\frac{d}{dx}(ax^2) = 2ax = (a+a)x. Vėl gauname rezultatą, kuris gana panašus į vieno kintamojo rezultatą, tik su įmesta transponavimo operacija.

Šiuo metu dėsningumas turėtų atrodyti gana įtartinai, todėl pabandykime išsiaiškinti kodėl. Kai imame tokias matricų išvestines, pirmiausia darykime prielaidą, kad gauta išraiška bus kita matricų išraiška: išraiška, kurią galime užrašyti per matricų ir jų transponuotųjų sandaugas bei sumas. Jei tokia išraiška egzistuoja, ji turės būti teisinga visoms matricoms. Visų pirma ji turės būti teisinga 1×11 \times 1 matricoms, kuriuo atveju matricų sandauga yra tiesiog skaičių sandauga, matricų suma yra tiesiog suma, o transponavimas visai nieko nedaro! Kitaip tariant, kad ir kokią išraišką gautume, ji privalo atitikti vieno kintamojo išraišką. Tai reiškia, kad šiek tiek pasipraktikavus matricų išvestines dažnai galima atspėti tiesiog žinant, kaip turi atrodyti atitinkama vieno kintamojo išraiška!

Išbandykime tai. Tarkime, X\mathbf{X} yra n×mn \times m matrica, U\mathbf{U} yra n×rn \times r, o V\mathbf{V} yra r×mr \times m. Pabandykime apskaičiuoti

ddV∥X−UV∥22=  ?(22.4.6)\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2} = \;? \tag{22.4.6}

Šis skaičiavimas svarbus srityje, vadinamoje matricų faktorizacija. Tačiau mums tai tiesiog išvestinė, kurią reikia apskaičiuoti. Pabandykime įsivaizduoti, kokia ji būtų 1×11\times1 matricoms. Tuo atveju gauname išraišką

ddv(x−uv)2=−2(x−uv)u,\frac{d}{dv} (x-uv)^{2}= -2(x-uv)u,

kur išvestinė yra gana standartinė. Jei bandysime tai paversti atgal į matricų išraišką, gausime

ddV∥X−UV∥22=−2(X−UV)U.\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2(\mathbf{X} - \mathbf{U}\mathbf{V})\mathbf{U}.

Tačiau jei pažiūrėsime, tai visai neveikia. Prisiminkite, kad X\mathbf{X} yra n×mn \times m, kaip ir UV\mathbf{U}\mathbf{V}, todėl matrica 2(X−UV)2(\mathbf{X} - \mathbf{U}\mathbf{V}) yra n×mn \times m. Kita vertus, U\mathbf{U} yra n×rn \times r, o negalime padauginti n×mn \times m ir n×rn \times r matricų, nes dimensijos nesutampa!

Norime gauti ddV\frac{d}{d\mathbf{V}}, kuri yra tokios pat formos kaip V\mathbf{V}, t. y. r×mr \times m. Taigi kažkaip turime paimti n×mn \times m matricą ir n×rn \times r matricą, jas sudauginti (galbūt su transponavimais) ir gauti r×mr \times m. Tai galime padaryti dauginant U⊤U^\top iš (X−UV)(\mathbf{X} - \mathbf{U}\mathbf{V}). Taigi galime atspėti, kad (22.4.6) sprendinys yra

ddV∥X−UV∥22=−2U⊤(X−UV).\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\mathbf{U}^\top(\mathbf{X} - \mathbf{U}\mathbf{V}).

Kad parodytume, jog tai veikia, nepateikti išsamaus skaičiavimo būtų neatsargu. Jei jau tikime, kad ši praktinė taisyklė veikia, galite praleisti šį išvedimą. Kad apskaičiuotume

ddV∥X−UV∥22,\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^2,

turime rasti kiekvienam aa ir bb

ddvab∥X−UV∥22=ddvab∑i,j(xij−∑kuikvkj)2.\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= \frac{d}{dv_{ab}} \sum_{i, j}\left(x_{ij} - \sum_k u_{ik}v_{kj}\right)^2.

Prisiminę, kad visi X\mathbf{X} ir U\mathbf{U} elementai yra konstantos ddvab\frac{d}{dv_{ab}} atžvilgiu, galime išvestinę perkelti į sumos vidų ir pritaikyti grandinės taisyklę kvadratui, gaudami

ddvab∥X−UV∥22=∑i,j2(xij−∑kuikvkj)(−∑kuikdvkjdvab).\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= \sum_{i, j}2\left(x_{ij} - \sum_k u_{ik}v_{kj}\right)\left(-\sum_k u_{ik}\frac{dv_{kj}}{dv_{ab}} \right).

Kaip ir ankstesniame išvedime, galime pastebėti, kad dvkjdvab\frac{dv_{kj}}{dv_{ab}} yra nenulinė tik jei k=ak=a ir j=bj=b. Jei kuri nors iš tų sąlygų negalioja, narys sumoje lygus nuliui ir jį galime laisvai atmesti. Matome, kad

ddvab∥X−UV∥22=−2∑i(xib−∑kuikvkb)uia.\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\sum_{i}\left(x_{ib} - \sum_k u_{ik}v_{kb}\right)u_{ia}.

Svarbi subtilybė čia ta, kad reikalavimas k=ak=a nepasireiškia vidinėje sumoje, nes tas kk yra fiktyvusis kintamasis, pagal kurį sumuojame vidiniame naryje. Žymėjimo požiūriu tvarkingesnis pavyzdys: panagrinėkite, kodėl

ddx1(∑ixi)2=2(∑ixi).\frac{d}{dx_1} \left(\sum_i x_i \right)^{2}= 2\left(\sum_i x_i \right).

Nuo šio taško galime pradėti atpažinti sumos komponentes. Pirma,

∑kuikvkb=[UV]ib.\sum_k u_{ik}v_{kb} = [\mathbf{U}\mathbf{V}]_{ib}.

Taigi visa išraiška sumos viduje yra

xib−∑kuikvkb=[X−UV]ib.x_{ib} - \sum_k u_{ik}v_{kb} = [\mathbf{X}-\mathbf{U}\mathbf{V}]_{ib}.

Tai reiškia, kad savo išvestinę dabar galime užrašyti kaip

ddvab∥X−UV∥22=−2∑i[X−UV]ibuia.\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\sum_{i}[\mathbf{X}-\mathbf{U}\mathbf{V}]_{ib}u_{ia}.

Norime, kad tai atrodytų kaip matricos a,ba, b elementas, kad galėtume naudoti tą patį metodą kaip ankstesniame pavyzdyje ir gauti matricų išraišką, o tai reiškia, kad turime sukeisti uiau_{ia} indeksų tvarką. Jei pastebėsime, kad uia=[U⊤]aiu_{ia} = [\mathbf{U}^\top]_{ai}, tada galime užrašyti

ddvab∥X−UV∥22=−2∑i[U⊤]ai[X−UV]ib.\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\sum_{i} [\mathbf{U}^\top]_{ai}[\mathbf{X}-\mathbf{U}\mathbf{V}]_{ib}.

Tai yra matricų sandauga, todėl galime daryti išvadą, kad

ddvab∥X−UV∥22=−2[U⊤(X−UV)]ab.\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2[\mathbf{U}^\top(\mathbf{X}-\mathbf{U}\mathbf{V})]_{ab}.

taigi (22.4.6) sprendinį galime užrašyti

ddV∥X−UV∥22=−2U⊤(X−UV).\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\mathbf{U}^\top(\mathbf{X} - \mathbf{U}\mathbf{V}).

Tai atitinka sprendinį, kurį atspėjome aukščiau!

Šiuo metu pagrįsta klausti: „Kodėl negaliu tiesiog užrašyti visų išmoktų matematinės analizės taisyklių matricinių versijų? Aišku, kad tai vis tiek mechaniška. Kodėl tiesiog to neatlikus!“ Iš tiesų tokių taisyklių yra, o (Petersen ir Pedersen, 2008) pateikia puikią santrauką. Tačiau dėl begalės būdų, kuriais galima derinti matricų operacijas, palyginti su pavieniais skaičiais, matricų išvestinių taisyklių yra gerokai daugiau nei vieno kintamojo. Dažnai geriausia dirbti su indeksais arba, kai tinka, palikti automatiniam diferencijavimui.

22.4.8 Santrauka

  • Aukštesnėse dimensijose galime apibrėžti gradientus, kurie atlieka tą patį vaidmenį kaip išvestinės vienoje dimensijoje. Jie leidžia pamatyti, kaip daugelio kintamųjų funkcija keičiasi, kai savavališkai mažai pakeičiame įvestis.
  • Atgalinio sklidimo algoritmą galima laikyti daugelio kintamųjų grandinės taisyklės organizavimo metodu, leidžiančiu efektyviai apskaičiuoti daug dalinių išvestinių.
  • Matricų matematinė analizė leidžia glaustai užrašyti matricinių išraiškų išvestines.

22.4.9 Pratimai

  1. Turėdami stulpelio vektorių β\boldsymbol{\beta}, apskaičiuokite ir f(x)=β⊤xf(\mathbf{x}) = \boldsymbol{\beta}^\top\mathbf{x}, ir g(x)=x⊤βg(\mathbf{x}) = \mathbf{x}^\top\boldsymbol{\beta} išvestines. Kodėl gaunate tą patį atsakymą?
  2. Tegu v\mathbf{v} yra nn dimensijų vektorius. Kam lygu ∂∂v∥v∥2\frac{\partial}{\partial\mathbf{v}}\|\mathbf{v}\|_2?
  3. Tegu L(x,y)=log⁡(ex+ey)L(x, y) = \log(e^x + e^y). Apskaičiuokite gradientą. Kokia yra gradiento komponenčių suma?
  4. Tegu f(x,y)=x2y+xy2f(x, y) = x^2y + xy^2. Parodykite, kad vienintelis kritinis taškas yra (0,0)(0,0). Nagrinėdami f(x,x)f(x, x), nustatykite, ar (0,0)(0,0) yra maksimumas, minimumas, ar nei vienas, nei kitas.
  5. Tarkime, minimizuojame funkciją f(x)=g(x)+h(x)f(\mathbf{x}) = g(\mathbf{x}) + h(\mathbf{x}). Kaip geometriškai galime interpretuoti sąlygą ∇f=0\nabla f = 0 per gg ir hh?

Diskusijos

Diskusijos

Diskusijos