-
-
Notifications
You must be signed in to change notification settings - Fork 25.8k
[WIP] Multiple-metric grid search #2759
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
Closed
Closed
Changes from all commits
Commits
Show all changes
57 commits
Select commit
Hold shift + click to select a range
b217697
Refactor cv code
AlexanderFabisch c4d6278
Clean up
AlexanderFabisch 1599952
Refactor RFE and add _check_scorable
AlexanderFabisch 5e52031
FIX typo in docstring
AlexanderFabisch 4b5f468
Merge `fit_grid_point` into `_cross_val_score`
AlexanderFabisch 38081fd
Return time
AlexanderFabisch 30c86ea
Move set_params back to fit_grid_point
AlexanderFabisch 389ed8d
Log score and time in 'cross_val_score'
AlexanderFabisch 1fa3ec3
check_scorable returns scorer
AlexanderFabisch 5b8933d
Clean up
AlexanderFabisch 70aaef2
Replace '_fit_estimator' by '_cross_val_score'
AlexanderFabisch 13c7915
Fix PEP8, style and documentation
AlexanderFabisch 7b951d8
Remove wrong variable names
AlexanderFabisch 5b211cd
Remove helper function '_fit'
AlexanderFabisch 365368e
Merge branch 'refactor_cv' of https://github.com/AlexanderFabisch/sci…
mblondel 13bc90e
Add evaluate_scorers function.
mblondel 4b2cd18
Add more tests for evaluate_scorers.
mblondel 91ff498
Support ranking by regression.
mblondel 4a934f0
Support SVC.
mblondel 314497a
Handle multi-label case.
mblondel 754c72d
Test ranking with more than two relevance levels.
mblondel 79656d5
Merge branch 'multiple_grid_search' of https://github.com/mblondel/sc…
mblondel f6a44a0
Merge branch 'master' into multiple_grid_search
mblondel 7f4d7ad
Rename evaluate_scorers to _evaluate_scorers.
mblondel a756083
Remove _score utility function.
mblondel b4255d8
Support for multiple scorers in cross_val_score.
mblondel 264013f
Refactoring for allowing mutiple scorers.
mblondel 0feed96
Define `parameters` upfront.
mblondel 0a66748
Use more informative name.
mblondel 6f68bfb
Put __repr__ back.
mblondel 114bec6
Deprecate fit_grid_point.
mblondel aff769d
Add grid_search_cv.
mblondel 55f4126
Refactor code.
mblondel 4bd6c91
Add randomized_search_cv.
mblondel b02a7e8
Remove multi-output multiclass support from scorers for now.
mblondel 47dd41c
Update docstrings.
mblondel 75a8762
Merge branch 'master' into multiple_grid_search
mblondel c4905c3
Support multiple metrics directly in GridSearchCV and
mblondel 40f6ef7
Merge branch 'master' into multiple_grid_search
mblondel aad77c8
Simplify inner loop.
mblondel a7e79f3
Fix incorrect comment.
mblondel 8040432
Fix comments.
mblondel 2a96384
Return training time only.
mblondel 5933d98
Remove return_parameters.
mblondel 4ee0a8e
Cosmit: used += instead of extend.
mblondel c08bdd8
Add cross_val_report.
mblondel e0dfe23
Remove score_func from cross_val_report.
mblondel 9b5fe9a
Accept tuples too.
mblondel 0346fa3
Accept callables in _evaluate_scorers.
mblondel 015e01e
Unused imports.
mblondel eaa3aeb
Clone early.
mblondel 5d8570b
Merge branch 'master' into multiple_grid_search
mblondel 96c36c7
Multiple scorer support in validation_curve.
mblondel d4ffc1f
Add rudimentary validation with contours example.
mblondel c33f0f9
Support param_grid in validation_curve.
mblondel 34ba906
Return training times.
mblondel 7317c31
Remove cross_val_report.
mblondel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import matplotlib.pyplot as plt | ||
import numpy as np | ||
from sklearn.datasets import load_digits | ||
from sklearn.svm import SVC | ||
from sklearn.learning_curve import validation_curve | ||
from sklearn.externals.joblib import Memory | ||
|
||
memory = Memory(cachedir=".", verbose=0) | ||
|
||
@memory.cache | ||
def grid(X, y, Cs, gammas): | ||
param_grid = {"C": Cs, "gamma": gammas} | ||
|
||
tr, te, times = validation_curve(SVC(kernel="rbf"), X, y, param_grid, cv=3) | ||
|
||
shape = (len(Cs), len(gammas)) | ||
tr = tr.mean(axis=1).reshape(shape) | ||
te = te.mean(axis=1).reshape(shape) | ||
times = times.mean(axis=1).reshape(shape) | ||
|
||
return tr, te, times | ||
|
||
digits = load_digits() | ||
X, y = digits.data, digits.target | ||
|
||
gammas = np.logspace(-6, -1, 5) | ||
Cs = np.logspace(-3, 3, 5) | ||
|
||
tr, te, times = grid(X, y, Cs, gammas) | ||
|
||
|
||
for title, values in (("Training accuracy", tr), | ||
("Test accuracy", te), | ||
("Training time", times)): | ||
|
||
plt.figure() | ||
|
||
plt.title(title) | ||
plt.xlabel("C") | ||
plt.xscale("log") | ||
|
||
plt.ylabel("gamma") | ||
plt.yscale("log") | ||
|
||
X1, X2 = np.meshgrid(Cs, gammas) | ||
cs = plt.contour(X1, X2, values) | ||
|
||
plt.colorbar(cs) | ||
|
||
plt.show() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,7 +27,8 @@ | |
from .utils.fixes import unique | ||
from .externals.joblib import Parallel, delayed, logger | ||
from .externals.six import with_metaclass | ||
from .metrics.scorer import check_scoring | ||
from .metrics.scorer import check_scoring, _evaluate_scorers | ||
|
||
|
||
__all__ = ['Bootstrap', | ||
'KFold', | ||
|
@@ -1041,7 +1042,7 @@ def __len__(self): | |
def cross_val_score(estimator, X, y=None, scoring=None, cv=None, n_jobs=1, | ||
verbose=0, fit_params=None, score_func=None, | ||
pre_dispatch='2*n_jobs'): | ||
"""Evaluate a score by cross-validation | ||
"""Evaluate test score by cross-validation | ||
|
||
Parameters | ||
---------- | ||
|
@@ -1055,10 +1056,12 @@ def cross_val_score(estimator, X, y=None, scoring=None, cv=None, n_jobs=1, | |
The target variable to try to predict in the case of | ||
supervised learning. | ||
|
||
scoring : string, callable or None, optional, default: None | ||
scoring : string, callable, list of strings/callables or None, optional, | ||
default: None | ||
A string (see model evaluation documentation) or | ||
a scorer callable object / function with signature | ||
``scorer(estimator, X, y)``. | ||
Lists can be used for randomized search of multiple metrics. | ||
|
||
cv : cross-validation generator, optional, default: None | ||
A cross-validation generator. If None, a 3-fold cross | ||
|
@@ -1094,78 +1097,57 @@ def cross_val_score(estimator, X, y=None, scoring=None, cv=None, n_jobs=1, | |
|
||
Returns | ||
------- | ||
scores : array of float, shape=(len(list(cv)),) | ||
scores : array of float, shape=(n_folds,) or (n_scoring, n_folds) | ||
Array of scores of the estimator for each run of the cross validation. | ||
The returned array is 2d if `scoring` is a list. | ||
""" | ||
X, y = check_arrays(X, y, sparse_format='csr', allow_lists=True) | ||
cv = _check_cv(cv, X, y, classifier=is_classifier(estimator)) | ||
scorer = check_scoring(estimator, score_func=score_func, scoring=scoring) | ||
# We clone the estimator to make sure that all the folds are | ||
# independent, and that it is pickle-able. | ||
parallel = Parallel(n_jobs=n_jobs, verbose=verbose, | ||
pre_dispatch=pre_dispatch) | ||
scores = parallel(delayed(_fit_and_score)(clone(estimator), X, y, scorer, | ||
train, test, verbose, None, | ||
fit_params) | ||
for train, test in cv) | ||
return np.array(scores)[:, 0] | ||
|
||
|
||
def _fit_and_score(estimator, X, y, scorer, train, test, verbose, parameters, | ||
fit_params, return_train_score=False, | ||
return_parameters=False): | ||
"""Fit estimator and compute scores for a given dataset split. | ||
|
||
Parameters | ||
---------- | ||
estimator : estimator object implementing 'fit' | ||
The object to use to fit the data. | ||
|
||
X : array-like of shape at least 2D | ||
The data to fit. | ||
|
||
y : array-like, optional, default: None | ||
The target variable to try to predict in the case of | ||
supervised learning. | ||
|
||
scoring : callable | ||
A scorer callable object / function with signature | ||
``scorer(estimator, X, y)``. | ||
|
||
train : array-like, shape = (n_train_samples,) | ||
Indices of training samples. | ||
if isinstance(scoring, (tuple, list)): | ||
scorers = [check_scoring(estimator, scoring=s) for s in scoring] | ||
ret_1d = False | ||
else: | ||
scorers = [check_scoring(estimator, score_func=score_func, | ||
scoring=scoring)] | ||
ret_1d = True | ||
|
||
test : array-like, shape = (n_test_samples,) | ||
Indices of test samples. | ||
parallel = Parallel(n_jobs=n_jobs, verbose=verbose, | ||
pre_dispatch=pre_dispatch) | ||
|
||
verbose : integer | ||
The verbosity level. | ||
# `out` is a list of size n_folds. Each element of the list is a tuple | ||
# (test_scores, n_test, train_time) | ||
out = parallel(delayed(_fit_and_score)(clone(estimator), X, y, scorers, | ||
train, test, verbose, None, | ||
fit_params) | ||
for train, test in cv) | ||
|
||
parameters : dict or None | ||
Parameters to be set on the estimator. | ||
# Retrieve n_scorers x n_folds 2d-array. | ||
test_scores = np.array([o[0] for o in out]).T | ||
|
||
fit_params : dict or None | ||
Parameters that will be passed to ``estimator.fit``. | ||
if ret_1d: | ||
return test_scores[0] | ||
else: | ||
return test_scores | ||
|
||
return_train_score : boolean, optional, default: False | ||
Compute and return score on training set. | ||
|
||
return_parameters : boolean, optional, default: False | ||
Return parameters that has been used for the estimator. | ||
def _fit_and_score(estimator, X, y, scorers, train, test, verbose, parameters, | ||
fit_params, return_train_scores=False): | ||
"""Fit estimator and compute scores for a given dataset split. | ||
|
||
Returns | ||
------- | ||
test_score : float | ||
Score on test set. | ||
train_score : array of floats, optional | ||
Scores on training set. | ||
|
||
train_score : float, optional | ||
Score on training set. | ||
test_score : array of floats | ||
Scores on test set. | ||
|
||
n_test_samples : int | ||
Number of test samples. | ||
|
||
scoring_time : float | ||
Time spent for fitting and scoring in seconds. | ||
train_time : float | ||
Time spent for fitting in seconds. | ||
|
||
parameters : dict or None, optional | ||
The parameters that have been evaluated. | ||
|
@@ -1188,30 +1170,35 @@ def _fit_and_score(estimator, X, y, scorer, train, test, verbose, parameters, | |
if parameters is not None: | ||
estimator.set_params(**parameters) | ||
|
||
start_time = time.time() | ||
|
||
X_train, y_train = _safe_split(estimator, X, y, train) | ||
X_test, y_test = _safe_split(estimator, X, y, test, train) | ||
|
||
start_time = time.time() | ||
|
||
if y_train is None: | ||
estimator.fit(X_train, **fit_params) | ||
else: | ||
estimator.fit(X_train, y_train, **fit_params) | ||
test_score = _score(estimator, X_test, y_test, scorer) | ||
if return_train_score: | ||
train_score = _score(estimator, X_train, y_train, scorer) | ||
|
||
scoring_time = time.time() - start_time | ||
train_time = time.time() - start_time | ||
|
||
test_scores = _evaluate_scorers(estimator, X_test, y_test, scorers) | ||
|
||
if return_train_scores: | ||
if len(scorers) == 1: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is the benefit of this if? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the |
||
train_scores = np.array([scorers[0](estimator, X_train, y_train)]) | ||
else: | ||
train_scores = _evaluate_scorers(estimator, X_train, y_train, | ||
scorers) | ||
|
||
if verbose > 2: | ||
msg += ", score=%f" % test_score | ||
msg += ", score=%s" % test_scores | ||
if verbose > 1: | ||
end_msg = "%s -%s" % (msg, logger.short_format_time(scoring_time)) | ||
end_msg = "%s -%s" % (msg, logger.short_format_time(train_time)) | ||
print("[CV] %s %s" % ((64 - len(end_msg)) * '.', end_msg)) | ||
|
||
ret = [train_score] if return_train_score else [] | ||
ret.extend([test_score, _num_samples(X_test), scoring_time]) | ||
if return_parameters: | ||
ret.append(parameters) | ||
ret = [train_scores] if return_train_scores else [] | ||
ret += [test_scores, _num_samples(X_test), train_time, parameters] | ||
return ret | ||
|
||
|
||
|
@@ -1247,18 +1234,6 @@ def _safe_split(estimator, X, y, indices, train_indices=None): | |
return X_subset, y_subset | ||
|
||
|
||
def _score(estimator, X_test, y_test, scorer): | ||
"""Compute the score of an estimator on a given test set.""" | ||
if y_test is None: | ||
score = scorer(estimator, X_test) | ||
else: | ||
score = scorer(estimator, X_test, y_test) | ||
if not isinstance(score, numbers.Number): | ||
raise ValueError("scoring must return a number, got %s (%s) instead." | ||
% (str(score), type(score))) | ||
return score | ||
|
||
|
||
def _permutation_test_score(estimator, X, y, cv, scorer): | ||
"""Auxiliary function for permutation_test_score""" | ||
avg_score = [] | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 we should use
time.clock()
here.http://stackoverflow.com/questions/85451/python-time-clock-vs-time-time-accuracy