New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[MRG+ ½] Fixing #7155 in stochastic_gradient.py #7159
Conversation
Thanks for the PR! Could you also add a non regression test please? |
all_or_any=all) | ||
is_fitted = True | ||
except _NotFittedError: | ||
is_fitted = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure it's the right fix.
is check_is_fitted really needed to know if predict_proba is supported?
can't you just remove the call to check_is_fitted ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I remove the call, GridSearchCV would throw loss hinge error when predict_proba is called. The problem came from this line. It tried to get the predict_proba attribute of SGDClassifier class but the default loss is hinge. So I changed it to raise the probability estimates are not available for loss=hinge
error only if the estimator has already been fitted.
Indeed it is ;)
Adding a test along the lines of def test_sgd_loss_param_grid_search():
# Make sure the predict_proba works when loss is specified
# as one of the parameters in the param_grid
rng = np.random.RandomState(0)
X = rng.random_sample([5, 2])
y = rng.random_integers(0, 1, [5, ])
param_grid = {
'loss': ['log'],
}
clf_grid = grid_search.GridSearchCV(estimator=SGDClassifier(random_state=42),
param_grid=param_grid)
clf_grid.fit(X, y).predict_proba(X) added to |
@raghavrv Your instruction is really helpful! I added the test along with a test to make sure when predict_proba is called the error raised is ======================================================================
ERROR: test_loss_param_grid_search (sklearn.linear_model.tests.test_sgd.DenseSGDClassifierTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/data/Dropbox/Python/scikit-learn/sklearn/linear_model/tests/test_sgd.py", line 836, in test_loss_param_grid_search
clf_grid.predict_proba(X)
File "/home/data/Dropbox/Python/scikit-learn/sklearn/utils/metaestimators.py", line 35, in __get__
self.get_attribute(obj)
File "/home/data/Dropbox/Python/scikit-learn/sklearn/linear_model/stochastic_gradient.py", line 756, in predict_proba
self._check_proba()
File "/home/data/Dropbox/Python/scikit-learn/sklearn/linear_model/stochastic_gradient.py", line 721, in _check_proba
" loss=%r" % self.loss)
AttributeError: probability estimates are not available for loss='hinge'
======================================================================
ERROR: test_not_fitted_error (sklearn.linear_model.tests.test_sgd.DenseSGDClassifierTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/data/Dropbox/Python/scikit-learn/sklearn/linear_model/tests/test_sgd.py", line 846, in test_not_fitted_error
clf.predict_proba, X)
File "/home/data/Dropbox/Python/scikit-learn/sklearn/linear_model/stochastic_gradient.py", line 756, in predict_proba
self._check_proba()
File "/home/data/Dropbox/Python/scikit-learn/sklearn/linear_model/stochastic_gradient.py", line 721, in _check_proba
" loss=%r" % self.loss)
AttributeError: probability estimates are not available for loss='hinge'
======================================================================
ERROR: test_loss_param_grid_search (sklearn.linear_model.tests.test_sgd.SparseSGDClassifierTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/data/Dropbox/Python/scikit-learn/sklearn/linear_model/tests/test_sgd.py", line 837, in test_loss_param_grid_search
clf_grid.predict_log_proba(X)
File "/home/data/Dropbox/Python/scikit-learn/sklearn/utils/metaestimators.py", line 35, in __get__
self.get_attribute(obj)
File "/home/data/Dropbox/Python/scikit-learn/sklearn/linear_model/stochastic_gradient.py", line 822, in predict_log_proba
self._check_proba()
File "/home/data/Dropbox/Python/scikit-learn/sklearn/linear_model/stochastic_gradient.py", line 721, in _check_proba
" loss=%r" % self.loss)
AttributeError: probability estimates are not available for loss='hinge'
======================================================================
ERROR: test_not_fitted_error (sklearn.linear_model.tests.test_sgd.SparseSGDClassifierTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/data/Dropbox/Python/scikit-learn/sklearn/linear_model/tests/test_sgd.py", line 846, in test_not_fitted_error
clf.predict_proba, X)
File "/home/yichuanliu/anaconda3/envs/scikit-learn-dev/lib/python3.5/unittest/case.py", line 1258, in assertRaisesRegex
return context.handle('assertRaisesRegex', args, kwargs)
File "/home/yichuanliu/anaconda3/envs/scikit-learn-dev/lib/python3.5/unittest/case.py", line 176, in handle
callable_obj(*args, **kwargs)
File "/home/data/Dropbox/Python/scikit-learn/sklearn/linear_model/tests/test_sgd.py", line 44, in predict_proba
return super(SparseSGDClassifier, self).predict_proba(X)
File "/home/data/Dropbox/Python/scikit-learn/sklearn/linear_model/stochastic_gradient.py", line 756, in predict_proba
self._check_proba()
File "/home/data/Dropbox/Python/scikit-learn/sklearn/linear_model/stochastic_gradient.py", line 721, in _check_proba
" loss=%r" % self.loss)
AttributeError: probability estimates are not available for loss='hinge'
----------------------------------------------------------------------
Ran 168 tests in 1.292s
FAILED (errors=4) |
@@ -32,7 +32,7 @@ | |||
from .sgd_fast import Huber | |||
from .sgd_fast import EpsilonInsensitive | |||
from .sgd_fast import SquaredEpsilonInsensitive | |||
|
|||
from ..exceptions import NotFittedError as _NotFittedError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think you should add a newline after this, to replace the one you removed.
@@ -757,6 +764,7 @@ def predict_proba(self): | |||
return self._predict_proba | |||
|
|||
def _predict_proba(self, X): | |||
check_is_fitted(self, ["t_", "coef_", "intercept_"], all_or_any=all) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now this gets checked twice no? Do we want that?
@nelson-liu Thanks for the comments! Newlines added. |
Also why do we even set the fit params like Additionally I think we could simplify this by combining the |
@raghavrv Following your idea, I used decorator on a single method |
@@ -496,7 +501,7 @@ def partial_fit(self, X, y, classes=None, sample_weight=None): | |||
Returns | |||
------- | |||
self : returns an instance of self. | |||
""" | |||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
trailing white space
@raghavrv PEP8 problem fixed! |
Thanks! When Before further reviews - @MechCoder and @agramfort does this approach seem okay? |
@yl565 Could you revert back the PEP8 changes unrelated to this PR please? |
@raghavrv Thanks for all your help and comments! Will revert the PEP8 unrelated to this PR later tonight.
I have tried that but it will give the following testing error: ======================================================================
FAIL: sklearn.tests.test_common.test_non_meta_estimators('SGDClassifier', <class 'sklearn.linear_model.stochastic_gradient.SGDClassifier'>)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/yichuanliu/anaconda3/envs/scikit-learn-dev/lib/python3.5/site-packages/nose/case.py", line 198, in runTest
self.test(*self.arg)
File "/home/data/Dropbox/Python/scikit-learn/sklearn/utils/testing.py", line 343, in wrapper
return fn(*args, **kwargs)
File "/home/data/Dropbox/Python/scikit-learn/sklearn/utils/estimator_checks.py", line 1060, in check_estimators_unfitted
est.predict_proba, X)
File "/home/data/Dropbox/Python/scikit-learn/sklearn/utils/testing.py", line 424, in assert_raise_message
(message, error_message))
AssertionError: Error message does not include the expected string: 'fit'. Observed error message: "probability estimates are notavailable for loss='hinge' |
While initializing the BTW can you check why when |
I was thinking along the lines of from sklearn.model_selection import GridSearchCV
from sklearn.exceptions import NotFittedError
from sklearn.utils.validation import check_is_fitted
class _CheckProba(object):
def __init__(self, fn):
self.fn = fn
def __call__(self, *args, **kw):
return self.fn(*args, **kw)
def __get__(self, obj, type=None):
if obj.loss not in ("log", "modified_huber"):
raise AttributeError("probability estimates are not available for"
" loss=%r" % obj.loss)
return self.__class__(self.fn.__get__(obj, type))
class Estim(object):
def __init__(self, loss='hinge'):
self.loss=loss
@_CheckProba
def predict_proba(self, X):
check_is_fitted(self, 'coef_')
return np.arange(X.shape[0])
def fit(self, X, y=None):
self.coef_ = 1
return self
def __repr__(self):
return "Estim(loss=%r)" % self.loss Test the following ... >>> X = [[1, 2], [2, 3]]
>>> y = [0, 1]
>>> hasattr(Estim().fit(X, y), 'predict_proba')
False
>>> hasattr(Estim(loss='log').fit(X, y), 'predict_proba')
True
>>> Estim(loss='log').predict_proba(X)
NotFittedError |
fn_check_obj(obj) with obj as the metaestimator instance. | ||
The function will be executed before ducktyping. Typically used for | ||
checking if the estimator has been fitted. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would leave the doctests as they were or add them as tests to sklearn/utils/tests/test_metaestimators.py.
You could also add additional tests in the same file as you did at some point of this PR. Adding tests for if_fitted_delegate_has_method would be good too.
Only being able to sniff out supported methods after fitting seems to be somewhat of a problem :( This "only" happens with GridSearchCV and RandomizedSearchCV, right? |
It happens when capabilities can't be determined from parameters alone. Currently that means |
Other estimators which could delegate to a Then in terms of duck-typing I see two choices:
For reference, here is a quick-and-dirty listing of attributes we hasattr (on things that mostly aren't data):
For example:
|
So maybe we apply |
Sooo we want to warn people if they check
|
More practically, can we separate the long-term solution from one which On 8 September 2016 at 01:58, Yichuan Liu notifications@github.com wrote:
|
@jnothman hm it kinda solves the problem after fit. I'm ok with it. Should we still target that for 0.18? |
@jnothman Do you think it is better to revert back to 265fc70? |
@yl565 I think that was the suggestion. |
This allows ducktyping of the decorated method based on ``attribute_name``. | ||
This allows ducktyping of the decorated method based on | ||
``delegate.attribute_name`` where ``delegate`` is the first item in | ||
``delegate_names`` that is an attribute of the base object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this sentence hard to understand. maybe make it two sentences. And say "Here delegate
is the first item in delegate_names
for which hasattr(object, delegate)
is true? not sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about the following:
Using this class to create a decorator for a class method object.attribute_name
.
An AttributeError
will be raised if none of the delegates (specified in
delegate_names
) is an attribute of object
(i.e. hasattr(object, delegate )
is False
) or none of the delegates has an attribute attribute_name
(i.e.
hasattr(delegate, attribute_name)
is False
).
This allows ducktyping of the decorated method object.attribute_name
based on
object.delegate.attribute_name
where delegate
is the first item in
delegate_names
for which hasattr(object, delegate )
is True
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like " for a class method object.attribute_name
":
attribute_name
can be any attribute, not necessarily a method- a class method named "object.attribute_name" is weird or even contradictory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just spit the second sentence as @amueller suggested.
Besides the unaddressed comments still visible in the diff view of this PR on github, LGTM. |
@@ -14,16 +14,22 @@ class _IffHasAttrDescriptor(object): | |||
"""Implements a conditional property using the descriptor protocol. | |||
|
|||
Using this class to create a decorator will raise an ``AttributeError`` | |||
if the ``attribute_name`` is not present on the base object. | |||
if none of the delegates (specified in ``delegate_names``) is an attribute | |||
of the base object or none of the delegates has an attribute |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"none of the delegates has" -> "the first found delegate does not have"
@yl565 let me finish this PR for you. We want to have it merged into based before cutting the 0.18.X release branch tonight. |
@ogrisel Sorry for couldn't response earlier, have been busy preparing a paper. Please go head. Thanks. |
No problem! |
Update data to latest available on figshare (v5). Expression and mutation have not changed since v4, so do not expect changes from this upgrade. Update sklearn to 0.18.0. See what's new at http://scikit-learn.org/0.18/whats_new.html. Particularly exciting is: > Fix incomplete `predict_proba` method delegation from `model_selection.GridSearchCV` to `linear_model.SGDClassifier` (scikit-learn/scikit-learn#7159) by Yichuan Liu (@yl565). `3.TCGA-MLexample_Pathway.ipynb` had incorrect data location. Fixed so all root notebooks would execute.
* Upgrades: sklearn to 0.18.0, data to v5 Update data to latest available on figshare (v5). Expression and mutation have not changed since v4, so do not expect changes from this upgrade. Update sklearn to 0.18.0. See what's new at http://scikit-learn.org/0.18/whats_new.html. Particularly exciting is: > Fix incomplete `predict_proba` method delegation from `model_selection.GridSearchCV` to `linear_model.SGDClassifier` (scikit-learn/scikit-learn#7159) by Yichuan Liu (@yl565). `3.TCGA-MLexample_Pathway.ipynb` had incorrect data location. Fixed so all root notebooks would execute. * Rerun with up-to-date local conda env * Fix import deprecation warning * Fix deprecation warning with cv grid scores
Fixes #7155
Now calling predict_proba/predict_log_proba of SGDClassifier throw the correct error when not fitted and calling predict_proba/predict_log_proba of GridSearchCV will not throw error when estimator=SGDClassifier and param_grid={'loss':['log']}