Il lazo viene inserito attraverso LARS (un processo iterativo, che inizia con una stima iniziale ). Di default ma puoi cambiarlo nella maggior parte dell'implementazione (e sostituirlo con l'ottimale hai già). Il più vicino è , minore è il numero di iterazioni LARS che dovrai passare per arrivare a .β 0 = 0 p β ∗ o l d β ∗ o l d β ∗ n e w β ∗ n e wβ0β0= 0pβ*o l dβ*o l dβ*n e wβ*n e w
MODIFICARE:
A causa dei commenti di user2763361
aggiungo ulteriori dettagli alla mia risposta originale.
Dai commenti sottostanti mi risulta che l'utente2763361 suggerisce di integrare la mia risposta originale per trasformarla in una che può essere utilizzata direttamente (dagli scaffali) pur essendo molto efficiente.
Per fare la prima parte, illustrerò la soluzione che propongo passo dopo passo su un esempio di giocattolo. Per soddisfare la seconda parte, lo farò usando un recente solutore di punti per interni di alta qualità. Questo perché, è più facile ottenere un'implementazione ad alte prestazioni della soluzione che propongo utilizzando una libreria in grado di risolvere il problema del lazo con l'approccio del punto interno piuttosto che cercare di hackerare l'algoritmo LARS o simplex per avviare l'ottimizzazione da un non punto di partenza standard (sebbene sia possibile anche quella seconda sede).
Si noti che a volte si afferma (nei libri più vecchi) che l'approccio dei punti interni per risolvere i programmi lineari è più lento dell'approccio simplex e che potrebbe essere stato molto tempo fa, ma generalmente non è vero oggi e certamente non è vero per problemi su larga scala (questo è il motivo per cui la maggior parte delle biblioteche professionali cplex
usa l'algoritmo del punto interno) e la domanda riguarda almeno implicitamente problemi su larga scala. Nota anche che il solutore di punti interni che uso gestisce completamente le matrici sparse, quindi non credo che ci sarà un grande divario di prestazioni con LARS (una motivazione originale per l'utilizzo di LARS era che molti solutori di LP popolari all'epoca non gestivano bene le matrici sparse e queste sono le caratteristiche del problema LASSO).
Una (molto) buona implementazione open source dell'algoritmo del punto interno è ipopt
, nella COIN-OR
libreria. Un altro motivo Userò ipopt
è che ha ha un'interfaccia R, ipoptr
. Troverai una guida all'installazione più completa qui , di seguito fornisco i comandi standard per installarlo ubuntu
.
nel bash
, fare:
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
Quindi, come root, in R
do (presumo svn
abbia copiato il file di sovversione ~/
come per impostazione predefinita):
install.packages("~/CoinIpopt/Ipopt/contrib/RInterface",repos=NULL,type="source")
Da qui, sto dando un piccolo esempio (principalmente dall'esempio di giocattolo fornito da Jelmer Ypma come parte del suo R
involucro a 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 ) )
Il punto è che, se si dispone di nuovi dati, è sufficiente
- aggiorna ( non sostituisce) la matrice del vincolo e il vettore della funzione obiettivo per tenere conto delle nuove osservazioni.
cambiare i punti di partenza del punto interno da
x0 = c (rep (0, m), rep (1, m))
al vettore di soluzione che hai trovato in precedenza (prima che fossero aggiunti nuovi dati). La logica qui è la seguente. Indica il nuovo vettore di coefficienti (quelli corrispondenti al set di dati dopo l'aggiornamento) e quelli originali. anche il vettore nel codice sopra (questo è il solito inizio per il metodo del punto interno). Quindi l'idea è che se: β o l d β i n i tβn e wβo l dβi n i tx0
| βi n i t- βn e w|1> | βn e w- βo l d|1( 1 )
quindi, si può ottenere molto più velocemente avviando il punto interno da
anziché . Il guadagno sarà tanto più importante quando le dimensioni del set di dati ( e ) sono maggiori.βn e wβo l dβi n i tnp
Per quanto riguarda le condizioni alle quali sussiste la disuguaglianza (1), sono:
- quando è grande rispetto a (questo è di solito il caso in cui , il numero di variabili di progettazione è grande rispetto a , il numero di osservazioni)λ| βO L S|1pn
- quando le nuove osservazioni non sono patologicamente influenti, ad esempio quando sono coerenti con il processo stocastico che ha generato i dati esistenti.
- quando la dimensione dell'aggiornamento è piccola rispetto alla dimensione dei dati esistenti.