-
Notifications
You must be signed in to change notification settings - Fork 554
Added support for named arguments to objective function. #590
Conversation
|
Hello @Hvass-Labs! Thanks for submitting the PR.
|
|
Thanks for this. Let's see if we can get some feedback from others on introducing a new argument. Personally I quite like the idea of having a adaptor that takes care of the boiler plate conversion. Mostly because there are already a lot of arguments which leads to long doc strings, and long function signatures.
As a general rule a PR is edited by the contributor, including adding tests, making sure existing tests pass, documentation, code, etc. Frequently others will make suggestions or ask for changes which the person who created the PR then implements. Very rarely people do collaborate on a PR but this means a third person has to review it so it can be merged. Keeping the roles separated works very well for us. Your comments on This means I won't +1 this PR until you remove the TODO comments or convert them. |
| @@ -1,10 +1,11 @@ | |||
| # TODO: This doc-string is way too short and quite meaningless. | |||
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.
We are working on this code base together and expect to work through issues collaboratively. If you've taken the time to understand this section, you probably understand there are tradeoffs. We can work through them. As it stands, these TODO comments feel like directed angst rather than a commitment to contribute to reducing confusion.
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.
It is an indication that your code is frustrating to work on for others and the TODOs point to specific areas that might be improved. I asked myself last night if my old code was also undocumented and unpolished. I looked at ArrayOps which I did almost 12 years ago and look at how beautifully it was designed, implemented and documented. Even though it has been so long and I haven't worked on it since, and it is very advanced C++ programming, I was still able to grasp it again by simply browsing my docs and commented code. Take a look at the manual http://www.hvass-labs.org/projects/arrayops/files/ArrayOps2.pdf Then take a look at e.g. plots.py in your project which was particularly bad, or even the files we are discussing here. ArrayOps had almost no users (I think it was used in teaching advanced C++ but not really used in production). Your library has lots of users already. Why not make the code beautiful so others can contribute more easily? Right now there's a huge barrier to making contributions because your code is often impossible to understand.
| from ..utils import eval_callbacks | ||
|
|
||
|
|
||
| class _WrapFuncNamedArgs: |
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.
Instead of creating (and having to maintain) our own function wrapper we could use functools.wraps like so:
In [3]: def adaptor(f):
...: @wraps(f)
...: def wrapper(*args):
...: print('modify args', args)
...: return f(*args)
...: return wrapper
...:
In [4]: def my_objective(x, y, z):
...: """doc here"""
...: return x + y + z
...:
In [5]: adapted_obj = adaptor(my_objective)
In [6]: adapted_obj?
Signature: adapted_obj(x, y, z)
Docstring: doc here
File: ~/Downloads/<ipython-input-4-84edd263a788>
Type: functionit takes care of forwarding doc strings, and signatures, etc.
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.
Thanks for the tip, I'll take a look.
| n_calls -= len(y0) | ||
| # TODO: I tried to clean this up a little, but it is VERY confusing. | ||
| # Pass user suggested initialisation points to the optimizer. | ||
| if x0 is not None: |
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.
x0 should already be initialised as an empty list by the time we get here, so we should keep the old test (if x0).
I like the comment but splitting up the if clause makes it harder to understand
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.
As I recall there was an if x0: statement here. That is not Python, that is C-style programming! And it can actually fail in Python if the object has overrided some dunder-method (I can't remember which one but I think there's a PyCon talk about this ...) In Python you should make it explicit what you are testing against e.g. by writing if x0 is not None:
I think I'll re-submit the wrapper function as discussed below and I'll remove all these changes to the other files and just add the wrapper-function with as few changes as possible. It would be nice if you would go over some of these files and polish them.
| # Bayesian optimization loop | ||
| # Bayesian optimization loop. | ||
| for n in range(n_calls): | ||
| # Get next point in the search-space. |
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.
The next three comments restate what the line of code says so I would remove them
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.
The code is easy to understand for you because you look at it every day. For newcomers the comments help and they don't do any harm to experts. But I'm not going to argue with you. I am just informing you that it would be easier for others to help you, if your code was better documented.
|
I would provide the wrapper in
|
|
One more thought on having named arguments. Without any changes to scikit-optimize having named arguments (or a nice way to unpack them) already works (as long as the dimensions are defined in the same order): The use case in #583 and when I've wanted the arguments to be named was when I had to pass them on to something else (like a scikit-learn estimator) where you want a This makes me think we should remind people of argument unpacking in python and instead add support for objectives that receive a "single argument" like so: |
|
@betatim Thanks again for the quick response! We don't agree on a lot of issues especially regarding commenting of code and who should do what on a project, but you have a strong sense of making clean API design and I appreciate that! Why don't you just lead the project? It is already my impression that you are the main developer on this project? You should just take charge. I like that you keep reducing the ideas for API design until it is as simple as possible. That is how good design is done. It is important that you scrutinize the contributions from other team members as well. I don't think you would ever have approved I am also learning something from your ideas and feedback, including Python features I wasn't aware of, so thanks for that! Your final idea on this issue was this: I hadn't thought about unpacking However, this has a few problems:
I thought about making a function-wrapper e.g. in I now see that my wrapper-function is actually incorrect because |
|
Hi, Thanks for your contribution. Can you remove your inline comments so that we can focus the review on changes that are concerned with this proposal? (i.e., adding support for named arguments) We acknowledged that there might things that are difficult for newcomers to understand (as is with any complex bit of research code) and are open to discuss them, but in order to not waste everyone's time, it is probably more efficient to discuss them in standalone and targeted PRs. |
|
A much better version of this is made in PR #592. So I am closing this PR. |
This is a solution for issue #570 about named arguments for the objective function.
@betatim proposed using the Python
inspect.signature()function - first time I heard about it :-)But I think this a simpler solution that also has backwards compatibility with the current calling style of
func(x)wherexis a list. The user just needs to give all dimensions proper names (otherwise an exception is raised), and then setuse_arg_names=Truewhen calling e.g.gp_minimize(). Theuse_arg_namesis then passed all the way down tobase_minimize()which wraps the objective-function so it will be called with the named arguments instead of a listxas usual.I found the function
base_minimize()very confusing. I added a few TODO-comments which is a hope that you will spend an hour cleaning and commenting this function before you merge the code. It is unlikely that anyone else can / will do it for you.The code below tests the new feature. Try changing
use_arg_namesso it is called with both the old and new methods. You can also try changing the order of the arguments tonamed_fitness()and it should still work. Also try changing a name e.g. fromactivationtoactivation2and it should crash.