Actualización del ajuste del lazo con nuevas observaciones

12

Estoy ajustando una regresión lineal regularizada L1 a un conjunto de datos muy grande (con n >> p.) Las variables se conocen de antemano, pero las observaciones llegan en pequeños fragmentos. Me gustaría mantener el ajuste del lazo después de cada trozo.

Obviamente, puedo volver a ajustar todo el modelo después de ver cada nuevo conjunto de observaciones. Sin embargo, esto sería bastante ineficiente dado que hay muchos datos. La cantidad de datos nuevos que llega a cada paso es muy pequeña, y es poco probable que el ajuste cambie mucho entre los pasos.

¿Hay algo que pueda hacer para reducir la carga computacional general?

Estaba mirando el algoritmo LARS de Efron et al., Pero estaría feliz de considerar cualquier otro método de adaptación si se puede hacer que "arranque en caliente" de la manera descrita anteriormente.

Notas:

  1. Principalmente estoy buscando un algoritmo, pero los punteros a los paquetes de software existentes que pueden hacer esto también pueden ser reveladores.
  2. Además de las trayectorias de lazo actuales, el algoritmo es, por supuesto, bienvenido para mantener otro estado.

Bradley Efron, Trevor Hastie, Iain Johnstone y Robert Tibshirani, Regresión de ángulo mínimo , Annals of Statistics (con discusión) (2004) 32 (2), 407-499.

NPE
fuente

Respuestas:

7

El lazo se ajusta a través de LARS (un proceso iterativo, que comienza en una estimación inicial ). De forma predeterminada, pero puede cambiar esto en la mayoría de las implementaciones (y reemplazarlo por el óptimo que ya tiene). Lo más cercano a es , cuanto menor sea el número de iteraciones LARS que tendrá que realizar para llegar a .β 0 = 0 p β o l d β o l d β n e w β n e wβ0β0=0pβoldβoldβnewβnew

EDITAR:

Debido a los comentarios de user2763361, agrego más detalles a mi respuesta original.

De los comentarios a continuación, deduzco que user2763361 sugiere complementar mi respuesta original para convertirla en una que se pueda usar directamente (fuera de los estantes) y al mismo tiempo ser muy eficiente.

Para hacer la primera parte, ilustraré la solución que propongo paso a paso en un ejemplo de juguete. Para satisfacer la segunda parte, lo haré utilizando un solucionador de puntos interior reciente y de alta calidad. Esto se debe a que es más fácil obtener una implementación de alto rendimiento de la solución que propongo usando una biblioteca que pueda resolver el problema del lazo mediante el enfoque de punto interior en lugar de intentar hackear el algoritmo LARS o simplex para comenzar la optimización desde un punto de partida estándar (aunque ese segundo lugar también es posible).

Tenga en cuenta que a veces se afirma (en libros más antiguos) que el enfoque de punto interior para resolver programas lineales es más lento que el enfoque simple y eso puede haber sido cierto hace mucho tiempo, pero generalmente no es cierto hoy en día y ciertamente no es cierto para problemas a gran escala (esta es la razón por la cual la mayoría de las bibliotecas profesionales cplexusan el algoritmo de punto interior) y la pregunta es al menos implícitamente sobre problemas a gran escala. Tenga en cuenta también que el solucionador de puntos interiores que uso maneja completamente matrices dispersas, por lo que no creo que haya una gran brecha de rendimiento con LARS (una motivación original para usar LARS era que muchos solucionadores LP populares en ese momento no manejaban bien las matrices dispersas y Estos son rasgos característicos del problema LASSO).

Una (muy) buena implementación de código abierto del algoritmo de punto interior está ipopten la COIN-ORbiblioteca. Otra razón por la que va a utilizar ipoptes que tiene tiene una interfaz R, ipoptr. Encontrará una guía de instalación más exhaustiva aquí , a continuación le doy los comandos estándar para instalarla ubuntu.

en el bash, hacer:

sudo apt-get install gcc g++ gfortran subversion patch wget
svn co https://projects.coin-or.org/svn/Ipopt/stable/3.11 CoinIpopt
cd ~/CoinIpopt
./configure
make 
make install

Luego, como root, in Rdo (supongo que svnha copiado el archivo de subversión ~/como lo hace por defecto):

install.packages("~/CoinIpopt/Ipopt/contrib/RInterface",repos=NULL,type="source")

A partir de aquí, estoy dando un pequeño ejemplo (principalmente del ejemplo de juguete dado por Jelmer Ypma como parte de su Rwraper ipopt):

