Hasil replikasi untuk regresi linier glmnet menggunakan pengoptimal generik

10

Seperti yang dinyatakan judul, saya mencoba mereplikasi hasil dari glmnet linear menggunakan pengoptimal LBFGS dari perpustakaan lbfgs. Pengoptimal ini memungkinkan kita untuk menambahkan istilah regularizer L1 tanpa harus khawatir tentang diferensiabilitas, selama fungsi objektif kami (tanpa istilah regularizer L1) adalah cembung.

minβRp12nβ0+Xβy22+αλβ1+12(1α)λβ22
XRn×pyRpα[0,1]λ>0xp

Kode di bawah ini mendefinisikan fungsi, dan kemudian menyertakan tes untuk membandingkan hasilnya. Seperti yang Anda lihat, hasilnya dapat diterima kapan alpha = 1, tetapi jauh untuk nilai-nilai alpha < 1.Kesalahan semakin memburuk saat kita beralih alpha = 1ke alpha = 0, seperti yang ditunjukkan plot berikut ("metrik perbandingan" adalah jarak Euclidean rata-rata antara estimasi parameter glmnet dan lbfgs untuk jalur regularisasi yang diberikan).

masukkan deskripsi gambar di sini

Oke, jadi ini kodenya. Saya telah menambahkan komentar sedapat mungkin. Pertanyaan saya adalah: Mengapa hasil saya berbeda glmnetdengan nilai untuk alpha < 1? Ini jelas ada hubungannya dengan istilah regularisasi L2, tapi sejauh yang saya tahu, saya sudah menerapkan istilah ini persis seperti yang tertulis di kertas. Bantuan apa pun akan sangat dihargai!

library(lbfgs)
linreg_lbfgs <- function(X, y, alpha = 1, scale = TRUE, lambda) {
  p <- ncol(X) + 1; n <- nrow(X); nlambda <- length(lambda)

  # Scale design matrix
  if (scale) {
    means <- colMeans(X)
    sds <- apply(X, 2, sd)
    sX <- (X - tcrossprod(rep(1,n), means) ) / tcrossprod(rep(1,n), sds)
  } else {
    means <- rep(0,p-1)
    sds <- rep(1,p-1)
    sX <- X
  }
  X_ <- cbind(1, sX)

  # loss function for ridge regression (Sum of squared errors plus l2 penalty)
  SSE <- function(Beta, X, y, lambda0, alpha) {
    1/2 * (sum((X%*%Beta - y)^2) / length(y)) +
      1/2 * (1 - alpha) * lambda0 * sum(Beta[2:length(Beta)]^2) 
                    # l2 regularization (note intercept is excluded)
  }

  # loss function gradient
  SSE_gr <- function(Beta, X, y, lambda0, alpha) {
    colSums(tcrossprod(X%*%Beta - y, rep(1,ncol(X))) *X) / length(y) + # SSE grad
  (1-alpha) * lambda0 * c(0, Beta[2:length(Beta)]) # l2 reg grad
  }

  # matrix of parameters
  Betamat_scaled <- matrix(nrow=p, ncol = nlambda)

  # initial value for Beta
  Beta_init <- c(mean(y), rep(0,p-1)) 

  # parameter estimate for max lambda
  Betamat_scaled[,1] <- lbfgs(call_eval = SSE, call_grad = SSE_gr, vars = Beta_init, 
                              X = X_, y = y, lambda0 = lambda[2], alpha = alpha,
                              orthantwise_c = alpha*lambda[2], orthantwise_start = 1, 
                              invisible = TRUE)$par

  # parameter estimates for rest of lambdas (using warm starts)
  if (nlambda > 1) {
    for (j in 2:nlambda) {
      Betamat_scaled[,j] <- lbfgs(call_eval = SSE, call_grad = SSE_gr, vars = Betamat_scaled[,j-1], 
                                  X = X_, y = y, lambda0 = lambda[j], alpha = alpha,
                                  orthantwise_c = alpha*lambda[j], orthantwise_start = 1, 
                                  invisible = TRUE)$par
    }
  }

  # rescale Betas if required
  if (scale) {
    Betamat <- rbind(Betamat_scaled[1,] -
colSums(Betamat_scaled[-1,]*tcrossprod(means, rep(1,nlambda)) / tcrossprod(sds, rep(1,nlambda)) ), Betamat_scaled[-1,] / tcrossprod(sds, rep(1,nlambda)) )
  } else {
    Betamat <- Betamat_scaled
  }
  colnames(Betamat) <- lambda
  return (Betamat)
}

# CODE FOR TESTING
# simulate some linear regression data
n <- 100
p <- 5
X <- matrix(rnorm(n*p),n,p)
true_Beta <- sample(seq(0,9),p+1,replace = TRUE)
y <- drop(cbind(1,X) %*% true_Beta)

library(glmnet)

# function to compare glmnet vs lbfgs for a given alpha
glmnet_compare <- function(X, y, alpha) {
  m_glmnet <- glmnet(X, y, nlambda = 5, lambda.min.ratio = 1e-4, alpha = alpha)
  Beta1 <- coef(m_glmnet)
  Beta2 <- linreg_lbfgs(X, y, alpha = alpha, scale = TRUE, lambda = m_glmnet$lambda)
  # mean Euclidean distance between glmnet and lbfgs results
  mean(apply (Beta1 - Beta2, 2, function(x) sqrt(sum(x^2))) ) 
}

# compare results
alpha_seq <- seq(0,1,0.2)
plot(alpha_seq, sapply(alpha_seq, function(alpha) glmnet_compare(X,y,alpha)), type = "l", ylab = "Comparison metric")

