Notas para contenedor de docker:

Comando de docker para ejecución de la nota de forma local:

nota: cambiar <ruta a mi directorio> por la ruta de directorio que se desea mapear a /datos dentro del contenedor de docker.

docker run --rm -v <ruta a mi directorio>:/datos --name jupyterlab_r_kernel_local -p 8888:8888 -d palmoreck/jupyterlab_r_kernel:1.1.0

password para jupyterlab: qwerty

Detener el contenedor de docker:

docker stop jupyterlab_r_kernel_local

Documentación de la imagen de docker palmoreck/jupyterlab_r_kernel:1.1.0 en liga.


Nota basada en liga1, liga2

En esta nota se consideran resolver problemas de la forma:

$$\min f_o(x)$$

con $f_o:\mathbb{R}^n \rightarrow \mathbb{R}$ fuertemente convexa y $f_o \in \mathcal{C}^2(\text{dom}f_o)$ para buscar óptimos locales. Además se asume que los puntos iniciales $x^{(0)}$ de los métodos iterativos están en $\text{dom}f_o$ y los conjuntos $f_o(x^{(0)})$-subnivel son conjuntos cerrados. Ver 1.4.Polinomios_de_Taylor_y_diferenciacion_numerica y 4.1.Optimizacion_numerica_y_machine_learning para definiciones utilizadas en esta nota.

También se asume que existe un punto óptimo $x^*$ por lo que el problema tiene solución y el valor óptimo se denota por $p^* = f_o(x^*) = \inf f_o(x)$

Las suposición que una función $f$ sea convexa asegura que una condición necesaria y suficiente para que $x^*$ sea óptimo es: $\nabla f(x^*) = 0$ la cual es en general es un conjunto de $n$ ecuaciones no lineales en $n$ variables y que resuelve el problema de optimización planteado al inicio.

Método de descenso en gradiente (implementación en R)

Algoritmo de descenso en gradiente

Dado un punto inicial $x$ en $\text{dom}f_o$

Repetir el siguiente bloque para $k=0,1,2,...$

  1. Calcular la dirección de descenso $\Delta x = - \nabla f_o(x)$.
  2. Búsqueda de línea. Elegir un tamaño de paso $t > 0$.
  3. Hacer la actualización: $x = x + t\Delta x$.

hasta convergencia (satisfacer criterio de paro).

Nota: los pasos en el algoritmo anterior representan una guía para la implementación. Al describirse los pasos de un algoritmo no implica que se tengan que implementar uno a continuación del otro como se describe. Si una implementación respeta la lógica y al mismo método, entonces pueden seguirse los pasos de una forma distinta.


In [1]:
install.packages("latex2exp",lib="/usr/local/lib/R/site-library/",
                repos="https://cran.itam.mx/")

In [1]:
library(ggplot2)
library(latex2exp)

In [2]:
inc_index<-function(vec,index,h){
    '
    Auxiliary function for gradient and Hessian computation.
    Args:
        vec (double): vector
        index (int): index.
        h (float):   quantity that vec[index] will be increased.
    Returns:
        vec (double): vector with vec[index] increased by h.
    '
    vec[index]<-vec[index]+h
    vec
}

In [3]:
gradient_approximation<-function(f,x,h=1e-8){
    '
    Numerical approximation of gradient for function f using forward differences.
    Args:
        f (expression): definition of function f.
        x (double): vector that holds values where gradient will be computed.
        h (float): step size for forward differences, tipically h=1e-8
    Returns:
        gf (array): numerical approximation to gradient of f.

    '
    n<-length(x)
    gf<-vector("double",n)
    for(i in 1:n){
        gf[i]=(f(inc_index(x,i,h))-f(x))
    }
    gf/h
}

In [4]:
Hessian_approximation<-function(f,x,h=1e-6){
    '
    Numerical approximation of Hessian for function f using forward differences.
    Args:
        f (expression): definition of function f.
        x (double): vector that holds values where Hessian will be computed.
        h (float): step size for forward differences, tipically h=1e-6
    Returns:
        Hf (double): matrix of numerical approximation to Hessian of f.
    '
    n<-length(x)
    Hf<-matrix(rep(0,n^2),nrow=n,ncol=n)
    f_x<-f(x)
    for(i in 1:n){
        x_inc_in_i<-inc_index(x,i,h)
        f_x_inc_in_i<-f(x_inc_in_i)
        for(j in i:n){
            dif<-f(inc_index(x_inc_in_i,j,h))-f_x_inc_in_i-f(inc_index(x,j,h))+f_x
            Hf[i,j]<-dif
            if(j!=i)
                Hf[j,i]<-dif
        }
    }
    Hf/h^2
}