library('ipoptr')
# Experiment parameters.
lambda <- 1                                # Level of L1 regularization.
n      <- 100                              # Number of training examples.
e      <- 1                                # Std. dev. in noise of outputs.
beta   <- c( 0, 0, 2, -4, 0, 0, -1, 3 )    # "True" regression coefficients.
# Set the random number generator seed.
ranseed <- 7
set.seed( ranseed )
# CREATE DATA SET.
# Generate the input vectors from the standard normal, and generate the
# responses from the regression with some additional noise. The variable 
# "beta" is the set of true regression coefficients.
m     <- length(beta)                           # Number of features.
A     <- matrix( rnorm(n*m), nrow=n, ncol=m )   # The n x m matrix of examples.
noise <- rnorm(n, sd=e)                         # Noise in outputs.
y     <- A %*% beta + noise                     # The outputs.
# DEFINE LASSO FUNCTIONS
# m, lambda, y, A are all defined in the ipoptr_environment
eval_f <- function(x) {
    # separate x in two parts
    w <- x[  1:m ]          # parameters
    u <- x[ (m+1):(2*m) ]

    return( sum( (y - A %*% w)^2 )/2 + lambda*sum(u) )
}
# ------------------------------------------------------------------
eval_grad_f <- function(x) {
    w <- x[ 1:m ]
    return( c( -t(A) %*% (y - A %*% w),  
               rep(lambda,m) ) )
}
# ------------------------------------------------------------------
eval_g <- function(x) {
    # separate x in two parts
    w <- x[  1:m ]          # parameters
    u <- x[ (m+1):(2*m) ]
    return( c( w + u, u - w ) )
}
eval_jac_g <- function(x) {
    # return a vector of 1 and minus 1, since those are the values of the non-zero elements
    return( c( rep( 1, 2*m ), rep( c(-1,1), m ) ) )
}
# ------------------------------------------------------------------
# rename lambda so it doesn't cause confusion with lambda in auxdata
eval_h <- function( x, obj_factor, hessian_lambda ) {
    H <- t(A) %*% A
    H <- unlist( lapply( 1:m, function(i) { H[i,1:i] } ) )
    return( obj_factor * H )
}
eval_h_structure <- c( lapply( 1:m, function(x) { return( c(1:x) ) } ),
                       lapply( 1:m, function(x) { return( c() ) } ) )
# The starting point.
x0 = c( rep(0, m), 
        rep(1, m) )
# The constraint functions are bounded from below by zero.
constraint_lb = rep(   0, 2*m )
constraint_ub = rep( Inf, 2*m )
ipoptr_opts <- list( "jac_d_constant"   = 'yes',
                     "hessian_constant" = 'yes',
                     "mu_strategy"      = 'adaptive',
                     "max_iter"         = 100,
                     "tol"              = 1e-8 )
# Set up the auxiliary data.
auxdata <- new.env()
auxdata$m <- m
    auxdata$A <- A
auxdata$y <- y
    auxdata$lambda <- lambda
# COMPUTE SOLUTION WITH IPOPT.
# Compute the L1-regularized maximum likelihood estimator.
print( ipoptr( x0=x0, 
               eval_f=eval_f, 
               eval_grad_f=eval_grad_f, 
               eval_g=eval_g, 
               eval_jac_g=eval_jac_g,
               eval_jac_g_structure=eval_jac_g_structure,
               constraint_lb=constraint_lb, 
               constraint_ub=constraint_ub,
               eval_h=eval_h,
               eval_h_structure=eval_h_structure,
               opts=ipoptr_opts,
               ipoptr_environment=auxdata ) )

Mi punto es que si tiene datos nuevos, solo necesita

  1. actualizar ( no reemplazar) la matriz de restricción y el vector de función objetivo para tener en cuenta las nuevas observaciones.
  2. cambiar los puntos de partida del punto interior de

    x0 = c (rep (0, m), rep (1, m))

    al vector de solución que encontró anteriormente (antes de agregar nuevos datos). La lógica aquí es la siguiente. Denotan El nuevo vector de coeficientes (los correspondientes al conjunto de datos después de la actualización) y los originales. También denote el vector en el código anterior (este es el comienzo habitual para el método de punto interior). Entonces la idea es que si: β o l d β i n i tβnewβoldβinitx0

|βinitβnew|1>|βnewβold|1(1)

entonces, uno puede obtener mucho más rápido al iniciar el punto interior desde lugar de la ingenua . La ganancia será tanto más importante cuando las dimensiones del conjunto de datos ( y ) son más grandes.βnewβoldβinitnp

En cuanto a las condiciones bajo las cuales se mantiene la desigualdad (1), son:

  • cuando es grande en comparación con (este suele ser el caso cuando , el número de variables de diseño es grande en comparación con , el número de observaciones)λ|βOLS|1pn
  • cuando las nuevas observaciones no son patológicamente influyentes, por ejemplo, cuando son consistentes con el proceso estocástico que ha generado los datos existentes.
  • cuando el tamaño de la actualización es pequeño en relación con el tamaño de los datos existentes.
usuario603
fuente
Gracias, pero me temo que no te sigo. LARS produce una ruta lineal por partes (con exactamente puntos para los ángulos mínimos y posiblemente más puntos para el lazo). Cada punto tiene su propio conjunto de . Cuando agregamos más observaciones, todas las versiones beta pueden moverse (excepto , que siempre es .) ¿Podría ampliar su respuesta? Gracias. p+1ββ00p
NPE
@aix: ¿quieres actualizar todo el camino del lazo o solo la solución? (es decir, ¿se ha corregido la penalización por escasez?).
user603
Estaba buscando actualizar todo el camino. Sin embargo, si hay una buena manera de hacerlo por una penalización fija ( en la fórmula a continuación), este puede ser un buen comienzo. ¿Es esto lo que estás proponiendo? λ
β^lasso=argminβ{12i=1N(yiβ0j=1pxijβj)2+λj=1p|βj|}
NPE
@aix. Sí, todo depende de la implementación que use y de las instalaciones a las que tenga acceso. Por ejemplo: si tiene acceso a un buen solucionador de lp, puede alimentarlo con los valores óptimos pasados ​​de y llevará el paso 1-2 a la nueva solución de manera muy eficiente. Debe agregar estos detalles a su pregunta. β
user603
1
¿Alguna biblioteca que conozca puede hacer esto sin necesidad de editar el código fuente?
user2763361