@ hxd1011 Saya mencoba kode Anda, berikut adalah beberapa tes (saya membuat beberapa perubahan kecil agar sesuai dengan struktur glmnet - perhatikan kami tidak mengatur istilah intersep, dan fungsi kerugian harus ditingkatkan). Ini untuk alpha = 0, tetapi Anda dapat mencoba alpha- hasilnya tidak cocok.

rm(list=ls())
set.seed(0)
# simulate some linear regression data
n <- 1e3
p <- 20
x <- matrix(rnorm(n*p),n,p)
true_Beta <- sample(seq(0,9),p+1,replace = TRUE)
y <- drop(cbind(1,x) %*% true_Beta)

library(glmnet)
alpha = 0

m_glmnet = glmnet(x, y, alpha = alpha, nlambda = 5)

# linear regression loss and gradient
lr_loss<-function(w,lambda1,lambda2){
  e=cbind(1,x) %*% w -y
  v= 1/(2*n) * (t(e) %*% e) + lambda1 * sum(abs(w[2:(p+1)])) + lambda2/2 * crossprod(w[2:(p+1)])
  return(as.numeric(v))
}

lr_loss_gr<-function(w,lambda1,lambda2){
  e=cbind(1,x) %*% w -y
  v= 1/n * (t(cbind(1,x)) %*% e) + c(0, lambda1*sign(w[2:(p+1)]) + lambda2*w[2:(p+1)])
  return(as.numeric(v))
}

outmat <- do.call(cbind, lapply(m_glmnet$lambda, function(lambda) 
  optim(rnorm(p+1),lr_loss,lr_loss_gr,lambda1=alpha*lambda,lambda2=(1-alpha)*lambda,method="L-BFGS")$par
))

glmnet_coef <- coef(m_glmnet)
apply(outmat - glmnet_coef, 2, function(x) sqrt(sum(x^2)))
pengguna3294195
sumber
Saya tidak yakin pertanyaan Anda tentang topik (saya pikir itu mungkin, karena ini tentang teknik optimasi yang mendasarinya), dan saya tidak dapat benar-benar memeriksa kode Anda sekarang, tetapi lbfgsmemunculkan poin tentang orthantwise_cargumen tentang glmnetkesetaraan.
Firebug
Masalahnya tidak benar-benar dengan lbfgsdan orthantwise_c, seperti kapan alpha = 1, solusinya hampir sama persis dengan glmnet. Ini ada hubungannya dengan sisi regularisasi L2 hal yaitu kapan alpha < 1. Saya pikir membuat semacam modifikasi untuk definisi SSEdan SSE_grharus memperbaikinya, tapi saya tidak yakin apa yang harus modifikasi - sejauh yang saya tahu, fungsi-fungsi tersebut didefinisikan persis seperti yang dijelaskan dalam kertas glmnet.
user3294195
Ini mungkin lebih dari stackoverflow, pertanyaan pemrograman.
Matthew Gunn
3
Saya pikir ini lebih berkaitan dengan optimasi & regularisasi daripada kode itu sendiri, itulah sebabnya saya diposting di sini.
user3294195
1
Untuk pertanyaan optimasi murni, scicomp.stackexchange.com juga merupakan opsi. Saya tidak yakin apakah pertanyaan khusus bahasa ada di topik di sana? (mis. "lakukan ini dalam R")
GeoMatt22

Jawaban:

11

tl; versi dr:

Tujuannya secara implisit mengandung faktor penskalaan , di mana adalah standar deviasi sampel.s^=sd(y)sd(y)

Versi yang lebih panjang

Jika Anda membaca cetakan halus dari dokumentasi glmnet, Anda akan melihat:

Perhatikan bahwa fungsi objektif untuk '"gaussian"' adalah

               1/2  RSS/nobs + lambda*penalty,                  

dan untuk model lainnya

               -loglik/nobs + lambda*penalty.                   

Perhatikan juga bahwa untuk '"gaussian"', 'glmnet' membuat standar y untuk memiliki varian unit sebelum menghitung urutan lambda-nya (dan kemudian tidak menstandardisasi koefisien yang dihasilkan); jika Anda ingin mereproduksi / membandingkan hasil dengan perangkat lunak lain, yang terbaik untuk memasok y standar.

Sekarang ini berarti tujuannya sebenarnya dan glmnet itu melaporkan .

12ny/s^Xβ22+λαβ1+λ(1α)β22,
β~=s^β

Sekarang, ketika Anda menggunakan laso murni ( ), maka unstandardization dari glmnet's berarti bahwa jawabannya setara. Di sisi lain, dengan punggungan murni, maka Anda perlu skala hukuman dengan faktor agar jalan setuju, karena faktor tambahan muncul dari alun-alun dalam penalti . Untuk intermediate , tidak ada cara mudah untuk skala penalti koefisien untuk mereproduksi output.α=1β~1/s^glmnets^2αglmnets

Setelah saya skala untuk memiliki varian unit, saya menemukan ymasukkan deskripsi gambar di sini

yang masih belum sama persis. Ini sepertinya disebabkan oleh dua hal:

  1. Urutan lambda mungkin terlalu pendek untuk algoritma penurunan siklik-start-hangat untuk sepenuhnya konvergen.
  2. Tidak ada istilah kesalahan dalam data Anda ( dari regresi adalah 1).R2
  3. Perhatikan juga ada bug dalam kode seperti yang disediakan di mana diperlukan lambda[2]untuk fit awal, tapi itu seharusnya lambda[1].

Setelah saya memperbaiki item 1-3, saya mendapatkan hasil berikut (meskipun YMMV tergantung pada seed acak):

masukkan deskripsi gambar di sini

Andrew M
sumber