python-glmnet icon indicating copy to clipboard operation
python-glmnet copied to clipboard

ElasticNet.fit raises ValueError when not converging instead of just issuing a warning

Open mathurinm opened this issue 2 years ago • 1 comments

Thank you for your package, and for making it available on conda.

If I set a max_iter which is too low, instead of getting a convergence warning as in sklearn behavior, it simply fails with an error. Can this be fixed easily? I'm trying to get the solution for a single lambda (and from what I understood, if I use a default apth, I have no guarantee that glmnet will go to the end of it, it may early stop, which I don't want). Reproduce with:

from celer.datasets import make_correlated_data
from sklearn.linear_model import ElasticNet
import glmnet
from numpy.linalg import norm
import numpy as np

np.random.seed(0)
X = np.random.randn(100, 200)
X = np.asfortranarray(X)
y = np.random.randn(100)
alpha_max = norm(X.T @ y, ord=np.inf) / len(y)


clf2 = glmnet.ElasticNet(alpha=1, lambda_path=[
                         alpha_max, alpha_max/100], standardize=False, fit_intercept=False, tol=1e-10, max_iter=1).fit(X, y)

output:

/home/mathurin/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/errors.py:66: RuntimeWarning: Model did not converge for smaller values of lambda, returning solution for the largest 3 values.
  warnings.warn("Model did not converge for smaller values of lambda, "
/home/mathurin/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/errors.py:66: RuntimeWarning: Model did not converge for smaller values of lambda, returning solution for the largest 3 values.
  warnings.warn("Model did not converge for smaller values of lambda, "
/home/mathurin/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/util.py:202: RuntimeWarning: lambda_path has a single value, this may be an intercept-only model.
  warnings.warn("lambda_path has a single value, this may be an "
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-1-97963ff96fcf> in <module>
     10 
     11 
---> 12 clf2 = glmnet.ElasticNet(alpha=1, lambda_path=[
     13                          alpha_max, alpha_max/100], standardize=False, fit_intercept=False, tol=1e-10, max_iter=1).fit(X, y)

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/linear.py in fit(self, X, y, sample_weight, relative_penalties, groups)
    236                 self._cv = GroupKFold(n_splits=self.n_splits)
    237 
--> 238             cv_scores = _score_lambda_path(self, X, y, groups,
    239                                            sample_weight,
    240                                            relative_penalties,

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/util.py in _score_lambda_path(est, X, y, groups, sample_weight, relative_penalties, scoring, n_jobs, verbose)
     64         warnings.simplefilter(action, UndefinedMetricWarning)
     65 
---> 66         scores = Parallel(n_jobs=n_jobs, verbose=verbose, backend='threading')(
     67             delayed(_fit_and_score)(est, scorer, X, y, sample_weight, relative_penalties,
     68                                     est.lambda_path_, train_idx, test_idx)

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in __call__(self, iterable)
   1041             # remaining jobs.
   1042             self._iterating = False
-> 1043             if self.dispatch_one_batch(iterator):
   1044                 self._iterating = self._original_iterator is not None
   1045 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in dispatch_one_batch(self, iterator)
    859                 return False
    860             else:
--> 861                 self._dispatch(tasks)
    862                 return True
    863 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in _dispatch(self, batch)
    777         with self._lock:
    778             job_idx = len(self._jobs)
--> 779             job = self._backend.apply_async(batch, callback=cb)
    780             # A job can complete so quickly than its callback is
    781             # called before we get here, causing self._jobs to

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/_parallel_backends.py in apply_async(self, func, callback)
    206     def apply_async(self, func, callback=None):
    207         """Schedule a func to be run"""
--> 208         result = ImmediateResult(func)
    209         if callback:
    210             callback(result)

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/_parallel_backends.py in __init__(self, batch)
    570         # Don't delay the application, to avoid keeping the input
    571         # arguments in memory
--> 572         self.results = batch()
    573 
    574     def get(self):

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in __call__(self)
    260         # change the default number of processes to -1
    261         with parallel_backend(self._backend, n_jobs=self._n_jobs):
--> 262             return [func(*args, **kwargs)
    263                     for func, args, kwargs in self.items]
    264 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in <listcomp>(.0)
    260         # change the default number of processes to -1
    261         with parallel_backend(self._backend, n_jobs=self._n_jobs):
--> 262             return [func(*args, **kwargs)
    263                     for func, args, kwargs in self.items]
    264 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/util.py in _fit_and_score(est, scorer, X, y, sample_weight, relative_penalties, score_lambda_path, train_inx, test_inx)
    117 
    118     lamb = np.clip(score_lambda_path, m.lambda_path_[-1], m.lambda_path_[0])
--> 119     return scorer(m, X[test_inx, :], y[test_inx], lamb=lamb)
    120 
    121 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/scorer.py in _passthrough_scorer(estimator, *args, **kwargs)
    187 def _passthrough_scorer(estimator, *args, **kwargs):
    188     """Function that wraps estimator.score"""
--> 189     return estimator.score(*args, **kwargs)
    190 
    191 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/linear.py in score(self, X, y, lamb)
    437 
    438         # pred will have shape (n_samples, n_lambda)
--> 439         pred = self.predict(X, lamb=lamb)
    440 
    441         # Reverse the args of the r2_score function from scikit-learn. The

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/linear.py in predict(self, X, lamb)
    414             Predicted response value for each sample given each value of lambda
    415         """
--> 416         return self.decision_function(X, lamb)
    417 
    418     def score(self, X, y, lamb=None):

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/linear.py in decision_function(self, X, lamb)
    392         # single value of lambda
    393         if lamb.shape[0] == 1:
--> 394             z = z.squeeze(axis=-1)
    395         return z
    396 

ValueError: cannot select an axis to squeeze out which has size not equal to one

ping @agramfort

mathurinm avatar Dec 07 '21 16:12 mathurinm

Changing the seed in the above script to 1 gives me a different error:

ValueError                                Traceback (most recent call last)
ValueError: unexpected array size: new_size=1, got array with arr_size=0


The above exception was the direct cause of the following exception:

ValueError                                Traceback (most recent call last)
<ipython-input-4-49f76e8921b5> in <module>
     10 
     11 
---> 12 clf2 = glmnet.ElasticNet(alpha=1, lambda_path=[
     13                          alpha_max, alpha_max/100], standardize=False, fit_intercept=False, tol=1e-10, max_iter=1).fit(X, y)

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/linear.py in fit(self, X, y, sample_weight, relative_penalties, groups)
    236                 self._cv = GroupKFold(n_splits=self.n_splits)
    237 
--> 238             cv_scores = _score_lambda_path(self, X, y, groups,
    239                                            sample_weight,
    240                                            relative_penalties,

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/util.py in _score_lambda_path(est, X, y, groups, sample_weight, relative_penalties, scoring, n_jobs, verbose)
     64         warnings.simplefilter(action, UndefinedMetricWarning)
     65 
---> 66         scores = Parallel(n_jobs=n_jobs, verbose=verbose, backend='threading')(
     67             delayed(_fit_and_score)(est, scorer, X, y, sample_weight, relative_penalties,
     68                                     est.lambda_path_, train_idx, test_idx)

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in __call__(self, iterable)
   1041             # remaining jobs.
   1042             self._iterating = False
-> 1043             if self.dispatch_one_batch(iterator):
   1044                 self._iterating = self._original_iterator is not None
   1045 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in dispatch_one_batch(self, iterator)
    859                 return False
    860             else:
--> 861                 self._dispatch(tasks)
    862                 return True
    863 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in _dispatch(self, batch)
    777         with self._lock:
    778             job_idx = len(self._jobs)
--> 779             job = self._backend.apply_async(batch, callback=cb)
    780             # A job can complete so quickly than its callback is
    781             # called before we get here, causing self._jobs to

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/_parallel_backends.py in apply_async(self, func, callback)
    206     def apply_async(self, func, callback=None):
    207         """Schedule a func to be run"""
--> 208         result = ImmediateResult(func)
    209         if callback:
    210             callback(result)

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/_parallel_backends.py in __init__(self, batch)
    570         # Don't delay the application, to avoid keeping the input
    571         # arguments in memory
--> 572         self.results = batch()
    573 
    574     def get(self):

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in __call__(self)
    260         # change the default number of processes to -1
    261         with parallel_backend(self._backend, n_jobs=self._n_jobs):
--> 262             return [func(*args, **kwargs)
    263                     for func, args, kwargs in self.items]
    264 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/joblib/parallel.py in <listcomp>(.0)
    260         # change the default number of processes to -1
    261         with parallel_backend(self._backend, n_jobs=self._n_jobs):
--> 262             return [func(*args, **kwargs)
    263                     for func, args, kwargs in self.items]
    264 

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/util.py in _fit_and_score(est, scorer, X, y, sample_weight, relative_penalties, score_lambda_path, train_inx, test_inx)
    114     """
    115     m = clone(est)
--> 116     m = m._fit(X[train_inx, :], y[train_inx], sample_weight[train_inx], relative_penalties)
    117 
    118     lamb = np.clip(score_lambda_path, m.lambda_path_[-1], m.lambda_path_[0])

~/anaconda3/envs/benchopt_lasso/lib/python3.8/site-packages/glmnet/linear.py in _fit(self, X, y, sample_weight, relative_penalties)
    372         ca = ca[:, :self.n_lambda_]
    373         nin = nin[:self.n_lambda_]
--> 374         self.coef_path_ = solns(_x.shape[1], ca, ia, nin)
    375 
    376         return self

ValueError: failed in converting 4th argument `nin' of _glmnet.solns to C/Fortran array

mathurinm avatar Dec 07 '21 16:12 mathurinm