Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[MRG] Add verbose option to VotingClassifier - #10974

Closed
Framartin wants to merge 5 commits into
scikit-learn:masterfrom
Framartin:verbose-voting-classifier
Closed

[MRG] Add verbose option to VotingClassifier#10974
Framartin wants to merge 5 commits into
scikit-learn:masterfrom
Framartin:verbose-voting-classifier

Conversation

@Framartin

@Framartin Framartin commented Apr 14, 2018

Copy link
Copy Markdown
Contributor

Reference Issues/PRs

Fixes #10360
Closes #10367 #10365

What does this implement/fix? Explain your changes.

  • Add a boolean verbose option to VotingClassifier. If True, 100 is passed to the verbose option of Parallel in order to print all progress messages to stdout.
  • Update the docstring of VotingClassifier.
  • Add a unit test to check stdout and stderr with the default value of the verbose option, False, and True.

Any other comments?

I used the capfd pytest fixture in my unit test. Is it ok? Is there a particular reason for not using it in other verbose tests (found with git grep test_.*verbose)? Because it seems quite convenient.

@Framartin Framartin changed the title Add verbose option to VotingClassifier [MRG] Add verbose option to VotingClassifier Apr 14, 2018
Comment thread sklearn/ensemble/voting_classifier.py Outdated
verbose: int, optional
The verbosity level: if non zero, progress messages are printed.
Above 50, the output is sent to stdout. The frequency of the messages
increases with the verbosity level. If it more than 10, all iterations

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"iterations" does not really apply here.

How many estimators does one need to have in a VotingClassifier for anything lower than 10 to be helpful here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! Thanks a lot for your review.

I replace "iterations" by "fit calls" in fcbfbcd.

I would say that a value lower than 10 is useful when:

  • the number of estimator is big (let say 500)
  • each estimator is pretty quick to fit (let say 1 second)
  • n_jobs is strictly greater than 1

I'm thinking of a situation similar to this call:

from time import sleep
from sklearn.externals.joblib import Parallel, delayed
r = Parallel(n_jobs=2, verbose=5)(delayed(sleep)(1.) for _ in range(500))

In this case, the user may want to have feedback to know that (s)he has 4.16 minutes to get a coffee (500 / 2 * 1 / 60), but without being spammed by 500 lines. Does it make sense?

@jnothman

jnothman commented Apr 15, 2018 via email

Copy link
Copy Markdown
Member

@Framartin

Copy link
Copy Markdown
Contributor Author

I don't think it is, except maybe for some isolated cases.

Do you suggest that the verbose option should only take 3 values?

  • False: no verbose
  • 'stderr': 49 is passed to the verbose option of Parallel to print all fit calls to stderr
  • 'stdout': 51 is passed to the verbose option of Parallel to print all fit calls to stdout

@Framartin

Copy link
Copy Markdown
Contributor Author

@jnothman I think you're right. In a second thought, I suggest another solution, implemented by 3fa6b91: a boolean value should be passed to verbose.

  • If False (default), nothing is printed.
  • If True, all progress messages are printed to stdout, by passing 100 to the verbose option of Parallel.

I think it's simpler and most suitable to the use of VotingClassifier. Are you ok with that?

@jnothman

jnothman commented Apr 22, 2018 via email

Copy link
Copy Markdown
Member

@jnothman jnothman left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please provide a sample output to remind me what Parallel's verbose output looks like? Thanks.

@jnothman

Copy link
Copy Markdown
Member

Please add an entry to the change log at doc/whats_new/v0.20.rst. Like the other entries there, please reference this pull request with :issue: and credit yourself (and other contributors if applicable) with :user:

@Framartin

Copy link
Copy Markdown
Contributor Author

Thanks a lot for your review. I have added this change to doc/whats_new/v0.20.rst by 6c8bf0b.

Could you please provide a sample output to remind me what Parallel's verbose output looks like? Thanks.

Sure. Please see the snippet below:

>>> import numpy as np
>>> from sklearn.ensemble import VotingClassifier
>>> from sklearn.linear_model import LogisticRegression
>>> from sklearn.ensemble import RandomForestClassifier
>>> from sklearn.naive_bayes import GaussianNB
>>> clf1 = LogisticRegression(random_state=123)
>>> clf2 = RandomForestClassifier(random_state=123)
>>> clf3 = GaussianNB()
>>> X = np.array([[-1.1, -1.5], [-1.2, -1.4], [-3.4, -2.2], [1.1, 1.2]])
>>> y = np.array([1, 1, 2, 2])
>>> VotingClassifier(estimators=[
...     ('lr', clf1), ('rf', clf2), ('gnb', clf3)],
...     voting='soft', verbose=True).fit(X, y)
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.0s remaining:    0.0s
[Parallel(n_jobs=1)]: Done   2 out of   2 | elapsed:    0.0s remaining:    0.0s
[Parallel(n_jobs=1)]: Done   3 out of   3 | elapsed:    0.0s remaining:    0.0s
[Parallel(n_jobs=1)]: Done   3 out of   3 | elapsed:    0.0s finished

@jnothman

jnothman commented Apr 23, 2018 via email

Copy link
Copy Markdown
Member

the sums of the predicted probabilities, which is recommended for
an ensemble of well-calibrated classifiers.

verbose : bool, optional (default=False)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use an int here? I find it weird to remove a level of control here.

