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 por coordenadas

Consideramos con la norma $1$ el método steepest descent:

$$\Delta x_{\text{nsd}} = \text{argmin} \{ \nabla f_o(x)^Tv : ||v||_1 \leq 1, \nabla f_o(x)^Tv < 0 \} $$

se prueba que $\Delta x_{\text{sd}} = - \frac{\partial f_o(x)}{\partial x_i} e_i$ con $e_i$ $i$-ésimo vector canónico y el índice $i$ es la entrada del vector $\nabla f_o(x)$ de máxima magnitud: $i$ tal que $\left |(\nabla f_o(x))_i \right | = ||\nabla f_o(x)||_\infty$.

Ver 4.2.Algoritmos_para_optimizacion_sin_restricciones. Entonces:

Algoritmo de descenso por coordenadas

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

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

  1. Obtener el índice $i$ de $\nabla f_o(x)$ con máximo valor absoluto: $\left |(\nabla f_o(x))_i \right | = ||\nabla f_o(x)||_\infty$.
  2. Calcular la dirección de descenso más pronunciado bajo la norma $1$: $\Delta x_{\text{sd}} = - \frac{\partial f_o(x)}{\partial x_i} e_i$ con $e_i$ $i$-ésimo vector canónico.
  3. Búsqueda de línea. Elegir un tamaño de paso $t > 0$.
  4. 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]:
options(repr.plot.width=6, repr.plot.height=6) #esta línea sólo se ejecuta para jupyterlab con R

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

In [3]:
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 [4]:
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 [5]:
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 [6]:
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 [7]:
Euclidian_norm<-function(vec){
    sqrt(sum(vec*vec))
}

In [8]:
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
}

Coordinate descent


In [9]:
coordinate_descent<-function(f, x_0, tol, 
                             tol_backtracking, 
                             x_ast, p_ast, maxiter){
    '
    Method of coordinate descent to numerically approximate solution of min f.
    Args:
        f (expression): definition of function f.
        x_0 (double): vector of initial point for coordinate 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){
        ind_maximo <- which.max(abs(gfeval))
        e_canonical <- vector("integer",n)
        e_canonical[ind_maximo] <- gfeval[ind_maximo]
        dir_desc <- -e_canonical
        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 por coordenadas con $x_0=(5,5,1,0)^T$:


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

In [11]:
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 [12]:
l<-coordinate_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	6.32e+00	1.12e+00	1.00e+01	5.00e-01
3	2.00e+00	3.54e-01	1.00e+00	5.00e-01
4	2.30e-07	3.85e-08	1.18e-14	5.00e-01
5	5.15e-08	7.02e-09	3.94e-16	5.00e-01
Error of x with respect to x_ast: 7.02e-09
Approximate solution:[1] 2.000000e+00 2.000000e+00 6.077471e-09 0.000000e+00

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

In [14]:
gg <- ggplot()

In [15]:
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) $$\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 por coordenadas con $x_0=(0.5,0.5)^T$.

Descenso por coordenadas para caso $C=10$


In [16]:
cte<-10

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

In [18]:
x_ast <- c(0,0)
x_0 <- c(0.5,0.5)
tol <- 1e-7
tol_backtracking <- 1e-14
maxiter <- 50
p_ast <- fo(x_ast)
l<-coordinate_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.35e+00	5.15e-01	2.03e-01	1.25e-01
3	5.90e-01	5.01e-01	1.30e-01	1.25e-01
4	3.13e-01	3.12e-02	4.88e-03	1.00e+00
5	7.81e-02	7.81e-03	3.05e-04	1.25e-01
6	1.95e-02	1.95e-03	1.91e-05	1.25e-01
7	4.88e-03	4.88e-04	1.19e-06	1.25e-01
8	1.22e-03	1.22e-04	7.45e-08	1.25e-01
9	3.05e-04	3.05e-05	4.66e-09	1.25e-01
10	7.63e-05	7.62e-06	2.91e-10	1.25e-01
11	1.91e-05	1.91e-06	1.83e-11	1.25e-01
12	4.77e-06	4.72e-07	1.11e-12	1.25e-01
13	1.19e-06	1.24e-07	7.72e-14	1.25e-01
14	2.98e-07	2.61e-08	3.11e-15	1.25e-01
15	7.46e-08	1.48e-08	8.08e-16	1.25e-01
Error of x with respect to x_ast: 1.48e-08
Approximate solution:[1] -8.063495e-09 -1.245058e-08

