From 578de9f762e5196812fe8d8d2baf6b579d147670 Mon Sep 17 00:00:00 2001 From: Jiaming Yuan Date: Fri, 8 Oct 2021 12:28:38 +0800 Subject: [PATCH] Fix cv `verbose_eval` (#7291) --- python-package/xgboost/training.py | 12 +++--- tests/python/test_callback.py | 59 ++++++++++++++++++++---------- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/python-package/xgboost/training.py b/python-package/xgboost/training.py index 82b05171f0c7..c7fd14e435cf 100644 --- a/python-package/xgboost/training.py +++ b/python-package/xgboost/training.py @@ -472,13 +472,15 @@ def cv(params, dtrain, num_boost_round=10, nfold=3, stratified=False, folds=None if is_new_callback: assert all(isinstance(c, callback.TrainingCallback) for c in callbacks), "You can't mix new and old callback styles." - if isinstance(verbose_eval, bool) and verbose_eval: + if verbose_eval: verbose_eval = 1 if verbose_eval is True else verbose_eval - callbacks.append(callback.EvaluationMonitor(period=verbose_eval, - show_stdv=show_stdv)) + callbacks.append( + callback.EvaluationMonitor(period=verbose_eval, show_stdv=show_stdv) + ) if early_stopping_rounds: - callbacks.append(callback.EarlyStopping( - rounds=early_stopping_rounds, maximize=maximize)) + callbacks.append( + callback.EarlyStopping(rounds=early_stopping_rounds, maximize=maximize) + ) callbacks = callback.CallbackContainer(callbacks, metric=feval, is_cv=True) else: callbacks = _configure_deprecated_callbacks( diff --git a/tests/python/test_callback.py b/tests/python/test_callback.py index 4018bf0be256..204e5383a975 100644 --- a/tests/python/test_callback.py +++ b/tests/python/test_callback.py @@ -1,3 +1,4 @@ +from typing import Union import xgboost as xgb import pytest import os @@ -22,29 +23,47 @@ def setup_class(cls): cls.X_valid = X[split:, ...] cls.y_valid = y[split:, ...] - def run_evaluation_monitor(self, D_train, D_valid, rounds, verbose_eval): - evals_result = {} + def run_evaluation_monitor( + self, + D_train: xgb.DMatrix, + D_valid: xgb.DMatrix, + rounds: int, + verbose_eval: Union[bool, int] + ): + def check_output(output: str) -> None: + if int(verbose_eval) == 1: + # Should print each iteration info + assert len(output.split('\n')) == rounds + elif int(verbose_eval) > rounds: + # Should print first and latest iteration info + assert len(output.split('\n')) == 2 + else: + # Should print info by each period additionaly to first and latest + # iteration + num_periods = rounds // int(verbose_eval) + # Extra information is required for latest iteration + is_extra_info_required = num_periods * int(verbose_eval) < (rounds - 1) + assert len(output.split('\n')) == ( + 1 + num_periods + int(is_extra_info_required) + ) + + evals_result: xgb.callback.TrainingCallback.EvalsLog = {} + params = {'objective': 'binary:logistic', 'eval_metric': 'error'} with tm.captured_output() as (out, err): - xgb.train({'objective': 'binary:logistic', - 'eval_metric': 'error'}, D_train, - evals=[(D_train, 'Train'), (D_valid, 'Valid')], - num_boost_round=rounds, - evals_result=evals_result, - verbose_eval=verbose_eval) + xgb.train( + params, D_train, + evals=[(D_train, 'Train'), (D_valid, 'Valid')], + num_boost_round=rounds, + evals_result=evals_result, + verbose_eval=verbose_eval, + ) output: str = out.getvalue().strip() + check_output(output) - if int(verbose_eval) == 1: - # Should print each iteration info - assert len(output.split('\n')) == rounds - elif int(verbose_eval) > rounds: - # Should print first and latest iteration info - assert len(output.split('\n')) == 2 - else: - # Should print info by each period additionaly to first and latest iteration - num_periods = rounds // int(verbose_eval) - # Extra information is required for latest iteration - is_extra_info_required = num_periods * int(verbose_eval) < (rounds - 1) - assert len(output.split('\n')) == 1 + num_periods + int(is_extra_info_required) + with tm.captured_output() as (out, err): + xgb.cv(params, D_train, num_boost_round=rounds, verbose_eval=verbose_eval) + output = out.getvalue().strip() + check_output(output) def test_evaluation_monitor(self): D_train = xgb.DMatrix(self.X_train, self.y_train)