In [5]:
line_search_by_backtracking<-function(f,dir_desc,x,
                                      der_direct, alpha=.15, beta=.5){
    '
    Line search that sufficiently decreases f restricted to a ray in the direction dir_desc.
    Args:
        alpha (float): parameter in line search with backtracking, tipically .15
        beta (float): parameter in line search with backtracking, tipically .5
        f (expression): definition of function f.
        dir_desc (double): vector of descent direction.
        x (double): vector that holds values where line search will be performed.
        der_direct (float): directional derivative of f.
    Returns:
        t (float): positive number for stepsize along dir_desc that sufficiently decreases f.
    '
    t<-1
    if (alpha > 1/2){
        print('alpha must be less than or equal to 1/2')
        t <- -1
    }
    if (beta>1){
        print('beta must be less than 1')
        t <- -1
    }
    if (t!=-1){
        eval1 <- f(x+t*dir_desc)
        eval2 <- f(x) + alpha*t*der_direct
        while (eval1 > eval2){
            t<-beta*t
            eval1 <- f(x+t*dir_desc)
            eval2 <- f(x)+alpha*t*der_direct
        }
    }
    t
}

In [6]:
Euclidian_norm<-function(vec){
    sqrt(sum(vec*vec))
}

In [7]:
compute_error<-function(x_obj,x_approx){
    '
    Relative or absolute error between x_obj and x_approx.
    '
    if (Euclidian_norm(x_obj) > .Machine$double.eps){
        Err<-Euclidian_norm(x_obj-x_approx)/Euclidian_norm(x_obj)
    }else
        Err<-Euclidian_norm(x_obj-x_approx)
    Err
}

In [8]:
gradient_descent<-function(f, x_0, tol, 
                        tol_backtracking, x_ast, p_ast, maxiter){
    '
    Method of gradient descent to numerically approximate solution of min f.
    Args:
        f (expression): definition of function f.
        x_0 (double): vector of initial point for gradient descent method.
        tol (float): tolerance that will halt method. Controls norm of gradient of f.
        tol_backtracking (float): tolerance that will halt method. Controls value of line search by backtracking.
        x_ast (double): vector solution of min f, now its required that user knows the solution...
        p_ast (double): vector value of f(x_ast), now its required that user knows the solution...
        maxiter (int): maximum number of iterations
    Returns:
        x (double): vector approximation of x_ast.
        iteration (int): number of iterations.
        Err_plot (double): vector array of absolute error between p_ast and f(x) with x approximation.
                          of x_ast. Useful for plotting.
        x_plot (double): vector array that containts in columns vector of approximations. Last column
                        contains x, approximation of solution. Useful for plotting.
    '
    iteration <- 1
    x <- x_0
    
    feval <- f(x)
    gfeval <- gradient_approximation(f,x)

    normgf <- Euclidian_norm(gfeval)
    
    Err_plot_aux <- vector("double",maxiter)
    Err_plot_aux[iteration] <- compute_error(p_ast,feval)
    
    Err <- compute_error(x_ast,x)
    n <- length(x)
    x_plot <- matrix(0,nrow=n,ncol=maxiter)
    x_plot[,iteration] <- x
    
    cat(sprintf("I\tNormagf\t\tError x_ast\tError p_ast\tline search\n"))
    cat(sprintf("%d\t%.2e\t%0.2e\t%0.2e\t%s\n",iteration,normgf,Err,Err_plot_aux[iteration],"---"))
    iteration<-iteration + 1
    while(normgf>tol && iteration <= maxiter){
        dir_desc <- -gfeval
        der_direct <- sum(gfeval*dir_desc)
        t <- line_search_by_backtracking(f,dir_desc,x,der_direct)
        x <- x + t*dir_desc
        feval <- f(x)
        gfeval <- gradient_approximation(f,x)
        normgf <- Euclidian_norm(gfeval)
        Err_plot_aux[iteration] <- compute_error(p_ast,feval)
        x_plot[,iteration] <- x
        Err <- compute_error(x_ast,x)
        cat(sprintf("%d\t%.2e\t%0.2e\t%0.2e\t%0.2e\n",iteration,normgf,Err,Err_plot_aux[iteration],t))
        if (t<tol_backtracking){ #if t is less than tol_backtracking then we need to check the reason
            iter_salida <- iteration
            iteration <- maxiter - 1
        }
        iteration <- iteration + 1
    } #while
    cat(sprintf("Error of x with respect to x_ast: %.2e\n",Err))
    cat(sprintf("Approximate solution:"))
    print(x)
    cond <- Err_plot_aux > .Machine$double.eps*10**(-2)
    Err_plot <- Err_plot_aux[cond]
    if (iteration == maxiter && t < tol_backtracking){
        print("Backtracking value less than tol_backtracking, check approximation")
        iteration<-iter_salida
        x_plot <- x_plot[,1:iteration]
        iteration <- iteration -1
    }
    else{
        if (iteration == maxiter) print("Reached maximum number of iterations, check approximation")
        iteration <- iteration - 1
        x_plot <- x_plot[,1:iteration]
        }
   list(x,iteration,Err_plot,x_plot)
}

