143 lines
4.8 KiB
Python
143 lines
4.8 KiB
Python
# Author: Alexandre Gramfort <alexandre.gramfort@inria.fr>
|
|
# Olivier Grisel <olivier.grisel@ensta.org>
|
|
#
|
|
# License: BSD Style.
|
|
"""Implementation of coordinate descent for the Elastic Net with sparse data.
|
|
"""
|
|
|
|
import warnings
|
|
import numpy as np
|
|
import scipy.sparse as sp
|
|
|
|
from ...utils.extmath import safe_sparse_dot
|
|
from ..base import LinearModel
|
|
from . import cd_fast_sparse
|
|
|
|
|
|
class ElasticNet(LinearModel):
|
|
"""Linear Model trained with L1 and L2 prior as regularizer
|
|
|
|
This implementation works on scipy.sparse X and dense `coef_`.
|
|
|
|
rho=1 is the lasso penalty. Currently, rho <= 0.01 is not
|
|
reliable, unless you supply your own sequence of alpha.
|
|
|
|
Parameters
|
|
----------
|
|
alpha : float
|
|
Constant that multiplies the L1 term. Defaults to 1.0
|
|
rho : float
|
|
The ElasticNet mixing parameter, with 0 < rho <= 1.
|
|
fit_intercept: bool
|
|
Whether the intercept should be estimated or not. If False, the
|
|
data is assumed to be already centered.
|
|
|
|
TODO: fit_intercept=True is not yet implemented
|
|
|
|
Notes
|
|
-----
|
|
The parameter rho corresponds to alpha in the glmnet R package
|
|
while alpha corresponds to the lambda parameter in glmnet.
|
|
"""
|
|
def __init__(self, alpha=1.0, rho=0.5, fit_intercept=False,
|
|
normalize=False, max_iter=1000, tol=1e-4):
|
|
if fit_intercept:
|
|
raise NotImplementedError("fit_intercept=True is not implemented")
|
|
self.alpha = alpha
|
|
self.rho = rho
|
|
self.fit_intercept = fit_intercept
|
|
self.normalize = normalize
|
|
self.intercept_ = 0.0
|
|
self.max_iter = max_iter
|
|
self.tol = tol
|
|
self._set_coef(None)
|
|
|
|
def _set_coef(self, coef_):
|
|
self.coef_ = coef_
|
|
if coef_ is None:
|
|
self.sparse_coef_ = None
|
|
else:
|
|
# sparse representation of the fitted coef for the predict method
|
|
self.sparse_coef_ = sp.csr_matrix(coef_)
|
|
|
|
def fit(self, X, y):
|
|
"""Fit current model with coordinate descent
|
|
|
|
X is expected to be a sparse matrix. For maximum efficiency, use a
|
|
sparse matrix in CSC format (scipy.sparse.csc_matrix)
|
|
"""
|
|
X = sp.csc_matrix(X)
|
|
y = np.asarray(y, dtype=np.float64)
|
|
|
|
if X.shape[0] != y.shape[0]:
|
|
raise ValueError("X and y have incompatible shapes.\n" +
|
|
"Note: Sparse matrices cannot be indexed w/" +
|
|
"boolean masks (use `indices=True` in CV).")
|
|
|
|
# NOTE: we are explicitly not centering the data the naive way to
|
|
# avoid breaking the sparsity of X
|
|
|
|
n_samples, n_features = X.shape[0], X.shape[1]
|
|
if self.coef_ is None:
|
|
self.coef_ = np.zeros(n_features, dtype=np.float64)
|
|
|
|
alpha = self.alpha * self.rho * n_samples
|
|
beta = self.alpha * (1.0 - self.rho) * n_samples
|
|
X_data = np.array(X.data, np.float64)
|
|
|
|
# TODO: add support for non centered data
|
|
coef_, self.dual_gap_, self.eps_ = \
|
|
cd_fast_sparse.enet_coordinate_descent(
|
|
self.coef_, alpha, beta, X_data, X.indices, X.indptr, y,
|
|
self.max_iter, self.tol)
|
|
|
|
# update self.coef_ and self.sparse_coef_ consistently
|
|
self._set_coef(coef_)
|
|
|
|
if self.dual_gap_ > self.eps_:
|
|
warnings.warn('Objective did not converge, you might want'
|
|
'to increase the number of iterations')
|
|
|
|
# XXX TODO: implement intercept_ fitting
|
|
|
|
# return self for chaining fit and predict calls
|
|
return self
|
|
|
|
def decision_function(self, X):
|
|
"""Decision function of the linear model
|
|
|
|
Parameters
|
|
----------
|
|
X : scipy.sparse matrix of shape [n_samples, n_features]
|
|
|
|
Returns
|
|
-------
|
|
array, shape = [n_samples] with the predicted real values
|
|
"""
|
|
return np.ravel(safe_sparse_dot(self.sparse_coef_, X.T,
|
|
dense_output=True) + self.intercept_)
|
|
|
|
|
|
class Lasso(ElasticNet):
|
|
"""Linear Model trained with L1 prior as regularizer
|
|
|
|
This implementation works on scipy.sparse X and dense `coef_`. Technically
|
|
this is the same as Elastic Net with the L2 penalty set to zero.
|
|
|
|
Parameters
|
|
----------
|
|
alpha : float
|
|
Constant that multiplies the L1 term. Defaults to 1.0
|
|
`coef_` : ndarray of shape n_features
|
|
The initial coeffients to warm-start the optimization
|
|
fit_intercept: bool
|
|
Whether the intercept should be estimated or not. If False, the
|
|
data is assumed to be already centered.
|
|
|
|
"""
|
|
def __init__(self, alpha=1.0, fit_intercept=False, normalize=False,
|
|
max_iter=1000, tol=1e-4):
|
|
super(Lasso, self).__init__(
|
|
alpha=alpha, rho=1.0, fit_intercept=fit_intercept,
|
|
normalize=normalize, max_iter=max_iter, tol=tol)
|