Atualizando o ajuste do laço com novas observações

12

Estou ajustando uma regressão linear regularizada por L1 a um conjunto de dados muito grande (com n >> p.) As variáveis ​​são conhecidas antecipadamente, mas as observações chegam em pequenos pedaços. Eu gostaria de manter o ajuste do laço após cada pedaço.

Obviamente, posso reorganizar todo o modelo depois de ver cada novo conjunto de observações. Isso, no entanto, seria bastante ineficiente, pois há muitos dados. A quantidade de novos dados que chega a cada etapa é muito pequena e é improvável que o ajuste mude muito entre as etapas.

Há algo que eu possa fazer para reduzir a carga computacional geral?

Eu estava analisando o algoritmo LARS de Efron et al., Mas ficaria feliz em considerar qualquer outro método de ajuste se for possível "iniciar a quente" da maneira descrita acima.

Notas:

  1. Eu estou procurando principalmente um algoritmo, mas ponteiros para pacotes de software existentes que podem fazer isso também podem ser interessantes.
  2. Além das trajetórias atuais do laço, é claro que o algoritmo é bem-vindo para manter outro estado.

Bradley Efron, Trevor Hastie, Iain Johnstone e Robert Tibshirani, regressão de menor ângulo , Anais de Estatística (com discussão) (2004) 32 (2), 407--499.

NPE
fonte

Respostas:

7

O laço é ajustado através do LARS (um processo iterativo, que começa com uma estimativa inicial ). Por padrão β 0 = 0 p mas você pode mudar isso em mais de implementação (e substituí-lo pelo ótimo β * o l d você já tem). O mais próximo β * o l d é p * n e w , quanto menor o número de LARS iteração você terá que passo para chegar ao p * n e w .β0 0β0 0=0 0pβoeudβoeudβneWβneW

EDITAR:

Devido aos comentários de, user2763361eu adiciono mais detalhes à minha resposta original.

A partir dos comentários abaixo, concluo que o user2763361 sugere complementar minha resposta original para transformá-la em uma que possa ser usada diretamente (fora das prateleiras), além de ser muito eficiente.

Para fazer a primeira parte, ilustrarei a solução que proponho passo a passo em um exemplo de brinquedo. Para satisfazer a segunda parte, farei isso usando um solucionador de pontos interior recente e de alta qualidade. Isso ocorre porque, é mais fácil obter uma implementação de alto desempenho da solução que proponho, usando uma biblioteca que pode resolver o problema do laço pela abordagem do ponto interior, em vez de tentar invadir o algoritmo LARS ou simplex para iniciar a otimização a partir de um método não- ponto de partida padrão (embora esse segundo local também seja possível).

Observe que algumas vezes é reivindicado (em livros antigos) que a abordagem do ponto interior para resolver programas lineares é mais lenta que a abordagem simplex e que pode ter sido verdade há muito tempo, mas geralmente não é verdadeira hoje e certamente não é verdadeira para problemas de grande escala (é por isso que a maioria das bibliotecas profissionais como cplex usa o algoritmo de ponto interior) e a pergunta é pelo menos implicitamente sobre problemas de grande escala. Observe também que o solucionador de pontos interno que eu uso lida totalmente com matrizes esparsas, então não acho que haverá uma grande diferença de desempenho com o LARS (uma motivação original para usar o LARS era que muitos solucionadores de LP populares da época não estavam lidando bem com matrizes esparsas e estas são características do problema do LASSO).

Uma (muito) boa implementação de código aberto do algoritmo de ponto interior é ipopt, na COIN-ORbiblioteca. Outra razão que eu vou usar ipopté que ele tem uma interface R ipoptr,. Você encontrará um guia de instalação mais completo aqui , abaixo eu dou os comandos padrão para instalá-loubuntu .

no bash, faça:

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

Então, como root, em Rdo (suponho svnque copiei o arquivo de subversão ~/como faz por padrão):

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

A partir daqui, estou dando um pequeno exemplo (principalmente do exemplo de brinquedo dado por Jelmer Ypma como parte de seu Rwraper para 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 ) )

O que quero dizer é que, se você tiver novos dados, precisará apenas

  1. atualizar ( não substituir) a matriz de restrição e o vetor de função objetivo para dar conta das novas observações.
  2. altere os pontos de partida do ponto interior de

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

    βneWβoeudβEunEutx0

|βEunEut-βneW|1>|βneW-βoeud|1(1)

βneW muito mais rápido iniciando o ponto interior de βoeud ao invés de ingênuo βEunEut. O ganho será ainda mais importante quando as dimensões do conjunto de dados (n e p) são maiores.

Quanto às condições sob as quais a desigualdade (1) se aplica, elas são:

  • quando λ é grande comparado a |βOeuS|1 (esse geralmente é o caso quando p, o número de variáveis ​​de design é grande em comparação com n, o número de observações)
  • quando as novas observações não são patologicamente influentes, por exemplo, quando são consistentes com o processo estocástico que gerou os dados existentes.
  • quando o tamanho da atualização for pequeno em relação ao tamanho dos dados existentes.
user603
fonte
Obrigado, mas tenho medo de não seguir. O LARS produz um caminho linear por partes (com exatamentep+1 pontos para o menor ângulo e possivelmente mais pontos para o laço.) Cada ponto tem seu próprio conjunto de β. Quando adicionamos mais observações, todos os betas podem se mover (excetoβ0 0, que é sempre 0 0p.) Por favor, você poderia expandir sua resposta? Obrigado.
NPE
@aix: você deseja atualizar todo o caminho do laço ou apenas a solução? (ou seja, a pena de escarsidade é fixada?).
user603
Eu estava olhando para atualizar o caminho inteiro. No entanto, se houver uma boa maneira de fazer isso por uma penalidade fixa (λna fórmula abaixo), este pode ser um bom começo. É isso que você está propondo?
β^euumasso=argminβ{12Eu=1N(yEu-β0 0-j=1pxEujβj)2+λj=1p|βj|}
NPE
@aix. Sim, tudo depende da implementação usada e dos recursos aos quais você tem acesso. Por exemplo: se você tiver acesso a um bom solucionador lp, poderá alimentá-lo com os valores ótimos anteriores deβe levará o passo 1-2 para a nova solução com muita eficiência. Você deve adicionar esses detalhes à sua pergunta.
user603
1
Você conhece alguma biblioteca que pode fazer isso sem precisar editar o código-fonte?
user2763361