Ejemplos

1) $$\min \quad (x_1-2)^2+(2-x_2)^2+x_3^2+x_4^4$$

La solución del problema anterior es $x^* = (2,2,0,0)^T$.

Método de descenso gradiente con $x_0=(5,5,1,0)^T$:


In [9]:
fo <-function(x)(x[1]-2)**2 + (2-x[2])**2 + x[3]**2 + x[4]**4

In [10]:
x_ast <- c(2,2,0,0)
x_0 <- c(5,5,1,0)
tol <- 1e-8
tol_backtracking <- 1e-14
maxiter <- 5
p_ast <- fo(x_ast)

In [11]:
l<-gradient_descent(fo, x_0, tol, tol_backtracking, x_ast, p_ast, maxiter)


I	Normagf		Error x_ast	Error p_ast	line search
1	8.72e+00	1.54e+00	1.90e+01	---
2	3.53e-07	6.10e-08	2.98e-14	5.00e-01
3	2.20e-15	3.06e-09	7.50e-17	5.00e-01
Error of x with respect to x_ast: 3.06e-09
Approximate solution:[1]  2e+00  2e+00 -5e-09  0e+00

In [12]:
x <- l[[1]]
total_of_iterations <- l[[2]]
Err_plot <- l[[3]]
x_plot <- l[[4]]

In [13]:
gg <- ggplot()

In [14]:
gg +
geom_line(aes(x=1:total_of_iterations,y=Err_plot)) + 
xlab('Iterations') + ylab(TeX('Error relativo entre f_o(x^k) y p^*'))


Ejemplo: influencia del número de condición

2) El método de descenso en gradiente es altamente sensible a la forma de las curvas de nivel de la función objetivo $f_o$. Para observar esto considérese el problema: $$\min \frac{1}{2}\left(x_1^2+Cx_2^2 \right)$$

La solución del problema anterior es $x^*=(0,0)^T$.

Método de descenso en gradiente con $x_0=(0.5,0.5)^T$.

Descenso en gradiente para caso $C=10$


In [15]:
cte<-10

In [16]:
fo <- function(x) 1/2*(x[1]**2+cte*x[2]**2)

In [17]:
x_ast <- c(0,0)
x_0 <- c(0.5,0.5)
tol <- 1e-8
tol_backtracking <- 1e-14
maxiter <- 50
p_ast <- fo(x_ast)
l<-gradient_descent(fo, x_0, tol, tol_backtracking, x_ast, p_ast, maxiter)
x <- l[[1]]
total_of_iterations <- l[[2]]
Err_plot <- l[[3]]
x_plot <- l[[4]]


