Skip to content
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

Unify the univariate and multivariate TPE #2618

Merged

Conversation

HideakiImamura
Copy link
Member

@HideakiImamura HideakiImamura commented Apr 22, 2021

Depends on #2615 and #2616.

Part of works for #2614.

Motivation

The univariate TPE is a special case of the multivariate TPE, but the implementation of them in Optuna is overwrapped now. This PR aims to resolve the redundancy.

It seems to be difficult to support full backward compatibility including the behavior when the seed is fixed. The reason is that mus, sigmas, and weights in multivariate TPE are arranged in the order of observation, while those in current univariate TPE are arranged in the order of ascending mus. In this PR, we will adapt our logic to the multivariate TPE. We will verify through benchmarking experiments that the performance of the univariate TPE is not significantly impaired by this change.

Description of the changes

  • Unify the univariate and multivariate TPE
  • Fix some tests

TODOs

  • Performance benchmark on kurobako

Benchmark Results

I took a benchmark between this PR and the current master. In summary, the changes made by this PR do not significantly impair the performance of the algorithm.

Environments:

optuna: this PR (9d25958) and the current master (be407fd)
python: 3.8
kurobako: 0.2.9
algorithms: multivariate-tpe-master-PRUNER, multivariate-tpe-this-PR-PRUNER, tpe-master-PRUNER, tpe-this-PR-PRUNER

Each algorithm was run 100 times with the same settings, and the mean and variance of the performance were plotted.

Results

With NopPruner

hpo-bench-naval-ddecd9410805f2a70a5be7c2d5627c3b8b7af26160dbad87f5197a6a11d5f1b3
hpo-bench-parkinson-c54587a492ee2045a50b6b9c5db265781435c0610625a77256bad107e726d964
hpo-bench-protein-71dc1cce554890df75bc2b292694102521136a26a8afd6107e56672a1f9c9e54
hpo-bench-slice-c4296ca79fed7b86790575cb9c5b2dab8426fd1c9b028ea209def49e73d5cb70
nasbench-c-02fe653c3da0cb4ad55ff50834c60ca94a61fc7730a851507264ce322379ed19

With MedianPruner

hpo-bench-naval-ddecd9410805f2a70a5be7c2d5627c3b8b7af26160dbad87f5197a6a11d5f1b3
hpo-bench-parkinson-c54587a492ee2045a50b6b9c5db265781435c0610625a77256bad107e726d964
hpo-bench-protein-71dc1cce554890df75bc2b292694102521136a26a8afd6107e56672a1f9c9e54
hpo-bench-slice-c4296ca79fed7b86790575cb9c5b2dab8426fd1c9b028ea209def49e73d5cb70
nasbench-c-02fe653c3da0cb4ad55ff50834c60ca94a61fc7730a851507264ce322379ed19

With HyperbandPruner

hpo-bench-naval-ddecd9410805f2a70a5be7c2d5627c3b8b7af26160dbad87f5197a6a11d5f1b3
hpo-bench-parkinson-c54587a492ee2045a50b6b9c5db265781435c0610625a77256bad107e726d964
hpo-bench-protein-71dc1cce554890df75bc2b292694102521136a26a8afd6107e56672a1f9c9e54
hpo-bench-slice-c4296ca79fed7b86790575cb9c5b2dab8426fd1c9b028ea209def49e73d5cb70
nasbench-c-02fe653c3da0cb4ad55ff50834c60ca94a61fc7730a851507264ce322379ed19

@HideakiImamura HideakiImamura added the enhancement Change that does not break compatibility and not affect public interfaces, but improves performance. label Apr 22, 2021
@github-actions github-actions bot added the optuna.samplers Related to the `optuna.samplers` submodule. This is automatically labeled by github-actions. label Apr 22, 2021
@codecov-commenter
Copy link

codecov-commenter commented Apr 22, 2021

Codecov Report

Merging #2618 (71db3fc) into master (bfb41ab) will decrease coverage by 0.02%.
The diff coverage is 98.93%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2618      +/-   ##
==========================================
- Coverage   91.66%   91.64%   -0.03%     
==========================================
  Files         140      139       -1     
  Lines       11522    11311     -211     
