.. _sec_autograd:
Otomatik Türev Alma
===================
:numref:`sec_calculus` içinde açıkladığımız gibi, türev alma neredeyse
tüm derin öğrenme optimizasyon algoritmalarında çok önemli bir adımdır.
Bu türevleri almak için gerekli hesaplamalar basittir ve sadece biraz
temel kalkülüs gerektirirken, karmaşık modeller için güncellemeleri elle
yapmak bir sancılı olabilir (ve genellikle hataya açık olabilir).
Derin öğrenme çerçeveleri, türevleri otomatik olarak hesaplayarak, yani
*otomatik türev alma* yoluyla bu çalışmayı hızlandırır. Uygulamada,
tasarladığımız modele dayalı olarak sistem, çıktıyı üretmek için hangi
verinin hangi işlemlerle birleştirildiğini izleyen bir *hesaplama
grafiği (çizgesi)* oluşturur. Otomatik türev alma, sistemin daha sonra
gradyanları geri yaymasını (backpropagation) sağlar. Burada *geri
yayma*, her bir parametreye göre kısmi türevleri doldurarak, hesaplama
grafiğini izlemek anlamına gelir.
Basit Bir Örnek
---------------
Bir oyuncak örnek olarak, :math:`y = 2\mathbf{x}^{\top}\mathbf{x}`
fonksiyonunun :math:`\mathbf{x}` sütun vektörüne göre türevini almakla
ilgilendiğimizi söyleyelim. Başlamak için, ``x`` değişkenini oluşturalım
ve ona bir başlangıç değeri atayalım.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
from mxnet import autograd, np, npx
npx.set_np()
x = np.arange(4.0)
x
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([0., 1., 2., 3.])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
import torch
x = torch.arange(4.0)
x
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor([0., 1., 2., 3.])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
import tensorflow as tf
x = tf.range(4, dtype=tf.float32)
x
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
:math:`y`'nin :math:`\mathbf{x}`'e göre gradyanını hesaplamadan önce,
onu saklayabileceğimiz bir yere ihtiyacımız var. Bir parametreye göre
her türev aldığımızda yeni bir bellek ayırmamamız önemlidir çünkü aynı
parametreleri binlerce kez veya milyonlarca kez güncelleyeceğiz ve
belleğimiz hızla tükenebilir. Skaler değerli bir fonksiyonun bir
:math:`\mathbf{x}` vektörüne göre gradyanının kendisinin vektör değerli
olduğuna ve :math:`\mathbf{x}` ile aynı şekle sahip olduğuna dikkat
edin.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# `attach_grad` çağırarak tensörün gradyanı için bellek tahsis ederiz.
x.attach_grad()
# `x`'e göre alınan bir gradyanı hesapladıktan sonra, değerleri 0'a ilklenmiş
# `grad` özelliği ile ona erişebileceğiz.
x.grad
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([0., 0., 0., 0.])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
x.requires_grad_(True) # `x = torch.arange(4.0, requires_grad=True)` ile aynıdır
x.grad # None varsayılan değerdir
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
x = tf.Variable(x)
.. raw:: html
.. raw:: html
Şimdi :math:`y`'yi hesaplayalım.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# Hesaplama grafiğini oluşturmak için kodumuzu bir `autograd.record` kapsamına yerleştirin
with autograd.record():
y = 2 * np.dot(x, x)
y
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array(28.)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
y = 2 * torch.dot(x, x)
y
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor(28., grad_fn=)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# Bütün hesaplamaları teybe kaydet
with tf.GradientTape() as t:
y = 2 * tf.tensordot(x, x, axes=1)
y
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
``x``, 4 uzunluklu bir vektör olduğu için, ``x`` ve ``x``'in iç çarpımı
gerçekleştirilir ve ``y``'ye atadığımız skaler çıktı elde edilir. Daha
sonra, geri yayma için işlevi çağırarak ve gradyanı yazdırarak ``x``'in
her bir bileşenine göre ``y`` gradyanını otomatik olarak
hesaplayabiliriz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
y.backward()
x.grad
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
[21:44:51] src/base.cc:49: GPU context requested, but no GPUs found.
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([ 0., 4., 8., 12.])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
y.backward()
x.grad
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor([ 0., 4., 8., 12.])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
x_grad = t.gradient(y, x)
x_grad
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
:math:`y = 2\mathbf{x}^{\top}\mathbf{x}` fonksiyonunun
:math:`\mathbf{x}`'e göre gradyanı :math:`4\mathbf{x}` olmalıdır.
İstenilen gradyanın doğru hesaplandığını hızlıca doğrulayalım.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
x.grad == 4 * x
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([ True, True, True, True])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
x.grad == 4 * x
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor([True, True, True, True])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
x_grad == 4 * x
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Şimdi başka bir ``x`` fonksiyonunu hesaplayalım.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
with autograd.record():
y = x.sum()
y.backward()
x.grad # Yeni hesaplanan gradyan tarafından üzerine yazılır
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([1., 1., 1., 1.])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# PyTorch, gradyanı varsayılan olarak biriktirir, önceki değerleri temizlememiz gerekir.
x.grad.zero_()
y = x.sum()
y.backward()
x.grad
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor([1., 1., 1., 1.])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
with tf.GradientTape() as t:
y = tf.reduce_sum(x)
t.gradient(y, x) # Yeni hesaplanan gradyan tarafından üzerine yazılır
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Skaler Olmayan Değişkenler için Geriye Dönüş
--------------------------------------------
Teknik olarak, ``y`` skaler olmadığında, ``y`` vektörünün ``x``
vektörüne göre türevinin en doğal yorumu bir matristir. Daha yüksek
kademeli ve daha yüksek boyutlu ``y`` ve ``x`` için, türevin sonucu
yüksek kademeli bir tensör olabilir.
Bununla birlikte, bu daha egzotik nesneler gelişmiş makine öğrenmesinde
(derin öğrenmedekiler dahil) ortaya çıkarken, daha sıklıkla bir vektör
üzerinde geriye doğru dönük çağırdığımızda, bir *grup* eğitim örneğinde
kayıp fonksiyonlarının her bir bileşeni için türevlerini hesaplamaya
çalışıyoruz. Burada amacımız, türev matrisini hesaplamak değil, gruptaki
her örnek için ayrı ayrı hesaplanan kısmi türevlerin toplamını
hesaplamaktır.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# Vektör değerli bir değişken olan `y` (`x`'in işlevi) üzerinde `geridönüş`'ü çağırdığımızda,
# `y`'deki öğeleri toplayarak yeni bir skaler değişken oluşturulur.
# Daha sonra bu skaler değişkenin `x`'e göre gradyanı hesaplanır.
with autograd.record():
y = x * x # `y` bir vektördür
y.backward()
x.grad # y = sum(x * x)'ye eşittir
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([0., 2., 4., 6.])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# Skaler olmayan üzerinde `geridönüş` çağırmak, farklılaştırılmış işlevin kendine göre
# `self` gradyanını belirten bir `gradyan` argümanının iletilmesini gerektirir. Bizim durumumuzda,
# sadece kısmi türevleri toplamak istiyoruz, bu nedenle birlerden oluşan gradyanı iletmek uygundur.
x.grad.zero_()
y = x * x
# y.backward(torch.ones(len(x))) aşağısı ile aynıdır
y.sum().backward()
x.grad
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor([0., 2., 4., 6.])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
with tf.GradientTape() as t:
y = x * x
t.gradient(y, x) # `y = tf.reduce_sum(x * x)` ile aynı
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Hesaplamanın Ayrılması
----------------------
Bazen, bazı hesaplamaları kaydedilen hesaplama grafiğinin dışına taşımak
isteriz. Örneğin, ``y``'nin ``x``'in bir fonksiyonu olarak
hesaplandığını ve daha sonra ``z``'nin hem ``y``'nin hem de ``x``'in bir
fonksiyonu olarak hesaplandığını varsayalım. Şimdi, ``z``'nin gradyanını
``x``'e göre hesaplamak istediğimizi, ancak nedense ``y``'yi bir sabit
olarak kabul etmek istediğimizi ve ``x``'in ``y`` hesaplandıktan sonra
oynadığı rolü hesaba kattığımızı hayal edin.
Burada, ``y`` ile aynı değere sahip yeni bir ``u`` değişkeni döndürmek
için ``y``'yi ayırabiliriz, ancak bu ``y``'nin hesaplama grafiğinde
nasıl hesaplandığına dair tüm bilgileri yok sayar. Başka bir deyişle,
gradyan ``u``'dan ``x``'e geriye doğru akmayacaktır. Bu nedenle,
aşağıdaki geri yayma işlevi, ``z = x * x * x``'in ``x``'e göre kısmi
türevi hesaplamak yerine, ``z = u * x``'in ``x``'e göre kısmi türevini
``u`` sabitmiş gibi davranarak hesaplar.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
with autograd.record():
y = x * x
u = y.detach()
z = u * x
z.backward()
x.grad == u
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([ True, True, True, True])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
x.grad.zero_()
y = x * x
u = y.detach()
z = u * x
z.sum().backward()
x.grad == u
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor([True, True, True, True])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
# `t.gradient`i birden fazla çalıştırmak için `persistent=True` şekilde ayarlayın
with tf.GradientTape(persistent=True) as t:
y = x * x
u = tf.stop_gradient(y)
z = u * x
x_grad = t.gradient(z, x)
x_grad == u
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
``y`` hesaplaması kaydedildiğinden, ``y = x * x``'nin ``x``'e göre
türevi olan ``2 * x``'i elde etmek için ``y`` üzerinden geri yaymayı
başlatabiliriz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
y.backward()
x.grad == 2 * x
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([ True, True, True, True])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
x.grad.zero_()
y.sum().backward()
x.grad == 2 * x
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor([True, True, True, True])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
t.gradient(y, x) == 2 * x
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Python Kontrol Akışının Gradyanını Hesaplama
--------------------------------------------
Otomatik türev almayı kullanmanın bir yararı, bir Python kontrol akışı
labirentinden (örneğin, koşullu ifadeler, döngüler ve rastgele fonksiyon
çağrıları) geçen bir fonksiyondan hesaplama çizgesi oluşturup, elde
edilen değişkenin gradyanını yine de hesaplayabilmemizdir. Aşağıdaki kod
parçacığında, ``while`` döngüsünün yineleme sayısının ve ``if``
ifadesinin değerlendirmesinin ``a`` girdisinin değerine bağlı olduğuna
dikkat edin.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def f(a):
b = a * 2
while np.linalg.norm(b) < 1000:
b = b * 2
if b.sum() > 0:
c = b
else:
c = 100 * b
return c
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def f(a):
b = a * 2
while b.norm() < 1000:
b = b * 2
if b.sum() > 0:
c = b
else:
c = 100 * b
return c
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def f(a):
b = a * 2
while tf.norm(b) < 1000:
b = b * 2
if tf.reduce_sum(b) > 0:
c = b
else:
c = 100 * b
return c
.. raw:: html
.. raw:: html
Hadi gradyanı hesaplayalım.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
a = np.random.normal()
a.attach_grad()
with autograd.record():
d = f(a)
d.backward()
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
a = tf.Variable(tf.random.normal(shape=()))
with tf.GradientTape() as t:
d = f(a)
d_grad = t.gradient(d, a)
d_grad
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Şimdi yukarıda tanımlanan ``f`` fonksiyonunu analiz edebiliriz.
Fonksiyonun ``a`` girdisinde parçalı doğrusal olduğuna dikkat edin.
Diğer bir deyişle, herhangi bir ``a`` için, ``k`` değerinin ``a``
girdisine bağlı olduğu ``f(a) = k * a`` olacak şekilde sabit bir ``k``
vardır. Sonuç olarak ``d / a``, gradyanın doğru olduğunu doğrulamamıza
izin verir.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
a.grad == d / a
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array(True)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
a.grad == d / a
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor(False)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
d_grad == d / a
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Özet
----
- Derin öğrenme çerçeveleri türevlerin hesaplanmasını
otomatikleştirebilir. Kullanmak için, önce kısmi türevleri bulmak
istediğimiz değişkenlere gradyanlar ekleriz. Daha sonra hedef
değerimizin hesaplamasını kaydeder, geri yayma için işlevi uygularız
ve elde edilen gradyanlara erişiriz.
Alıştırmalar
------------
1. İkinci türevi hesaplamak neden birinci türeve göre çok daha
pahalıdır?
2. Geri yayma için işlevi çalıştırdıktan sonra, hemen tekrar çalıştırın
ve ne olduğunu görün.
3. ``d``'nin ``a``'ya göre türevini hesapladığımız kontrol akışı
örneğinde, ``a`` değişkenini rastgele bir vektör veya matris olarak
değiştirirsek ne olur. Bu noktada, ``f(a)`` hesaplamasının sonucu
artık skaler değildir. Sonuca ne olur? Bunu nasıl analiz ederiz?
4. Kontrol akışının gradyanını bulmak için yeniden bir örnek tasarlayın.
Sonucu çalıştırın ve analiz edin.
5. :math:`f(x) = \sin(x)` olsun. :math:`f(x)` ve
:math:`\frac{df(x)}{dx}` grafiklerini çizin, buradaki ikinci terim
:math:`f'(x) = \cos(x)` kullanılmadan hesaplansın.
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html