ジェネリックオプティマイザーを使用したglmnet線形回帰の結果の複製


10

タイトルが示すように、ライブラリのLBFGSオプティマイザーを使用して、glmnet linearの結果を複製しようとしていますlbfgs。このオプティマイザーを使用すると、目的関数(L1レギュラライザー項なし)が凸型である限り、微分可能性について心配する必要なくL1レギュラライザー項を追加できます。

弾性正味線形回帰問題glmnet紙はによって与えられる ここで、XRN×pは計画行列であり、YRのPは、観測値のベクトルであり、αは[01]弾性ネットパラメータであり、λ>0は正則化パラメーターです。オペレータXpは、通常のLPノルムを表します。

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

以下のコードは関数を定義し、結果を比較するためのテストを含みます。ご覧のとおり、結果はで許容可能alpha = 1ですが、次のプロットが示すように、alpha < 1.からalpha = 1に行くにつれてエラーが悪化しalpha = 0ます(「比較メトリック」はglmnetのパラメーター推定値間の平均ユークリッド距離です)および特定の正則化パスのlbfgs)。

ここに画像の説明を入力してください

さて、これがコードです。可能な限りコメントを追加しました。私の質問は:なぜ私の結果はglmnetの値の結果と異なるのalpha < 1ですか?それは明らかにL2正則化用語と関係がありますが、私が知る限り、この用語を論文のとおり正確に実装しました。どんな助けでも大歓迎です!

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私はあなたのコードを試しました、ここにいくつかのテストがあります(glmnetの構造と一致するようにいくつかの微調整を行いました-インターセプト項を正則化していないため、損失関数をスケーリングする必要があります)。これはのためのalpha = 0ものですが、何でも試すことができますalpha-結果が一致しません。

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)))

あなたの質問がトピックに関するものかどうかはわかりません(根本的な最適化手法に関するため、そうかもしれないと思います)。コードを実際に確認することはできませんが、同等性に関する議論についてlbfgsは指摘しorthantwise_cますglmnet
Firebug 2016

問題はと本当にないlbfgsorthantwise_c、ときのようにalpha = 1解決が近いと全く同じです、glmnet。それは物事のL2正則化の側面に関係していalpha < 1ます。私は、の定義に変更のいくつかの種類を作ると思うSSEし、SSE_grそれを修正する必要がありますが、私は修正がどうあるべきかわからない-私の知る限り、これらの機能はglmnetの論文に記載とおりに定義されています。
user3294195

これは、スタックオーバーフロー、プログラミングの問題の可能性があります。
Matthew Gunn 2016

3
コード自体ではなく、最適化と正則化に関係していると思ったので、ここに投稿しました。
user3294195 2016

1
純粋な最適化の質問では、scicomp.stackexchange.comもオプションです。言語固有の質問がそこにあるかどうかはわかりませんか?(「Rでこれを行う」など)
GeoMatt22 '28

回答:


11

tl; drバージョン:

s^=sd(y)sd(y)

長いバージョン

glmnetのドキュメントの細かい部分を読むと、次のことがわかります。

「 "ガウス"」の目的関数は

               1/2  RSS/nobs + lambda*penalty,                  

他のモデルでは

               -loglik/nobs + lambda*penalty.                   

また、「 "gaussian"」の場合、「glmnet」はラムダシーケンスを計算する前にyを標準化して単位分散を持たせます(次に、結果の係数を非標準化します)。結果を他のソフトウェアと再現/比較する場合は、標準化されたyを提供することをお勧めします。

これは、目的が実際にであることを意味します

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

α=1β~1/s^glmnets^2αglmnets

yここに画像の説明を入力してください

これはまだ完全には一致しません。これは次の2つの原因が原因のようです:

  1. ラムダシーケンスは、ウォームスタート循環座標降下アルゴリズムを完全に収束させるには短すぎる場合があります。
  2. R2
  3. またlambda[2]、最初の適合に必要なコードにバグがあることに注意してくださいlambda[1]

アイテム1〜3を修正すると、次の結果が得られます(ただし、YMMVはランダムシードによって異なります)。

ここに画像の説明を入力してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.