जेनेरिक ऑप्टिमाइज़र का उपयोग करके ग्लमेनेट रैखिक प्रतिगमन के लिए दोहराए जाने वाले परिणाम


10

जैसा कि शीर्षक में कहा गया है, मैं लाइब्रेरी से LBFGS ऑप्टिमाइज़र का उपयोग करके glmnet रैखिक से परिणाम दोहराने की कोशिश कर रहा हूँ lbfgs। यह ऑप्टिमाइज़र हमें एल 1 रेगुलराइज़र शब्द जोड़ने की अनुमति देता है, बिना भिन्नता के बारे में चिंता किए बिना, जब तक कि हमारा उद्देश्य फ़ंक्शन (एल 1 रेग्युलर टर्म के बिना) उत्तल नहीं होता है।

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

नीचे दिए गए कोड फ़ंक्शन को परिभाषित करते हैं, और फिर परिणामों की तुलना करने के लिए एक परीक्षण शामिल करते हैं। जैसा कि आप देख सकते हैं, जब परिणाम स्वीकार्य होते हैं alpha = 1, लेकिन alpha < 1.त्रुटि के मानों के लिए रास्ता बंद हो जाता alpha = 1है alpha = 0, जैसा कि हम से जाना जाता है , जैसा कि निम्नलिखित कथानक से पता चलता है ("तुलना मीट्रिक" का मतलब है ग्लूनेट के पैरामीटर अनुमानों के बीच यूक्लिडियन दूरी और दिए गए नियमितीकरण पथ के लिए lbfgs)।

यहां छवि विवरण दर्ज करें

ठीक है, तो यहाँ कोड है। मैंने जहाँ भी संभव हो टिप्पणी जोड़ दी है। मेरा प्रश्न है: मेरे परिणाम उन glmnetमूल्यों से भिन्न क्यों हैं alpha < 1? यह स्पष्ट रूप से एल 2 नियमितीकरण शब्द के साथ करना है, लेकिन जहां तक ​​मैं बता सकता हूं, मैंने इस शब्द को कागज के अनुसार बिल्कुल लागू किया है। कोई भी सहायताकाफी प्रशंसनीय होगी!

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

समस्या वास्तव में नहीं है lbfgsऔर orthantwise_c, जब alpha = 1, समाधान बिल्कुल उसी के पास है glmnet। यह चीजों के L2 नियमितीकरण पक्ष के साथ करना है, जब कि alpha < 1। मैं की परिभाषा के लिए संशोधन के कुछ प्रकार बनाने लगता है SSEऔर SSE_grइसे ठीक करना चाहिए, लेकिन मुझे यकीन है कि क्या संशोधन किया जाना चाहिए नहीं कर रहा हूँ - जहाँ तक मुझे पता है, उन कार्यों वास्तव में glmnet पत्र में वर्णित के रूप में परिभाषित कर रहे हैं।
user3294195

यह स्टैकओवरफ़्लो, प्रोग्रामिंग प्रश्न का अधिक हो सकता है।
मैथ्यू गन

3
मैंने सोचा कि इसका कोड के बजाय अनुकूलन और नियमितीकरण के साथ अधिक है, यही कारण है कि मैंने इसे यहां पोस्ट किया है।
user3294195

1
शुद्ध अनुकूलन प्रश्न के लिए, scicomp.stackexchange.com भी एक विकल्प है। मुझे यकीन नहीं है कि क्या भाषा के विशिष्ट प्रश्न विषय पर हैं? (उदाहरण के लिए "आर में ऐसा करें")
जियोमैट 22

जवाबों:


11

tl; डॉ वर्जन:

उद्देश्य में स्पष्ट रूप से एक स्केलिंग फैक्टर , जहां नमूना मानक विचलन है।s^=sd(y)sd(y)

लंबा संस्करण

यदि आप glmnet प्रलेखन का बढ़िया प्रिंट पढ़ते हैं, तो आप देखेंगे:

ध्यान दें कि "" गाऊसी "के लिए उद्देश्य फ़ंक्शन है

               1/2  RSS/nobs + lambda*penalty,                  

और अन्य मॉडलों के लिए यह है

               -loglik/nobs + lambda*penalty.                   

यह भी ध्यान दें कि "" गॉसियन "के लिए, 'ग्लमेनेट' अपने लैम्ब्डा अनुक्रम की गणना करने से पहले इकाई विचरण करने के लिए y को मानकीकृत करता है (और फिर परिणामी गुणांक को अस्थिर करता है); यदि आप अन्य सॉफ़्टवेयर के साथ परिणामों को पुन: उत्पन्न / तुलना करना चाहते हैं, तो एक मानकीकृत y की आपूर्ति करना सबसे अच्छा है।

अब इसका मतलब है कि उद्देश्य वास्तव में और उस glmnet की रिपोर्ट ।

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

अब, जब आप शुद्ध लसो ( ) का उपयोग कर रहे थे , तब ग्लमैनेट के के अनियंत्रित होने का अर्थ है कि उत्तर समतुल्य हैं। दूसरी ओर, एक शुद्ध रिज के साथ, तब आपको सहमत होने के लिए मार्ग के लिए एक कारक द्वारा दंड को स्केल करने की आवश्यकता है , क्योंकि का एक अतिरिक्त कारक वर्ग से बाहर निकलता है in जुर्माना। मध्यवर्ती , आउटपुट को पुन: उत्पन्न करने के लिए गुणांक के दंड को स्केल करने का एक आसान तरीका नहीं है ।α=1β~1/s^glmnets^2αglmnets

एक बार जब मैं इकाई विचरण करने के लिए को मापता हूं, तो मुझे पता चलता है yयहां छवि विवरण दर्ज करें

जो अभी भी बिल्कुल मेल नहीं खाता है। यह दो चीजों के कारण लगता है:

  1. लैम्ब्डा अनुक्रम पूरी तरह से अभिसरण होने के लिए वार्म-स्टार्ट चक्रीय समन्वय वंश एल्गोरिथ्म के लिए बहुत छोटा हो सकता है।
  2. आपके डेटा में कोई त्रुटि अवधि नहीं है ( प्रतिगमन का 1 है)।R2
  3. ध्यान दें कि कोड में एक बग भी है, बशर्ते कि यह lambda[2]शुरुआती फिट के लिए हो, लेकिन यह होना चाहिए lambda[1]

एक बार जब मैं आइटमों को 1-3 से ठीक कर लेता हूं, तो मुझे निम्नलिखित परिणाम मिलते हैं (हालांकि यादृच्छिक बीज के आधार पर YMMV):

यहां छवि विवरण दर्ज करें

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.