In [19]:
total_of_iterations


15

In [20]:
gg <- ggplot()

In [21]:
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 [22]:
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 por coordenadas para $f_o$ con $C=10$'))



In [23]:
x_plot


A matrix: 2 × 15 of type dbl
0.5 0.5000.50000-8.063495e-09-8.063495e-09-8.063495e-09-8.063495e-09-8.063495e-09-8.063495e-09-8.063495e-09-8.063495e-09-8.063495e-09-8.063495e-09-8.063495e-09-8.063495e-09
0.5-0.1250.03125 3.125000e-02-7.812505e-03 1.953120e-03-4.882863e-04 1.220653e-04-3.052258e-05 7.624395e-06-1.912349e-06 4.718372e-07-1.242093e-07 2.480232e-08-1.245058e-08

Ejemplo

1) $$\min \quad e^{(x_1+3x_2-0.1)}+e^{x_1-3x_2-0.1}+e^{-x_1-0.1}$$


In [24]:
fo <- function(x) exp(x[1]+3*x[2]-0.1) + exp(x[1]-3*x[2]-0.1) + exp(-x[1]-0.1)

In [25]:
x_ast <- c(-3.4654e-01,-7.6725e-06)

In [26]:
x_ast


  1. -0.34654
  2. -7.6725e-06

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

l<-coordinate_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	9.05e-01	1.00e+00	6.07e-02	---
2	2.71e-01	3.06e-01	5.61e-03	5.00e-01
3	7.64e-02	8.60e-02	4.46e-04	5.00e-01
4	2.14e-02	2.42e-02	3.49e-05	5.00e-01
5	5.98e-03	6.64e-03	2.73e-06	5.00e-01
6	1.67e-03	1.98e-03	2.13e-07	5.00e-01
7	4.67e-04	4.31e-04	1.60e-08	5.00e-01
8	1.31e-04	2.45e-04	6.08e-10	5.00e-01
9	3.65e-05	6.00e-05	5.95e-10	5.00e-01
10	1.01e-05	1.11e-04	6.89e-10	5.00e-01
11	2.75e-06	9.64e-05	6.96e-10	5.00e-01
12	7.56e-07	1.00e-04	6.97e-10	5.00e-01
13	2.22e-07	9.92e-05	6.97e-10	5.00e-01
14	8.88e-08	9.95e-05	6.97e-10	5.00e-01
15	6.28e-08	9.94e-05	6.97e-10	5.00e-01
16	8.88e-08	9.94e-05	6.97e-10	5.00e-01
17	0.00e+00	9.94e-05	6.97e-10	6.25e-02
Error of x with respect to x_ast: 9.94e-05
Approximate solution:[1] -3.465736e-01 -5.551115e-09

In [28]:
total_of_iterations


17

In [29]:
gg <- ggplot()

In [30]:
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 [31]:
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 por coordenadas para $f_o$'))



In [32]:
x_plot


A matrix: 2 × 17 of type dbl
0-0.4524187-0.3167227-0.3549266-0.3442377-0.3472268-0.3463909-0.3466247-0.3465593-0.3465776-0.3465725-0.3465739-0.3465735-0.3465736-0.3465736-0.3465736-3.465736e-01
0 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000-5.551115e-09

Comentario: el método de descenso por coordenadas es lento en situaciones como la situación en el lado izquierdo y rápido como el lado derecho del siguiente dibujo:

Imagen tomada del libro de J. Nocedal & S. J. Wright, Numerical Optimization, Springer.

Referencias:

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