.. _sec_gd:
Gradyan İnişi
=============
Bu bölümde *gradyan inişi*\ nin altında yatan temel kavramları
tanıtacağız. Derin öğrenmede nadiren kullanılmasına rağmen, gradyan
inişi anlamak rasgele gradyan iniş algoritmalarını anlamak için
anahtardır. Örneğin, optimizasyon problemi aşırı büyük bir öğrenme oranı
nedeniyle ıraksayabilir. Bu olay halihazırda gradyan inişinde
görülebilir. Benzer şekilde, ön şartlandırma gradyan inişinde yaygın bir
tekniktir ve daha gelişmiş algoritmalara taşır. Basit bir özel durumla
başlayalım.
Tek Boyutlu Gradyan İnişi
-------------------------
Bir boyuttaki gradyan iniş, gradyan iniş algoritmasının amaç işlevinin
değerini neden azaltabileceğini açıklamak için mükemmel bir örnektir.
Bir sürekli türevlenebilir gerçek değerli fonksiyon düşünün
:math:`f: \mathbb{R} \rightarrow \mathbb{R}`. Bir Taylor açılımı
kullanarak şunu elde ederiz:
.. math:: f(x + \epsilon) = f(x) + \epsilon f'(x) + \mathcal{O}(\epsilon^2).
:label: gd-taylor
Yani, birinci dereceden açılımda :math:`f(x+\epsilon)`, :math:`f(x)`
fonksiyon değeri ve :math:`x`'deki birinci türev :math:`f'(x)`
tarafından verilir. Küçük :math:`\epsilon` için negatif gradyan yönünde
hareket etmenin :math:`f`'nın azalacağını varsaymak mantıksız değildir.
İşleri basit tutmak için sabit bir adım boyutu :math:`\eta > 0` seçip
:math:`\epsilon = -\eta f'(x)`'i seçeriz. Bunu yukarıdaki Taylor açılımı
içine koyarsak:
.. math:: f(x - \eta f'(x)) = f(x) - \eta f'^2(x) + \mathcal{O}(\eta^2 f'^2(x)).
:label: gd-taylor-2
Türev :math:`f'(x) \neq 0` kaybolmazsa :math:`\eta f'^2(x)>0`'dan dolayı
ilerleme kaydediyoruz. Dahası, daha yüksek dereceden terimlerin alakasız
hale gelmesi için :math:`\eta`'yi her zaman yeterince küçük seçebiliriz.
Bu nedenle şuraya varırız:
.. math:: f(x - \eta f'(x)) \lessapprox f(x).
Bu demektir ki, bunu kullanırsak
.. math:: x \leftarrow x - \eta f'(x)
:math:`x` yinelemek için :math:`f(x)` işlevinin değeri azalabilir. Bu
nedenle, gradyan inişinde ilk :math:`x` değeri ve sabit bir
:math:`\eta > 0` değeri seçiyoruz ve daha sonra onları durdurma koşuluna
ulaşılana kadar :math:`x`'i sürekli yinelemek için kullanıyoruz, örneğin
:math:`|f'(x)|`'in büyüklüğü yeterince küçük olduğunda veya yineleme
sayısı belirli bir değere ulaşınca.
Basitlik için, gradyan inişinin nasıl uygulanacağını göstermek için
:math:`f(x)=x^2` amaç işlevini seçiyoruz. :math:`x=0`'in :math:`f(x)`'yi
en aza indiren çözüm olduğunu bilmemize rağmen, :math:`x`'in nasıl
değiştiğini gözlemlemek için gene de bu basit işlevi kullanıyoruz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
%matplotlib inline
from d2l import mxnet as d2l
from mxnet import np, npx
npx.set_np()
def f(x): # Amaç fonksiyonu
return x ** 2
def f_grad(x): # Amaç fonksiyonunun gradyanı (türevi)
return 2 * x
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
%matplotlib inline
import numpy as np
import torch
from d2l import torch as d2l
def f(x): # Amaç fonksiyonu
return x ** 2
def f_grad(x): # Amaç fonksiyonunun gradyanı (türevi)
return 2 * x
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
%matplotlib inline
import numpy as np
import tensorflow as tf
from d2l import tensorflow as d2l
def f(x): # Amaç fonksiyonu
return x ** 2
def f_grad(x): # Amaç fonksiyonunun gradyanı (türevi)
return 2 * x
.. raw:: html
.. raw:: html
Ardından, ilk değer olarak :math:`x=10`'u kullanıyoruz ve
:math:`\eta=0.2`'yi varsayıyoruz. :math:`x`'i 10 kez yinelemek için
gradyan iniş kullanırsak, sonunda :math:`x` değerinin en iyi çözüme
yaklaştığını görebiliriz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def gd(eta, f_grad):
x = 10.0
results = [x]
for i in range(10):
x -= eta * f_grad(x)
results.append(float(x))
print(f'donem 10, x: {x:f}')
return results
results = gd(0.2, f_grad)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: 0.060466
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def gd(eta, f_grad):
x = 10.0
results = [x]
for i in range(10):
x -= eta * f_grad(x)
results.append(float(x))
print(f'donem 10, x: {x:f}')
return results
results = gd(0.2, f_grad)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: 0.060466
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def gd(eta, f_grad):
x = 10.0
results = [x]
for i in range(10):
x -= eta * f_grad(x)
results.append(float(x))
print(f'donem 10, x: {x:f}')
return results
results = gd(0.2, f_grad)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: 0.060466
.. raw:: html
.. raw:: html
:math:`x` üzerinde optimizasyonun ilerlemesi aşağıdaki gibi çizilebilir.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def show_trace(results, f):
n = max(abs(min(results)), abs(max(results)))
f_line = np.arange(-n, n, 0.01)
d2l.set_figsize()
d2l.plot([f_line, results], [[f(x) for x in f_line], [
f(x) for x in results]], 'x', 'f(x)', fmts=['-', '-o'])
show_trace(results, f)
.. figure:: output_gd_79c039_27_0.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def show_trace(results, f):
n = max(abs(min(results)), abs(max(results)))
f_line = torch.arange(-n, n, 0.01)
d2l.set_figsize()
d2l.plot([f_line, results], [[f(x) for x in f_line], [
f(x) for x in results]], 'x', 'f(x)', fmts=['-', '-o'])
show_trace(results, f)
.. figure:: output_gd_79c039_30_0.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def show_trace(results, f):
n = max(abs(min(results)), abs(max(results)))
f_line = tf.range(-n, n, 0.01)
d2l.set_figsize()
d2l.plot([f_line, results], [[f(x) for x in f_line], [
f(x) for x in results]], 'x', 'f(x)', fmts=['-', '-o'])
show_trace(results, f)
.. figure:: output_gd_79c039_33_0.svg
.. raw:: html
.. raw:: html
.. _subsec_gd-learningrate:
Öğrenme Oranı
~~~~~~~~~~~~~
:math:`\eta` öğrenme oranı algoritma tasarımcısı tarafından
ayarlanabilir. Çok küçük bir öğrenme oranı kullanırsak, :math:`x`'in çok
yavaş güncellenmesine neden olur ve daha iyi bir çözüm elde etmek için
daha fazla yineleme gerektirir. Böyle bir durumda ne olduğunu göstermek
için, :math:`\eta = 0.05` için aynı optimizasyon problemindeki
ilerlemeyi göz önünde bulundurun. Gördüğümüz gibi, 10 adımdan sonra bile
en uygun çözümden çok uzaktayız.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
show_trace(gd(0.05, f_grad), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: 3.486784
.. figure:: output_gd_79c039_39_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
show_trace(gd(0.05, f_grad), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: 3.486784
.. figure:: output_gd_79c039_42_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
show_trace(gd(0.05, f_grad), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: 3.486784
.. figure:: output_gd_79c039_45_1.svg
.. raw:: html
.. raw:: html
Tersine, aşırı yüksek öğrenim oranı kullanırsak,
:math:`\left|\eta f'(x)\right|` birinci dereceden Taylor genişleme
formülü için çok büyük olabilir. Yani, :eq:`gd-taylor-2`
denklemdeki :math:`\mathcal{O}(\eta^2 f'^2(x))` terimi önemli hale
gelebilir. Bu durumda, :math:`x` yinelemesinin :math:`f(x)` değerini
düşüreceğini garanti edemeyiz. Örneğin, öğrenme oranını :math:`\eta=1.1`
olarak ayarladığımızda, :math:`x` optimal çözümü :math:`x=0`'i geçersiz
kılar ve kademeli olarak ıraksar.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
show_trace(gd(1.1, f_grad), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: 61.917364
.. figure:: output_gd_79c039_51_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
show_trace(gd(1.1, f_grad), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: 61.917364
.. figure:: output_gd_79c039_54_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
show_trace(gd(1.1, f_grad), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: 61.917364
.. figure:: output_gd_79c039_57_1.svg
.. raw:: html
.. raw:: html
Yerel Minimum
~~~~~~~~~~~~~
Dışbükey olmayan fonksiyonlarde ne olduğunu göstermek için, bazı sabit
:math:`c` için :math:`f(x) = x \cdot \cos(cx)` durumunu düşünün. Bu
işlevde sonsuz sayıda yerel minimum vardır. Öğrenme oranının seçimimize
bağlı olarak ve sorunun ne kadar iyi şartlandırıldığına bağlı olarak,
birçok çözümden birine varabiliriz. Aşağıdaki örnek, (gerçekçi olmayan)
yüksek öğrenme oranının nasıl vasat bir yerel minimum seviyesine yol
açacağını göstermektedir.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
c = np.array(0.15 * np.pi)
def f(x): # Amaç fonksiyonu
return x * np.cos(c * x)
def f_grad(x): # Amaç fonksiyonunun gradyanı (türevi)
return np.cos(c * x) - c * x * np.sin(c * x)
show_trace(gd(2, f_grad), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: -1.528165
.. figure:: output_gd_79c039_63_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
c = torch.tensor(0.15 * np.pi)
def f(x): # Amaç fonksiyonu
return x * torch.cos(c * x)
def f_grad(x): # Amaç fonksiyonunun gradyanı (türevi)
return torch.cos(c * x) - c * x * torch.sin(c * x)
show_trace(gd(2, f_grad), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: -1.528166
.. figure:: output_gd_79c039_66_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
c = tf.constant(0.15 * np.pi)
def f(x): # Amaç fonksiyonu
return x * tf.cos(c * x)
def f_grad(x): # Amaç fonksiyonunun gradyanı (türevi)
return tf.cos(c * x) - c * x * tf.sin(c * x)
show_trace(gd(2, f_grad), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: -1.528165
.. figure:: output_gd_79c039_69_1.svg
.. raw:: html
.. raw:: html
Çok Değişkenli Gradyan İnişi
----------------------------
Artık tek değişkenli durumun daha iyi bir sezgisine sahip olduğumuza
göre, :math:`\mathbf{x} = [x_1, x_2, \ldots, x_d]^\top`'nın bulunduğu
hali ele alalım. Yani, amaç fonksiyonu
:math:`f: \mathbb{R}^d \to \mathbb{R}` vektörleri skalerlere eşlesin.
Buna göre gradyan çok değişkenli. :math:`d` tane kısmi türevden oluşan
bir vektördür:
.. math:: \nabla f(\mathbf{x}) = \bigg[\frac{\partial f(\mathbf{x})}{\partial x_1}, \frac{\partial f(\mathbf{x})}{\partial x_2}, \ldots, \frac{\partial f(\mathbf{x})}{\partial x_d}\bigg]^\top.
Gradyandaki her kısmi türev elemanı
:math:`\partial f(\mathbf{x})/\partial x_i`, :math:`x_i` girdisine göre
:math:`\mathbf{x}`'deki :math:`f` değişiminin oranını gösterir. Tek
değişkenli durumda daha önce olduğu gibi, ne yapmamız gerektiğine dair
bir fikir edinmek için çok değişkenli fonksiyonlarla ilgili Taylor
açılımını kullanabiliriz. Özellikle, elimizde şu var:
.. math:: f(\mathbf{x} + \boldsymbol{\epsilon}) = f(\mathbf{x}) + \mathbf{\boldsymbol{\epsilon}}^\top \nabla f(\mathbf{x}) + \mathcal{O}(\|\boldsymbol{\epsilon}\|^2).
:label: gd-multi-taylor
Başka bir deyişle, :math:`\boldsymbol{\epsilon}`'te ikinci dereceden
terimlere kadar en dik iniş yönü :math:`-\nabla f(\mathbf{x})` negatif
gradyan ile verilir. Uygun bir öğrenme hızı :math:`\eta > 0` seçilmesi,
prototipik gradyan iniş algoritmasını sağlar:
.. math:: \mathbf{x} \leftarrow \mathbf{x} - \eta \nabla f(\mathbf{x}).
Algoritmanın pratikte nasıl davrandığını görmek için girdi olarak iki
boyutlu vektörü, :math:`\mathbf{x} = [x_1, x_2]^\top`, çıktı olarak bir
skaleri ile bir amaç fonksiyonu, :math:`f(\mathbf{x})=x_1^2+2x_2^2`,
oluşturalım. Gradyan :math:`\nabla f(\mathbf{x}) = [2x_1, 4x_2]^\top`
ile verilir. :math:`\mathbf{x}`'in yörüngesini :math:`[-5, -2]` ilk
konumundan gradyan inişle gözlemleyeceğiz.
Başlangıç olarak, iki yardımcı fonksiyona daha ihtiyacımız var.
Birincisi bir güncelleme işlevi kullanır ve ilk değere 20 kez uygular.
İkinci yardımcı :math:`\mathbf{x}`'in yörüngesini görselleştirir.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def train_2d(trainer, steps=20, f_grad=None): #@save
"""Özelleştirilmiş bir eğitici ile 2B amaç işlevini optimize edin."""
# `s1` ve `s2` daha sonra kullanılacak dahili durum değişkenleridir
x1, x2, s1, s2 = -5, -2, 0, 0
results = [(x1, x2)]
for i in range(steps):
if f_grad:
x1, x2, s1, s2 = trainer(x1, x2, s1, s2, f_grad)
else:
x1, x2, s1, s2 = trainer(x1, x2, s1, s2)
results.append((x1, x2))
print(f'epoch {i + 1}, x1: {float(x1):f}, x2: {float(x2):f}')
return results
def show_trace_2d(f, results): #@save
"""Optimizasyon sırasında 2B değişkenlerin izini gösterin."""
d2l.set_figsize()
d2l.plt.plot(*zip(*results), '-o', color='#ff7f0e')
x1, x2 = np.meshgrid(np.arange(-5.5, 1.0, 0.1),
np.arange(-3.0, 1.0, 0.1))
d2l.plt.contour(x1, x2, f(x1, x2), colors='#1f77b4')
d2l.plt.xlabel('x1')
d2l.plt.ylabel('x2')
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def train_2d(trainer, steps=20, f_grad=None): #@save
"""Özelleştirilmiş bir eğitici ile 2B amaç işlevini optimize edin."""
# `s1` ve `s2` daha sonra kullanılacak dahili durum değişkenleridir
x1, x2, s1, s2 = -5, -2, 0, 0
results = [(x1, x2)]
for i in range(steps):
if f_grad:
x1, x2, s1, s2 = trainer(x1, x2, s1, s2, f_grad)
else:
x1, x2, s1, s2 = trainer(x1, x2, s1, s2)
results.append((x1, x2))
print(f'epoch {i + 1}, x1: {float(x1):f}, x2: {float(x2):f}')
return results
def show_trace_2d(f, results): #@save
"""Optimizasyon sırasında 2B değişkenlerin izini gösterin."""
d2l.set_figsize()
d2l.plt.plot(*zip(*results), '-o', color='#ff7f0e')
x1, x2 = torch.meshgrid(torch.arange(-5.5, 1.0, 0.1),
torch.arange(-3.0, 1.0, 0.1))
d2l.plt.contour(x1, x2, f(x1, x2), colors='#1f77b4')
d2l.plt.xlabel('x1')
d2l.plt.ylabel('x2')
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def train_2d(trainer, steps=20, f_grad=None): #@save
"""Özelleştirilmiş bir eğitici ile 2B amaç işlevini optimize edin."""
# `s1` ve `s2` daha sonra kullanılacak dahili durum değişkenleridir
x1, x2, s1, s2 = -5, -2, 0, 0
results = [(x1, x2)]
for i in range(steps):
if f_grad:
x1, x2, s1, s2 = trainer(x1, x2, s1, s2, f_grad)
else:
x1, x2, s1, s2 = trainer(x1, x2, s1, s2)
results.append((x1, x2))
print(f'epoch {i + 1}, x1: {float(x1):f}, x2: {float(x2):f}')
return results
def show_trace_2d(f, results): #@save
"""Optimizasyon sırasında 2B değişkenlerin izini gösterin."""
d2l.set_figsize()
d2l.plt.plot(*zip(*results), '-o', color='#ff7f0e')
x1, x2 = tf.meshgrid(tf.range(-5.5, 1.0, 0.1),
tf.range(-3.0, 1.0, 0.1))
d2l.plt.contour(x1, x2, f(x1, x2), colors='#1f77b4')
d2l.plt.xlabel('x1')
d2l.plt.ylabel('x2')
.. raw:: html
.. raw:: html
Daha sonra, :math:`\eta = 0.1` öğrenme oranı için optimizasyon
değişkeninin :math:`\mathbf{x}` yörüngesini gözlemliyoruz. 20 adımdan
sonra :math:`\mathbf{x}` değerinin minimuma :math:`[0, 0]`'da
yaklaştığını görüyoruz. İlerleme oldukça yavaş olsa da oldukça iyi
huyludur.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def f_2d(x1, x2): # Amaç fonksiyonu
return x1 ** 2 + 2 * x2 ** 2
def f_2d_grad(x1, x2): # Amaç fonksiyonun gradyanı
return (2 * x1, 4 * x2)
def gd_2d(x1, x2, s1, s2, f_grad):
g1, g2 = f_grad(x1, x2)
return (x1 - eta * g1, x2 - eta * g2, 0, 0)
eta = 0.1
show_trace_2d(f_2d, train_2d(gd_2d, f_grad=f_2d_grad))
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
epoch 20, x1: -0.057646, x2: -0.000073
.. figure:: output_gd_79c039_87_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def f_2d(x1, x2): # Amaç fonksiyonu
return x1 ** 2 + 2 * x2 ** 2
def f_2d_grad(x1, x2): # Amaç fonksiyonun gradyanı
return (2 * x1, 4 * x2)
def gd_2d(x1, x2, s1, s2, f_grad):
g1, g2 = f_grad(x1, x2)
return (x1 - eta * g1, x2 - eta * g2, 0, 0)
eta = 0.1
show_trace_2d(f_2d, train_2d(gd_2d, f_grad=f_2d_grad))
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
epoch 20, x1: -0.057646, x2: -0.000073
/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_gd_79c039_90_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def f_2d(x1, x2): # Amaç fonksiyonu
return x1 ** 2 + 2 * x2 ** 2
def f_2d_grad(x1, x2): # Amaç fonksiyonun gradyanı
return (2 * x1, 4 * x2)
def gd_2d(x1, x2, s1, s2, f_grad):
g1, g2 = f_grad(x1, x2)
return (x1 - eta * g1, x2 - eta * g2, 0, 0)
eta = 0.1
show_trace_2d(f_2d, train_2d(gd_2d, f_grad=f_2d_grad))
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
epoch 20, x1: -0.057646, x2: -0.000073
.. figure:: output_gd_79c039_93_1.svg
.. raw:: html
.. raw:: html
Uyarlamalı Yöntemler
--------------------
:numref:`subsec_gd-learningrate` içinde görebildiğimiz gibi, “doğru”
:math:`\eta` öğrenme oranını elde etmek çetrefillidir. Eğer çok küçük
seçersek, çok az ilerleme kaydederiz. Eğer çok büyük seçersek, çözüm
salınır ve en kötü ihtimalle ıraksayabilir. :math:`\eta`'yi otomatik
olarak belirleyebilirsek veya bir öğrenme oranı seçmek zorunda kalmaktan
kurtulsak ne olur? Bu durumda sadece amaç fonksiyonun değerine ve
gradyanlarına değil, aynı zamanda *eğri*\ sinin değerine de bakan ikinci
dereceden yöntemler yardımcı olabilir. Bu yöntemler, hesaplama maliyeti
nedeniyle doğrudan derin öğrenmeye uygulanamamakla birlikte, aşağıda
belirtilen algoritmaların arzu edilen özelliklerinin çoğunu taklit eden
gelişmiş optimizasyon algoritmalarının nasıl tasarlanacağına dair
yararlı önseziler sağlarlar.
Newton Yöntemi
~~~~~~~~~~~~~~
:math:`f: \mathbb{R}^d \rightarrow \mathbb{R}` fonksiyonun Taylor
açılımının gözden geçirirsek, ilk dönemden sonra durmaya gerek yoktur.
Aslında, böyle yazabilirsiniz:
.. math:: f(\mathbf{x} + \boldsymbol{\epsilon}) = f(\mathbf{x}) + \boldsymbol{\epsilon}^\top \nabla f(\mathbf{x}) + \frac{1}{2} \boldsymbol{\epsilon}^\top \nabla^2 f(\mathbf{x}) \boldsymbol{\epsilon} + \mathcal{O}(\|\boldsymbol{\epsilon}\|^3).
:label: gd-hot-taylor
Hantal notasyondan kaçınmak için
:math:`\mathbf{H} \stackrel{\mathrm{def}}{=} \nabla^2 f(\mathbf{x})`'i
:math:`f`'in Hessian'i olarak tanımlıyoruz, ki bu bir :math:`d \times d`
matristir. Küçük :math:`d` ve basit sorunlar için :math:`\mathbf{H}`'nin
hesaplaması kolaydır. Öte yandan derin sinir ağları için
:math:`\mathbf{H}`, :math:`\mathcal{O}(d^2)` girdilerinin depolanması
maliyeti nedeniyle yasaklayıcı derecede büyük olabilir. Ayrıca geri
yayma yoluyla hesaplamak çok pahalı olabilir. Şimdilik böyle düşünceleri
göz ardı edelim ve hangi algoritmayı alacağımıza bakalım.
Sonuçta, minimum :math:`f`, :math:`\nabla f = 0`'i karşılar.
:numref:`subsec_calculus-grad` içindeki kalkülüs kurallarını izleyerek
:eq:`gd-hot-taylor` türevlerini :math:`\boldsymbol{\epsilon}` ile
ilgili olarak alıp ve yüksek dereceden terimleri göz ardı ederek şuna
varırız:
.. math::
\nabla f(\mathbf{x}) + \mathbf{H} \boldsymbol{\epsilon} = 0 \text{ ve böylece }
\boldsymbol{\epsilon} = -\mathbf{H}^{-1} \nabla f(\mathbf{x}).
Yani, optimizasyon probleminin bir parçası olarak Hessian
:math:`\mathbf{H}`'nin tersini almalıyız.
Basit bir örnek olarak, :math:`f(x) = \frac{1}{2} x^2` için
:math:`\nabla f(x) = x` ve :math:`\mathbf{H} = 1`'e sahibiz. Bu nedenle
herhangi bir :math:`x` için :math:`\epsilon = -x` elde ederiz. Başka bir
deyişle, bir *tek* adım, herhangi bir ayarlamaya gerek kalmadan mükemmel
bir şekilde yakınsama için yeterlidir! Ne yazık ki, burada biraz
şanslıyız: Taylor açılımı
:math:`f(x+\epsilon)= \frac{1}{2} x^2 + \epsilon x + \frac{1}{2} \epsilon^2`'ten
dolayı kesindir.
Diğer problemlerde neler olduğunu görelim. Bazı sabit :math:`c` için
dışbükey hiperbolik kosinüs fonksiyonu :math:`f(x) = \cosh(cx)` göz
önüne alındığında, :math:`x=0`'daki küresel minimum seviyeye birkaç
yinelemeden sonra ulaşıldığını görebiliriz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
c = np.array(0.5)
def f(x): # Amaç fonksiyonu
return np.cosh(c * x)
def f_grad(x): # Amaç fonksiyonun gradyanı
return c * np.sinh(c * x)
def f_hess(x): # Amaç fonksiyonunun Hessian'i
return c**2 * np.cosh(c * x)
def newton(eta=1):
x = 10.0
results = [x]
for i in range(10):
x -= eta * f_grad(x) / f_hess(x)
results.append(float(x))
print('donem 10, x:', x)
return results
show_trace(newton(), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: 0.0
.. figure:: output_gd_79c039_99_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
c = torch.tensor(0.5)
def f(x): # Amaç fonksiyonu
return torch.cosh(c * x)
def f_grad(x): # Amaç fonksiyonun gradyanı
return c * torch.sinh(c * x)
def f_hess(x): # Amaç fonksiyonunun Hessian'i
return c**2 * torch.cosh(c * x)
def newton(eta=1):
x = 10.0
results = [x]
for i in range(10):
x -= eta * f_grad(x) / f_hess(x)
results.append(float(x))
print('donem 10, x:', x)
return results
show_trace(newton(), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: tensor(0.)
.. figure:: output_gd_79c039_102_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
c = tf.constant(0.5)
def f(x): # Amaç fonksiyonu
return tf.cosh(c * x)
def f_grad(x): # Amaç fonksiyonun gradyanı
return c * tf.sinh(c * x)
def f_hess(x): # Amaç fonksiyonunun Hessian'i
return c**2 * tf.cosh(c * x)
def newton(eta=1):
x = 10.0
results = [x]
for i in range(10):
x -= eta * f_grad(x) / f_hess(x)
results.append(float(x))
print('donem 10, x:', x)
return results
show_trace(newton(), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: tf.Tensor(0.0, shape=(), dtype=float32)
.. figure:: output_gd_79c039_105_1.svg
.. raw:: html
.. raw:: html
Şimdi bir sabit :math:`c` için :math:`f(x) = x \cos(c x)` gibi bir
*dışbükey olmayan* işlevi düşünelim. Herşeyin sonunda, Newton'un
yönteminde Hessian'a böldüğümüze dikkat edin. Bu, ikinci türev *negatif*
ise :math:`f` değeri *artma* yönüne doğru yürüyebileceğimiz anlamına
gelir. Bu algoritmanın ölümcül kusurudur. Pratikte neler olduğunu
görelim.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
c = np.array(0.15 * np.pi)
def f(x): # Amaç fonksiyonu
return x * np.cos(c * x)
def f_grad(x): # Amaç fonksiyonun gradyanı
return np.cos(c * x) - c * x * np.sin(c * x)
def f_hess(x): # Amaç fonksiyonun Hessian'i
return - 2 * c * np.sin(c * x) - x * c**2 * np.cos(c * x)
show_trace(newton(), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: 26.834133
.. figure:: output_gd_79c039_111_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
c = torch.tensor(0.15 * np.pi)
def f(x): # Amaç fonksiyonu
return x * torch.cos(c * x)
def f_grad(x): # Amaç fonksiyonun gradyanı
return torch.cos(c * x) - c * x * torch.sin(c * x)
def f_hess(x): # Amaç fonksiyonun Hessian'i
return - 2 * c * torch.sin(c * x) - x * c**2 * torch.cos(c * x)
show_trace(newton(), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: tensor(26.8341)
.. figure:: output_gd_79c039_114_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
c = tf.constant(0.15 * np.pi)
def f(x): # Amaç fonksiyonu
return x * tf.cos(c * x)
def f_grad(x): # Amaç fonksiyonun gradyanı
return tf.cos(c * x) - c * x * tf.sin(c * x)
def f_hess(x): # Amaç fonksiyonun Hessian'i
return - 2 * c * tf.sin(c * x) - x * c**2 * tf.cos(c * x)
show_trace(newton(), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: tf.Tensor(26.834133, shape=(), dtype=float32)
.. figure:: output_gd_79c039_117_1.svg
.. raw:: html
.. raw:: html
Olağanüstü derecede yanlış gitti. Bunu nasıl düzeltebiliriz? Bunun
yerine mutlak değerini alarak Hessian'i “düzeltmek” bir yol olabilir.
Başka bir strateji de öğrenme oranını geri getirmektir. Bu amacı yenmek
gibi görünüyor, ama tam olarak değil. İkinci dereceden bilgilere sahip
olmak, eğrilik büyük olduğunda dikkatli olmamızı ve amaç fonksiyon daha
düz olduğunda daha uzun adımlar atmamızı sağlar. Bunun biraz daha küçük
bir öğrenme oranıyla nasıl çalıştığını görelim, :math:`\eta = 0.5`
diyelim. Gördüğümüz gibi, oldukça verimli bir algoritmamız var.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
show_trace(newton(0.5), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: 7.26986
.. figure:: output_gd_79c039_123_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
show_trace(newton(0.5), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: tensor(7.2699)
.. figure:: output_gd_79c039_126_1.svg
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
show_trace(newton(0.5), f)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
donem 10, x: tf.Tensor(7.26986, shape=(), dtype=float32)
.. figure:: output_gd_79c039_129_1.svg
.. raw:: html
.. raw:: html
Yakınsaklık Analizi
~~~~~~~~~~~~~~~~~~~
Biz sadece bazı dışbükey ve üç kez türevlenebilir amaç fonksiyonu
:math:`f` için Newton yönteminin yakınsama oranını analiz ediyoruz,
burada ikinci türev sıfırdan farklı, yani :math:`f'' > 0`. Çok
değişkenli kanıt, aşağıdaki tek boyutlu argümanın basit bir uzantısıdır
ve sezgi açısından bize pek yardımcı olmadığından ihmal edilir.
:math:`k`'inci yinelemesinde :math:`x` değerini :math:`x^{(k)}` ile
belirtelim ve :math:`e^{(k)} \stackrel{\mathrm{def}}{=} x^{(k)} - x^*`
yinelemesinde eniyilikten uzaklık olmasına izin verelim. Taylor açılımı
ile :math:`f'(x^*) = 0` durumu aşağıdaki gibi yazılabilir:
.. math:: 0 = f'(x^{(k)} - e^{(k)}) = f'(x^{(k)}) - e^{(k)} f''(x^{(k)}) + \frac{1}{2} (e^{(k)})^2 f'''(\xi^{(k)}),
ki bu bazı :math:`\xi^{(k)} \in [x^{(k)} - e^{(k)}, x^{(k)}]` için
tutar. Yukarıdaki açılımı :math:`f''(x^{(k)})` ile bölmek aşağıdaki
ifadeye yol açar:
.. math:: e^{(k)} - \frac{f'(x^{(k)})}{f''(x^{(k)})} = \frac{1}{2} (e^{(k)})^2 \frac{f'''(\xi^{(k)})}{f''(x^{(k)})}.
:math:`x^{(k+1)} = x^{(k)} - f'(x^{(k)}) / f''(x^{(k)})` güncellemesine
sahip olduğumuzu hatırlayın. Bu güncelleme denklemini yerine koyarak ve
her iki tarafın mutlak değerini alarak şuna ulaşırız:
.. math:: \left|e^{(k+1)}\right| = \frac{1}{2}(e^{(k)})^2 \frac{\left|f'''(\xi^{(k)})\right|}{f''(x^{(k)})}.
Sonuç olarak,
:math:`\left|f'''(\xi^{(k)})\right| / (2f''(x^{(k)})) \leq c` ile
sınırlı olan bir bölgede olduğumuzda, dördüncü dereceden azalan bir
hatamız var:
.. math:: \left|e^{(k+1)}\right| \leq c (e^{(k)})^2.
Bir taraftan, optimizasyon araştırmacıları buna *doğrusal* yakınsama
derler, oysa
:math:`\left|e^{(k+1)}\right| \leq \alpha \left|e^{(k)}\right|` gibi bir
koşul bir *sabit* yakınsama oranı olarak adlandırılır. Bu analizin bir
dizi uyarıyla birlikte geldiğini unutmayın. İlk olarak, hızlı yakınsama
bölgesine ulaşacağımıza dair gerçekten çok fazla bir garantimiz yok.
Bunun yerine, sadece bir kez ulaştığımızda yakınsamanın çok hızlı
olacağını biliyoruz. İkincisi, bu analiz :math:`f`'in daha yüksek
mertebeden türevlere kadar iyi-huylu olmasını gerektirir. :math:`f`'ın
değerlerini nasıl değiştirebileceği konusunda "şaşırtıcı" özelliklere
sahip olmamasını sağlamaya geliyor.
Ön Şartlandırma
~~~~~~~~~~~~~~~
Oldukça şaşırtıcı bir şekilde hesaplamak ve tam Hessian'ı saklamak çok
pahalıdır. Alternatifler bulmak bu nedenle arzu edilir. Meseleleri
iyileştirmenin bir yolu da *ön şartlandırma*\ dır. Hessian'ın bütünüyle
hesaplanmasını önler, sadece *köşesel* girdilerini hesaplar. Bu,
aşağıdaki biçimdeki algoritmaların güncellenmesine yol açar:
.. math:: \mathbf{x} \leftarrow \mathbf{x} - \eta \mathrm{diag}(\mathbf{H})^{-1} \nabla f(\mathbf{x}).
Bu tam Newton'un yöntemi kadar iyi olmasa da, onu kullanmamaktan çok
daha iyidir. Bunun neden iyi bir fikir olabileceğini görmek için bir
değişkenin milimetre cinsinden yüksekliği ve diğeri kilometre cinsinden
yüksekliği belirten bir durum göz önünde bulundurun. Her ikisi için
doğal ölçeğin de metre olarak olduğunu varsayarsak, parametrelemelerde
korkunç bir uyumsuzluğumuz var. Neyse ki, ön şartlandırma kullanmak bunu
ortadan kaldırır. Gradyan inişi ile etkin bir şekilde ön koşullandırma,
her değişken için farklı bir öğrenme oranı seçilmesi anlamına gelir
(:math:`\mathbf{x}` vektörünün koordinatı). Daha sonra göreceğimiz gibi
ön şartlandırma, rasgele gradyan iniş optimizasyon algoritmalarındaki
bazı yaratıcılığı yönlendirir.
Doğru Üzerinde Arama ile Gradyan İnişi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Gradyan inişindeki en önemli sorunlardan biri, hedefi aşmamız veya
yetersiz ilerleme kaydetmemizdir. Sorun için basit bir düzeltme, gradyan
iniş ile birlikte doğru üzerinde arama kullanmaktır. Yani,
:math:`\nabla f(\mathbf{x})` tarafından verilen yönü kullanırız ve
ardından :math:`\eta` öğrenme oranının
:math:`f(\mathbf{x} - \eta \nabla f(\mathbf{x}))`'yi en aza indirmek
için ikili arama yaparız.
Bu algoritma hızla yakınsar (analiz ve kanıt için bkz.,
:cite:`Boyd.Vandenberghe.2004`). Ancak, derin öğrenme amacıyla bu o
kadar da mümkün değildir, çünkü doğru üzerinde aramanın her adımı, tüm
veri kümesindeki amaç fonksiyonunu değerlendirmemizi gerektirir. Bunu
tamamlamak çok maliyetlidir.
Özet
----
- Öğrenme oranları önemlidir. Çok büyükse ıraksarız, çok küçükse
ilerleme kaydetmeyiz.
- Gradyan inişi yerel minimumda takılabilir.
- Yüksek boyutlarda öğrenme oranının ayarlanması karmaşıktır.
- Ön şartlandırma ayarların ölçeklendirilmesine yardımcı olabilir.
- Newton'un yöntemi dışbükey problemlerde düzgün çalışmaya başladıktan
sonra çok daha hızlıdır.
- Dışbükey olmayan problemler için herhangi bir ayarlama yapmadan
Newton'un yöntemini kullanmaktan sakının.
Alıştırmalar
------------
1. Gradyan inişi için farklı öğrenme oranları ve amaç fonksiyonları ile
deney yapın.
2. :math:`[a, b]` aralığında dışbükey işlevini en aza indirmek için
doğru üzerinde arama gerçekleştirin.
1. İkili arama için türevlere ihtiyacınız var mı, mesela
:math:`[a, (a+b)/2]` veya :math:`[(a+b)/2, b]`'yi seçip
seçmeyeceğinize karar vermek için?
2. Algoritma için yakınsama oranı ne kadar hızlıdır?
3. Algoritmayı uygulayın ve :math:`\log (\exp(x) + \exp(-2x -3))`'ü
en aza indirmek için uygulayın.
3. Gradyan inişinin son derece yavaş olduğu :math:`\mathbb{R}^2`'te
tanımlanan bir amaç fonksiyonu tasarlayın. İpucu: Farklı
koordinatları farklı şekilde ölçeklendirin.
4. Aşağıdaki ön şartlandırmaları kullanarak Newton yönteminin
hafifsiklet versiyonunu uygulayın:
1. Ön koşul olarak köşegen Hessian kullanın.
2. Gerçek (muhtemelen işaretli) değerler yerine bunun mutlak
değerlerini kullanın.
3. Bunu yukarıdaki probleme uygulayın.
5. Yukarıdaki algoritmayı bir takım amaç fonksiyonuna uygulayın
(dışbükey olan veya olmayan). Koordinatları :math:`45` derece
döndürürseniz ne olur?
`Tartışmalar `__