-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Document Behavior of optuna.pruners.PercentilePruner
, optuna.pruners.SuccessiveHalvingPruner
and optuna.pruners.HyperbandPruner
#6092
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
base: master
Are you sure you want to change the base?
Conversation
To reviewers, please note this PR: At first glance, the changes in this PR do not follow the pattern in the previous PR. |
@gen740 @kAIto47802 @sawa3030 Could you review this PR? |
As @nabenabe0928 pointed out, I think it would be better to follow the approach in #6055 by at least addressing the following two points: what would happen if all intermediate values are NaN , and how typical NaN values are handled. |
@ParagEkbote |
I apologize for not updating the PR. I was a bit busy and in the following week, I will update the PR as suggested. cc: @nabenabe0928 |
I'd like to thank the reviewers for their patience :) I have updated the docs as per the previous PR, the updated scripts are:
import optuna
import numpy as np
import logging
import sys
# Set up logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def nan_objective(trial):
trial_number = trial.number
if trial_number % 3 == 0:
logger.info(f"Trial {trial_number}: Returning NaN")
return float('nan')
else:
value = trial.suggest_float('x', -10, 10)
result = value ** 2
logger.info(f"Trial {trial_number}: Returning {result}")
return result
def partial_nan_objective(trial):
x = trial.suggest_float('x', -10, 10)
for step in range(10):
value = x ** 2 + step
if step % 3 == 0:
logger.info(f"Trial {trial.number}, Step {step}: Reporting NaN")
trial.report(float('nan'), step)
else:
logger.info(f"Trial {trial.number}, Step {step}: Reporting {value}")
trial.report(value, step)
if trial.should_prune():
logger.info(f"Trial {trial.number} pruned at step {step}")
raise optuna.exceptions.TrialPruned()
return x ** 2
def test_percentile_pruner_with_nan_values():
logger.info("\n=== Test 1: Basic study with NaN final values ===")
study1 = optuna.create_study(
pruner=optuna.pruners.PercentilePruner(percentile=25.0, n_startup_trials=0, n_warmup_steps=0),
direction="minimize"
)
study1.optimize(nan_objective, n_trials=10)
logger.info("Completed trials:")
for trial in study1.trials:
logger.info(f"Trial {trial.number}: State={trial.state}, Value={trial.value}")
logger.info("\n=== Test 2: Study with NaN intermediate values ===")
study2 = optuna.create_study(
pruner=optuna.pruners.PercentilePruner(percentile=25.0, n_startup_trials=5, n_warmup_steps=2),
direction="minimize"
)
study2.optimize(partial_nan_objective, n_trials=10)
logger.info("Completed trials with intermediate values:")
for trial in study2.trials:
logger.info(f"Trial {trial.number}: State={trial.state}, Value={trial.value}")
if trial.intermediate_values:
logger.info(f" Intermediate values: {trial.intermediate_values}")
if __name__ == "__main__":
test_percentile_pruner_with_nan_values()
import optuna
import numpy as np
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def nan_objective(trial):
if trial.number % 3 == 0:
logger.info(f"Trial {trial.number}: Returning NaN")
return float('nan')
value = trial.suggest_float('x', -10, 10)
result = value ** 2
logger.info(f"Trial {trial.number}: Returning {result}")
return result
def partial_nan_objective(trial):
x = trial.suggest_float('x', -10, 10)
for step in range(10):
value = x ** 2 + step
if step % 3 == 0:
logger.info(f"Trial {trial.number}, Step {step}: Reporting NaN")
trial.report(float('nan'), step)
else:
logger.info(f"Trial {trial.number}, Step {step}: Reporting {value}")
trial.report(value, step)
if trial.should_prune():
logger.info(f"Trial {trial.number} pruned at step {step}")
raise optuna.exceptions.TrialPruned()
return x ** 2
def test_successive_halving_with_nan_values():
logger.info("\n=== Test 1: SuccessiveHalving with NaN final values ===")
study1 = optuna.create_study(
pruner=optuna.pruners.SuccessiveHalvingPruner(min_resource=1, reduction_factor=2),
direction="minimize"
)
study1.optimize(nan_objective, n_trials=10)
logger.info("Completed trials:")
for trial in study1.trials:
logger.info(f"Trial {trial.number}: State={trial.state}, Value={trial.value}")
logger.info("\n=== Test 2: Intermediate NaNs with SuccessiveHalving ===")
study2 = optuna.create_study(
pruner=optuna.pruners.SuccessiveHalvingPruner(min_resource=1, reduction_factor=2),
direction="minimize"
)
study2.optimize(partial_nan_objective, n_trials=10)
logger.info("Completed trials:")
for trial in study2.trials:
logger.info(f"Trial {trial.number}: State={trial.state}, Value={trial.value}")
if trial.intermediate_values:
logger.info(f" Intermediate values: {trial.intermediate_values}")
if __name__ == "__main__":
test_successive_halving_with_nan_values()
import optuna
import numpy as np
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def nan_objective(trial):
if trial.number % 3 == 0:
logger.info(f"Trial {trial.number}: Returning NaN")
return float('nan')
value = trial.suggest_float('x', -10, 10)
result = value ** 2
logger.info(f"Trial {trial.number}: Returning {result}")
return result
def partial_nan_objective(trial):
x = trial.suggest_float('x', -10, 10)
for step in range(10):
value = x ** 2 + step
if step % 3 == 0:
logger.info(f"Trial {trial.number}, Step {step}: Reporting NaN")
trial.report(float('nan'), step)
else:
logger.info(f"Trial {trial.number}, Step {step}: Reporting {value}")
trial.report(value, step)
if trial.should_prune():
logger.info(f"Trial {trial.number} pruned at step {step}")
raise optuna.exceptions.TrialPruned()
return x ** 2
def test_hyperband_pruner_with_nan_values():
logger.info("\n=== Test 1: Study with Hyperband and NaN final values ===")
study1 = optuna.create_study(
pruner=optuna.pruners.HyperbandPruner(),
direction="minimize"
)
study1.optimize(nan_objective, n_trials=10)
logger.info("Completed trials:")
for trial in study1.trials:
logger.info(f"Trial {trial.number}: State={trial.state}, Value={trial.value}")
logger.info("\n=== Test 2: Hyperband study with intermediate NaNs ===")
study2 = optuna.create_study(
pruner=optuna.pruners.HyperbandPruner(min_resource=1, max_resource=10, reduction_factor=3),
direction="minimize"
)
study2.optimize(partial_nan_objective, n_trials=10)
logger.info("Completed trials:")
for trial in study2.trials:
logger.info(f"Trial {trial.number}: State={trial.state}, Value={trial.value}")
if trial.intermediate_values:
logger.info(f" Intermediate values: {trial.intermediate_values}")
if __name__ == "__main__":
test_hyperband_pruner_with_nan_values() Could you please review the changes? |
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 added the minor comment. PTAL!
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #6092 +/- ##
==========================================
+ Coverage 88.40% 88.42% +0.02%
==========================================
Files 207 207
Lines 14029 14029
==========================================
+ Hits 12402 12405 +3
+ Misses 1627 1624 -3 ☔ View full report in Codecov by Sentry. |
@@ -17,6 +17,11 @@ class SuccessiveHalvingPruner(BasePruner): | |||
`Asynchronous Successive Halving <https://proceedings.mlsys.org/paper_files/paper/2020/file/ | |||
a06f20b349c6cf09a6b171c71b88bbfc-Paper.pdf>`__ for detailed descriptions. | |||
|
|||
The pruner handles NaN values in the following manner: |
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.
According to the implementation, the pruning decision is based on the intermediate value at the last reported step, as well as the intermediate values of other trials at the same rung key. Could you update the explanation to reflect this behavior more accurately?
Motivation
Fixes #5202
I have added documentation of how the Percentile Pruner, SuccessiveHalvingPruner and HyperbandPruner handles Nan values.
It is important to note that we are using synthetic dataset for testing.
Could you please review?
cc: @HideakiImamura
Description of the changes
I have used the following scripts to test it out: