Aproximación de gradiente y método de descenso
El objetivo de un problema de optimización es minimizar o maximizar una función \(f\) sobre un conjunto \(X\):
\[ \min f(x) \] \[ s.a. \quad x\in X \]
Calculando gradientes
Una forma más sencilla de calcular los gradientes es usar una aproximación de diferencias finitas:
\[ f'(x) = \lim_{h\to 0}\frac{f(x+h)-f(x)}{h} \]
fijando algún \(h\) y aproximando el gradiente por
\[ f'(x) \approx \frac{f(x+h)-f(x)}{h}. \]
Este método para calcular el gradiente tiene dos desventajas:
Es lento. Para una función de \(n\) variables, necesitamos evaluarla al menos \(n+1\) veces para obtener el gradiente completo.
No es preciso, como muestra el siguiente ejemplo.
Por ejemplo, considerar un punto \(x = (−2, −1)\). Para \(h \in [10^{-15}, 10^{-1}]\), calcular la aproximación por diferencias finitas de la derivada parcial de \(f\) con respecto a la segunda variable y graficar la dependencia de esta aproximación con respecto a \(h\). Incluir en el gráfico la derivada parcial exacta.
Mi primer método de descenso
Lo primero que vamos a intentar resolver es un problema de optimización sin restricciones. Es decir \[ \min f(x), \quad x\in\mathbb{R}^n. \]
Dado que el gradiente es la dirección crecimiento más rápido, la idea es moverse en la dirección opuesta. Esto da lugar a lo que se conoce como algoritmo de descenso por gradiente.
\[ x_{k+1} = x_k -\alpha_k\nabla f(x_k), \]
donde \(\alpha_k>0\) se lo define como el tamaño de paso. Si esto converge, entonces \(x_{k+1}\) y \(x_k\) están cerca uno del otro, y por lo tanto \(\nabla f(x_k)\to 0\). Esto significa que este método converge a un punto estacionario.
using Random
function create_anim(
f,
path,
xlims,
ylims,
file_name = joinpath(pwd(), randstring(12) * ".gif");
xbounds = xlims,
ybounds = ylims,
fps = 15,
)
xs = range(xlims...; length = 100)
ys = range(ylims...; length = 100)
plt = contourf(xs, ys, f; color = :jet)
# add constraints if provided
if !(xbounds == xlims && ybounds == ylims)
x_rect = [xbounds[1]; xbounds[2]; xbounds[2]; xbounds[1]; xbounds[1]]
y_rect = [ybounds[1]; ybounds[1]; ybounds[2]; ybounds[2]; ybounds[1]]
plot!(x_rect, y_rect; line = (2, :dash, :red), label="")
end
# add an empty plot
plot!(Float64[], Float64[]; line = (4, :arrow, :black), label = "")
# extract the last plot series
plt_path = plt.series_list[end]
# create the animation and save it
anim = Animation()
for x in eachcol(path)
push!(plt_path, x[1], x[2]) # add a new point
frame(anim)
end
gif(anim, file_name; fps = fps, show_msg = false)
return nothing
end