==========================================
- Hits        10562    10366     -196     
+ Misses        960      945      -15     
Impacted Files Coverage Δ
optuna/samplers/_tpe/parzen_estimator.py 98.87% <98.80%> (-1.13%) ⬇️
optuna/samplers/_tpe/sampler.py 94.66% <100.00%> (+1.02%) ⬆️
optuna/_callbacks.py 100.00% <0.00%> (ø)
optuna/storages/__init__.py 100.00% <0.00%> (ø)
optuna/integration/allennlp.py 88.17% <0.00%> (+0.05%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bfb41ab...71db3fc. Read the comment docs.

@HideakiImamura HideakiImamura marked this pull request as draft April 22, 2021 07:34
@github-actions
Copy link
Contributor

github-actions bot commented May 9, 2021

This pull request has not seen any recent activity.

@github-actions github-actions bot added the stale Exempt from stale bot labeling. label May 9, 2021
@HideakiImamura
Copy link
Member Author

I took a benchmark between this PR and the current master. In summary, the changes made by this PR do not significantly impair the performance of the algorithm.

Environments:

optuna: this PR (9d25958) and the current master (be407fd)
python: 3.8
kurobako: 0.2.9

Each algorithm was run 100 times with the same settings, and the mean and variance of the performance were plotted.

Results

With NopPruner

hpo-bench-naval-ddecd9410805f2a70a5be7c2d5627c3b8b7af26160dbad87f5197a6a11d5f1b3
hpo-bench-parkinson-c54587a492ee2045a50b6b9c5db265781435c0610625a77256bad107e726d964
hpo-bench-protein-71dc1cce554890df75bc2b292694102521136a26a8afd6107e56672a1f9c9e54
hpo-bench-slice-c4296ca79fed7b86790575cb9c5b2dab8426fd1c9b028ea209def49e73d5cb70
nasbench-a-f7734d47490690221086445e6217606ba300517d8fedeca9b94637ca6f49a6e9

With MedianPruner

hpo-bench-naval-ddecd9410805f2a70a5be7c2d5627c3b8b7af26160dbad87f5197a6a11d5f1b3
hpo-bench-parkinson-c54587a492ee2045a50b6b9c5db265781435c0610625a77256bad107e726d964
hpo-bench-protein-71dc1cce554890df75bc2b292694102521136a26a8afd6107e56672a1f9c9e54
hpo-bench-slice-c4296ca79fed7b86790575cb9c5b2dab8426fd1c9b028ea209def49e73d5cb70
nasbench-a-f7734d47490690221086445e6217606ba300517d8fedeca9b94637ca6f49a6e9

With HyperbandPruner

hpo-bench-naval-ddecd9410805f2a70a5be7c2d5627c3b8b7af26160dbad87f5197a6a11d5f1b3
hpo-bench-parkinson-c54587a492ee2045a50b6b9c5db265781435c0610625a77256bad107e726d964
hpo-bench-protein-71dc1cce554890df75bc2b292694102521136a26a8afd6107e56672a1f9c9e54
hpo-bench-slice-c4296ca79fed7b86790575cb9c5b2dab8426fd1c9b028ea209def49e73d5cb70
nasbench-a-f7734d47490690221086445e6217606ba300517d8fedeca9b94637ca6f49a6e9

@github-actions github-actions bot removed the stale Exempt from stale bot labeling. label May 13, 2021
@HideakiImamura HideakiImamura assigned c-bata and hvy and unassigned Crissman and himkt May 14, 2021
@HideakiImamura
Copy link
Member Author

@himkt @Crissman Let me remove assignments of this PR since you already have a lot of assigned PRs.

@c-bata @hvy Could you review this PR if you have time?

@c-bata
Copy link
Member

c-bata commented May 14, 2021

@HideakiImamura Could you please assign another reviewer instead of me?

@HideakiImamura
Copy link
Member Author

@c-bata Sure! Let me remove the assignment.

@hvy
Copy link
Member

hvy commented May 17, 2021

Assigned @keisuke-umezawa !

@hvy
Copy link
Member

hvy commented May 17, 2021

Could you reduce the diff now that #2615 is merged?

@hvy
Copy link
Member

hvy commented May 20, 2021

Sorry, could you merge the latest master again? I just merge #2616 .

@HideakiImamura HideakiImamura marked this pull request as ready for review May 20, 2021 04:44
@HideakiImamura
Copy link
Member Author

@hvy Thanks for your careful reviews. I updated the code following your suggestions. PTAL.

I take a benchmark with the NAS bench (C) to test the floating value.

@HideakiImamura
Copy link
Member Author

@hvy @keisuke-umezawa I have benchmarked and updated the results using the latest version, modified based on the discussion during the previous mob review. There appears to be generally no difference between this PR and master. PTAL.

Copy link
Member

@hvy hvy left a comment

Choose a reason for hiding this comment

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

Thanks a lot for the updated benchmarks and plots. They look promising 👍

optuna/samplers/_tpe/parzen_estimator.py Show resolved Hide resolved
@HideakiImamura
Copy link
Member Author

@hvy Thank you for your helpful and insightful reviews. I updated the code following your suggestions.

  • I simplify the logic of _calculate_numerical_params
  • I add the logic considering the consider_endpoints flag
  • I bring up some test cases for the univariate TPE to test consider_endpoints, consider_magic_clip, and consider_prior.

PTAL.

@HideakiImamura
Copy link
Member Author

Note: Currently, the _ParzenEstimator calculates mus, sigmas, and weights, taking into account observations outside of [low, high] during init. The restriction to the [low, high] range is done when the sample method is called. This mechanism gives unexpected results in the estimation of sigmas.

This is due to the fact that when calculating the sigmas in _calculate_numerical_params, the mus is sorted and then the low and high are added. This can be avoided by adding low and high and then sorting mus, but changing the algorithm is out of the scope of this PR and will not be addressed here. We will leave this issue for future work.

# If ``multivariate`` = True and ``group`` = True, we ignore the trials that are not
# included in each subspace.
# If ``multivariate`` = False, we consider such trials.
if multivariate and any([param_name not in trial.params for param_name in param_names]):
Copy link
Member

Choose a reason for hiding this comment

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

In my understanding, the procedure of calculation does not depend on multivariate, because multivariate is always true when any([param_name not in trial.params for param_name in param_names]) is true. If my understanding is correct, I think we can omit multivariate from the arguments of this method.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry for the confusion. Here, we want to check any([param_name not in trial.params for param_name in param_names]) only when multivariate = True. In the current master, we don't check any([param_name not in trial.params for param_name in param_names]) when multivariate = False, so for the consistency, I include multivariate here.

Copy link
Member Author

Choose a reason for hiding this comment

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

I updated the code comment like: If multivariate = False, we skip the check

Copy link
Member

@keisuke-umezawa keisuke-umezawa left a comment

Choose a reason for hiding this comment

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

Other than the calculation of sigmas, LGTM! I will check it again after the discussion of sigmas is settled.

sorted_mus_with_endpoints = np.asarray([], dtype=float)
prior_mu = 0.5 * (low + high)
prior_sigma = 1.0 * (high - low)

if consider_prior:
Copy link
Member

Choose a reason for hiding this comment

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

I can decrease the number of nest from 2 to 1 by the following code.

          if consider_prior:
              mus = np.empty(n_observations + 1)
              mus[:n_observations] = observations
              mus[n_observations] = prior_mu
              sigmas = np.empty(n_observations + 1)
              sigmas[n_observations] = prior_sigma
          else:
              mus = observations
              sigmas = np.empty(n_observations)
  
          if multivariate:
              assert sigmas0 is not None
              sigmas[:n_observations] = sigmas0 * (high - low)
          else:
              assert sigmas0 is None
              sorted_indices = np.argsort(mus)
              sorted_mus = mus[sorted_indices]
              sorted_mus_with_endpoints = np.empty(len(mus) + 2, dtype=float)
              sorted_mus_with_endpoints[0] = low 
              sorted_mus_with_endpoints[1:-1] = sorted_mus
              sorted_mus_with_endpoints[-1] = high
  
              sorted_sigmas = np.maximum(
                  sorted_mus_with_endpoints[1:-1] - sorted_mus_with_endpoints[0:-2],
                  sorted_mus_with_endpoints[2:] - sorted_mus_with_endpoints[1:-1],
              )   
              sigmas[:n_observations] = sorted_sigmas[np.argsort(sorted_indices)]

If it is better for readability, could you use the above snippet?

Copy link
Member Author

Choose a reason for hiding this comment

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

Looks great. Thanks for your insightful review!

@HideakiImamura
Copy link
Member Author

@keisuke-umezawa Thanks for your reviews! I updated the codes following your suggestions. PTAL.

Copy link
Member

@hvy hvy left a comment

Choose a reason for hiding this comment

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

Thanks for the updates. The code around the mus and sigmas are simpler now.

By the way, are the benchmarks difficult to run? I'm wondering if we should run another round since there has been some changes.

tests/samplers_tests/tpe_tests/test_parzen_estimator.py Outdated Show resolved Hide resolved
optuna/samplers/_tpe/parzen_estimator.py Outdated Show resolved Hide resolved
optuna/samplers/_tpe/parzen_estimator.py Outdated Show resolved Hide resolved
@HideakiImamura
Copy link
Member Author

@hvy Thanks for your review! I applied all of your suggestions. PTAL.

By the way, I can re-run the benchmark experiments easily. This PR will be merged after the release of v2.8.0, so I think we should have the latest benchmark result.

@hvy
Copy link
Member

hvy commented Jun 1, 2021

By the way, I can re-run the benchmark experiments easily. This PR will be merged after the release of v2.8.0, so I think we should have the latest benchmark result.

Sounds great, let's do that. 👍

@hvy hvy added this to the v2.9.0 milestone Jun 1, 2021
@hvy
Copy link
Member

hvy commented Jun 3, 2021

Note: Benchmarks have been updated.

Copy link
Member

@keisuke-umezawa keisuke-umezawa left a comment

Choose a reason for hiding this comment

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

LGTM! Thank you for the long PR.

Copy link
Member

@hvy hvy left a comment

Choose a reason for hiding this comment

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

Great work and thanks, LGTM given the benchmarks and the many discussions!

@hvy hvy merged commit 92bbec2 into optuna:master Jun 7, 2021
@hvy hvy added compatibility Change that breaks compatibility. and removed enhancement Change that does not break compatibility and not affect public interfaces, but improves performance. labels Jun 8, 2021
@hvy
Copy link
Member

hvy commented Jun 8, 2021

Updated the label to compatibility as per suggestion by @not522 . Thanks.

@not522 not522 mentioned this pull request Jul 20, 2021
@HideakiImamura HideakiImamura deleted the unify-univariate-and-multivariate-tpe branch June 9, 2023 02:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compatibility Change that breaks compatibility. optuna.samplers Related to the `optuna.samplers` submodule. This is automatically labeled by github-actions.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants