La respuesta más bien anti-climática a " ¿Alguien sabe por qué es esto? " Es que simplemente a nadie le importa lo suficiente como para implementar una rutina de regresión de cresta no negativa. Una de las razones principales es que las personas ya han comenzado a implementar
rutinas de red elásticas no negativas (por ejemplo, aquí y aquí ). Elastic net incluye la regresión de cresta como un caso especial (uno esencialmente configura la parte LASSO para que tenga una ponderación cero). Estos trabajos son relativamente nuevos, por lo que aún no se han incorporado a scikit-learn o un paquete de uso general similar. Es posible que desee consultar a los autores de estos documentos para obtener el código.
EDITAR:
Como @amoeba y yo discutimos sobre los comentarios, la implementación real de esto es relativamente simple. Digamos que uno tiene el siguiente problema de regresión para:
y= 2 x1- x2+ ϵ ,ϵ ∼ N( 0 , 0.22)
donde y x 2 son ambos normales estándar, tales como: x p ~ N ( 0 , 1 ) . Tenga en cuenta que uso variables predictoras estandarizadas para no tener que normalizar después. Por simplicidad, tampoco incluyo una intercepción. Podemos resolver de inmediato este problema de regresión utilizando la regresión lineal estándar. Entonces en R debería ser algo como esto:X1X2Xpag∼ N( 0 , 1 )
rm(list = ls());
library(MASS);
set.seed(123);
N = 1e6;
x1 = rnorm(N)
x2 = rnorm(N)
y = 2 * x1 - 1 * x2 + rnorm(N,sd = 0.2)
simpleLR = lm(y ~ -1 + x1 + x2 )
matrixX = model.matrix(simpleLR); # This is close to standardised
vectorY = y
all.equal(coef(simpleLR), qr.solve(matrixX, vectorY), tolerance = 1e-7) # TRUE
βXλ--√yopagypag( XTX+ λ I)- 1XTy( X¯TX¯)- 1X¯Ty¯¯simboliza la versión aumentada. Revise las diapositivas 18-19 de estas notas para ver si están completas, las encontré bastante sencillas. Entonces, en R, a algunos nos gustaría lo siguiente:
myLambda = 100;
simpleRR = lm.ridge(y ~ -1 + x1 + x2, lambda = myLambda)
newVecY = c(vectorY, rep(0, 2))
newMatX = rbind(matrixX, sqrt(myLambda) * diag(2))
all.equal(coef(simpleRR), qr.solve(newMatX, newVecY), tolerance = 1e-7) # TRUE
minβEl | El | y¯- X¯βEl | El |22
myRSS <- function(X,y,b){ return( sum( (y - X%*%b)^2 ) ) }
bfgsOptim = optim(myRSS, par = c(1,1), X = newMatX, y= newVecY,
method = 'L-BFGS-B')
all.equal(coef(simpleRR), bfgsOptim$par, check.attributes = FALSE,
tolerance = 1e-7) # TRUE
minβEl | El | y¯- X¯βEl | El |22β≥ 0
bfgsOptimConst = optim(myRSS, par = c(1,1), X=newMatX, y= newVecY,
method = 'L-BFGS-B', lower = c(0,0))
all(bfgsOptimConst$par >=0) # TRUE
(bfgsOptimConst$par) # 2.000504 0.000000
lo que muestra que la tarea original de regresión de cresta no negativa se puede resolver reformulando como un simple problema de optimización restringida. Algunas advertencias:
- Usé (prácticamente) variables predictoras normalizadas. Deberá tener en cuenta la normalización usted mismo.
- Lo mismo ocurre con la no normalización de la intercepción.
- Solía
optim
's L-BFGS-B argumento. Es el solucionador más vainilla R que acepta límites. Estoy seguro de que encontrará docenas de mejores solucionadores.
- En general, los problemas lineales de mínimos cuadrados se presentan como tareas de optimización cuadrática . Esta es una exageración para esta publicación, pero tenga en cuenta que puede obtener una mejor velocidad si es necesario.
- Como se menciona en los comentarios, puede omitir la regresión de cresta como parte de regresión lineal aumentada y codificar directamente la función de costo de cresta como un problema de optimización. Esto sería mucho más simple y esta publicación significativamente más pequeña. En aras de la discusión, también añado esta segunda solución.
- No soy completamente conversacional en Python, pero esencialmente puede replicar este trabajo utilizando las funciones de optimización de NumPy linalg.solve y SciPy .
- λ
Código para el punto 5:
myRidgeRSS <- function(X,y,b, lambda){
return( sum( (y - X%*%b)^2 ) + lambda * sum(b^2) )
}
bfgsOptimConst2 = optim(myRidgeRSS, par = c(1,1), X = matrixX, y = vectorY,
method = 'L-BFGS-B', lower = c(0,0), lambda = myLambda)
all(bfgsOptimConst2$par >0) # TRUE
(bfgsOptimConst2$par) # 2.000504 0.000000