Allow setting dataop choices with optuna#1661
Conversation
|
This is really cool! Thanks a lot @jeromedockes! |
|
Thanks a lot for the PR!!
The DataOp now has a .skb.make_optuna_search()
From an API perspective I would much prefer if we could avoid making a function for each hyper-parameter search method. It will scale very poorly as we add methods. I do realize that we might need different functions, as there will be different signatures, but can we find commonalities?
|
You mean something like this? predictions.skb.make_param_search(kind="random", ...)Is it going to be necessary? Are there so many other param search methods we can add? |
|
You mean something like
predictions.skb.make_param_search(kind="random", ...)
Yes
|
|
The main parameters which differ between those search strategies are:
all but In the return value, the optuna search will have a I guess it's a tradeoff between having several functions or having a few "param a is ignored unless param b = x" in the docstring and different return types. Scikit-learn chose to have a different class for each search strategy. Regarding the addition of a new search strategy, if we go for multiple methods the cost is to create a new method (which is annoying and clutters the namespace), if we go for a single method, it depends on how many new parameters the new strategy needs to introduce (docstring gets more complex as the ratio shared parameters / all parameters decreases). As strategies to add I'm guessing you have in mind the HalvingRandomSearchCV and HalvingGridSearchCV, once those stop being experimental in scikit-learn? And there are also other packages like hyperopt, not sure if we will want to support more or if optuna is enough (I would say just optuna should be good for the foreseeable future). |
|
In any case I think I'll split this PR. I'll remove the |
|
Thanks @jeromedockes ! After discussion with @rcap107 , here are my thoughts: This PR caters to optuna power user. Many people are not optuna users, but would like to flip a switch to use optuna. Looking at the example https://output.circle-artifacts.com/output/job/8860ed56-510b-47b7-b260-b71da3da42a1/artifacts/0/doc/auto_examples/data_ops/13_optuna_choices.html#sphx-glr-auto-examples-data-ops-13-optuna-choices-py with @rcap107 it seems that we could do a: The internals of import optuna
def objective(trial):
learner = pred.skb.make_learner(choose=trial)
cv_results = skrub.cross_validate(learner, environment=env, cv=cv)
return cv_results["test_score"].mean()
study = optuna.create_study(direction="maximize")
study.optimize(objective, n_trials=16)
best_learner = pred.skb.make_learner(choose=study.best_trial)We would then modify the existing example to show first at the top how to easily use optuna, just flipping a switch from code using RandomSearch, and then continue with your example for advanced users. We'll also need to do a bit of impedance matching, converting "n_iter" which is scikit-learn's convention to "n_trials", and exposing the optuna solver. |
|
thanks @GaelVaroquaux yes that is the plan, to have the scikit-learn estimator interface for most users, and the more bare-bones direct optuna use for optuna users. As I mentioned I opted for splitting the PR so that I could make progress on the core functionality while we decided on the |
|
circleci failure is unrelated and will be fixed by #1794 |
|
I'm looking at this PR. It's really amazing, thank you! I'll give some feedback as I go, as I worry that I'll be interrupt. Sorry for the resulting mess and crowding. Feedback 1: I worry that we are adding an example with a run-time of 4mn. We have another PR doing this. it's not going to scale well, and soon it will impact negatively skrub development. Would it be possible to play a bit wit the parameters of the example (for instance number of trials) to decrease the cost of this new example a bit? Maybe use a faster learner (I love ExtraTrees for instance, or even a Logistic?). Feedback 2: I see that we are tuning the number of estimators of RandomForests. I don't consider this as the most important hyper parameter. I typically consider the depth as the most important feature to tune. |
| ) | ||
| rf = RandomForestRegressor( | ||
| n_estimators=skrub.choose_int(20, 100, name="n_estimators"), | ||
| ) |
There was a problem hiding this comment.
In the interest of compute time, could we replace one of these two by a logistic regression?
There was a problem hiding this comment.
I used a ExtraTreesRegressor and a Ridge and reduced the number of trials
GaelVaroquaux
left a comment
There was a problem hiding this comment.
A few other minor comments.
That's all I have. Thank you very much!!
Co-authored-by: Gael Varoquaux <gael.varoquaux@normalesup.org>
|
Thanks for the review @GaelVaroquaux !
I also edited a bit the user guide page (@rcap107 wrote the original version actually, thanks Riccardo!) . Part of it is very similar to the sphinx gallery example but that may not be a problem 🤔 . |
|
Thanks heaps for your changes!! Regarding compute time:
A factor 2 is really good to get. Thanks a lot. If we can get a bit more, it's better
Do you want to try this to see if you can get something that is still convincing and closer to 1mn? |
|
ok I just sampled half of the dataset, the examples takes just under a minute now on circleci |
|
Thank you so much @jeromedockes |
|
thank you both for the reviews & discussion :) looking forward to the upcoming release! |
This PR enables tuning the choices in a DataOp with optuna, either by using the usual
make_randomized_searchbut withbackend='optuna', or by passing an optuna Trial to .skb.make_learner in which case the learner is created with params chosen by the TrialHere is a complete example with
make_randomized_search:out:
And here is a more advanced example showing direct use of optuna for more fine-grained control:
out:
parallelization
Optuna's n_jobs is just for thread-based parallelization. Here
make_randomized_search()will use joblib for parallelization (and create an on-disk storage for the optuna study to support multiprocessing), unless n_jobs=1 or the joblib backend is threading, in which case optuna's n_jobs is used. this means skrub users get multiprocessing optuna search out of the box, which requires a complex manual setup for other optuna usersoriginal, outdated description
setting skrub.DataOp choices with OptunaApparently @rcap107 was asked a few times about combining skrub dataops and optuna, which is a great idea.
This is very rough first draft of what this could look like. Still missing some functionality, tests & docs etc.
search estimator interface
The DataOp now has a
.skb.make_optuna_search()method, which can be used to search for the best choice outcomes with optuna.For example:
prints
We can pass our own optuna study to control how it is created
For example above change
make_optuna_search()to:and we can monitor the search progress / explore results with
❯ optuna-dashboard sqlite:///db.sqlite3low-level interface
For more control over the use of optuna, we can pass a trial to
.skb.make_learner()to get a skrublearner initialized with choice outcomes suggested by the optuna trial. this is useful for more advanced optuna features (multi-objective, ask-and-tell interface, ...). Here is a simple example revisited from the optuna tutorial:prints something similar to
<