Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

[MRG] scorer: add sample_weight support (+test) #3401

Merged
merged 4 commits into from Jul 19, 2014

Conversation

Projects
None yet
6 participants
Owner

vene commented Jul 16, 2014

Wraps up #3098 (a part of #1574), ready for review.

Initial description by @ndawe:

This is part of the larger #1574 and adds support for sample weights in the scorer interface.

Owner

vene commented Jul 16, 2014

The test is simple but the change is equally simple, just passing the sample_weight parameter through.

@amueller amueller and 1 other commented on an outdated diff Jul 16, 2014

sklearn/metrics/tests/test_score_objects.py
+ sample_weight = np.ones_like(y_test)
+ sample_weight[:10] = 0
+
+ # get sensible estimators for each metric
+ sensible_regr = DummyRegressor(strategy='median')
+ sensible_regr.fit(X_train, y_train)
+ sensible_clf = DecisionTreeClassifier()
+ sensible_clf.fit(X_train, y_train)
+ estimator = dict([(name, sensible_regr)
+ for name in REGRESSION_SCORER_NAMES] +
+ [(name, sensible_clf)
+ for name in CLF_SCORER_NAMES])
+
+ for name, scorer in SCORERS.items():
+ try:
+ weighted = scorer(estimator[name], X_test, y_test,
@amueller

amueller Jul 16, 2014

Owner

I would rather have a more explicit test. For example having for the regressor [0, 0, 1, 1, 2, 2] as output and then using weights=[0, 0, 1, 1, 0, 0] and once 1-weights and check that the expected thing happens.

@vene

vene Jul 16, 2014

Owner

@amueller The difficulty is that, unlike when testing the metrics directly, for testing the scorer it's tricky to anticipate the output of the classifier / regressor.

@amueller

amueller Jul 16, 2014

Owner

not if you use the dummys ;) What is wrong with my suggestion?

@vene

vene Jul 16, 2014

Owner

The 'expected thing' would need to be manually computed for each different scorer, right?

That would be just like coming up with a different hand-crafted test for each scorer, which I could do, if you insist.

@amueller

amueller Jul 16, 2014

Owner

You are right, if you are testing all score_functions, that doesn't make
any sense.

On 07/16/2014 05:23 PM, Vlad Niculae wrote:

