# Import potrebných balíčkov
import numpy as np
import matplotlib.pyplot as plt
from sympy.utilities.lambdify import lambdify
import sympy as sp
# Funkcie z predchádzajúceho notebook-u
def plot_func(xx, yy, f, X=None):
if not X is None:
Xmin, Xmax = X[:, 0].min(), X[:, 0].max()
Ymin, Ymax = X[:, 1].min(), X[:, 1].max()
if (Xmin < xx.min() or Xmax > xx.max() or
Ymin < yy.min() or Ymax > yy.max()):
xx = np.linspace(Xmin, Xmax, 100)
yy = np.linspace(Ymin, Ymax, 100)
xx, yy = np.meshgrid(xx, yy)
plt.scatter(X[:, 0], X[:, 1], zorder=10)
zz = f(xx, yy)
plt.contour(xx, yy, zz, cmap='Spectral')
# obe osi v rovnakej škále + legenda
plt.gca().set_aspect('equal')
plt.xlabel('x'); plt.ylabel('y')
plt.colorbar(label='z')
def grad_desc(grad_f, init_point,
num_steps, learning_rate):
X = np.zeros((num_steps + 1, 2))
X[0] = init_point
for i in range(num_steps):
X[i+1] = X[i] - learning_rate * grad_f(*X[i])
return X
Skúsme teraz ten istý postup aplikovať na o trochu náročnejší problém: na roztiahnutý paraboloid. Problém bude v tom, že nebude možné nájsť rýchlosť učenia, ktorá by dobre fungovala v jednom aj v druhom smere.
Povedzme, že funkcia, ktorú chceme minimalizovať, bude mať nasledujúci tvar:
\begin{equation} z = f(x, y) = (5x)^2 + y^2 \end{equation}Na začiatok si funkciu znovu zadefinujme a vizualizujme. Funkciu zadefinujeme najprv symbolicky, aby sme neskôr mohli využiť automatický spôsob na symbolický výpočet gradientu:
symx, symy = sp.symbols('x y')
symf = (5*symx)**2 + symy ** 2
symf
f = lambdify((symx, symy), symf, "numpy")
Vizualizáciu funkcie realizujeme rovnako, ako predtým:
xx, yy = np.mgrid[-10:10.2:0.2, -10:10.2:0.2]
zz = f(xx, yy)
plot_func(xx, yy, f)
Pomocou balíčka sympy znovu automaticky určte gradient a jeho symbolické vyjadrenie preveďte na klasickú numerickú funkciu grad_f:
Ak sa teraz pokúsime danú účelovú funkciu minimalizovať pomocou metódy klesajúceho gradientu, narazíme na problém: bude ťažké nastaviť rýchlosť učenia, ktorá by fungovala v oboch smeroch. Buď nastavíme rýchlosť učenia vysokú a dôjde ku oscilácii v smere, kde funkcia klesá prudšie alebo nastavíme rýchlosť učenia nízku a minimalizácia v druhom smere bude postupovať extrémne pomaly.
Môžeme si to overiť aj prakticky. S $\gamma = 0.1$ dochádza ku oscilácii:
X = grad_desc(grad_f, init_point=[-9, -8],
num_steps=20, learning_rate=0.1)
plot_func(xx, yy, f, X)
Ak zvolíme $\gamma=0.01$, minimalizácia v smere $y$ postupuje pomaly:
X = grad_desc(grad_f, init_point=[-9, -8],
num_steps=20, learning_rate=0.01)
plot_func(xx, yy, f, X)
Jeden spôsob, ako sa brániť voči problému, na ktorý sme narazili pri použití metódy klesajúceho gradientu, je zaviesť do minimalizačného pravidla ďalší člen – hybnosť. V tom prípade berieme pri výpočte nového bodu do úvahy aj veľkosť zmeny z predchádzajúcej iterácie:
\begin{align} \Delta \mathbf{x}_{i+1} &= \alpha \Delta \mathbf{x}_i - \gamma \nabla f(\mathbf{x}_i) \\[0.75em] \mathbf{x}_{i+1} &= \mathbf{x}_i + \Delta \mathbf{x}_{i+1}, \end{align}kde $\alpha$ je koeficien hybnosti (určuje aká váha sa pri výpočte prikladá zmene z predchádzajúcej iterácie $\Delta \mathbf{x}_i$).
Výhodou je, že ak minimalizácia dlho postupuje jedným smerom, hybnosť sa bude kumulovať a kroky v tom smere sa budú postupne zväčšovať. Naopak v smere, kde dochádza ku oscilácii sa bude neustále meniť znamienko zmeny, čo bude mať tendenciu osciláciu tlmiť.
Doplňte do nasledujúcej bunky kód metódy klesajúceho gradientu s hybnosťou (podľa vzorcov uvedených vyššie):
def grad_desc_momentum(grad_f, init_point,
num_steps, learning_rate, alpha):
X = np.zeros((num_steps + 1, 2))
X[0] = init_point
deltaX = 0
for i in range(num_steps):
# sem treba doplniť kód
return X
Pozrime sa teraz, ako sa bude nášmu novému algoritmu dariť na probléme roztiahnutého paraboloidu:
X = grad_desc_momentum(grad_f, init_point=[-9, -8],
num_steps=20, learning_rate=0.01, alpha=0.8)
plot_func(xx, yy, f, X)
Ako vidno, upravený algoritmus nemá problém nájsť minimum, pretože hybnosť akceleruje jeho postup v smere, kde sa znamienko gradientu dlhodobo nemení a naopak tlmí osciláciu v smere, kde minimum preskakujeme.