====== Calculul Multivariabil ======
Acum că avem o înțelegere destul de solidă a derivatelor unei funcții de o singură variabilă, să ne întoarcem la întrebarea noastră inițială unde consideram o funcție de pierdere a potențial miliarde de ponderi.
===== Diferențierea în Dimensiuni Superioare =====
Ceea ce ne spune este că dacă schimbăm o singură pondere din aceste miliarde lăsând pe oricare alta fixă, știm ce se va întâmpla! Aceasta nu este nimic mai mult decât o funcție de o singură variabilă, deci putem scrie
$$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).$$
Vom numi derivata într-o variabilă lăsând celelalte variabile fixe //derivată parțială//, și vom folosi notația $\frac{\partial}{\partial w_1}$ pentru derivata în :eqref:''%%eq_part_der%%''.
Acum, să luăm asta și să schimbăm $w_2$ puțin în $w_2 + \epsilon_2$:
$$
\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}
$$
Am folosit din nou ideea că $\epsilon_1\epsilon_2$ este un termen de ordin superior pe care îl putem elimina în același mod în care am putut elimina $\epsilon^{2}$ în secțiunea anterioară, împreună cu ceea ce am văzut în :eqref:''%%eq_part_der%%''. Continuând în această manieră, putem scrie că
$$
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).
$$
Asta poate părea o harababură, dar putem face acest lucru mai familiar observând că suma din dreapta arată exact ca un produs scalar, deci dacă lăsăm
$$
\boldsymbol{\epsilon} = [\epsilon_1, \ldots, \epsilon_N]^\top \; \textrm{și} \;
\nabla_{\mathbf{x}} L = \left[\frac{\partial L}{\partial x_1}, \ldots, \frac{\partial L}{\partial x_N}\right]^\top,
$$
atunci
$$L(\mathbf{w} + \boldsymbol{\epsilon}) \approx L(\mathbf{w}) + \boldsymbol{\epsilon}\cdot \nabla_{\mathbf{w}} L(\mathbf{w}).$$
Vom numi vectorul $\nabla_{\mathbf{w}} L$ //gradientul// lui $L$.
Ecuația :eqref:''%%eq_nabla_use%%'' merită chibzuită pentru un moment. Are exact formatul pe care l-am întâlnit într-o dimensiune, doar că am convertit totul la vectori și produse scalare. Ne permite să spunem aproximativ cum se va schimba funcția $L$ dată fiind orice perturbație la intrare. Așa cum vom vedea în secțiunea următoare, acest lucru ne va oferi un instrument important în înțelegerea geometrică a modului în care putem învăța folosind informația conținută în gradient.
Dar mai întâi, să vedem această aproximare la lucru cu un exemplu. Să presupunem că lucrăm cu funcția
$$
f(x, y) = \log(e^x + e^y) \textrm{ cu gradientul } \nabla f (x, y) = \left[\frac{e^x}{e^x+e^y}, \frac{e^y}{e^x+e^y}\right].
$$
Dacă ne uităm la un punct precum $(0, \log(2))$, vedem că
$$
f(x, y) = \log(3) \textrm{ cu gradientul } \nabla f (x, y) = \left[\frac{1}{3}, \frac{2}{3}\right].
$$
Astfel, dacă vrem să aproximăm $f$ la $(\epsilon_1, \log(2) + \epsilon_2)$, vedem că ar trebui să avem instanța specifică a :eqref:''%%eq_nabla_use%%'':
$$
f(\epsilon_1, \log(2) + \epsilon_2) \approx \log(3) + \frac{1}{3}\epsilon_1 + \frac{2}{3}\epsilon_2.
$$
Putem testa asta în cod pentru a vedea cât de bună este aproximarea.
#@tab pytorch
%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}'
===== Geometria Gradienților și Coborârea pe Gradient =====
Considerați din nou expresia din :eqref:''%%eq_nabla_use%%'':
$$
L(\mathbf{w} + \boldsymbol{\epsilon}) \approx L(\mathbf{w}) + \boldsymbol{\epsilon}\cdot \nabla_{\mathbf{w}} L(\mathbf{w}).
$$
Să presupunem că vreau să folosesc asta pentru a ajuta la minimizarea pierderii noastre $L$. Să înțelegem geometric algoritmul de coborâre pe gradient descris pentru prima dată în . Ceea ce vom face este următoarele:
- Începem cu o alegere aleatoare pentru parametrii inițiali $\mathbf{w}$.
- Găsim direcția $\mathbf{v}$ care face ca $L$ să scadă cel mai rapid la $\mathbf{w}$.
- Facem un pas mic în acea direcție: $\mathbf{w} \rightarrow \mathbf{w} + \epsilon\mathbf{v}$.
- Repetăm.
Singurul lucru pe care nu știm exact cum să-l facem este să calculăm vectorul $\mathbf{v}$ în al doilea pas. Vom numi o astfel de direcție //direcția celei mai abrupte coborâri//. Folosind înțelegerea geometrică a produselor scalare din , vedem că putem rescrie :eqref:''%%eq_nabla_use%%'' ca
$$
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).
$$
Rețineți că am luat direcția noastră să aibă lungimea unu pentru comoditate, și am folosit $\theta$ pentru unghiul dintre $\mathbf{v}$ și $\nabla_{\mathbf{w}} L(\mathbf{w})$. Dacă vrem să găsim direcția care scade $L$ cât mai rapid posibil, vrem să facem această expresie cât mai negativă posibil. Singurul mod în care direcția pe care o alegem intră în această ecuație este prin $\cos(\theta)$, și astfel dorim să facem acest cosinus cât mai negativ posibil. Acum, reamintindu-ne forma cosinusului, putem face acest lucru cât mai negativ posibil făcând $\cos(\theta) = -1$ sau echivalent făcând unghiul dintre gradient și direcția noastră aleasă să fie $\pi$ radiani, sau echivalent $180$ de grade. Singurul mod de a obține acest lucru este să ne îndreptăm în direcția exact opusă: alegeți $\mathbf{v}$ să puncteze în direcția exact opusă lui $\nabla_{\mathbf{w}} L(\mathbf{w})$!
Aceasta ne aduce la unul dintre cele mai importante concepte matematice în învățarea automată: direcția celei mai abrupte coborâri punctează în direcția lui $-\nabla_{\mathbf{w}}L(\mathbf{w})$. Astfel algoritmul nostru informal poate fi rescris după cum urmează.
- Începem cu o alegere aleatoare pentru parametrii inițiali $\mathbf{w}$.
- Calculăm $\nabla_{\mathbf{w}} L(\mathbf{w})$.
- Facem un pas mic în opusul acelei direcții: $\mathbf{w} \leftarrow \mathbf{w} - \epsilon\nabla_{\mathbf{w}} L(\mathbf{w})$.
- Repetăm.
Acest algoritm de bază a fost modificat și adaptat în multe feluri de mulți cercetători, dar conceptul de bază rămâne același în toate. Folosiți gradientul pentru a găsi direcția care scade pierderea cât mai rapid posibil, și actualizați parametrii pentru a face un pas în acea direcție.
===== O Notă despre Optimizarea Matematică =====
De-a lungul acestei cărți, ne concentrăm direct pe tehnici de optimizare numerică din motivul practic că toate funcțiile pe care le întâlnim în cadrul învățării profunde sunt prea complexe pentru a fi minimizate explicit.
Cu toate acestea, este un exercițiu util să luăm în considerare ce ne spune înțelegerea geometrică obținută mai sus despre optimizarea funcțiilor direct.
Să presupunem că dorim să găsim valoarea lui $\mathbf{x}_0$ care minimizează o funcție $L(\mathbf{x})$. Să presupunem că, mai mult, cineva ne dă o valoare și ne spune că este valoarea care minimizează $L$. Există ceva ce putem verifica pentru a vedea dacă răspunsul lor este măcar plauzibil?
Considerați din nou :eqref:''%%eq_nabla_use%%'': $$
L(\mathbf{x}_0 + \boldsymbol{\epsilon}) \approx L(\mathbf{x}_0) + \boldsymbol{\epsilon}\cdot \nabla_{\mathbf{x}} L(\mathbf{x}_0).
$$
Dacă gradientul nu este zero, știm că putem face un pas în direcția $-\epsilon \nabla_{\mathbf{x}} L(\mathbf{x}_0)$ pentru a găsi o valoare a lui $L$ care este mai mică. Astfel, dacă suntem cu adevărat la un minim, acesta nu poate fi cazul! Putem concluziona că dacă $\mathbf{x}_0$ este un minim, atunci $\nabla_{\mathbf{x}} L(\mathbf{x}_0) = 0$. Numim punctele cu $\nabla_{\mathbf{x}} L(\mathbf{x}_0) = 0$ //puncte critice//.
Acest lucru este drăguț, deoarece în unele setări rare, //putem// găsi explicit toate punctele unde gradientul este zero, și să-l găsim pe cel cu cea mai mică valoare.
Pentru un exemplu concret, considerați funcția $$
f(x) = 3x^4 - 4x^3 -12x^2.
$$
Această funcție are derivata $$
\frac{df}{dx} = 12x^3 - 12x^2 -24x = 12x(x-2)(x+1).
$$
Singurele locații posibile ale minimelor sunt la $x = -1, 0, 2$, unde funcția ia valorile $-5,0, -32$ respectiv, și astfel putem concluziona că minimizăm funcția noastră când $x = 2$. Un grafic rapid confirmă acest lucru.
#@tab pytorch
x = torch.arange(-2, 3, 0.01)
f = (3 * x**4) - (4 * x**3) - (12 * x**2)
d2l.plot(x, f, 'x', 'f(x)')
Acest lucru subliniază un fapt important de știut când lucrați fie teoretic, fie numeric: singurele puncte posibile unde putem minimiza (sau maximiza) o funcție vor avea gradientul egal cu zero, totuși, nu orice punct cu gradient zero este adevăratul minim //global// (sau maxim).
===== Regula Lanțului Multivariată =====
Să presupunem că avem o funcție de patru variabile ($w, x, y$, și $z$) pe care o putem face compunând mulți termeni:
$$\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}$$
Astfel de lanțuri de ecuații sunt comune când lucrați cu rețele neuronale, deci încercarea de a înțelege cum să calculați gradienții unor astfel de funcții este cheie. Putem începe să vedem indicii vizuale ale acestei conexiuni în dacă aruncăm o privire la ce variabile se relaționează direct una cu cealaltă.
{{:wiki:img:chain-net1.svg|Relațiile funcției de mai sus unde nodurile reprezintă valori și muchiile arată dependența funcțională.}}
Nimic nu ne oprește doar să compunem totul din :eqref:''%%eq_multi_func_def%%'' și să scriem că
$$
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.
$$
Putem lua apoi derivata doar folosind derivate de o singură variabilă, dar dacă am face asta ne-am găsi rapid inundați cu termeni, mulți dintre care sunt repetiții! Într-adevăr, se poate vedea că, de exemplu:
$$
\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}
$$
Dacă am dori apoi de asemenea să calculăm $\frac{\partial f}{\partial x}$, am ajunge cu o ecuație similară din nou cu mulți termeni repetați, și mulți termeni repetați //împărtășiți// între cele două derivate. Aceasta reprezintă o cantitate masivă de muncă irosită, și dacă am avea nevoie să calculăm derivate în acest fel, întreaga revoluție a învățării profunde s-ar fi blocat înainte să înceapă!
Să spargem problema. Vom începe prin a încerca să înțelegem cum se schimbă $f$ când schimbăm $a$, esențial presupunând că $w, x, y$, și $z$ toate nu există. Vom raționa așa cum am făcut atunci când am lucrat cu gradientul pentru prima dată. Să luăm $a$ și să adăugăm o cantitate mică $\epsilon$ la el.
$$
\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}
$$
Prima linie urmează din definiția derivatei parțiale, și a doua urmează din definiția gradientului. Este împovărător notațional să urmărim exact unde evaluăm fiecare derivată, ca în expresia $\frac{\partial f}{\partial u}(u(a, b), v(a, b))$, așa că adesea abreviem asta la mult mai memorabilul
$$
\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}.
$$
Este util să ne gândim la sensul procesului. Încercăm să înțelegem cum o funcție de forma $f(u(a, b), v(a, b))$ își schimbă valoarea cu o schimbare în $a$. Există două căi prin care acest lucru poate avea loc: există calea unde $a \rightarrow u \rightarrow f$ și unde $a \rightarrow v \rightarrow f$. Putem calcula ambele aceste contribuții prin regula lanțului: $\frac{\partial w}{\partial u} \cdot \frac{\partial u}{\partial x}$ și respectiv $\frac{\partial w}{\partial v} \cdot \frac{\partial v}{\partial x}$, și adunate.
Imaginați-vă că avem o rețea diferită de funcții unde funcțiile din dreapta depind de cele care sunt conectate la stânga așa cum este arătat în .
{{:wiki:img:chain-net2.svg|Un alt exemplu mai subtil al regulii lanțului.}}
Pentru a calcula ceva precum $\frac{\partial f}{\partial y}$, trebuie să sumăm peste toate (în acest caz $3$) căile de la $y$ la $f$ dând
$$
\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}.
$$
Înțelegerea regulii lanțului în acest mod va aduce mari dividende atunci când încercați să înțelegeți cum curg gradienții prin rețele, și de ce diverse alegeri arhitecturale precum cele din LSTM-uri () sau straturi reziduale () pot ajuta la modelarea procesului de învățare controlând fluxul gradientului.
===== Algoritmul Backpropagation =====
Să ne întoarcem la exemplul din :eqref:''%%eq_multi_func_def%%'' secțiunea anterioară unde
$$
\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}
$$
Dacă vrem să calculăm să zicem $\frac{\partial f}{\partial w}$ putem aplica regula lanțului multi-variată pentru a vedea:
$$
\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}
$$
Să încercăm să folosim această descompunere pentru a calcula $\frac{\partial f}{\partial w}$. Observați că tot ce avem nevoie aici sunt diversele parțiale de un singur pas:
$$
\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}
$$
Dacă scriem asta în cod devine o expresie destul de gestionabilă.
#@tab all
# 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}')
Cu toate acestea, rețineți că acest lucru încă nu face ușor de calculat ceva precum $\frac{\partial f}{\partial x}$. Motivul pentru asta este //modul// în care am ales să aplicăm regula lanțului. Dacă ne uităm la ce am făcut mai sus, am păstrat întotdeauna $\partial w$ în numitor când am putut. În acest fel, am ales să aplicăm regula lanțului văzând cum $w$ a schimbat orice altă variabilă. Dacă asta este ceea ce am vrut, aceasta ar fi o idee bună. Totuși, gândiți-vă înapoi la motivația noastră din învățarea profundă: vrem să vedem cum fiecare parametru schimbă //pierderea//. În esență, vrem să aplicăm regula lanțului păstrând $\partial f$ în numărător oricând putem!
Pentru a fi mai expliciți, rețineți că putem scrie
$$
\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}
$$
Rețineți că această aplicare a regulii lanțului ne face să calculăm explicit $\frac{\partial f}{\partial u}, \frac{\partial f}{\partial v}, \frac{\partial f}{\partial a}, \frac{\partial f}{\partial b}, \; \textrm{și} \; \frac{\partial f}{\partial w}$. Nimic nu ne oprește să includem și ecuațiile:
$$
\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}
$$
și apoi să ținem evidența cum se schimbă $f$ când schimbăm //orice// nod din întreaga rețea. Să implementăm asta.
#@tab all
# 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}')
Faptul că noi calculăm derivatele de la $f$ înapoi către intrări mai degrabă decât de la intrări înainte către ieșiri (așa cum am făcut în primul fragment de cod de mai sus) este ceea ce dă acestui algoritm numele său: //backpropagation//. Rețineți că există doi pași: 1. Calculați valoarea funcției, și derivatele parțiale de un pas din față în spate. Deși nu s-a făcut mai sus, acest lucru poate fi combinat într-o singură //trecere înainte//. 2. Calculați gradientul lui $f$ din spate în față. Numim asta //trecerea înapoi//.
Acesta este exact ceea ce fiecare algoritm de învățare profundă implementează pentru a permite calculul gradientului pierderii în raport cu fiecare pondere din rețea într-o singură trecere. Este un fapt uimitor că avem o astfel de descompunere.
Pentru a vedea cum să încapsulăm asta, să aruncăm o privire rapidă la acest exemplu.
#@tab pytorch
# 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()}')
Tot ceea ce am făcut mai sus poate fi făcut automat apelând ''%%f.backwards()%%''.
===== Hessiene =====
Ca și în cazul calculului cu o singură variabilă, este util să luăm în considerare derivatele de ordin superior pentru a obține o idee despre cum putem obține o aproximare mai bună a unei funcții decât folosind doar gradientul.
Există o problemă imediată pe care o întâmpinăm atunci când lucrăm cu derivate de ordin superior ale funcțiilor de mai multe variabile, și anume că există un număr mare de ele. Dacă avem o funcție $f(x_1, \ldots, x_n)$ de $n$ variabile, atunci putem lua $n^{2}$ multe derivate secundare, anume pentru orice alegere de $i$ și $j$:
$$
\frac{d^2f}{dx_idx_j} = \frac{d}{dx_i}\left(\frac{d}{dx_j}f\right).
$$
Aceasta este asamblată în mod tradițional într-o matrice numită //Hessiana//:
$$\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}.$$
Nu fiecare intrare a acestei matrice este independentă. Într-adevăr, putem arăta că atâta timp cât ambele //parțiale mixte// (derivate parțiale în raport cu mai mult de o variabilă) există și sunt continue, putem spune că pentru orice $i$, și $j$,
$$
\frac{d^2f}{dx_idx_j} = \frac{d^2f}{dx_jdx_i}.
$$
Aceasta urmează considerând mai întâi perturbarea unei funcții în direcția $x_i$, și apoi perturbând-o în $x_j$ și apoi comparând rezultatul acesteia cu ce se întâmplă dacă perturbăm mai întâi $x_j$ și apoi $x_i$, cu cunoștința că ambele aceste ordini duc la aceeași schimbare finală în ieșirea lui $f$.
Ca și în cazul variabilelor unice, putem folosi aceste derivate pentru a obține o idee mult mai bună despre cum se comportă funcția lângă un punct. În particular, o putem folosi pentru a găsi cea mai bună pătratică potrivită lângă un punct $\mathbf{x}_0$, așa cum am văzut într-o singură variabilă.
Să vedem un exemplu. Să presupunem că $f(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}$. Aceasta este forma generală pentru o pătratică în două variabile. Dacă ne uităm la valoarea funcției, gradientul ei, și Hessiana ei :eqref:''%%eq_hess_def%%'', toate la punctul zero:
$$
\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}
$$
ne putem primi polinomul original înapoi spunând
$$
f(\mathbf{x}) = f(0) + \nabla f (0) \cdot \mathbf{x} + \frac{1}{2}\mathbf{x}^\top \mathbf{H} f (0) \mathbf{x}.
$$
În general, dacă am calculat această expansiune în orice punct $\mathbf{x}_0$, vedem că
$$
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).
$$
Aceasta funcționează pentru orice intrare dimensională, și oferă cea mai bună aproximare pătratică oricărei funcții la un punct. Pentru a da un exemplu, să reprezentăm grafic funcția
$$
f(x, y) = xe^{-x^2-y^2}.
$$
Se poate calcula că gradientul și Hessiana sunt $$
\nabla f(x, y) = e^{-x^2-y^2}\begin{pmatrix}1-2x^2 \\ -2xy\end{pmatrix} \; \textrm{și} \; \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}.
$$
Și astfel, cu puțină algebră, vedem că aproximarea pătratică la $[-1,0]^\top$ este
$$
f(x, y) \approx e^{-1}\left(-1 - (x+1) +(x+1)^2+y^2\right).
$$
#@tab pytorch
# 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
Aceasta formează baza pentru Algoritmul lui Newton discutat în , unde efectuăm optimizarea numerică iterativ găsind cea mai bună pătratică potrivită, și apoi minimizând exact acea pătratică.
===== Puțin Calcul Matriceal =====
Derivatele funcțiilor care implică matrice se dovedesc a fi deosebit de frumoase. Această secțiune poate deveni greoaie notațional, deci poate fi sărită la o primă citire, dar este util de știut cum derivatele funcțiilor care implică operații comune matriceale sunt adesea mult mai curate decât s-ar putea anticipa inițial, în special având în vedere cât de centrale sunt operațiile matriceale pentru aplicațiile de învățare profundă.
Să începem cu un exemplu. Să presupunem că avem un vector coloană fix $\boldsymbol{\beta}$, și vrem să luăm funcția produs $f(\mathbf{x}) = \boldsymbol{\beta}^\top\mathbf{x}$, și să înțelegem cum se schimbă produsul scalar atunci când schimbăm $\mathbf{x}$.
O mică notație care va fi utilă când lucrăm cu derivate de matrice în ML este numită //derivata matriceală de layout numitor// unde asamblăm derivatele parțiale în forma oricărui vector, matrice, sau tensor este în numitorul diferențialei. În acest caz, vom scrie
$$
\frac{df}{d\mathbf{x}} = \begin{bmatrix}
\frac{df}{dx_1} \\
\vdots \\
\frac{df}{dx_n}
\end{bmatrix},
$$
unde am potrivit forma vectorului coloană $\mathbf{x}$.
Dacă scriem funcția noastră în componente aceasta este
$$
f(\mathbf{x}) = \sum_{i = 1}^{n} \beta_ix_i = \beta_1x_1 + \cdots + \beta_nx_n.
$$
Dacă luăm acum derivata parțială în raport cu să zicem $\beta_1$, observați că totul este zero în afară de primul termen, care este doar $x_1$ înmulțit cu $\beta_1$, deci obținem că
$$
\frac{df}{dx_1} = \beta_1,
$$
sau mai general că
$$
\frac{df}{dx_i} = \beta_i.
$$
Putem acum reasambla asta într-o matrice pentru a vedea
$$
\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}.
$$
Aceasta ilustrează câțiva factori despre calculul matriceal pe care îi vom întâlni adesea pe parcursul acestei secțiuni:
* Mai întâi, Calculele vor deveni destul de complicate.
* În al doilea rând, Rezultatele finale sunt mult mai curate decât procesul intermediar, și vor arăta întotdeauna similar cu cazul variabilei unice. În acest caz, observați că $\frac{d}{dx}(bx) = b$ și $\frac{d}{d\mathbf{x}} (\boldsymbol{\beta}^\top\mathbf{x}) = \boldsymbol{\beta}$ sunt ambele similare.
* În al treilea rând, transpusele pot apărea adesea aparent de nicăieri. Motivul principal pentru aceasta este convenția că potrivim forma numitorului, astfel când înmulțim matrice, va trebui să luăm transpuse pentru a ne potrivi înapoi la forma termenului original.
Pentru a continua construirea intuiției, să încercăm un calcul care este puțin mai greu. Să presupunem că avem un vector coloană $\mathbf{x}$, și o matrice pătrată $A$ și vrem să calculăm
$$\frac{d}{d\mathbf{x}}(\mathbf{x}^\top A \mathbf{x}).$$
Pentru a conduce spre o notație mai ușor de manipulat, să considerăm această problemă folosind notația Einstein. În acest caz putem scrie funcția ca
$$
\mathbf{x}^\top A \mathbf{x} = x_ia_{ij}x_j.
$$
Pentru a calcula derivata noastră, trebuie să înțelegem pentru fiecare $k$, care este valoarea
$$
\frac{d}{dx_k}(\mathbf{x}^\top A \mathbf{x}) = \frac{d}{dx_k}x_ia_{ij}x_j.
$$
Prin regula produsului, aceasta este
$$
\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}.
$$
Pentru un termen ca $\frac{dx_i}{dx_k}$, nu este greu de văzut că acesta este unu când $i=k$ și zero altfel. Asta înseamnă că fiecare termen unde $i$ și $k$ sunt diferite dispar din această sumă, deci singurii termeni care rămân în acea primă sumă sunt cei unde $i=k$. Același raționament este valabil pentru al doilea termen unde avem nevoie de $j=k$. Aceasta dă
$$
\frac{d}{dx_k}x_ia_{ij}x_j = a_{kj}x_j + x_ia_{ik}.
$$
Acum, numele indicilor în notația Einstein sunt arbitrari—faptul că $i$ și $j$ sunt diferiți este imaterial pentru acest calcul în acest punct, deci putem re-indexa astfel încât să folosească amândoi $i$ pentru a vedea că
$$
\frac{d}{dx_k}x_ia_{ij}x_j = a_{ki}x_i + x_ia_{ik} = (a_{ki} + a_{ik})x_i.
$$
Acum, aici este locul unde începem să avem nevoie de ceva practică pentru a merge mai departe. Să încercăm să identificăm acest rezultat în termeni de operații matriceale. $a_{ki} + a_{ik}$ este componenta $k, i$ a lui $\mathbf{A} + \mathbf{A}^\top$. Aceasta dă
$$
\frac{d}{dx_k}x_ia_{ij}x_j = [\mathbf{A} + \mathbf{A}^\top]_{ki}x_i.
$$
Similar, acest termen este acum produsul matricei $\mathbf{A} + \mathbf{A}^\top$ cu vectorul $\mathbf{x}$, deci vedem că
$$
\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.
$$
Astfel, vedem că intrarea $k$ a derivatei dorite din :eqref:''%%eq_mat_goal_1%%'' este doar intrarea $k$ a vectorului din dreapta, și astfel cele două sunt la fel. Astfel rezultă
$$
\frac{d}{d\mathbf{x}}(\mathbf{x}^\top A \mathbf{x}) = (\mathbf{A} + \mathbf{A}^\top)\mathbf{x}.
$$
Aceasta a necesitat semnificativ mai multă muncă decât ultima, dar rezultatul final este mic. Mai mult decât atât, considerați următorul calcul pentru derivate tradiționale de variabilă unică:
$$
\frac{d}{dx}(xax) = \frac{dx}{dx}ax + xa\frac{dx}{dx} = (a+a)x.
$$
Echivalent $\frac{d}{dx}(ax^2) = 2ax = (a+a)x$. Din nou, obținem un rezultat care arată destul de asemănător cu rezultatul variabilei unice dar cu o transpusă aruncată înăuntru.
În acest punct, modelul ar trebui să arate destul de suspect, așa că să încercăm să ne dăm seama de ce. Când luăm derivate matriceale ca aceasta, să presupunem mai întâi că expresia pe care o obținem va fi o altă expresie matriceală: o expresie pe care o putem scrie în termeni de produse și sume de matrice și transpusele lor. Dacă o astfel de expresie există, va trebui să fie adevărată pentru toate matricele. În particular, va trebui să fie adevărată pentru matrice $1 \times 1$, caz în care produsul matriceal este doar produsul numerelor, suma matriceală este doar suma, și transpusa nu face nimic deloc! Cu alte cuvinte, orice expresie obținem //trebuie// să se potrivească cu expresia variabilei unice. Asta înseamnă că, cu puțină practică, se pot ghici adesea derivate matriceale doar știind cum trebuie să arate expresia asociată variabilei unice!
Să încercăm asta. Să presupunem că $\mathbf{X}$ este o matrice $n \times m$, $\mathbf{U}$ este un $n \times r$ și $\mathbf{V}$ este un $r \times m$. Să încercăm să calculăm
$$\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2} = \;?$$
Acest calcul este important într-o zonă numită factorizarea matricelor. Pentru noi, totuși, este doar o derivată de calculat. Să încercăm să ne imaginăm ce ar fi aceasta pentru matrice $1\times1$. În acel caz, obținem expresia
$$
\frac{d}{dv} (x-uv)^{2}= -2(x-uv)u,
$$
unde, derivata este destul de standard. Dacă încercăm să convertim asta înapoi într-o expresie matriceală obținem
$$
\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2(\mathbf{X} - \mathbf{U}\mathbf{V})\mathbf{U}.
$$
Totuși, dacă ne uităm la asta nu funcționează chiar. Reamintiți-vă că $\mathbf{X}$ este $n \times m$, la fel ca $\mathbf{U}\mathbf{V}$, deci matricea $2(\mathbf{X} - \mathbf{U}\mathbf{V})$ este $n \times m$. Pe de altă parte $\mathbf{U}$ este $n \times r$, și nu putem înmulți o matrice $n \times m$ și una $n \times r$ deoarece dimensiunile nu se potrivesc!
Vrem să obținem $\frac{d}{d\mathbf{V}}$, care este aceeași formă ca $\mathbf{V}$, care este $r \times m$. Așa că cumva trebuie să luăm o matrice $n \times m$ și o matrice $n \times r$, să le înmulțim împreună (poate cu niște transpuse) pentru a obține un $r \times m$. Putem face asta înmulțind $U^\top$ cu $(\mathbf{X} - \mathbf{U}\mathbf{V})$. Astfel, putem ghici că soluția la :eqref:''%%eq_mat_goal_2%%'' este
$$
\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\mathbf{U}^\top(\mathbf{X} - \mathbf{U}\mathbf{V}).
$$
Pentru a arăta că acest lucru funcționează, am fi neglijenți să nu oferim un calcul detaliat. Dacă credem deja că această regulă empirică funcționează, simțiți-vă liberi să săriți peste această derivare. Pentru a calcula
$$
\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^2,
$$
trebuie să găsim pentru fiecare $a$, și $b$
$$
\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.
$$
Reamintindu-ne că toate intrările lui $\mathbf{X}$ și $\mathbf{U}$ sunt constante în ceea ce privește $\frac{d}{dv_{ab}}$, putem împinge derivata în interiorul sumei, și aplica regula lanțului la pătrat pentru a obține
$$
\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).
$$
Ca și în derivarea anterioară, putem observa că $\frac{dv_{kj}}{dv_{ab}}$ este nenul doar dacă $k=a$ și $j=b$. Dacă oricare dintre acele condiții nu se respectă, termenul din sumă este zero, și îl putem elimina liber. Vedem că
$$
\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}.
$$
O subtilitate importantă aici este că cerința ca $k=a$ nu apare în interiorul sumei interne deoarece acel $k$ este o variabilă, pe care o sumăm în interiorul termenului intern. Pentru un exemplu notațional mai curat, considerați de ce
$$
\frac{d}{dx_1} \left(\sum_i x_i \right)^{2}= 2\left(\sum_i x_i \right).
$$
Din acest punct, putem începe să identificăm componente ale sumei. Mai întâi,
$$
\sum_k u_{ik}v_{kb} = [\mathbf{U}\mathbf{V}]_{ib}.
$$
Deci întreaga expresie din interiorul sumei este
$$
x_{ib} - \sum_k u_{ik}v_{kb} = [\mathbf{X}-\mathbf{U}\mathbf{V}]_{ib}.
$$
Asta înseamnă că putem scrie acum derivata noastră ca
$$
\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\sum_{i}[\mathbf{X}-\mathbf{U}\mathbf{V}]_{ib}u_{ia}.
$$
Vrem ca asta să arate ca elementul $a, b$ al unei matrice ca să putem folosi tehnica ca în exemplul anterior pentru a ajunge la o expresie matriceală, ceea ce înseamnă că trebuie să schimbăm ordinul indicilor pe $u_{ia}$. Dacă observăm că $u_{ia} = [\mathbf{U}^\top]_{ai}$, putem scrie atunci
$$
\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}.
$$
Acesta este un produs matriceal, și astfel putem concluziona că
$$
\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2[\mathbf{U}^\top(\mathbf{X}-\mathbf{U}\mathbf{V})]_{ab}.
$$
și astfel putem scrie soluția la :eqref:''%%eq_mat_goal_2%%''
$$
\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\mathbf{U}^\top(\mathbf{X} - \mathbf{U}\mathbf{V}).
$$
Aceasta se potrivește cu soluția pe care am ghicit-o mai sus!
Este rezonabil să întrebăm în acest punct, „De ce nu pot scrie doar versiuni matriceale ale tuturor regulilor de calcul pe care le-am învățat? Este clar că acest lucru este încă mecanic. De ce nu terminăm pur și simplu cu asta!” Și într-adevăr există astfel de reguli și ((Petersen.Pedersen.ea.2008)) oferă un rezumat excelent. Totuși, datorită pletoarei de moduri în care operațiile matriceale pot fi combinate comparativ cu valorile unice, există mult mai multe reguli derivate matriceale decât cele de variabilă unică. Este adesea cazul că este cel mai bine să lucrăm cu indicii, sau să lăsăm asta pe seama diferențierii automate când este cazul.
===== Rezumat =====
* În dimensiuni superioare, putem defini gradienți care servesc aceluiași scop ca derivatele într-o dimensiune. Aceștia ne permit să vedem cum se schimbă o funcție multi-variabilă când facem o schimbare mică arbitrară la intrări.
* Algoritmul backpropagation poate fi văzut ca o metodă de organizare a regulii lanțului multi-variate pentru a permite calculul eficient al multor derivate parțiale.
* Calculul matriceal ne permite să scriem derivatele expresiilor matriceale în moduri concise.
===== Exerciții =====
- Dat un vector coloană $\boldsymbol{\beta}$, calculați derivatele atât pentru $f(\mathbf{x}) = \boldsymbol{\beta}^\top\mathbf{x}$ cât și $g(\mathbf{x}) = \mathbf{x}^\top\boldsymbol{\beta}$. De ce obțineți același răspuns?
- Fie $\mathbf{v}$ un vector de dimensiune $n$. Cât este $\frac{\partial}{\partial\mathbf{v}}\|\mathbf{v}\|_2$?
- Fie $L(x, y) = \log(e^x + e^y)$. Calculați gradientul. Care este suma componentelor gradientului?
- Fie $f(x, y) = x^2y + xy^2$. Arătați că singurul punct critic este $(0,0)$. Considerând $f(x, x)$, determinați dacă $(0,0)$ este un maxim, minim, sau niciuna.
- Să presupunem că minimizăm o funcție $f(\mathbf{x}) = g(\mathbf{x}) + h(\mathbf{x})$. Cum putem interpreta geometric condiția $\nabla f = 0$ în termeni de $g$ și $h$?
[[https://discuss.d2l.ai/t/1090|Discuții]]