In sklearn/metrics/tests/test_score_objects.py:

  • sample_weight = np.ones_like(y_test)
  • sample_weight[:10] = 0
  • get sensible estimators for each metric

  • sensible_regr = DummyRegressor(strategy='median')
  • sensible_regr.fit(X_train, y_train)
  • sensible_clf = DecisionTreeClassifier()
  • sensible_clf.fit(X_train, y_train)
  • estimator = dict([(name, sensible_regr)
  •                  for name in REGRESSION_SCORER_NAMES] +
    
  •                 [(name, sensible_clf)
    
  •                  for name in CLF_SCORER_NAMES])
    
  • for name, scorer in SCORERS.items():
  •    try:
    
  •        weighted = scorer(estimator[name], X_test, y_test,
    

The 'expected thing' would need to be manually computed for each
different scorer, right?

That would be just like coming up with a different hand-crafted test
for each scorer, which I could do, if you insist.


Reply to this email directly or view it on GitHub
https://github.com/scikit-learn/scikit-learn/pull/3401/files#r15004137.

@arjoly arjoly commented on an outdated diff Jul 16, 2014

sklearn/metrics/scorer.py
Returns
-------
score : float
Score function applied to prediction of estimator on X.
"""
y_pred = clf.predict_proba(X)
+ if sample_weight is not None:
+ return self._sign * self._score_func(y, y_pred,
+ sample_weight=sample_weight,
+ **self._kwargs)
return self._sign * self._score_func(y, y_pred, **self._kwargs)
@arjoly

arjoly Jul 16, 2014

Owner

I would do

else:
    ...

@arjoly arjoly commented on an outdated diff Jul 16, 2014

sklearn/metrics/scorer.py
Returns
-------
score : float
Score function applied to prediction of estimator on X.
"""
y_pred = estimator.predict(X)
+ if sample_weight is not None:
+ return self._sign * self._score_func(y_true, y_pred,
+ sample_weight=sample_weight,
+ **self._kwargs)
return self._sign * self._score_func(y_true, y_pred, **self._kwargs)
@arjoly

arjoly Jul 16, 2014

Owner

I would do

else:
    ...

@arjoly arjoly commented on an outdated diff Jul 16, 2014

sklearn/metrics/scorer.py
@@ -152,6 +169,10 @@ def __call__(self, clf, X, y):
elif isinstance(y_pred, list):
y_pred = np.vstack([p[:, -1] for p in y_pred]).T
+ if sample_weight is not None:
+ return self._sign * self._score_func(y, y_pred,
+ sample_weight=sample_weight,
+ **self._kwargs)
return self._sign * self._score_func(y, y_pred, **self._kwargs)
@arjoly

arjoly Jul 16, 2014

Owner

I would do

else:
    ...

@arjoly arjoly commented on an outdated diff Jul 16, 2014

sklearn/metrics/tests/test_score_objects.py
@@ -229,3 +232,54 @@ def test_raises_on_score_list():
grid_search = GridSearchCV(clf, scoring=f1_scorer_no_average,
param_grid={'max_depth': [1, 2]})
assert_raises(ValueError, grid_search.fit, X, y)
+
+
+def test_scorer_sample_weight():
+ """Test that scorers support sample_weight or raise sensible errors"""
+
+ REGRESSION_SCORER_NAMES = ['r2', 'mean_absolute_error',
+ 'mean_squared_error']
+ CLF_SCORER_NAMES = ['accuracy', 'f1', 'roc_auc', 'average_precision',
+ 'precision', 'recall', 'log_loss',
+ 'adjusted_rand_score' # not really, but works
+ ]
@arjoly

arjoly Jul 16, 2014

Owner

Can you define those constants at the top of module?

@arjoly arjoly commented on an outdated diff Jul 16, 2014

sklearn/metrics/tests/test_score_objects.py
+def test_scorer_sample_weight():
+ """Test that scorers support sample_weight or raise sensible errors"""
+
+ REGRESSION_SCORER_NAMES = ['r2', 'mean_absolute_error',
+ 'mean_squared_error']
+ CLF_SCORER_NAMES = ['accuracy', 'f1', 'roc_auc', 'average_precision',
+ 'precision', 'recall', 'log_loss',
+ 'adjusted_rand_score' # not really, but works
+ ]
+
+ # Unlike the metrics invariance test, in the scorer case it's harder
+ # to ensure that, on the classifier output, weighted and unweighted
+ # scores really should be unequal.
+ X, y = make_classification(random_state=0)
+ X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
+ X_test += 10 * np.random.randn(*X_test.shape)
@arjoly

arjoly Jul 16, 2014

Owner

Can you avoid the * ?

@arjoly

arjoly Jul 16, 2014

Owner

Is this line needed?

@arjoly arjoly commented on an outdated diff Jul 16, 2014

sklearn/metrics/tests/test_score_objects.py
+ for name in REGRESSION_SCORER_NAMES] +
+ [(name, sensible_clf)
+ for name in CLF_SCORER_NAMES])
+
+ for name, scorer in SCORERS.items():
+ try:
+ weighted = scorer(estimator[name], X_test, y_test,
+ sample_weight=sample_weight)
+ ignored = scorer(estimator[name], X_test[10:], y_test[10:])
+ unweighted = scorer(estimator[name], X_test, y_test)
+ assert_not_equal(weighted, unweighted,
+ "scorer {} behaves identically when called with "
+ "sample weights: {} vs {}".format(name,
+ weighted,
+ unweighted))
+ assert_true(weighted == ignored,
@arjoly

arjoly Jul 16, 2014

Owner

assert_equal?

@arjoly arjoly commented on an outdated diff Jul 16, 2014

sklearn/metrics/tests/test_score_objects.py
+ weighted = scorer(estimator[name], X_test, y_test,
+ sample_weight=sample_weight)
+ ignored = scorer(estimator[name], X_test[10:], y_test[10:])
+ unweighted = scorer(estimator[name], X_test, y_test)
+ assert_not_equal(weighted, unweighted,
+ "scorer {} behaves identically when called with "
+ "sample weights: {} vs {}".format(name,
+ weighted,
+ unweighted))
+ assert_true(weighted == ignored,
+ "scorer {} behaves differently when ignoring "
+ "samples and setting sample_weight to 0: "
+ "{} vs {}".format(name, weighted, ignored))
+
+ except TypeError as e:
+ assert_true("sample_weight" in str(e),
@arjoly

arjoly Jul 16, 2014

Owner

there is an assert helper for this

Owner

arjoly commented Jul 16, 2014

LGTM when travis is green.

Owner

amueller commented Jul 16, 2014

lgtm

Owner

ndawe commented Jul 16, 2014

Thanks a lot for taking this over @vene! I've had no time to work on this lately.

@ndawe ndawe referenced this pull request Jul 16, 2014

Open

[WIP] sample_weight support #1574

2 of 6 tasks complete

@jnothman jnothman commented on an outdated diff Jul 17, 2014

sklearn/metrics/tests/test_score_objects.py
+ sensible_regr.fit(X_train, y_train)
+ sensible_clf = DecisionTreeClassifier()
+ sensible_clf.fit(X_train, y_train)
+ estimator = dict([(name, sensible_regr)
+ for name in REGRESSION_SCORERS] +
+ [(name, sensible_clf)
+ for name in CLF_SCORERS])
+
+ for name, scorer in SCORERS.items():
+ try:
+ weighted = scorer(estimator[name], X_test, y_test,
+ sample_weight=sample_weight)
+ ignored = scorer(estimator[name], X_test[10:], y_test[10:])
+ unweighted = scorer(estimator[name], X_test, y_test)
+ assert_not_equal(weighted, unweighted,
+ "scorer {} behaves identically when called with "
@jnothman

jnothman Jul 17, 2014

Owner

support for Py2.6 means we can't use {}. Use {0}, {1}, {2}, ...

@amueller amueller commented on the diff Jul 17, 2014

sklearn/metrics/tests/test_score_objects.py
@@ -25,6 +29,13 @@
from sklearn.multiclass import OneVsRestClassifier
+REGRESSION_SCORERS = ['r2', 'mean_absolute_error', 'mean_squared_error']
@amueller

amueller Jul 17, 2014

Owner

is that not defined somewhere else?

@vene

vene Jul 17, 2014

Owner

I couldn't find it for scorers, just for metrics.

On Thu, Jul 17, 2014 at 2:38 PM, Andreas Mueller notifications@github.com
wrote:

In sklearn/metrics/tests/test_score_objects.py:

@@ -25,6 +29,13 @@
from sklearn.multiclass import OneVsRestClassifier

+REGRESSION_SCORERS = ['r2', 'mean_absolute_error', 'mean_squared_error']

is that not defined somewhere else?


Reply to this email directly or view it on GitHub
https://github.com/scikit-learn/scikit-learn/pull/3401/files#r15054057.

Owner

arjoly commented Jul 19, 2014

Not much to do to get that merged.

Coverage Status

Coverage increased (+0.0%) when pulling 6a4aa1d on vene:scorer_weights into 4ec8630 on scikit-learn:master.

Owner

arjoly commented Jul 19, 2014

Travis is happy ! merging

@arjoly arjoly added a commit that referenced this pull request Jul 19, 2014

@arjoly arjoly Merge pull request #3401 from vene/scorer_weights
 [MRG] scorer: add sample_weight support (+test)
70f4d47

@arjoly arjoly merged commit 70f4d47 into scikit-learn:master Jul 19, 2014

1 check passed

continuous-integration/travis-ci The Travis CI build passed
Details
Owner

ndawe commented Jul 19, 2014

Thanks! 🍻

Owner

jnothman commented Jul 20, 2014

I'm glad to see this moving through. Thanks Noel and Vlad. I look forward
to the cross-validation support.

On 20 July 2014 07:47, Noel Dawe notifications@github.com wrote:

Thanks! [image: 🍻]


Reply to this email directly or view it on GitHub
#3401 (comment)
.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment