[MRG+1] label binarizer not used consistently in CalibratedClassifierCV#7799
[MRG+1] label binarizer not used consistently in CalibratedClassifierCV#7799agramfort merged 13 commits intoscikit-learn:masterfrom
Conversation
sklearn/calibration.py
Outdated
| return df, idx_pos_class | ||
|
|
||
| def fit(self, X, y, sample_weight=None): | ||
| def fit(self, X, y, sample_weight=None, classes=None): |
There was a problem hiding this comment.
Could we please move this parameter to the constructor?
|
@jnothman Done! |
sklearn/tests/test_calibration.py
Outdated
| def test_calibration_prob_sum(): | ||
| """Test that sum of probabilities is 1""" | ||
| num_classes = 2 | ||
| X, y = make_classification(n_samples=100, n_features=20, |
There was a problem hiding this comment.
Please make n_samples smaller to reduce test time.
sklearn/tests/test_calibration.py
Outdated
|
|
||
|
|
||
| def test_calibration_prob_sum(): | ||
| """Test that sum of probabilities is 1""" |
There was a problem hiding this comment.
We don't use docstrings in tests because they make nose output harder to read. Make this a comment instead. Also comment that this is a non-regression test for issue #... as it's otherwise a bit obscure to use LOO.
|
This otherwise looks good as a fix for the issue-as-presented. However, I think there are other oddities in the code here. It's also unclear to me that the current implementation correctly handles the case that classes are absent for some training sets. |
sklearn/calibration.py
Outdated
| calibrated_classifier = _CalibratedClassifier( | ||
| this_estimator, method=self.method) | ||
| this_estimator, method=self.method, | ||
| classes=np.unique(y[train])) |
There was a problem hiding this comment.
are you sure this should be y[train] and not y?
There was a problem hiding this comment.
I m not sure. I thought that since the base_estimator is fitted with y[train], the base_estimator will assume that only np.unique(y[train]) classes exist, but still there will be a problem if y[test] has some extra classes. So, should I change it to y @jnothman ?
There was a problem hiding this comment.
This is related to my concern that "It's also unclear to me that the current implementation correctly handles the case that classes are absent for some training sets."
Could you concoct a test case, with toy data and your own train-test splits, say, that checks this issue?
There was a problem hiding this comment.
@jnothman added the test (and I hope this is how you expected it to be?)
I can make the changes which you have listed above to the class CalibratedClassifierCV. Do you want me to commit it in this PR or should I make another PR after this one is merged?
sklearn/tests/test_calibration.py
Outdated
| clf = LinearSVC(C=1.0) | ||
| clf_prob = CalibratedClassifierCV(clf, method="sigmoid", cv=LeaveOneOut()) | ||
| clf_prob.fit(X, y) | ||
| probs = clf_prob.predict_proba(X) |
There was a problem hiding this comment.
I think this may be hiding the issue: is it an array of shape (10, 10) or of shape (10, 9)?
There was a problem hiding this comment.
The shape of probs array is (10, 10), and BTW this worked only when I changed the classes parameter to self.classes_ instead of np.unique(y[train]).
There was a problem hiding this comment.
are you able to drill down further and ensure that each constituent calibrated classifier is returning probability of 0 for a different class?
There was a problem hiding this comment.
We may need to specially handle the case where a binarized label's data is all zeros.
There was a problem hiding this comment.
Like you said, I also think that the current implementation does not handle the case where some classes are absent in training set.
There was a problem hiding this comment.
Not necessarily. If the base estimator happens to have only been trained on a subset of the classes, the mapping between the columns of the base_estimator's decision function and those of that CalibratedClassifierCV should output is not so trivial .
There was a problem hiding this comment.
@jnothman self.base_estimator.classes_ will be the class labels and we have to change that to integers with LabelEncoder.transform()(Already fitted with all the classes) and this should work?
There was a problem hiding this comment.
ignore that, you're not using ridge anymore :/
There was a problem hiding this comment.
@jnothman Some test which was already present was using Ridge in CalibratedClassifierCV, that's why I asked you. Maybe I will change that test to some other classifier.
|
And we can't assume always encoded labels in |
|
@jnothman Can we use |
|
yeah, I'm a bit confused why the LabelBinarizer is instantiated here in the first place... you can definitely replace it by LabelEncoder from what I see. (I haven't looked into your PR in detail though) |
|
|
|
yes, I think that should work. On 2 November 2016 at 14:25, Srivatsan notifications@github.com wrote:
|
sklearn/tests/test_calibration.py
Outdated
| clf = LinearSVC(C=1.0) | ||
| clf_prob = CalibratedClassifierCV(clf, method="sigmoid", cv=LeaveOneOut()) | ||
| clf_prob.fit(X, y) | ||
| probs = clf_prob.predict_proba(X) |
There was a problem hiding this comment.
@jnothman Can you check the tests? I couldn't think of any other way to test this.
There was a problem hiding this comment.
I meant to actually go through each of calibrated_classifiers_ and run predict_proba to confirm that each leaves a different column with zero-probability. Or am I wrong to think that's correct behaviour?
There was a problem hiding this comment.
Yes that can be checked
jnothman
left a comment
There was a problem hiding this comment.
Apparently my comment was left pending for a while
sklearn/tests/test_calibration.py
Outdated
| clf = LinearSVC(C=1.0) | ||
| clf_prob = CalibratedClassifierCV(clf, method="sigmoid", cv=LeaveOneOut()) | ||
| clf_prob.fit(X, y) | ||
| probs = clf_prob.predict_proba(X) |
sklearn/calibration.py
Outdated
| idx_pos_class = self.label_encoder_.\ | ||
| transform(self.base_estimator.classes_) | ||
| else: | ||
| idx_pos_class = np.arange(df.shape[1]) |
There was a problem hiding this comment.
Since Ridge has a decision_function method can we not use it with CalibratedClassifierCV. I did this workaround for such cases. Is it fine or Ridge should not work with CalibratedClassifierCV. @jnothman
There was a problem hiding this comment.
Regressors like Ridge should not work with CalibratedClassifierCV
sklearn/tests/test_calibration.py
Outdated
|
|
||
| for i, calibrated_classifier in \ | ||
| enumerate(cal_clf.calibrated_classifiers_): | ||
| assert_array_equal(calibrated_classifier.predict_proba(X)[:, i], |
There was a problem hiding this comment.
Let's make this slightly stronger by checking that this column and only this column is zero:
proba = calibrated_classifier.predict_proba(X)
assert_array_equal(proba[:, i], np.zeros(len(y)))
assert_equal(np.all(np.hstack([proba[:, :i], proba[:, i+1:]])), True)
sklearn/calibration.py
Outdated
| corresponds to Platt's method or 'isotonic' which is a | ||
| non-parametric approach based on isotonic regression. | ||
|
|
||
| classes : array-like, shape (n_classes,) |
There was a problem hiding this comment.
Unimportant, given that this is a private class, but conventionally we'd add ", optional" to this line
|
Please also add a what's new entry. |
e3fba0d to
6d9b675
Compare
|
I think this can also be squeezed into 0.18.1, if the release manager agrees it's a worthwhile bug-fix (sorry for all the what's new hacking you need to do @amueller). |
…CV (scikit-learn#7799) * label binarizer not used consistently in CalibratedClassifierCV * changed position of classes argument to make old tests run * moved parameter to constructor and added test * added test where train set doesnt have all classes * CalibratedClassifierCV can now handle cases where train set doesnt contain all labels * fixing flake error * fixing line lengths * removing np.full() * from __future__ import division for py2.7 * change is test file * added an extra test and removed a test with Ridge * stronger test * whats new entry
…CV (scikit-learn#7799) * label binarizer not used consistently in CalibratedClassifierCV * changed position of classes argument to make old tests run * moved parameter to constructor and added test * added test where train set doesnt have all classes * CalibratedClassifierCV can now handle cases where train set doesnt contain all labels * fixing flake error * fixing line lengths * removing np.full() * from __future__ import division for py2.7 * change is test file * added an extra test and removed a test with Ridge * stronger test * whats new entry
…CV (scikit-learn#7799) * label binarizer not used consistently in CalibratedClassifierCV * changed position of classes argument to make old tests run * moved parameter to constructor and added test * added test where train set doesnt have all classes * CalibratedClassifierCV can now handle cases where train set doesnt contain all labels * fixing flake error * fixing line lengths * removing np.full() * from __future__ import division for py2.7 * change is test file * added an extra test and removed a test with Ridge * stronger test * whats new entry
* tag '0.18.1': (144 commits) skip tree-test on 32bit do the warning test as we do it in other places. Replase assert_equal by assert_almost_equal in cosine test version bump 0.18.1 fix merge conflict mess in whatsnew add the python2.6 warning to 0.18.1 fix learning_curve test that I messed up in cherry-picking the "reentrant cv" PR. sync whatsnew with master [MRG] TST Ensure __dict__ is unmodified by predict, transform, etc (scikit-learn#7553) FIX scikit-learn#6420: Cloning decision tree estimators breaks criterion objects (scikit-learn#7680) Add whats new entry for scikit-learn#6282 (scikit-learn#7629) [MGR + 2] fix selectFdr bug (scikit-learn#7490) fixed whatsnew cherry-pick mess (somewhat) [MRG + 2] FIX LogisticRegressionCV to correctly handle string labels (scikit-learn#5874) [MRG + 2] Fixed parameter setting in SelectFromModel (scikit-learn#7764) [MRG+2] DOC adding separate `fit()` methods (and docstrings) for DecisionTreeClassifier and DecisionTreeRegressor (scikit-learn#7824) Fix docstring typo (scikit-learn#7844) n_features --> n_components [MRG + 1] DOC adding :user: role to whats_new (scikit-learn#7818) [MRG+1] label binarizer not used consistently in CalibratedClassifierCV (scikit-learn#7799) DOC : fix docstring of AIC/BIC in GMM ...
* releases: (144 commits) skip tree-test on 32bit do the warning test as we do it in other places. Replase assert_equal by assert_almost_equal in cosine test version bump 0.18.1 fix merge conflict mess in whatsnew add the python2.6 warning to 0.18.1 fix learning_curve test that I messed up in cherry-picking the "reentrant cv" PR. sync whatsnew with master [MRG] TST Ensure __dict__ is unmodified by predict, transform, etc (scikit-learn#7553) FIX scikit-learn#6420: Cloning decision tree estimators breaks criterion objects (scikit-learn#7680) Add whats new entry for scikit-learn#6282 (scikit-learn#7629) [MGR + 2] fix selectFdr bug (scikit-learn#7490) fixed whatsnew cherry-pick mess (somewhat) [MRG + 2] FIX LogisticRegressionCV to correctly handle string labels (scikit-learn#5874) [MRG + 2] Fixed parameter setting in SelectFromModel (scikit-learn#7764) [MRG+2] DOC adding separate `fit()` methods (and docstrings) for DecisionTreeClassifier and DecisionTreeRegressor (scikit-learn#7824) Fix docstring typo (scikit-learn#7844) n_features --> n_components [MRG + 1] DOC adding :user: role to whats_new (scikit-learn#7818) [MRG+1] label binarizer not used consistently in CalibratedClassifierCV (scikit-learn#7799) DOC : fix docstring of AIC/BIC in GMM ... Conflicts: removed sklearn/externals/joblib/__init__.py sklearn/externals/joblib/_parallel_backends.py sklearn/externals/joblib/testing.py
* dfsg: (144 commits) skip tree-test on 32bit do the warning test as we do it in other places. Replase assert_equal by assert_almost_equal in cosine test version bump 0.18.1 fix merge conflict mess in whatsnew add the python2.6 warning to 0.18.1 fix learning_curve test that I messed up in cherry-picking the "reentrant cv" PR. sync whatsnew with master [MRG] TST Ensure __dict__ is unmodified by predict, transform, etc (scikit-learn#7553) FIX scikit-learn#6420: Cloning decision tree estimators breaks criterion objects (scikit-learn#7680) Add whats new entry for scikit-learn#6282 (scikit-learn#7629) [MGR + 2] fix selectFdr bug (scikit-learn#7490) fixed whatsnew cherry-pick mess (somewhat) [MRG + 2] FIX LogisticRegressionCV to correctly handle string labels (scikit-learn#5874) [MRG + 2] Fixed parameter setting in SelectFromModel (scikit-learn#7764) [MRG+2] DOC adding separate `fit()` methods (and docstrings) for DecisionTreeClassifier and DecisionTreeRegressor (scikit-learn#7824) Fix docstring typo (scikit-learn#7844) n_features --> n_components [MRG + 1] DOC adding :user: role to whats_new (scikit-learn#7818) [MRG+1] label binarizer not used consistently in CalibratedClassifierCV (scikit-learn#7799) DOC : fix docstring of AIC/BIC in GMM ...
…CV (scikit-learn#7799) * label binarizer not used consistently in CalibratedClassifierCV * changed position of classes argument to make old tests run * moved parameter to constructor and added test * added test where train set doesnt have all classes * CalibratedClassifierCV can now handle cases where train set doesnt contain all labels * fixing flake error * fixing line lengths * removing np.full() * from __future__ import division for py2.7 * change is test file * added an extra test and removed a test with Ridge * stronger test * whats new entry
…CV (scikit-learn#7799) * label binarizer not used consistently in CalibratedClassifierCV * changed position of classes argument to make old tests run * moved parameter to constructor and added test * added test where train set doesnt have all classes * CalibratedClassifierCV can now handle cases where train set doesnt contain all labels * fixing flake error * fixing line lengths * removing np.full() * from __future__ import division for py2.7 * change is test file * added an extra test and removed a test with Ridge * stronger test * whats new entry
…CV (scikit-learn#7799) * label binarizer not used consistently in CalibratedClassifierCV * changed position of classes argument to make old tests run * moved parameter to constructor and added test * added test where train set doesnt have all classes * CalibratedClassifierCV can now handle cases where train set doesnt contain all labels * fixing flake error * fixing line lengths * removing np.full() * from __future__ import division for py2.7 * change is test file * added an extra test and removed a test with Ridge * stronger test * whats new entry
Reference Issue
#7796
What does this implement/fix? Explain your changes.
The
LabelBinarizer()was not used consistently inCalibratedClassifierCVIn the example provided in the issue #7796 , the when usingLeaveOneOutthe test set will have only one class, whereas the train set has two classes. Two different instances of theLabelBinarizerare used to encode the test and train set.So, I have added a new argument 'classes' in the
fit()function of_CalibratedClassifierclass (which contains the unique classes of the test set), as I didn't know of any other method to solve this issue. If someone has a better solution I can change it.