@Framartin Framartin Apr 30, 2018

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jnothman was suggesting in previous comments (see 1 and 2) that VotingClassifier isn't used for a huge number of estimators. And I think that the int level of control is useless if the number of estimators is smaller than, let's say, 50. But it's really helpful for 500 classifiers. Do you think that it is used for large amounts of classifiers? And that we should keep it as int?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the number of estimators has to do with having an int vs a bool. An int allows us to change the reporting level and add more details at higher levels at any point.

@Framartin Framartin Apr 30, 2018

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For few estimators, if the int is only passed to the verbose option of Parallel, the difference will be minor between having bool or int. For example, see the difference between the verbose options in the following snippet:

>>> from time import sleep
>>> from sklearn.externals.joblib import Parallel, delayed
>>> r = Parallel(n_jobs=2, verbose=1)(delayed(sleep)(1.) for _ in range(10))
[Parallel(n_jobs=2)]: Done  10 out of  10 | elapsed:    5.0s finished
>>> r = Parallel(n_jobs=2, verbose=5)(delayed(sleep)(1.) for _ in range(10))
[Parallel(n_jobs=2)]: Done  10 out of  10 | elapsed:    5.0s remaining:    0.0s
[Parallel(n_jobs=2)]: Done  10 out of  10 | elapsed:    5.0s finished
>>> r = Parallel(n_jobs=2, verbose=10)(delayed(sleep)(1.) for _ in range(10))
[Parallel(n_jobs=2)]: Done   1 tasks      | elapsed:    1.0s
[Parallel(n_jobs=2)]: Done   4 tasks      | elapsed:    2.0s
[Parallel(n_jobs=2)]: Done  10 out of  10 | elapsed:    5.0s finished

# value used in the MR if the boolean value is True:
>>> r = Parallel(n_jobs=2, verbose=100)(delayed(sleep)(1.) for _ in range(10))
[Parallel(n_jobs=2)]: Done   1 tasks      | elapsed:    1.0s
[Parallel(n_jobs=2)]: Done   2 tasks      | elapsed:    1.0s
[Parallel(n_jobs=2)]: Done   3 tasks      | elapsed:    2.0s
[Parallel(n_jobs=2)]: Done   4 tasks      | elapsed:    2.0s
[Parallel(n_jobs=2)]: Done   5 tasks      | elapsed:    3.0s
[Parallel(n_jobs=2)]: Done   6 tasks      | elapsed:    3.0s
[Parallel(n_jobs=2)]: Done   7 tasks      | elapsed:    4.0s
[Parallel(n_jobs=2)]: Done   8 out of  10 | elapsed:    4.0s remaining:    1.0s
[Parallel(n_jobs=2)]: Done  10 out of  10 | elapsed:    5.0s remaining:    0.0s
[Parallel(n_jobs=2)]: Done  10 out of  10 | elapsed:    5.0s finished

If there is a high number of classifiers, having higher control is interesting to have the first output (few messages) instead of the second one (which spams stdout):

>>> r = Parallel(n_jobs=2, verbose=5)(delayed(sleep)(.3) for _ in range(500))
[Parallel(n_jobs=2)]: Done  14 tasks      | elapsed:    2.1s
[Parallel(n_jobs=2)]: Done  68 tasks      | elapsed:   10.2s
[Parallel(n_jobs=2)]: Done 158 tasks      | elapsed:   23.7s
[Parallel(n_jobs=2)]: Done 284 tasks      | elapsed:   42.7s
[Parallel(n_jobs=2)]: Done 446 tasks      | elapsed:  1.1min
[Parallel(n_jobs=2)]: Done 500 out of 500 | elapsed:  1.3min finished

>>> r = Parallel(n_jobs=2, verbose=100)(delayed(sleep)(.3) for _ in range(500))
[Parallel(n_jobs=2)]: Done   1 tasks      | elapsed:    0.3s
[Parallel(n_jobs=2)]: Done   2 tasks      | elapsed:    0.3s
[Parallel(n_jobs=2)]: Done   3 tasks      | elapsed:    0.6s
[Parallel(n_jobs=2)]: Done   4 tasks      | elapsed:    0.6s
[Parallel(n_jobs=2)]: Done   5 tasks      | elapsed:    0.9s
[Parallel(n_jobs=2)]: Done   6 tasks      | elapsed:    0.9s
[Parallel(n_jobs=2)]: Done   7 tasks      | elapsed:    1.2s
[Parallel(n_jobs=2)]: Done   8 tasks      | elapsed:    1.2s
[Parallel(n_jobs=2)]: Done   9 tasks      | elapsed:    1.5s
[Parallel(n_jobs=2)]: Done  10 tasks      | elapsed:    1.5s
[Parallel(n_jobs=2)]: Done  11 tasks      | elapsed:    1.8s
[...]
[Parallel(n_jobs=2)]: Done 495 tasks      | elapsed:  1.2min
[Parallel(n_jobs=2)]: Done 496 tasks      | elapsed:  1.2min
[Parallel(n_jobs=2)]: Done 497 tasks      | elapsed:  1.2min
[Parallel(n_jobs=2)]: Done 500 out of 500 | elapsed:  1.3min finished

Do you suggest that having an int option is better in order to control the verbosity level somewhere else than in the Parallel call?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. the estimator itself can have different verbosity levels

@amueller

amueller commented Aug 7, 2019

Copy link
Copy Markdown
Member

are you still interested in finishing this up?

@jnothman

jnothman commented Jan 9, 2020

Copy link
Copy Markdown
Member

Closed by #16069

@jnothman jnothman closed this Jan 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Verbose VotingClassifier

3 participants