I	Normagf		Error x_ast	Error p_ast	line search
1	5.02e+00	7.07e-01	1.38e+00	---
2	1.32e+00	4.55e-01	1.74e-01	1.25e-01
3	4.94e-01	3.84e-01	7.82e-02	1.25e-01
4	5.50e-01	2.91e-01	5.22e-02	2.50e-01
5	2.77e-01	2.51e-01	3.22e-02	1.25e-01
6	4.85e-01	1.34e-01	1.89e-02	5.00e-01
7	1.61e-01	1.11e-01	6.73e-03	1.25e-01
8	1.94e-01	8.43e-02	4.94e-03	2.50e-01
9	8.45e-02	7.23e-02	2.70e-03	1.25e-01
10	8.53e-02	5.45e-02	1.68e-03	2.50e-01
11	1.07e-01	4.18e-02	1.31e-03	2.50e-01
12	4.33e-02	3.56e-02	6.61e-04	1.25e-01
13	4.56e-02	2.69e-02	4.23e-04	2.50e-01
14	2.51e-02	2.33e-02	2.76e-04	1.25e-01
15	3.89e-02	1.22e-02	1.37e-04	5.00e-01
16	1.38e-02	1.02e-02	5.62e-05	1.25e-01
17	1.59e-02	7.77e-03	3.89e-05	2.50e-01
18	7.54e-03	6.70e-03	2.30e-05	1.25e-01
19	1.43e-02	3.62e-03	1.53e-05	5.00e-01
20	4.54e-03	2.95e-03	4.89e-06	1.25e-01
21	5.66e-03	2.26e-03	3.77e-06	2.50e-01
22	2.32e-03	1.92e-03	1.93e-06	1.25e-01
23	2.43e-03	1.45e-03	1.23e-06	2.50e-01
24	1.35e-03	1.26e-03	8.06e-07	1.25e-01
25	2.05e-03	6.60e-04	3.90e-07	5.00e-01
26	7.37e-04	5.54e-04	1.64e-07	1.25e-01
27	8.42e-04	4.20e-04	1.12e-07	2.50e-01
28	4.06e-04	3.62e-04	6.71e-08	1.25e-01
29	7.55e-04	1.95e-04	4.32e-08	5.00e-01
30	2.42e-04	1.59e-04	1.42e-08	1.25e-01
31	3.00e-04	1.22e-04	1.08e-08	2.50e-01
32	1.25e-04	1.04e-04	5.63e-09	1.25e-01
33	1.29e-04	7.86e-05	3.57e-09	2.50e-01
34	1.65e-04	6.04e-05	2.90e-09	2.50e-01
35	6.41e-05	5.13e-05	1.38e-09	1.25e-01
36	6.95e-05	3.88e-05	9.04e-10	2.50e-01
37	3.66e-05	3.36e-05	5.73e-10	1.25e-01
38	6.04e-05	1.77e-05	3.09e-10	5.00e-01
39	2.06e-05	1.47e-05	1.18e-10	1.25e-01
40	2.44e-05	1.12e-05	8.43e-11	2.50e-01
41	1.11e-05	9.64e-06	4.78e-11	1.25e-01
42	2.23e-05	5.28e-06	3.53e-11	5.00e-01
43	6.88e-06	4.24e-06	1.03e-11	1.25e-01
44	8.75e-06	3.26e-06	8.35e-12	2.50e-01
45	3.44e-06	2.77e-06	4.01e-12	1.25e-01
46	3.70e-06	2.09e-06	2.62e-12	2.50e-01
47	1.97e-06	1.81e-06	1.66e-12	1.25e-01
48	3.19e-06	9.55e-07	8.91e-13	5.00e-01
49	1.10e-06	7.92e-07	3.37e-13	1.25e-01
50	1.29e-06	6.03e-07	2.46e-13	2.50e-01
Error of x with respect to x_ast: 6.03e-07
Approximate solution:[1]  5.906139e-07 -1.196995e-07

In [18]:
gg <- ggplot()

In [19]:
gg +
geom_line(aes(x=1:total_of_iterations,y=Err_plot)) + 
xlab('Iterations') + ylab(TeX('Error relativo entre f_o(x^k) y p^*'))



In [20]:
gg +
geom_point(aes(x=x_plot[1,],y=x_plot[2,]),size=2) +
annotate(geom='text', x=0.5, y=0.47, 
         label=TeX("x^{(0)}", output='character'), parse=TRUE) + 
xlab('x') + ylab('y') + 
ggtitle(TeX('Iter del método de descenso en gradiente para $f_o$ con $C=10$'))


Referencias:

  • S. P. Boyd, L. Vandenberghe, Convex Optimization, Cambridge University Press, 2009.