Sao chép kết quả cho hồi quy tuyến tính glmnet bằng trình tối ưu hóa chung


10

Như tiêu đề, tôi đang cố gắng sao chép các kết quả từ tuyến tính glmnet bằng trình tối ưu hóa LBFGS từ thư viện lbfgs. Trình tối ưu hóa này cho phép chúng ta thêm một thuật ngữ chính quy L1 mà không phải lo lắng về tính khác biệt, miễn là hàm mục tiêu của chúng ta (không có thuật ngữ chính quy L1) là lồi.

Vấn đề hồi quy tuyến tính mạng đàn hồi trong giấy glmnet được đưa ra bởi trong đó X \ in \ mathbb {R} ^ {n \ lần p} là ma trận thiết kế, y \ in \ mathbb {R} ^ p là vectơ quan sát, \ alpha \ in [0,1] là tham số mạng đàn hồi và \ lambda> 0 là tham số chính quy. Toán tử \ Vert x \ Vert_p biểu thị định mức Lp thông thường.

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

Đoạn mã dưới đây xác định hàm và sau đó bao gồm một bài kiểm tra để so sánh kết quả. Như bạn có thể thấy, kết quả có thể chấp nhận được khi nào alpha = 1, nhưng lại tắt cho các giá trị của alpha < 1.Lỗi trở nên tồi tệ hơn khi chúng ta đi alpha = 1đến alpha = 0, như biểu đồ sau đây cho thấy ("số liệu so sánh" là khoảng cách Euclide trung bình giữa các ước tính tham số của glmnet và lbfss cho một đường dẫn chính quy nhất định).

nhập mô tả hình ảnh ở đây

Được rồi, đây là mã. Tôi đã thêm ý kiến ​​bất cứ nơi nào có thể. Câu hỏi của tôi là: Tại sao kết quả của tôi khác với kết quả của glmnetcác giá trị alpha < 1? Nó rõ ràng có liên quan đến thuật ngữ chính quy L2, nhưng theo như tôi có thể nói, tôi đã thực hiện thuật ngữ này chính xác theo giấy tờ. Bất kì sự trợ giúp nào đều được đánh giá cao!

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 Tôi đã thử mã của bạn, đây là một số thử nghiệm (Tôi đã thực hiện một số điều chỉnh nhỏ để phù hợp với cấu trúc của glmnet - lưu ý chúng tôi không thường xuyên sử dụng thuật ngữ chặn và các chức năng mất phải được thu nhỏ). Đây là cho alpha = 0, nhưng bạn có thể thử bất kỳ alpha- kết quả không phù hợp.

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

Tôi không chắc câu hỏi của bạn là về chủ đề (tôi nghĩ có thể là, vì đó là về kỹ thuật tối ưu hóa cơ bản) và tôi thực sự không thể kiểm tra mã của bạn ngay bây giờ, nhưng lbfgsđưa ra quan điểm về orthantwise_clập luận liên quan glmnet.
Firebug

Vấn đề không thực sự xảy ra với lbfgsorthantwise_c, khi nào alpha = 1, giải pháp gần như giống hệt với glmnet. Nó phải làm với mặt chính quy L2 của mọi thứ tức là khi nào alpha < 1. Tôi nghĩ rằng thực hiện một số sửa đổi cho định nghĩa SSESSE_grnên sửa nó, nhưng tôi không chắc sửa đổi đó là gì - theo như tôi biết, các chức năng đó được định nghĩa chính xác như được mô tả trong bài báo glmnet.
dùng3294195

Đây có thể là nhiều hơn một stackoverflow, câu hỏi lập trình.
Matthew Gunn

3
Tôi nghĩ rằng nó có liên quan nhiều hơn đến tối ưu hóa & chính quy hóa hơn là bản thân mã, đó là lý do tại sao tôi đăng nó ở đây.
dùng3294195

1
Đối với một câu hỏi tối ưu hóa thuần túy, scicomp.stackexchange.com cũng là một lựa chọn. Tôi không chắc chắn nếu câu hỏi cụ thể ngôn ngữ là về chủ đề mặc dù? (ví dụ: "làm điều này trong R")
GeoMatt22

Câu trả lời:


11

phiên bản tl; dr:

Mục tiêu hoàn toàn chứa hệ số tỷ lệ , trong đó là độ lệch chuẩn mẫu.s^=sd(y)sd(y)

Phiên bản dài hơn

Nếu bạn đọc bản in đẹp của tài liệu glmnet, bạn sẽ thấy:

Lưu ý rằng hàm mục tiêu cho '"gaussian"' là

               1/2  RSS/nobs + lambda*penalty,                  

và đối với các mô hình khác, nó là

               -loglik/nobs + lambda*penalty.                   

Cũng lưu ý rằng đối với '"gaussian"', 'glmnet' chuẩn hóa y để có phương sai đơn vị trước khi tính toán chuỗi lambda của nó (và sau đó không chuẩn hóa các hệ số kết quả); nếu bạn muốn sao chép / so sánh kết quả với phần mềm khác, tốt nhất là cung cấp một y chuẩn.

Bây giờ điều này có nghĩa là mục tiêu thực sự là và báo cáo glmnet đó .

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

Bây giờ, khi bạn đang sử dụng một Lasso thuần ( ), thì việc không chuẩn hóa glmnet's có nghĩa là các câu trả lời là tương đương. Mặt khác, với một sườn núi thuần túy, thì bạn cần chia tỷ lệ hình phạt theo hệ số để đường dẫn đồng ý, bởi vì một yếu tố phụ của xuất hiện từ hình vuông trong hình phạt . Đối với trung gian , không có cách nào dễ dàng để chia tỷ lệ hình phạt của các hệ số để tái tạo đầu ra.α=1β~1/s^glmnets^2αglmnets

Khi tôi chia tỷ lệ để có phương sai đơn vị, tôi tìm thấy ynhập mô tả hình ảnh ở đây

mà vẫn không khớp chính xác. Điều này dường như là do hai điều:

  1. Trình tự lambda có thể quá ngắn để thuật toán gốc tọa độ khởi động theo chu kỳ khởi động ấm hoàn toàn hội tụ.
  2. Không có thuật ngữ lỗi trong dữ liệu của bạn ( của hồi quy là 1).R2
  3. Lưu ý rằng cũng có một lỗi trong mã như được cung cấp trong đó cần có lambda[2]sự phù hợp ban đầu, nhưng đó là lỗi lambda[1].

Khi tôi chỉnh sửa các mục 1-3, tôi nhận được kết quả sau (mặc dù YMMV tùy thuộc vào hạt giống ngẫu nhiên):

nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.