.. _sec_multivariable_calculus:
Çok Değişkenli Hesap
====================
Artık tek değişkenli bir fonksiyonun türevleri hakkında oldukça güçlü
bir anlayışa sahip olduğumuza göre, potansiyel olarak milyarlarca
ağırlığa sahip bir kayıp (yitim) fonksiyonunu düşündüğümüz esas sorumuza
dönelim.
Yüksek Boyutlu Türev Alma
-------------------------
:numref:`sec_single_variable_calculus` bize, bu milyarlarca ağırlıktan
diğer her birini sabit bırakarak sadece birini değiştirirsek ne
olacağını bildiğimizi söyler! Bu, tek değişkenli bir fonksiyondan başka
bir şey değildir, bu yüzden şöyle yazabiliriz
.. math:: 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).
:label: eq_part_der
Diğer değişkenleri sabit tutarken bir değişkendeki türeve *kısmi türev*
diyeceğiz ve :eq:`eq_part_der` denklemindeki türev için
:math:`\frac{\partial}{\partial w_1}` gösterimini kullanacağız.
Şimdi bunu alalım ve :math:`w_2`'yi biraz :math:`w_2 + \epsilon_2`
olarak değiştirelim:
.. math::
\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}
Bir kez daha, :math:`\epsilon_1 \epsilon_2`'nin daha yüksek bir terim
olduğu fikrini kullandık ve önceki bölümde gördüğümüz
:math:`\epsilon^{2}` ile aynı şekilde :eq:`eq_part_der`
denkleminden atabildik. Bu şekilde devam ederek şunu yazabiliriz:
.. math::
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).
Bu bir karmaşa gibi görünebilir, ancak sağdaki toplamın tam olarak bir
iç çarpıma benzediğini fark ederek bunu daha tanıdık hale getirebiliriz.
.. math::
\boldsymbol{\epsilon} = [\epsilon_1, \ldots, \epsilon_N]^\top \; \text{and} \;
\nabla_{\mathbf{x}} L = \left[\frac{\partial L}{\partial x_1}, \ldots, \frac{\partial L}{\partial x_N}\right]^\top,
o zaman da:
.. math:: L(\mathbf{w} + \boldsymbol{\epsilon}) \approx L(\mathbf{w}) + \boldsymbol{\epsilon}\cdot \nabla_{\mathbf{w}} L(\mathbf{w}).
:label: eq_nabla_use
:math:`\nabla_{\mathbf{w}}L`'yı :math:`L`'nin *gradyanı* olarak
adlandıracağız.
Denklem :eq:`eq_nabla_use` bir an üstünde düşünmeye değerdir. Tam
olarak bir boyutta karşılaştığımız formata sahip, sadece her şeyi
vektörlere ve nokta çarpımlarına dönüştürdük. Girdiye herhangi ufak bir
dürtme verildiğinde :math:`L` fonksiyonunun nasıl değişeceğini yaklaşık
olarak söylememizi sağlar. Bir sonraki bölümde göreceğimiz gibi, bu bize
gradyanda bulunan bilgileri kullanarak nasıl öğrenebileceğimizi
geometrik olarak anlamamız için önemli bir araç sağlayacaktır.
Ama önce bu yaklaşıklamayı bir örnekle iş başında görelim. Şu fonksiyon
ile çalıştığımızı varsayalım:
.. math::
f(x, y) = \log(e^x + e^y) \text{ ve gradyanı } \nabla f (x, y) = \left[\frac{e^x}{e^x+e^y}, \frac{e^y}{e^x+e^y}\right].
:math:`(0,\log(2))` gibi bir noktaya bakarsak görürüz ki
.. math::
f(x, y) = \log(3) \text{ ve gradyanı } \nabla f (x, y) = \left[\frac{1}{3}, \frac{2}{3}\right].
Bu nedenle, :math:`(\epsilon_1, \log(2) + \epsilon_2)` konumunda
:math:`f`'ye yaklaşmak istiyorsak, şu özel örneğe sahip olmamız
gerektiğini görürüz :eq:`eq_nabla_use`:
.. math::
f(\epsilon_1, \log(2) + \epsilon_2) \approx \log(3) + \frac{1}{3}\epsilon_1 + \frac{2}{3}\epsilon_2.
Yaklaşıklamanın ne kadar iyi olduğunu görmek için bunu kodda test
edebiliriz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
%matplotlib inline
from IPython import display
from mpl_toolkits import mplot3d
from d2l import mxnet as d2l
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}'
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
'approximation: 1.0819456577301025, true Value: 1.0821242332458496'
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
%matplotlib inline
import numpy as np
import torch
from IPython import display
from mpl_toolkits import mplot3d
from d2l import torch as d2l
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}'
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
'approximation: tensor([1.0819]), true Value: tensor([1.0821])'
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
%matplotlib inline
import numpy as np
from IPython import display
from mpl_toolkits import mplot3d
import tensorflow as tf
from d2l import tensorflow as d2l
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}'
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
'approximation: [1.0819457], true Value: [1.0821242]'
.. raw:: html
.. raw:: html
Gradyanların Geometrisi ve Gradyan (Eğim) İnişi
-----------------------------------------------
:eq:`eq_nabla_use` denklemindeki ifadeyi tekrar düşünün:
.. math::
L(\mathbf{w} + \boldsymbol{\epsilon}) \approx L(\mathbf{w}) + \boldsymbol{\epsilon}\cdot \nabla_{\mathbf{w}} L(\mathbf{w}).
Diyelim ki, bunu :math:`L` kaybımızı en aza indirmeye yardımcı olmak
için kullanmak istiyoruz. İlk önce :numref:`sec_autograd` içinde
açıklanan gradyan iniş algoritmasını geometrik olarak anlayalım.
Yapacağımız şey şudur:
1. :math:`\mathbf{w}` başlangıç parametreleri için rastgele bir seçimle
başlayın.
2. :math:`L`'nin :math:`\mathbf{w}` seviyesinde en hızlı azalmasını
sağlayan :math:`\mathbf{v}` yönünü bulun.
3. Bu yönde küçük bir adım atın:
:math:`\mathbf{w} \rightarrow \mathbf{w} + \epsilon\mathbf{v}`.
4. Tekrar edin.
Tam olarak nasıl yapılacağını bilmediğimiz tek şey, ikinci adımdaki
:math:`\mathbf{v}` vektörünü hesaplamaktır. Böyle bir yöne *en dik iniş
yönü* diyeceğiz. :numref:`sec_geometry-linear-algebraic-ops`
konusundan nokta çarpımlarının geometrik anlamını kullanarak, şunu,
:eq:`eq_nabla_use`, yeniden yazabileceğimizi görüyoruz:
.. math::
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).
Kolaylık olması açısından yönümüzü birim uzunluğa sahip olacak şekilde
aldığımızı ve :math:`\mathbf{v}` ile
:math:`\nabla_{\mathbf{w}} L(\mathbf{w})` arasındaki açı için
:math:`\theta`'yı kullandığımızı unutmayın. :math:`L`'nin olabildiğince
hızlı azalan yönünü bulmak istiyorsak, bu ifadeyi olabildiğince negatif
olarak ifade etmek isteriz. Seçtiğimiz yönün bu denkleme girmesinin tek
yolu :math:`\cos(\theta)` sayesindedir ve bu yüzden bu kosinüsü
olabildiğince negatif yapmak istiyoruz. Şimdi, kosinüs şeklini
hatırlarsak, bunu mümkün olduğunca negatif yapmak için
:math:`\cos(\theta) = -1` yapmamız veya eşdeğer olarak gradyan ile
seçtiğimiz yön arasındaki açıyı :math:`\pi` radyan olacak şekilde, diğer
anlamda :math:`180` derece yapmamız gerekecektir. Bunu başarmanın tek
yolu, tam ters yöne gitmektir: :math:`\nabla_{\mathbf{w}} L(\mathbf{w})`
yönünün tam tersini gösteren :math:`\mathbf{v}`'yi seçin!
Bu bizi makine öğrenmesindeki en önemli matematiksel kavramlardan birine
getiriyor: :math:`-\nabla_{\mathbf{w}}L(\mathbf{w})` yönündeki en dik
yokuş noktaların yönü. Böylece resmi olmayan algoritmamız aşağıdaki gibi
yeniden yazılabilir.
1. :math:`\mathbf{w}` ilk parametreleri için rastgele bir seçimle
başlayın.
2. :math:`\nabla_{\mathbf{w}} L(\mathbf{w})`'yi hesaplayın.
3. Ters yönde küçük bir adım atın:
:math:`\mathbf{w} \rightarrow \mathbf{w} - \epsilon\nabla_{\mathbf{w}} L(\mathbf{w})`.
4. Tekrar edin.
Bu temel algoritma, birçok araştırmacı tarafından birçok şekilde
değiştirilmiş ve uyarlanmıştır, ancak temel kavram hepsinde aynı kalır.
Kaybı olabildiğince hızlı azaltan yönü bulmak için gradyanı kullanın ve
bu yönde bir adım atmak için parametreleri güncelleyin.
Matematiksel Optimizasyon (Eniyileme) Üzerine Bir Not
-----------------------------------------------------
Bu kitap boyunca, derin öğrenme ortamında karşılaştığımız tüm işlevlerin
açıkça en aza indirilemeyecek kadar karmaşık olmasından dolayı pratik
nedenlerle doğrudan sayısal eniyileme tekniklerine odaklanıyoruz.
Bununla birlikte, yukarıda elde ettiğimiz geometrik anlayışın bize
fonksiyonları doğrudan optimize etme hakkında ne söylediğini düşünmek
faydalı bir alıştırmadır.
Bir :math:`L(\mathbf{x})` işlevini en aza indiren
:math:`\mathbf{x}_0`'nin değerini bulmak istediğimizi varsayalım.
Diyelim ki birisi bize bir değer veriyor ve bize :math:`L`'yi en aza
indiren şeyin bu değer olduğunu söylüyor. Yanıtın makul olup olmadığını
kontrol edebileceğimiz bir şey var mı?
Tekrar düşünün :eq:`eq_nabla_use`:
.. math::
L(\mathbf{x}_0 + \boldsymbol{\epsilon}) \approx L(\mathbf{x}_0) + \boldsymbol{\epsilon}\cdot \nabla_{\mathbf{x}} L(\mathbf{x}_0).
Gradyan sıfır değilse, :math:`L`'nin daha küçük değerini bulmak için
:math:`-\epsilon \nabla_{\mathbf{x}} L(\mathbf{x}_0)` yönünde bir adım
atabileceğimizi biliyoruz. Bu nedenle, gerçekten en düşük değerde isek,
böyle bir durum olamaz! :math:`\mathbf{x}_0` bir minimum ise,
:math:`\nabla_{\mathbf{x}} L(\mathbf{x}_{0}) = 0` olduğu sonucuna
varabiliriz. :math:`\nabla_{\mathbf{x}} L(\mathbf{x}_0) = 0` ifadesi
gerçek olan noktaları *kritik nokta* diye çağırıyoruz.
Bu güzel bir bilgi, çünkü bazı nadir durumlarda gradyanın sıfır olduğu
tüm noktaları açıkça *bulabiliriz* ve en küçük değere sahip olanı
bulabiliriz.
Somut bir örnek için bu işlevi düşünün:
.. math::
f(x) = 3x^4 - 4x^3 -12x^2.
Bu fonksiyonun türevi aşağıdadır:
.. math::
\frac{df}{dx} = 12x^3 - 12x^2 -24x = 12x(x-2)(x+1).
Minimumun olası konumları :math:`x = -1, 0, 2`'dir, burada fonksiyon
sırasıyla :math:`-5,0, -32` değerlerini alır ve böylece :math:`x =2`
olduğunda fonksiyonumuzu küçülttüğümüz sonucuna varabiliriz. Hızlı bir
çizim bunu doğrular.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
x = np.arange(-2, 3, 0.01)
f = (3 * x**4) - (4 * x**3) - (12 * x**2)
d2l.plot(x, f, 'x', 'f(x)')
.. figure:: output_multivariable-calculus_bf678a_15_0.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
x = torch.arange(-2, 3, 0.01)
f = (3 * x**4) - (4 * x**3) - (12 * x**2)
d2l.plot(x, f, 'x', 'f(x)')
.. figure:: output_multivariable-calculus_bf678a_18_0.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
x = tf.range(-2, 3, 0.01)
f = (3 * x**4) - (4 * x**3) - (12 * x**2)
d2l.plot(x, f, 'x', 'f(x)')
.. figure:: output_multivariable-calculus_bf678a_21_0.svg
.. raw:: html
.. raw:: html
Bu, teorik veya sayısal olarak çalışırken bilinmesi gereken önemli bir
gerçeğin altını çiziyor: Bir işlevi en aza indirebileceğimiz (veya en
yükseğe çıkarabileceğimiz) olası noktalar sıfıra eşit bir gradyana sahip
olacaktır, ancak sıfır gradyanlı her nokta gerçek *küresel (global)*
minimum (veya maksimum) değildir.
Çok Değişkenli Zincir Kuralı
----------------------------
Pek çok terim oluşturarak yapabileceğimiz dört değişkenli
(:math:`w, x, y` ve :math:`z`) bir fonksiyonumuz olduğunu varsayalım:
.. math:: \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}
:label: eq_multi_func_def
Sinir ağları ile çalışırken bu tür denklem zincirleri yaygındır, bu
nedenle bu tür işlevlerin gradyanlarının nasıl hesaplanacağını anlamaya
çalışmak çok önemlidir. Hangi değişkenlerin doğrudan birbiriyle ilişkili
olduğuna bakarsak, bu bağlantının görsel ipuçlarını
:numref:`fig_chain-1` içinde görmeye başlayabiliriz.
.. _fig_chain-1:
.. figure:: ../img/chain-net1.svg
Düğümlerin değerleri temsil ettiği ve kenarların işlevsel bağımlılığı
gösterdiği yukarıda geçen işlev ilişkileri.
Hiçbir şey bizi sadece :eq:`eq_multi_func_def` denkleminden her
şeyi birleştirmekten ve bunu yazmaktan alıkoyamaz
.. math::
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.
O zaman türevi sadece tek değişkenli türevler kullanarak alabiliriz,
ancak bunu yaparsak, kendimizi hızlı bir şekilde terimlere boğulmuş
buluruz, çoğu da tekrar eder! Gerçekten de, örneğin şunu görebiliriz:
.. math::
\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}
Daha sonra :math:`\frac{\partial f}{\partial x}`'i de hesaplamak
isteseydik, birçok tekrarlanan terim ve iki türev arasında birçok
*paylaşılan* tekrarlanan terimle tekrar benzer bir denklem elde ederdik.
Bu, büyük miktarda boşa harcanan işi temsil ediyor ve eğer türevleri bu
şekilde hesaplamamız gerekseydi, tüm derin öğrenme devrimi başlamadan
önce durmuş olurdu!
Sorunu çözelim. :math:`a`'yı değiştirdiğimizde :math:`f`'nin nasıl
değiştiğini anlamaya çalışarak başlayacağız, esasen :math:`w, x, y` ve
:math:`z` değerlerinin mevcut olmadığını varsayarak yapacağız. Gradyan
ile ilk kez çalışırken yaptığımız gibi akıl yürüteceğiz. Bir :math:`a`
alalım ve ona küçük bir miktar :math:`\epsilon` ekleyelim.
.. math::
\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}
İlk satır kısmi türev tanımından, ikincisi gradyan tanımından gelir.
:math:`\frac{\partial f}{\partial u}(u(a, b), v(a, b))` ifadesinde
olduğu gibi, her türevi tam olarak nerede değerlendirdiğimizi izlemek
gösterimsel olarak külfetlidir, bu nedenle sık sık bunu çok daha akılda
kalıcı olarak kısaltırız.
.. math::
\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}.
Sürecin anlamını düşünmekte fayda var. :math:`f(u (a, b), v(a, b))`
biçimindeki bir fonksiyonun :math:`a`'daki bir değişiklikle değerini
nasıl değiştirdiğini anlamaya çalışıyoruz. Bunun meydana gelebileceği
iki yol vardır: :math:`a \rightarrow u \rightarrow f` ve
:math:`a \rightarrow v \rightarrow f`. Bu katkıların her ikisini de
zincir kuralı aracılığıyla hesaplayabiliriz:
:math:`\frac{\partial w}{\partial u} \cdot \frac{\partial u}{\partial x}`
ve
:math:`\frac{\partial w}{\partial v} \cdot \frac{\partial v}{\partial x}`
hesaplanırlar ve toplanırlar.
Sağdaki işlevlerin, :numref:`fig_chain-2` şeklinde gösterildiği gibi
soldakilere bağlı olan işlevlere bağımlı olduğu farklı bir işlev ağımız
olduğunu hayal edin.
.. _fig_chain-2:
.. figure:: ../img/chain-net2.svg
Zincir kuralının daha ince bir başka örneği.
:math:`\frac{\partial f}{\partial y}` gibi bir şeyi hesaplamak için,
:math:`y`'den :math:`f`'e kadar tüm (bu durumda :math:`3`) yolları
toplamamız gerekir.
.. math::
\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}.
Zincir kuralını bu şekilde anlamak, gradyanların ağlar boyunca nasıl
aktığını ve neden LSTM'lerdeki (:numref:`sec_lstm`) veya artık
(residual) katmanlardaki (:numref:`sec_resnet`) gibi çeşitli mimari
seçimlerin gradyan akışını kontrol etmeye yardımcı olarak öğrenme
sürecini şekillendirdiğini anlamaya çalışırken büyük kazançlar
sağlayacaktır.
Geri Yayma Algoritması
----------------------
Önceki bölümdeki :eq:`eq_multi_func_def` örneğine dönelim:
.. math::
\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}
Diyelim ki :math:`\frac{\partial f}{\partial w}`'yi hesaplıyoruz, çok
değişkenli zincir kuralını uygularsak şunu görebiliriz:
.. math::
\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}
:math:`\frac{\partial f}{\partial w}` hesaplamak için bu ayrıştırmayı
kullanmayı deneyelim. Burada ihtiyacımız olan tek şeyin çeşitli tek
adımlık kısmi türevler olduğuna dikkat edin:
.. math::
\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}
Bunu kodda yazarsak, bu oldukça yönetilebilir bir ifade olur.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# Girdilerden çıktılara fonksiyonun değerini hesapla
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}')
# Tek adımlı kısmileri hesapla
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)
# Girdilerden çıktılara nihai sonucu hesapla
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}')
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
f at -1, 0, -2, 1 is 1024
df/dw at -1, 0, -2, 1 is -4096
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# Girdilerden çıktılara fonksiyonun değerini hesapla
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}')
# Tek adımlı kısmileri hesapla
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)
# Girdilerden çıktılara nihai sonucu hesapla
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}')
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
f at -1, 0, -2, 1 is 1024
df/dw at -1, 0, -2, 1 is -4096
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# Girdilerden çıktılara fonksiyonun değerini hesapla
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}')
# Tek adımlı kısmileri hesapla
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)
# Girdilerden çıktılara nihai sonucu hesapla
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}')
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
f at -1, 0, -2, 1 is 1024
df/dw at -1, 0, -2, 1 is -4096
.. raw:: html
.. raw:: html
Ancak, bunun hala :math:`\frac{\partial f}{\partial x}` gibi bir şeyi
hesaplamayı kolaylaştırmadığını unutmayın. Bunun nedeni, zincir kuralını
uygulamayı seçtiğimiz *yoldur*. Yukarıda ne yaptığımıza bakarsak,
elimizden geldiğince paydada her zaman :math:`\partial w` tuttuk. Bu
şekilde, :math:`w` değişkenin her değişkeni nasıl değiştirdiğini görerek
zincir kuralını uygulamayı seçtik. İstediğimiz buysa, bu iyi bir fikir
olabilir. Bununla birlikte, derin öğrenmedeki motivasyonumuza geri
dönün: Her parametrenin *kaybı* nasıl değiştirdiğini görmek istiyoruz.
Esasında, yapabildiğimiz her yerde :math:`\partial f`'yi payda tutan
zincir kuralını uygulamak istiyoruz!
Daha açık olmak gerekirse, şunu yazabileceğimize dikkat edin:
.. math::
\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}
Zincir kuralının bu uygulaması bizim açıkça
:math:`\frac{\partial f}{\partial u}, \frac{\partial f}{\partial v}, \frac{\partial f}{\partial a}, \frac{\partial f}{\partial b}, \; \text{ve} \; \frac{\partial f}{\partial w}`'ları
hesaplamamızı gerektirir. Aşağıdaki denklemleri de dahil etmekten bizi
hiçbir şey alıkoyamaz:
.. math::
\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}
Sonra tüm ağdaki *herhangi bir* düğümü değiştirdiğimizde :math:`f`
değerinin nasıl değiştiğini takip edebiliyoruz. Haydi uygulayalım.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# Girdilerden çıktılara fonksiyonun değerini hesapla
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}')
# Yukarıdaki ayrıştırmayı kullanarak türevi hesapla
# İlk önce tek adımlı kısmileri hesapla
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)
# Şimdi herhangi bir değeri çıktıdan girdiye değiştirdiğimizde f'nin nasıl değiştiğini hesapla
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}')
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
f at -1, 0, -2, 1 is 1024
df/dw at -1, 0, -2, 1 is -4096
df/dx at -1, 0, -2, 1 is -4096
df/dy at -1, 0, -2, 1 is -4096
df/dz at -1, 0, -2, 1 is -4096
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# Girdilerden çıktılara fonksiyonun değerini hesapla
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}')
# Yukarıdaki ayrıştırmayı kullanarak türevi hesapla
# İlk önce tek adımlı kısmileri hesapla
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)
# Şimdi herhangi bir değeri çıktıdan girdiye değiştirdiğimizde f'nin nasıl değiştiğini hesapla
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}')
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
f at -1, 0, -2, 1 is 1024
df/dw at -1, 0, -2, 1 is -4096
df/dx at -1, 0, -2, 1 is -4096
df/dy at -1, 0, -2, 1 is -4096
df/dz at -1, 0, -2, 1 is -4096
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# Girdilerden çıktılara fonksiyonun değerini hesapla
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}')
# Yukarıdaki ayrıştırmayı kullanarak türevi hesapla
# İlk önce tek adımlı kısmileri hesapla
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)
# Şimdi herhangi bir değeri çıktıdan girdiye değiştirdiğimizde f'nin nasıl değiştiğini hesapla
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}')
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
f at -1, 0, -2, 1 is 1024
df/dw at -1, 0, -2, 1 is -4096
df/dx at -1, 0, -2, 1 is -4096
df/dy at -1, 0, -2, 1 is -4096
df/dz at -1, 0, -2, 1 is -4096
.. raw:: html
.. raw:: html
Türevleri, girdilerden çıktılara, ileriye doğru, hesaplamaktansa,
:math:`f`'den girdilere doğru hesapladığımız gerçeği (yukarıdaki ilk kod
parçacığında yaptığımız gibi), bu algoritmaya adını veren şeydir: *Geri
yayma*. İki adım olduğunu unutmayın: 1. Fonksiyonun değerini ve önden
arkaya tek adımlık kısmi değerlerini hesaplayın. Yukarıda yapılmasa da,
bu tek bir *ileri geçişte* birleştirilebilir. 2. Arkadan öne doğru
:math:`f` gradyanını hesaplayın. Biz buna *geriye doğru geçiş* diyoruz.
Bu, her derin öğrenme algoritmasının, bir geçişte ağdaki her ağırlığa
göre kaybın gradyanının hesaplanmasına izin vermek, uyguladığı şeydir.
Böyle bir ayrışmaya sahip olmamız şaşırtıcı bir gerçektir.
Bunu nasıl içeri işlediğimizi görmek için bu örneğe hızlıca bir göz
atalım.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# ndarrays olarak ilklet, ardından gradyanları ekle
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()
# Hesaplamayı her zamanki gibi yap, gradyanları takip et
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
# Geriye doğru geçişi uygula
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}')
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
df/dw at -1.0, 0.0, -2.0, 1.0 is -4096.0
df/dx at -1.0, 0.0, -2.0, 1.0 is -4096.0
df/dy at -1.0, 0.0, -2.0, 1.0 is -4096.0
df/dz at -1.0, 0.0, -2.0, 1.0 is -4096.0
[21:41:24] src/base.cc:49: GPU context requested, but no GPUs found.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# ndarrays olarak ilklet, ardından gradyanları ekle
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)
# Hesaplamayı her zamanki gibi yap, gradyanları takip et
a, b = (w + x + y + z)**2, (w + x - y - z)**2
u, v = (a + b)**2, (a - b)**2
f = (u + v)**2
# Geriye doğru geçişi uygula
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()}')
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
df/dw at -1.0, 0.0, -2.0, 1.0 is -4096.0
df/dx at -1.0, 0.0, -2.0, 1.0 is -4096.0
df/dy at -1.0, 0.0, -2.0, 1.0 is -4096.0
df/dz at -1.0, 0.0, -2.0, 1.0 is -4096.0
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# ndarrays olarak ilklet, ardından gradyanları ekle
w = tf.Variable(tf.constant([-1.]))
x = tf.Variable(tf.constant([0.]))
y = tf.Variable(tf.constant([-2.]))
z = tf.Variable(tf.constant([1.]))
# Hesaplamayı her zamanki gibi yap, gradyanları takip et
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
# Geriye doğru geçişi uygula
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}')
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
df/dw at [-1.], [0.], [-2.], [1.] is [-4096.]
df/dx at [-1.], [0.], [-2.], [1.] is [-4096.]
df/dy at [-1.], [0.], [-2.], [1.] is [-4096.]
df/dz at [-1.], [0.], [-2.], [1.] is [-4096.]
.. raw:: html
.. raw:: html
Yukarıda yaptığımız şeylerin tümü, ``f.backwards()`` çağrısıyla otomatik
olarak yapılabilir.
Hessianlar
----------
Tek değişkenli hesapta olduğu gibi, bir işleve tek başına gradyanı
kullanmaktan daha iyi bir yaklaşıklama elde edebilmek için daha yüksek
dereceli türevleri düşünmek yararlıdır.
Birkaç değişkenli fonksiyonların daha yüksek dereceden türevleriyle
çalışırken karşılaşılan anlık bir sorun vardır ve bu da çok sayıda
olmasıdır. :math:`f(x_1, \ldots, x_n)` fonksiyonun :math:`n` değişkeni
varsa, o zaman :math:`n^{2}` tane ikinci türev alabiliriz, yani herhangi
bir :math:`i` ve :math:`j` seçeneği için:
.. math::
\frac{d^2f}{dx_idx_j} = \frac{d}{dx_i}\left(\frac{d}{dx_j}f\right).
Bu, geleneksel olarak *Hessian* adı verilen bir matris içinde toplanır:
.. math:: \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}.
:label: eq_hess_def
Bu matrisin her girdisi bağımsız değildir. Aslında, her iki *karışık
kısmi* (birden fazla değişkene göre kısmi türevler) var ve sürekli
olduğu sürece, herhangi bir :math:`i` ve :math:`j` için şunu
söyleyebiliriz:
.. math::
\frac{d^2f}{dx_idx_j} = \frac{d^2f}{dx_jdx_i}.
Bunu, önce bir işlevi :math:`x_i` yönünde ve ardından :math:`x_j`
yönünde dürtmeyi ve ardından bunun sonucunu önce :math:`x_j` ve sonra
:math:`x_i` yönünde dürtersek ne olacağıyla karşılaştırmak izler; burada
her iki sıranın da :math:`f`'nin çıktısında aynı nihai değişikliğe yol
açtığı bilgisine ulaşırız.
Tek değişkenlerde olduğu gibi, fonksiyonun bir noktanın yakınında nasıl
davrandığına dair daha iyi bir fikir edinmek için bu türevleri
kullanabiliriz. Özellikle, tek bir değişkende gördüğümüz gibi,
:math:`\mathbf{x}_0` noktasının yakınında en uygun ikinci derece
fonksiyonu bulmak için kullanabiliriz.
Bir örnek görelim.
:math:`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}`
olduğunu varsayalım. Bu, iki değişkenli bir ikinci dereceden fonksiyon
genel formudur. Fonksiyonun değerine, gradyanına ve Hessian
:eq:`eq_hess_def` değerine bakarsak, hepsi sıfır noktasında şöyle
gözükür:
.. math::
\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}
esas polinomumuzu şöyle diyerek geri elde edebiliriz;
.. math::
f(\mathbf{x}) = f(0) + \nabla f (0) \cdot \mathbf{x} + \frac{1}{2}\mathbf{x}^\top \mathbf{H} f (0) \mathbf{x}.
Genel olarak, bu genişletmeyi herhangi bir :math:`\mathbf {x}_0`
noktasında hesaplasaydık, şunu görürdük
.. math::
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).
Bu, herhangi bir boyuttaki girdi için çalışır ve bir noktadaki herhangi
bir işleve en iyi yaklaşıklayan ikinci derece polinomu sağlar. Bir örnek
vermek gerekirse, fonksiyonun grafiğini çizelim.
.. math::
f(x, y) = xe^{-x^2-y^2}.
Gradyan ve Hessian şöyle hesaplanabilir:
.. math::
\nabla f(x, y) = e^{-x^2-y^2}\begin{pmatrix}1-2x^2 \\ -2xy\end{pmatrix} \; \text{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}.
Böylece, biraz cebirle, :math:`[-1,0]^\top`'deki yaklaşık ikinci
dereceden polinomu görebiliriz:
.. math::
f(x, y) \approx e^{-1}\left(-1 - (x+1) +(x+1)^2+y^2\right).
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# Izgarayı oluştur ve işlevi hesapla
x, y = np.meshgrid(np.linspace(-2, 2, 101),
np.linspace(-2, 2, 101), indexing='ij')
z = x*np.exp(- x**2 - y**2)
# (1, 0)'da gradyan ve Hessian ile ikinci dereceden yaklaştırmayı hesapla
w = np.exp(-1)*(-1 - (x + 1) + (x + 1)**2 + y**2)
# İşlevi çiz
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
.. figure:: output_multivariable-calculus_bf678a_63_0.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# Izgarayı oluştur ve işlevi hesapla
x, y = torch.meshgrid(torch.linspace(-2, 2, 101),
torch.linspace(-2, 2, 101))
z = x*torch.exp(- x**2 - y**2)
# (1, 0)'da gradyan ve Hessian ile ikinci dereceden yaklaştırmayı hesapla
w = torch.exp(torch.tensor([-1.]))*(-1 - (x + 1) + 2 * (x + 1)**2 + 2 * y**2)
# Pİşlevi çiz
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
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
/home/d2l-worker/miniconda3/envs/d2l-tr-release-0/lib/python3.9/site-packages/torch/functional.py:478: UserWarning: torch.meshgrid: in an upcoming release, it will be required to pass the indexing argument. (Triggered internally at ../aten/src/ATen/native/TensorShape.cpp:2895.)
return _VF.meshgrid(tensors, **kwargs) # type: ignore[attr-defined]
.. figure:: output_multivariable-calculus_bf678a_66_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# Izgarayı oluştur ve işlevi hesapla
x, y = tf.meshgrid(tf.linspace(-2., 2., 101),
tf.linspace(-2., 2., 101))
z = x*tf.exp(- x**2 - y**2)
# (1, 0)'da gradyan ve Hessian ile ikinci dereceden yaklaştırmayı hesapla
w = tf.exp(tf.constant([-1.]))*(-1 - (x + 1) + 2 * (x + 1)**2 + 2 * y**2)
# İşlevi çiz
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
.. figure:: output_multivariable-calculus_bf678a_69_0.svg
.. raw:: html
.. raw:: html
Bu, :numref:`sec_gd` içinde tartışılan Newton algoritmasının temelini
oluşturur; sayısal optimizasyonu yinelemeli olarak uygulayarak en uygun
ikinci derece polinomu bulur, sonra da tam olarak bu ikinci dereceden
polinomu en aza indiririz.
Biraz Matris Hesabı
-------------------
Matrisleri içeren fonksiyonların türevlerinin oldukça iyi olduğu ortaya
çıktı. Bu bölüm gösterimsel olarak ağır hale gelebilir, bu nedenle ilk
okumada atlanabilir, ancak genel matris işlemlerini içeren
fonksiyonların türevlerinin genellikle başlangıçta tahmin
edebileceğinden çok daha temiz olduğunu bilmek yararlıdır, özellikle de
merkezi matris işlemlerinin derin öğrenme uygulamaları için ne kadar
olduğu göz önüne alındığında.
Bir örnekle başlayalım. Bir sabit sütun vektörümüz
:math:`\boldsymbol{\beta}` olduğunu ve
:math:`f(\mathbf{x}) = \boldsymbol{\beta}^\top\mathbf{x}` çarpım
fonksiyonunu almak ve :math:`\mathbf{x}`'i değiştirdiğimizde iç çarpım
nasıl değişir anlamak istediğimizi varsayalım.
Makine öğrenmesinde matris türevleriyle çalışırken faydalı olacak bir
gösterim parçası, kısmi türevlerimizi türevin paydada bulunduğu vektör,
matris veya tensörün şekline dönüştürdüğümüz *payda düzenli matris
türevi* olarak adlandırılır. Bu durumda şöyle yazacağız
.. math::
\frac{df}{d\mathbf{x}} = \begin{bmatrix}
\frac{df}{dx_1} \\
\vdots \\
\frac{df}{dx_n}
\end{bmatrix},
Burada :math:`\mathbf{x}` sütun vektörünün şekline eşleştirdik.
İşlevimizi bileşenlere yazarsak:
.. math::
f(\mathbf{x}) = \sum_{i = 1}^{n} \beta_ix_i = \beta_1x_1 + \cdots + \beta_nx_n.
Şimdi :math:`x_1` göre kısmi türevi alırsak, ilk terim hariç her şeyin
sıfır olduğuna dikkat edin, sadece :math:`x_1` ile :math:`\beta_1` ile
çarpılır, böylece bunu elde ederiz:
.. math::
\frac{df}{dx_1} = \beta_1,
ya da daha genel olarak
.. math::
\frac{df}{dx_i} = \beta_i.
Şimdi bunu bir matris olarak görmek için yeniden birleştirebiliriz
.. math::
\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}.
Bu, matris hesabı ile ilgili olarak bu bölümde sık sık karşılaşacağımız
birkaç etkeni göstermektedir:
- İlk olarak, hesaplamalar daha çok işin içine girecek.
- İkinci olarak, nihai sonuçlar ara süreçten çok daha temizdir ve her
zaman tek değişkenli duruma benzer görünecektir. Bu durumda,
:math:`\frac{d}{dx}(bx) = b` ve
:math:`\frac{d}{d\mathbf{x}} (\boldsymbol{\beta}^\top\mathbf{x}) = \boldsymbol{\beta}`'nin
ikisi de benzerdir.
- Üçüncüsü, devrikler genellikle herhangi bir yerden ortaya
çıkabilirler. Bunun temel nedeni, paydanın şeklini eşleştirmemizdir,
böylece matrisleri çarptığımızda, esas terimin şekline geri dönmek
için devrikler almamız gerekecek.
Önsezi oluşturmaya devam etmek için biraz daha zor bir hesaplama
deneyelim. Bir sütun vektörümüz :math:`\mathbf{x}` ve kare matrisimiz
:math:`A` olduğunu ve şunu hesaplamak istediğimizi varsayalım.
.. math:: \frac{d}{d\mathbf{x}}(\mathbf{x}^\top A \mathbf{x}).
:label: eq_mat_goal_1
Gösterimde daha kolay oynama yaparak ileriye doğru ilerlemek için, bu
problemi Einstein gösterimini kullanarak ele alalım. Bu durumda
fonksiyonu şu şekilde yazabiliriz:
.. math::
\mathbf{x}^\top A \mathbf{x} = x_ia_{ij}x_j.
Türevimizi hesaplamak için, her :math:`k` için, değerin ne olduğunu
anlamamız gerekir:
.. math::
\frac{d}{dx_k}(\mathbf{x}^\top A \mathbf{x}) = \frac{d}{dx_k}x_ia_{ij}x_j.
Çarpım kuralını uygulayalım:
.. math::
\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}.
:math:`\frac{dx_i}{dx_k}` gibi bir terim için, bunun :math:`i = k` için
bir ve aksi halde sıfır olduğunda görmek zor değildir. Bu, :math:`i` ve
:math:`k` değerlerinin farklı olduğu her terimin bu toplamdan kaybolduğu
anlamına gelir, bu nedenle bu ilk toplamda kalan tek terim,
:math:`i = k` olanlardır. Aynı çıkarsama, :math:`j = k`'ya ihtiyacımız
olduğu ikinci terim için de geçerlidir. Böylece:
.. math::
\frac{d}{dx_k}x_ia_{ij}x_j = a_{kj}x_j + x_ia_{ik}.
Şimdi, Einstein gösterimindeki indislerin isimleri keyfidir ---
:math:`i` ve :math:`j`'nin farklı olması bu noktada bu hesaplama için
önemsizdir, bu yüzden ikisinin de :math:`i` kullanması için yeniden
indeksleyebiliriz:
.. math::
\frac{d}{dx_k}x_ia_{ij}x_j = a_{ki}x_i + x_ia_{ik} = (a_{ki} + a_{ik})x_i.
Şimdi, burası daha ileri gitmek için biraz pratik yapmaya ihtiyacımız
olan yerdir. Bu sonucu matris işlemleri açısından belirlemeye çalışalım.
:math:`a_{ki} + a_{ik}`, :math:`\mathbf{A} + \mathbf{A}^\top`'nin
:math:`k,i`-inci bileşenidir.
.. math::
\frac{d}{dx_k}x_ia_{ij}x_j = [\mathbf{A} + \mathbf{A}^\top]_{ki}x_i.
Benzer şekilde, bu terim artık :math:`\mathbf{A} + \mathbf{A}^\top`
matrisinin :math:`\mathbf{x}` vektörü ile çarpımıdır, dolayısıyla şunu
görüyoruz
.. math::
\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.
Böylece, :eq:`eq_mat_goal_1` denkleminden istenen türevin
:math:`k.` girdisinin sağdaki vektörün :math:`k.` girdisi olduğunu ve
dolayısıyla ikisinin aynı olduğunu görüyoruz. Sonunda:
.. math::
\frac{d}{d\mathbf{x}}(\mathbf{x}^\top A \mathbf{x}) = (\mathbf{A} + \mathbf{A}^\top)\mathbf{x}.
Bu, önceki sonucumuzdan önemli ölçüde daha fazla çalışma gerektirdi,
ancak nihai sonuç küçük. Bundan daha fazlası, geleneksel tek değişkenli
türevler için aşağıdaki hesaplamayı düşünün:
.. math::
\frac{d}{dx}(xax) = \frac{dx}{dx}ax + xa\frac{dx}{dx} = (a+a)x.
Eşdeğer olarak :math:`\frac{d}{dx}(ax^2) = 2ax = (a+a)x`'dir. Yine, tek
değişkenli sonuca benzeyen, ancak içine devrik atılmış bir sonuç elde
ederiz.
Bu noktada, model oldukça şüpheli görünmelidir, bu yüzden nedenini
anlamaya çalışalım. Bunun gibi matris türevlerini aldığımızda, öncelikle
aldığımız ifadenin başka bir matris ifadesi olacağını varsayalım: Onu
matrislerin çarpımları ve toplamları ve bunların devrikleri cinsinden
yazabileceğimiz bir ifade. Böyle bir ifade varsa, tüm matrisler için
doğru olması gerekecektir. Özellikle, :math:`1 \times 1` matrisler için
doğru olması gerekecektir, ki bu durumda matris çarpımı sadece sayıların
çarpımıdır, matris toplamı sadece toplamdır ve devrik hiçbir şey yapmaz!
Başka bir deyişle, aldığımız ifade ne olursa olsun tek değişkenli
ifadeyle *eşleşmelidir*. Bu, biraz pratikle, yalnızca ilişkili tek
değişkenli ifadenin neye benzemesi gerektiğini bilerek matris
türevlerini tahmin edebileceğiniz anlamına gelir!
Bunu deneyelim. :math:`\mathbf{X}`'nin bir :math:`n \times m` matrisi,
:math:`\mathbf{U}`'nun :math:`n\times r` ve :math:`\mathbf{V}`'nin
:math:`r\times m` olduğunu varsayalım. Hesaplamayı deneyelim
.. math:: \frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2} = \;?
:label: eq_mat_goal_2
Bu hesaplama, matris çarpanlarına ayırma adı verilen bir alanda
önemlidir. Ancak bizim için bu, hesaplanması gereken bir türevdir. Bunun
:math:`1\times 1` matrisler için ne olacağını hayal etmeye çalışalım. Bu
durumda ifadeyi alırız
.. math::
\frac{d}{dv} (x-uv)^{2}= -2(x-uv)u,
Burada türev oldukça standarttır. Bunu tekrar bir matris ifadesine
dönüştürmeye çalışırsak,
.. math::
\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2(\mathbf{X} - \mathbf{U}\mathbf{V})\mathbf{U}.
Ancak buna bakarsak pek işe yaramıyor. :math:`\mathbf{X}`'in
:math:`n\times m` olduğunu ve :math:`\mathbf{U}\mathbf{V}` öyle olduğunu
hatırlayın, dolayısıyla :math:`2(\mathbf{X} - \mathbf{U}\mathbf{V})`,
:math:`n\times m`'dir. Öte yandan, :math:`\mathbf{U}`,
:math:`n\times r`'dir ve boyutlar eşleşmediğinden, :math:`n\times m` ve
a :math:`n\times r` matrisleri çarpamayız!
:math:`\frac{d}{d\mathbf{V}}`'i elde etmek istiyoruz, bu
:math:`\mathbf{V}` ile aynı şekle sahip, ki bu :math:`r \times m`.
Öyleyse bir şekilde bir :math:`n\times m` matrisi ve bir
:math:`n\times r` matrisi almalıyız, bunları bir :math:`r \times m`
matris elde etmek için birbirleriyle çarpmalıyız (belki bazı
devriklerle). Bunu :math:`\mathbf{U}^\top`'yu
:math:`(\mathbf{X} - \mathbf{U}\mathbf{V})` ile çarparak yapabiliriz.
Böylelikle, :eq:`eq_mat_goal_2` için çözümü tahmin edebiliriz:
.. math::
\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\mathbf{U}^\top(\mathbf{X} - \mathbf{U}\mathbf{V}).
Bunun işe yaradığını göstermek için, ayrıntılı bir hesaplama sağlamazsak
ihmal etmiş oluruz. Bu pratik kuralın işe yaradığına zaten
inanıyorsanız, bu türetmeyi atlamaktan çekinmeyin. Hesaplayalım:
.. math::
\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^2,
Her :math:`a` ve :math:`b` için bulmalıyız.
.. math::
\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.
:math:`\mathbf{X}` and :math:`\mathbf{U}`'nin tüm girdilerinin
:math:`\frac{d}{dv_{ab}}` bakımında sabitler olduğunu hatırlayarak,
türevi toplamın içine itebiliriz, ve zincir kuralını kareye uygularız:
.. math::
\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).
Önceki türetmede olduğu gibi, :math:`\frac{dv_{kj}}{dv_{ab}}`\ ’nın
yalnızca :math:`k = a` ve :math:`j = b` ise sıfırdan farklı olduğunu
görebiliriz. Bu koşullardan herhangi biri geçerli değilse, toplamdaki
terim sıfırdır ve onu özgürce atabiliriz. Bunu görüyoruz:
.. math::
\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}.
Buradaki önemli bir incelik, :math:`k = a` şartının iç toplamın içinde
oluşmamasıdır, çünkü :math:`k` iç terimin içinde topladığımız yapay bir
değişken. Gösterimsel olarak daha temiz bir örnek için nedenini
düşünelim:
.. math::
\frac{d}{dx_1} \left(\sum_i x_i \right)^{2}= 2\left(\sum_i x_i \right).
Bu noktadan itibaren, toplamın bileşenlerini belirlemeye başlayabiliriz.
İlk olarak,
.. math::
\sum_k u_{ik}v_{kb} = [\mathbf{U}\mathbf{V}]_{ib}.
Yani toplamın içindeki tüm ifade:
.. math::
x_{ib} - \sum_k u_{ik}v_{kb} = [\mathbf{X}-\mathbf{U}\mathbf{V}]_{ib}.
Bu, artık türevimizi şu şekilde yazabileceğimiz anlamına gelir:
.. math::
\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\sum_{i}[\mathbf{X}-\mathbf{U}\mathbf{V}]_{ib}u_{ia}.
Bunun bir matrisin :math:`a, b` öğesi gibi görünmesini istiyoruz ki
böylece bir matris ifadesine ulaşmak için önceki örnekte olduğu gibi
tekniği kullanabilelim, bu da indislerin sırasını :math:`u_{ia}`
üzerinden değiştirmemiz gerektiği anlamına gelir.
:math:`u_{ia} = [\mathbf{U}^\top]_{ai}` olduğunu fark edersek, bunu
yazabiliriz:
.. math::
\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}.
Bu bir matris çarpımıdır ve dolayısıyla şu sonuca varabiliriz:
.. math::
\frac{d}{dv_{ab}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2[\mathbf{U}^\top(\mathbf{X}-\mathbf{U}\mathbf{V})]_{ab}.
Böylece çözümü şu şekilde yazabiliriz :eq:`eq_mat_goal_2`
.. math::
\frac{d}{d\mathbf{V}} \|\mathbf{X} - \mathbf{U}\mathbf{V}\|_2^{2}= -2\mathbf{U}^\top(\mathbf{X} - \mathbf{U}\mathbf{V}).
Bu, yukarıda tahmin ettiğimiz çözüme uyuyor!
Bu noktada şunu sormak mantıklıdır, "Neden öğrendiğim tüm hesap
(kalkülüs) kurallarının matris versiyonlarını yazamıyorum? Bunun hala
mekanik olduğu açık. Neden bunun üstesinden gelmiyoruz!" Gerçekten de
böyle kurallar var ve :cite:`Petersen.Pedersen.ea.2008` mükemmel bir
özet sunuyor. Bununla birlikte, matris işlemlerinin tekli değerlere
kıyasla birleştirilebileceği çok sayıda yol olması nedeniyle, tek
değişkenli olanlara göre çok daha fazla matris türev kuralı vardır.
Genellikle en iyisi indislerle çalışmak veya uygun olduğunda bunu
otomatik türev almaya bırakmaktır.
Özet
----
- Daha yüksek boyutlarda, bir boyuttaki türevlerle aynı amaca hizmet
eden gradyanları tanımlayabiliriz. Bunlar, girdilerde rastgele küçük
bir değişiklik yaptığımızda çok değişkenli bir fonksiyonun nasıl
değiştiğini görmemizi sağlar.
- Geri yayma algoritması, birçok kısmi türevin verimli bir şekilde
hesaplanmasına izin vermek için çok değişkenli zincir kuralını
düzenlemenin bir yöntemi olarak görülebilir.
- Matris hesabı, matris ifadelerinin türevlerini öz olarak yazmamızı
sağlar.
Alıştırmalar
------------
1. :math:`\boldsymbol{\beta}` sütun vektörü verildiğinde, hem
:math:`f(\mathbf{x}) = \boldsymbol{\beta}^\top\mathbf{x}` hem de
:math:`g(\mathbf{x}) = \mathbf{x}^\top\boldsymbol{\beta}` türevlerini
hesaplayınız. Neden aynı cevabı alıyorsunuz?
2. :math:`\mathbf{v}` bir :math:`n` boyutlu vektör olsun.
:math:`\frac{\partial}{\partial\mathbf{v}}\|\mathbf{v}\|_2` nedir?
3. :math:`L(x, y) = \log(e^x + e^y)` olsun. Gradyanını hesaplayınız.
Gradyanın bileşenlerinin toplamı nedir?
4. :math:`f(x, y) = x^2y + xy^2` olsun. Tek kritik noktanın
:math:`(0,0)` olduğunu gösterin. :math:`f(x, x)`'i dikkate alarak,
:math:`(0,0)`'ın maksimum mu, minimum mu olduğunu veya hiçbiri
olmadığını belirleyin.
5. :math:`f(\mathbf{x}) = g(\mathbf{x}) + h(\mathbf{x})` işlevini
küçülttüğümüzü varsayalım. :math:`\nabla f = 0` koşulunu :math:`g` ve
:math:`h` cinsinden geometrik olarak nasıl yorumlayabiliriz?
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html