From 09f3332c0cf4ee2e011bef8fbbc70c342d04bc84 Mon Sep 17 00:00:00 2001 From: Karan Desai Date: Fri, 10 Mar 2017 02:33:08 +0530 Subject: [PATCH 1/4] ENH Add verbose option and corresponding tests for Pipeline. --- doc/modules/pipeline.rst | 13 ++++--- sklearn/pipeline.py | 68 +++++++++++++++++++++++++++------- sklearn/tests/test_pipeline.py | 62 ++++++++++++++++++++++++++++++- 3 files changed, 123 insertions(+), 20 deletions(-) diff --git a/doc/modules/pipeline.rst b/doc/modules/pipeline.rst index c90f35753fb00..c73081ae5def3 100644 --- a/doc/modules/pipeline.rst +++ b/doc/modules/pipeline.rst @@ -42,7 +42,7 @@ is an estimator object:: >>> pipe # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS Pipeline(memory=None, steps=[('reduce_dim', PCA(copy=True,...)), - ('clf', SVC(C=1.0,...))]) + ('clf', SVC(C=1.0,...))], verbose=False) The utility function :func:`make_pipeline` is a shorthand for constructing pipelines; @@ -57,7 +57,7 @@ filling in the names automatically:: steps=[('binarizer', Binarizer(copy=True, threshold=0.0)), ('multinomialnb', MultinomialNB(alpha=1.0, class_prior=None, - fit_prior=True))]) + fit_prior=True))], verbose=False) The estimators of a pipeline are stored as a list in the ``steps`` attribute:: @@ -77,7 +77,8 @@ Parameters of the estimators in the pipeline can be accessed using the >>> pipe.set_params(clf__C=10) # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS Pipeline(memory=None, steps=[('reduce_dim', PCA(copy=True, iterated_power='auto',...)), - ('clf', SVC(C=10, cache_size=200, class_weight=None,...))]) + ('clf', SVC(C=10, cache_size=200, class_weight=None,...))], + verbose=False) Attributes of named_steps map to keys, enabling tab completion in interactive environments:: @@ -153,7 +154,7 @@ object:: >>> pipe # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS Pipeline(..., steps=[('reduce_dim', PCA(copy=True,...)), - ('clf', SVC(C=1.0,...))]) + ('clf', SVC(C=1.0,...))], verbose=False) >>> # Clear the cache directory when you don't need it anymore >>> rmtree(cachedir) @@ -170,7 +171,7 @@ object:: >>> pipe.fit(digits.data, digits.target) ... # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS Pipeline(memory=None, - steps=[('reduce_dim', PCA(...)), ('clf', SVC(...))]) + steps=[('reduce_dim', PCA(...)), ('clf', SVC(...))], verbose=False) >>> # The pca instance can be inspected directly >>> print(pca1.components_) # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS [[ -1.77484909e-19 ... 4.07058917e-18]] @@ -192,7 +193,7 @@ object:: >>> cached_pipe.fit(digits.data, digits.target) ... # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS Pipeline(memory=..., - steps=[('reduce_dim', PCA(...)), ('clf', SVC(...))]) + steps=[('reduce_dim', PCA(...)), ('clf', SVC(...))], verbose=False) >>> print(cached_pipe.named_steps['reduce_dim'].components_) ... # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS [[ -1.77484909e-19 ... 4.07058917e-18]] diff --git a/sklearn/pipeline.py b/sklearn/pipeline.py index 0361e109015ff..7f8e26bc40cd0 100644 --- a/sklearn/pipeline.py +++ b/sklearn/pipeline.py @@ -11,6 +11,7 @@ from collections import defaultdict from abc import ABCMeta, abstractmethod +import time import numpy as np from scipy import sparse @@ -26,6 +27,7 @@ class _BasePipeline(six.with_metaclass(ABCMeta, BaseEstimator)): + """Handles parameter management for classifiers composed of named steps. """ @@ -84,6 +86,7 @@ def _validate_names(self, names): class Pipeline(_BasePipeline): + """Pipeline of transforms with a final estimator. Sequentially apply a list of transforms and a final estimator. @@ -119,6 +122,9 @@ class Pipeline(_BasePipeline): inspect estimators within the pipeline. Caching the transformers is advantageous when fitting is time consuming. + verbose : boolean, optional + Verbosity mode. + Attributes ---------- named_steps : bunch object, a dictionary with attribute access @@ -146,7 +152,7 @@ class Pipeline(_BasePipeline): ... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Pipeline(memory=None, steps=[('anova', SelectKBest(...)), - ('svc', SVC(...))]) + ('svc', SVC(...))], verbose=False) >>> prediction = anova_svm.predict(X) >>> anova_svm.score(X, y) # doctest: +ELLIPSIS 0.829... @@ -166,11 +172,12 @@ class Pipeline(_BasePipeline): # BaseEstimator interface - def __init__(self, steps, memory=None): + def __init__(self, steps, memory=None, verbose=False): # shallow copy of steps self.steps = tosequence(steps) self._validate_steps() self.memory = memory + self.verbose = verbose def get_params(self, deep=True): """Get parameters for this estimator. @@ -225,6 +232,14 @@ def _validate_steps(self): "'%s' (type %s) doesn't" % (estimator, type(estimator))) + def _print_final_step(self, start_time, time_elapsed_so_far): + time_elapsed = time.time() - start_time + time_elapsed_so_far += time_elapsed + print('[Pipeline] (step %d of %d) %s ... %.5fs' % + (len(self.steps), len(self.steps), self.steps[-1][0], + time_elapsed_so_far)) + print('[Pipeline] Total time elapsed: %.5fs' % time_elapsed_so_far) + @property def _estimator_type(self): return self.steps[-1][1]._estimator_type @@ -261,7 +276,10 @@ def _fit(self, X, y=None, **fit_params): step, param = pname.split('__', 1) fit_params_steps[step][param] = pval Xt = X + # Keep a record of time elapsed + time_elapsed_so_far = 0 for step_idx, (name, transformer) in enumerate(self.steps[:-1]): + step_start_time = time.time() if transformer is None: pass else: @@ -279,9 +297,16 @@ def _fit(self, X, y=None, **fit_params): # transformer. This is necessary when loading the transformer # from the cache. self.steps[step_idx] = (name, fitted_transformer) + + step_time_elapsed = time.time() - step_start_time + time_elapsed_so_far += step_time_elapsed + # Logging time elapsed for current step to stdout + if self.verbose: + print('[Pipeline] (step %d of %d) %s ... %.5fs' % + (step_idx + 1, len(self.steps), name, step_time_elapsed)) if self._final_estimator is None: - return Xt, {} - return Xt, fit_params_steps[self.steps[-1][0]] + return Xt, {}, time_elapsed_so_far + return Xt, fit_params_steps[self.steps[-1][0]], time_elapsed_so_far def fit(self, X, y=None, **fit_params): """Fit the model @@ -309,9 +334,12 @@ def fit(self, X, y=None, **fit_params): self : Pipeline This estimator """ - Xt, fit_params = self._fit(X, y, **fit_params) + Xt, fit_params, time_elapsed_so_far = self._fit(X, y, **fit_params) + final_step_start_time = time.time() if self._final_estimator is not None: self._final_estimator.fit(Xt, y, **fit_params) + if self.verbose: + self._print_final_step(final_step_start_time, time_elapsed_so_far) return self def fit_transform(self, X, y=None, **fit_params): @@ -342,13 +370,21 @@ def fit_transform(self, X, y=None, **fit_params): Transformed samples """ last_step = self._final_estimator - Xt, fit_params = self._fit(X, y, **fit_params) - if hasattr(last_step, 'fit_transform'): - return last_step.fit_transform(Xt, y, **fit_params) - elif last_step is None: + Xt, fit_params, time_elapsed_so_far = self._fit(X, y, **fit_params) + final_step_start_time = time.time() + if last_step is None: + if self.verbose: + print('[Pipeline] Step %s is NoneType.' % self.steps[-1][0]) + print('[Pipeline] Total time elapsed: %.3fs' % + time_elapsed_so_far) return Xt + elif hasattr(last_step, 'fit_transform'): + Xt = last_step.fit_transform(Xt, y, **fit_params) else: - return last_step.fit(Xt, y, **fit_params).transform(Xt) + Xt = last_step.fit(Xt, y, **fit_params).transform(Xt) + if self.verbose: + self._print_final_step(final_step_start_time, time_elapsed_so_far) + return Xt @if_delegate_has_method(delegate='_final_estimator') def predict(self, X): @@ -397,8 +433,12 @@ def fit_predict(self, X, y=None, **fit_params): ------- y_pred : array-like """ - Xt, fit_params = self._fit(X, y, **fit_params) - return self.steps[-1][-1].fit_predict(Xt, y, **fit_params) + Xt, fit_params, time_elapsed_so_far = self._fit(X, y, **fit_params) + final_step_start_time = time.time() + y_pred = self.steps[-1][-1].fit_predict(Xt, y, **fit_params) + if self.verbose: + self._print_final_step(final_step_start_time, time_elapsed_so_far) + return y_pred @if_delegate_has_method(delegate='_final_estimator') def predict_proba(self, X): @@ -598,7 +638,7 @@ def make_pipeline(*steps): Pipeline(memory=None, steps=[('standardscaler', StandardScaler(copy=True, with_mean=True, with_std=True)), - ('gaussiannb', GaussianNB(priors=None))]) + ('gaussiannb', GaussianNB(priors=None))], verbose=False) Returns ------- @@ -632,6 +672,7 @@ def _fit_transform_one(transformer, weight, X, y, class FeatureUnion(_BasePipeline, TransformerMixin): + """Concatenates results of multiple transformer objects. This estimator applies a list of transformer objects in parallel to the @@ -659,6 +700,7 @@ class FeatureUnion(_BasePipeline, TransformerMixin): Keys are transformer names, values the weights. """ + def __init__(self, transformer_list, n_jobs=1, transformer_weights=None): self.transformer_list = tosequence(transformer_list) self.n_jobs = n_jobs diff --git a/sklearn/tests/test_pipeline.py b/sklearn/tests/test_pipeline.py index d4c4844fe375d..577efd28963db 100644 --- a/sklearn/tests/test_pipeline.py +++ b/sklearn/tests/test_pipeline.py @@ -45,6 +45,7 @@ class NoFit(object): + """Small class to test parameter dispatching. """ @@ -67,11 +68,13 @@ def set_params(self, **params): class NoInvTransf(NoTrans): + def transform(self, X, y=None): return X class Transf(NoInvTransf): + def transform(self, X, y=None): return X @@ -87,6 +90,7 @@ def fit(self, X, y, **fit_params): class Mult(BaseEstimator): + def __init__(self, mult=1): self.mult = mult @@ -109,6 +113,7 @@ def score(self, X, y=None): class FitParamT(BaseEstimator): + """Mock classifier """ @@ -132,6 +137,7 @@ def score(self, X, y=None, sample_weight=None): class DummyTransf(Transf): + """Transformer which store the column means""" def fit(self, X, y): @@ -557,7 +563,7 @@ def make(): 'memory': None, 'm2__mult': 2, 'last__mult': 5, - }) + 'verbose': False}) pipeline.set_params(m2=None) exp = 5 @@ -911,3 +917,57 @@ def test_pipeline_memory(): assert_equal(ts, cached_pipe_2.named_steps['transf_2'].timestamp_) finally: shutil.rmtree(cachedir) + + +def check_pipeline_verbosity_fit_predict(pipe_method): + # Test that the verbosity of pipeline is proper + from sklearn.externals.six.moves import cStringIO as StringIO + import sys + old_stdout = sys.stdout + sys.stdout = StringIO() + pipe_method(X=None, y=None, clf__should_succeed=True) + verbose_output = sys.stdout + sys.stdout = old_stdout + + # check output + verbose_output.seek(0) + lines = verbose_output.readlines() + assert_true('[Pipeline] (step 1 of 2) transf ... ' in lines[0]) + assert_true('[Pipeline] (step 2 of 2) clf ... ' in lines[1]) + assert_true('[Pipeline] Total time elapsed: ' in lines[2]) + + +def test_pipeline_fit_verbosity(): + pipe = Pipeline([('transf', Transf()), ('clf', FitParamT())], verbose=True) + yield check_pipeline_verbosity_fit_predict, pipe.fit + yield check_pipeline_verbosity_fit_predict, pipe.fit_predict + + +def check_pipeline_verbosity_fit_transform(pipe_method, last_was_none=False): + # Test that the verbosity of pipeline is proper + from sklearn.externals.six.moves import cStringIO as StringIO + import sys + old_stdout = sys.stdout + sys.stdout = StringIO() + pipe_method(X=[[1, 2, 3], [4, 5, 6]], y=[[7], [8]]) + verbose_output = sys.stdout + sys.stdout = old_stdout + + # check output + verbose_output.seek(0) + lines = verbose_output.readlines() + assert_true('[Pipeline] (step 1 of 2) mult1 ... ' in lines[0]) + if last_was_none: + assert_true('[Pipeline] Step mult2 is NoneType.' in lines[1]) + else: + assert_true('[Pipeline] (step 2 of 2) mult2 ... ' in lines[1]) + assert_true('[Pipeline] Total time elapsed: ' in lines[2]) + + +def test_pipeline_verbosity_fit_transform(): + pipe = Pipeline([('mult1', Mult(mult=1)), ('mult2', Mult(mult=2))], + verbose=True) + yield check_pipeline_verbosity_fit_transform, pipe.fit_transform + pipe = Pipeline([('mult1', Mult(mult=1)), ('mult2', None)], + verbose=True) + yield check_pipeline_verbosity_fit_transform, pipe.fit_transform, True From a990034907b634cbe446d956b58f36ea19296551 Mon Sep 17 00:00:00 2001 From: Karan Desai Date: Fri, 10 Mar 2017 17:11:55 +0530 Subject: [PATCH 2/4] ENH Add verbose option and corresponding tests for FeatureUnion. --- doc/modules/pipeline.rst | 4 +- sklearn/pipeline.py | 68 ++++++++++++++++++++++++++-------- sklearn/tests/test_pipeline.py | 25 +++++++++++++ 3 files changed, 79 insertions(+), 18 deletions(-) diff --git a/doc/modules/pipeline.rst b/doc/modules/pipeline.rst index c73081ae5def3..abbfadbf39f2e 100644 --- a/doc/modules/pipeline.rst +++ b/doc/modules/pipeline.rst @@ -247,7 +247,7 @@ and ``value`` is an estimator object:: FeatureUnion(n_jobs=1, transformer_list=[('linear_pca', PCA(copy=True,...)), ('kernel_pca', KernelPCA(alpha=1.0,...))], - transformer_weights=None) + transformer_weights=None, verbose=False) Like pipelines, feature unions have a shorthand constructor called @@ -262,7 +262,7 @@ and ignored by setting to ``None``:: FeatureUnion(n_jobs=1, transformer_list=[('linear_pca', PCA(copy=True,...)), ('kernel_pca', None)], - transformer_weights=None) + transformer_weights=None, verbose=False) .. topic:: Examples: diff --git a/sklearn/pipeline.py b/sklearn/pipeline.py index 7f8e26bc40cd0..e3fe173a2bb6f 100644 --- a/sklearn/pipeline.py +++ b/sklearn/pipeline.py @@ -647,8 +647,16 @@ def make_pipeline(*steps): return Pipeline(_name_estimators(steps)) -def _fit_one_transformer(transformer, X, y): - return transformer.fit(X, y) +def _fit_one_transformer(trans, X, y, verbose=False, idx=None, + total_steps=None, name=None): + # idx, total_steps and name are not required when verbosity is disabled + step_start_time = time.time() + trans = trans.fit(X, y) + step_time_elapsed = time.time() - step_start_time + if verbose: + print('[FeatureUnion] (step %d of %d) %s ... %.5fs' % + (idx + 1, total_steps, name, step_time_elapsed)) + return trans def _transform_one(transformer, weight, X): @@ -659,16 +667,22 @@ def _transform_one(transformer, weight, X): return res * weight -def _fit_transform_one(transformer, weight, X, y, - **fit_params): - if hasattr(transformer, 'fit_transform'): - res = transformer.fit_transform(X, y, **fit_params) +def _fit_transform_one(trans, weight, X, y, verbose=False, idx=None, + total_steps=None, name=None, **fit_params): + # idx, total_steps and name are not required when verbosity is disabled + step_start_time = time.time() + if hasattr(trans, 'fit_transform'): + res = trans.fit_transform(X, y, **fit_params) else: - res = transformer.fit(X, y, **fit_params).transform(X) + res = trans.fit(X, y, **fit_params).transform(X) + step_time_elapsed = time.time() - step_start_time + if verbose: + print('[FeatureUnion] (step %d of %d) %s ... %.5fs' % + (idx + 1, total_steps, name, step_time_elapsed)) # if we have a weight for this transformer, multiply output if weight is None: - return res, transformer - return res * weight, transformer + return res, trans + return res * weight, trans class FeatureUnion(_BasePipeline, TransformerMixin): @@ -699,12 +713,17 @@ class FeatureUnion(_BasePipeline, TransformerMixin): Multiplicative weights for features per transformer. Keys are transformer names, values the weights. + verbose : boolean, optional + Verbosity mode. + """ - def __init__(self, transformer_list, n_jobs=1, transformer_weights=None): + def __init__(self, transformer_list, n_jobs=1, transformer_weights=None, + verbose=False): self.transformer_list = tosequence(transformer_list) self.n_jobs = n_jobs self.transformer_weights = transformer_weights + self.verbose = verbose self._validate_transformers() def get_params(self, deep=True): @@ -794,9 +813,18 @@ def fit(self, X, y=None): This estimator """ self._validate_transformers() + all_transformers = [(name, trans, weight) for name, trans, weight in + self._iter()] + total_steps = len(all_transformers) + # Keep a record of time elapsed + start_time = time.time() transformers = Parallel(n_jobs=self.n_jobs)( - delayed(_fit_one_transformer)(trans, X, y) - for _, trans, _ in self._iter()) + delayed(_fit_one_transformer)(trans, X, y, self.verbose, idx, + total_steps, name) + for idx, (name, trans, _) in enumerate(all_transformers)) + time_elapsed = time.time() - start_time + if self.verbose: + print('[FeatureUnion] Total time elapsed: %.5fs' % time_elapsed) self._update_transformer_list(transformers) return self @@ -818,10 +846,18 @@ def fit_transform(self, X, y=None, **fit_params): sum of n_components (output dimension) over transformers. """ self._validate_transformers() + all_transformers = [(name, trans, weight) for name, trans, weight in + self._iter()] + total_steps = len(all_transformers) + # Keep a record of time elapsed + start_time = time.time() result = Parallel(n_jobs=self.n_jobs)( - delayed(_fit_transform_one)(trans, weight, X, y, - **fit_params) - for name, trans, weight in self._iter()) + delayed(_fit_transform_one)(trans, weight, X, y, self.verbose, + idx, total_steps, name) + for idx, (name, trans, weight) in enumerate(all_transformers)) + time_elapsed = time.time() - start_time + if self.verbose: + print('[FeatureUnion] Total time elapsed: %.5fs' % time_elapsed) if not result: # All transformers are None @@ -900,7 +936,7 @@ def make_union(*transformers, **kwargs): TruncatedSVD(algorithm='randomized', n_components=2, n_iter=5, random_state=None, tol=0.0))], - transformer_weights=None) + transformer_weights=None, verbose=False) """ n_jobs = kwargs.pop('n_jobs', 1) if kwargs: diff --git a/sklearn/tests/test_pipeline.py b/sklearn/tests/test_pipeline.py index 577efd28963db..0d77df56c21bc 100644 --- a/sklearn/tests/test_pipeline.py +++ b/sklearn/tests/test_pipeline.py @@ -971,3 +971,28 @@ def test_pipeline_verbosity_fit_transform(): pipe = Pipeline([('mult1', Mult(mult=1)), ('mult2', None)], verbose=True) yield check_pipeline_verbosity_fit_transform, pipe.fit_transform, True + + +def check_feature_union_verbosity(feature_union_method): + # Test that the verbosity of feature union is proper + from sklearn.externals.six.moves import cStringIO as StringIO + import sys + old_stdout = sys.stdout + sys.stdout = StringIO() + feature_union_method(X=[[1, 2, 3], [4, 5, 6]], y=[[7], [8]]) + verbose_output = sys.stdout + sys.stdout = old_stdout + + # check output + verbose_output.seek(0) + lines = verbose_output.readlines() + assert_true('[FeatureUnion] (step 1 of 2) mult1 ... ' in lines[0]) + assert_true('[FeatureUnion] (step 2 of 2) mult2 ... ' in lines[1]) + assert_true('[FeatureUnion] Total time elapsed: ' in lines[2]) + + +def test_feature_union_verbosity(): + union = FeatureUnion([('mult1', Mult(mult=1)), ('mult2', Mult(mult=2))], + verbose=True) + yield check_feature_union_verbosity, union.fit + yield check_feature_union_verbosity, union.fit_transform From 49eadee6c7c48cb55e987e99cb05a9ea35091824 Mon Sep 17 00:00:00 2001 From: Karan Desai Date: Fri, 10 Mar 2017 20:14:41 +0530 Subject: [PATCH 3/4] ENH Add _pretty_print method to print from Pipeline properly. - Each line printed by Pipeline and FeatureUnion, when their verbosity mode is on, will be 70 characters long. --- sklearn/pipeline.py | 70 ++++++++++++++++++++++++---------- sklearn/tests/test_pipeline.py | 14 +++---- 2 files changed, 56 insertions(+), 28 deletions(-) diff --git a/sklearn/pipeline.py b/sklearn/pipeline.py index e3fe173a2bb6f..3dfb516fc123a 100644 --- a/sklearn/pipeline.py +++ b/sklearn/pipeline.py @@ -26,6 +26,22 @@ __all__ = ['Pipeline', 'FeatureUnion'] +def _pretty_print(step_info): + """Helper method to print the information about execution of a particular + step of Pipeline / FeatureUnion (if verbosity is enabled). It receives a + string having information about current step and prints it in such a way + that its length is 70 characters. + + Parameters + ---------- + step_info : str + String of form '[ClassName] (step x of y) step_name ... time_elapsed' + + """ + name, elapsed = step_info.split('...') + print('%s%s%s' % (name, '.' * (70 - len(name + elapsed)), elapsed)) + + class _BasePipeline(six.with_metaclass(ABCMeta, BaseEstimator)): """Handles parameter management for classifiers composed of named steps. @@ -232,13 +248,12 @@ def _validate_steps(self): "'%s' (type %s) doesn't" % (estimator, type(estimator))) - def _print_final_step(self, start_time, time_elapsed_so_far): - time_elapsed = time.time() - start_time - time_elapsed_so_far += time_elapsed - print('[Pipeline] (step %d of %d) %s ... %.5fs' % - (len(self.steps), len(self.steps), self.steps[-1][0], - time_elapsed_so_far)) - print('[Pipeline] Total time elapsed: %.5fs' % time_elapsed_so_far) + def _print_final_step(self, final_step_time_elapsed, time_elapsed_so_far): + _pretty_print('[Pipeline] (step %d of %d) %s ... %.5fs' % + (len(self.steps), len(self.steps), self.steps[-1][0], + final_step_time_elapsed)) + _pretty_print('[Pipeline] Total time elapsed: ... %.5fs' % + time_elapsed_so_far) @property def _estimator_type(self): @@ -302,8 +317,9 @@ def _fit(self, X, y=None, **fit_params): time_elapsed_so_far += step_time_elapsed # Logging time elapsed for current step to stdout if self.verbose: - print('[Pipeline] (step %d of %d) %s ... %.5fs' % - (step_idx + 1, len(self.steps), name, step_time_elapsed)) + _pretty_print('[Pipeline] (step %d of %d) %s ... %.5fs' % + (step_idx + 1, len(self.steps), name, + step_time_elapsed)) if self._final_estimator is None: return Xt, {}, time_elapsed_so_far return Xt, fit_params_steps[self.steps[-1][0]], time_elapsed_so_far @@ -338,8 +354,11 @@ def fit(self, X, y=None, **fit_params): final_step_start_time = time.time() if self._final_estimator is not None: self._final_estimator.fit(Xt, y, **fit_params) + final_step_time_elapsed = time.time() - final_step_start_time + time_elapsed_so_far += final_step_time_elapsed if self.verbose: - self._print_final_step(final_step_start_time, time_elapsed_so_far) + self._print_final_step(final_step_time_elapsed, + time_elapsed_so_far) return self def fit_transform(self, X, y=None, **fit_params): @@ -374,16 +393,20 @@ def fit_transform(self, X, y=None, **fit_params): final_step_start_time = time.time() if last_step is None: if self.verbose: - print('[Pipeline] Step %s is NoneType.' % self.steps[-1][0]) - print('[Pipeline] Total time elapsed: %.3fs' % - time_elapsed_so_far) + _pretty_print('[Pipeline] Step %s is NoneType ...' % + self.steps[-1][0]) + _pretty_print('[Pipeline] Total time elapsed: ... %.5fs' % + time_elapsed_so_far) return Xt elif hasattr(last_step, 'fit_transform'): Xt = last_step.fit_transform(Xt, y, **fit_params) else: Xt = last_step.fit(Xt, y, **fit_params).transform(Xt) + final_step_time_elapsed = time.time() - final_step_start_time + time_elapsed_so_far += final_step_time_elapsed if self.verbose: - self._print_final_step(final_step_start_time, time_elapsed_so_far) + self._print_final_step(final_step_time_elapsed, + time_elapsed_so_far) return Xt @if_delegate_has_method(delegate='_final_estimator') @@ -436,8 +459,11 @@ def fit_predict(self, X, y=None, **fit_params): Xt, fit_params, time_elapsed_so_far = self._fit(X, y, **fit_params) final_step_start_time = time.time() y_pred = self.steps[-1][-1].fit_predict(Xt, y, **fit_params) + final_step_time_elapsed = time.time() - final_step_start_time + time_elapsed_so_far += final_step_time_elapsed if self.verbose: - self._print_final_step(final_step_start_time, time_elapsed_so_far) + self._print_final_step(final_step_time_elapsed, + time_elapsed_so_far) return y_pred @if_delegate_has_method(delegate='_final_estimator') @@ -654,8 +680,8 @@ def _fit_one_transformer(trans, X, y, verbose=False, idx=None, trans = trans.fit(X, y) step_time_elapsed = time.time() - step_start_time if verbose: - print('[FeatureUnion] (step %d of %d) %s ... %.5fs' % - (idx + 1, total_steps, name, step_time_elapsed)) + _pretty_print('[FeatureUnion] (step %d of %d) %s ... %.5fs' % + (idx + 1, total_steps, name, step_time_elapsed)) return trans @@ -677,8 +703,8 @@ def _fit_transform_one(trans, weight, X, y, verbose=False, idx=None, res = trans.fit(X, y, **fit_params).transform(X) step_time_elapsed = time.time() - step_start_time if verbose: - print('[FeatureUnion] (step %d of %d) %s ... %.5fs' % - (idx + 1, total_steps, name, step_time_elapsed)) + _pretty_print('[FeatureUnion] (step %d of %d) %s ... %.5fs' % + (idx + 1, total_steps, name, step_time_elapsed)) # if we have a weight for this transformer, multiply output if weight is None: return res, trans @@ -824,7 +850,8 @@ def fit(self, X, y=None): for idx, (name, trans, _) in enumerate(all_transformers)) time_elapsed = time.time() - start_time if self.verbose: - print('[FeatureUnion] Total time elapsed: %.5fs' % time_elapsed) + _pretty_print( + '[FeatureUnion] Total time elapsed: ... %.5fs' % time_elapsed) self._update_transformer_list(transformers) return self @@ -857,7 +884,8 @@ def fit_transform(self, X, y=None, **fit_params): for idx, (name, trans, weight) in enumerate(all_transformers)) time_elapsed = time.time() - start_time if self.verbose: - print('[FeatureUnion] Total time elapsed: %.5fs' % time_elapsed) + _pretty_print( + '[FeatureUnion] Total time elapsed: ... %.5fs' % time_elapsed) if not result: # All transformers are None diff --git a/sklearn/tests/test_pipeline.py b/sklearn/tests/test_pipeline.py index 0d77df56c21bc..891419d04de76 100644 --- a/sklearn/tests/test_pipeline.py +++ b/sklearn/tests/test_pipeline.py @@ -932,8 +932,8 @@ def check_pipeline_verbosity_fit_predict(pipe_method): # check output verbose_output.seek(0) lines = verbose_output.readlines() - assert_true('[Pipeline] (step 1 of 2) transf ... ' in lines[0]) - assert_true('[Pipeline] (step 2 of 2) clf ... ' in lines[1]) + assert_true('[Pipeline] (step 1 of 2) transf ...' in lines[0]) + assert_true('[Pipeline] (step 2 of 2) clf ...' in lines[1]) assert_true('[Pipeline] Total time elapsed: ' in lines[2]) @@ -956,11 +956,11 @@ def check_pipeline_verbosity_fit_transform(pipe_method, last_was_none=False): # check output verbose_output.seek(0) lines = verbose_output.readlines() - assert_true('[Pipeline] (step 1 of 2) mult1 ... ' in lines[0]) + assert_true('[Pipeline] (step 1 of 2) mult1 ...' in lines[0]) if last_was_none: - assert_true('[Pipeline] Step mult2 is NoneType.' in lines[1]) + assert_true('[Pipeline] Step mult2 is NoneType ...' in lines[1]) else: - assert_true('[Pipeline] (step 2 of 2) mult2 ... ' in lines[1]) + assert_true('[Pipeline] (step 2 of 2) mult2 ...' in lines[1]) assert_true('[Pipeline] Total time elapsed: ' in lines[2]) @@ -986,8 +986,8 @@ def check_feature_union_verbosity(feature_union_method): # check output verbose_output.seek(0) lines = verbose_output.readlines() - assert_true('[FeatureUnion] (step 1 of 2) mult1 ... ' in lines[0]) - assert_true('[FeatureUnion] (step 2 of 2) mult2 ... ' in lines[1]) + assert_true('[FeatureUnion] (step 1 of 2) mult1 ...' in lines[0]) + assert_true('[FeatureUnion] (step 2 of 2) mult2 ...' in lines[1]) assert_true('[FeatureUnion] Total time elapsed: ' in lines[2]) From a3abe8d9e2fc3bbaf89822a1bca71240b2c1386d Mon Sep 17 00:00:00 2001 From: Karan Desai Date: Fri, 10 Mar 2017 20:18:24 +0530 Subject: [PATCH 4/4] Add a changelog entry about verbosity of Pipeline. --- doc/whats_new.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/whats_new.rst b/doc/whats_new.rst index ce72f193ed8dd..15e57938a6ae1 100644 --- a/doc/whats_new.rst +++ b/doc/whats_new.rst @@ -60,6 +60,10 @@ New features Enhancements ............ + - Added optional parameter ``verbose`` in :class:`pipeline.Pipeline` and + :class:`pipeline.FeatureUnion` for showing progress and timing of each + step. :issue:`8568` by :user:`Karan Desai `. + - Update Sphinx-Gallery from 0.1.4 to 0.1.7 for resolving links in documentation build with Sphinx>1.5 :issue:`8010`, :issue:`7986` :user:`Oscar Najera ` @@ -5059,4 +5063,6 @@ David Huard, Dave Morrill, Ed Schofield, Travis Oliphant, Pearu Peterson. .. _Anish Shah: https://github.com/AnishShah .. _Neeraj Gangwar: http://neerajgangwar.in -.. _Arthur Mensch: https://amensch.fr \ No newline at end of file +.. _Arthur Mensch: https://amensch.fr + +.. _Karan Desai: https://www.github.com/karandesai-96