-
Notifications
You must be signed in to change notification settings - Fork 393
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
CLEAN : rm duplicate code in fit_loop. #564
Conversation
Thanks for the PR. This is not an easy one :)
We really try to not make changes that affect the public API (such as
This was actually a very conscious decision we made. In most cases, I would agree that you should not repeat yourself. Here, we wanted to lay bare the structure of the fit loop without any indirection, which is why we chose not to use sub-methods for that. Would your use cases have been easier with this change? To argue in favor of the change: At the time we made the decision, the
I wonder if that is really necessary. Can you not perform the logging within Other changes or potential changes:
|
OK, my take on this is that
While I am in favor of fixing (1) to enable easy modification of the whole training loop I would also love to understand (2), i.e. @YannDubs what problems are you solving by overwriting I also agree with @BenjaminBossan that the fit loop as it currently is, is a deliberate choice and there is nothing inherently wrong with having a central point that outlines the complete training cycle, even if that means that there is minor code duplication. However, if we can retain the global structure while reducing complexity for the user, I'm all for it (and this PR goes in that direction). |
No I never have, but I'm usually more reluctant to modify large functions. This is something I really like in skorch, as there are usually a lot of specific and easy to understand functions that can be quickly modified, but I don't feel it is the case for
The use cases I had were usually very specific, I'm not saying others will want that. Here are 2 usecases I currently have to deal with:
Note that this saves the losses for training and testing using the prefix. Although I would prefer using callbacks, I don't see how to do that as I don't have access to I'm really not saying that both of the aforementioned methods are good ways of doing it, quite on the contrary : these are ad hoc tricks to get the job done. But I still believe that using 2 functions makes it easier to these such things.
Not necessary.
I'll make those changes if you all agree it's worth it. I'm not convinced about giving
See answer above. |
Thanks for your detailed answer. A few comments:
Interesting one. My first reflex would probably be to not have any validation data at all ( Multiple criteria: That's a tough one, as can be seen from the linked discussion. We really need a better story for that but as I'm not working on any use case that would involve this, it's hard for me to come up with a practical solution.
Not sure if I understand you correctly, but
That was one of our hopes when we designed skorch: We knew that we couldn't cover all use cases, but we wanted to make sure to at least make most of them not too hard to implement. Even if it means using "ad hoc tricks".
I was wondering about exactly that. Just a very wild guess: training a net that consists of separate components like a GAN? What would we lose by making the change? Regarding the name, how about something as plain as
At the moment, I'm inclined to say yes, but since it's a fundamental change, I'm conservative here. I would like to hear @thomasjpfan's opinion on that too. |
That's a really important point that I did not understand (I thought the first argument was
That would work. It requires quite a lot of additional code (as I have to implement the validation rather than just skip it), but it's probably cleaner.
And you've done a great job at doing just that, that's why I thought such a PR might be in line with your usual approach
You convinced me.
I thought about it but it seems strange to have the word |
True. Is there a word for either fit or validate? |
@thomasjpfan any opinion on this? |
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.
Skipping validation based on epoch looks like a valid use case.
I am +1 with this PR with a small update suggested in my review. I think with the suggestion, this will reduce the complexity of fit_loop
.
skorch/net.py
Outdated
return self | ||
|
||
def _single_epoch(self, dataset, training, epoch, **fit_params): |
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 can update this signature to
def _single_epoch(self, dataset, training, prefix,
step_fn, epoch, **fit_params):
...
And let the caller pass the arguments in:
for epoch in range(epochs):
self.notify('on_epoch_begin', **on_epoch_kwargs)
self._single_epoch(dataset_train, training=True, prefix="train",
step_fn=self.train_step, epoch=epoch, **fit_params)
if dataset_valid is not None:
self._single_epoch(dataset_valid, training=False, prefix="valid",
step_fn=self.validation_step, epoch=epoch, **fit_params)
self.notify("on_epoch_end", **on_epoch_kwargs)
I made the changes you asked for and added documentation. |
Just FYI, the failing tests are caused by changes in sklearn 0.22 and not by changes in this PR. We'll have to fix those tests first before we can make progress on this one. |
I still wonder what a good name for the new method could be, when seeing |
do you prefer |
|
The change LGTM. Just checking, does this solve your initial problem efficiently @YannDubs? I'm assuming you would implement a more efficient validation by adding something like this? def run_single_epoch(self, dataset, training, prefix, step_fn, **fit_params):
if training or (not training and len(self.history) % 10 == 0):
return super().run_single_epoch(dataset, training, prefix, step_fn, **fit_params)
# not reached I wonder if it makes sense to supply the epoch counter as an argument as well. |
@ottonemo Yes this is what I was doing, which is pretty efficient in terms of lines of code. Passing in the epoch argument is definitely cleaner for my usecase, but I don't have a strong feeling about it as it's easy to recover (as shown in your snippet) and is not used by default ... |
If you'd want to do that, this wouldn't work: - for _ in range(epochs):
+ for epoch in range(epochs): The reason is that epochs might not always start with 0 (e.g. using At a later point, we might want to add an example into the FAQ on how to skip validation as shown above. |
@YannDubs Any progress here? |
Co-Authored-By: Benjamin Bossan <BenjaminBossan@users.noreply.github.com>
@BenjaminBossan I think this should be merge, was there anything else you wanted me to do ? |
Thanks @YannDubs for this great addition. Now I was a bit too fast with merging, I think we should mention the changes in the CHANGES.md. Could you provide this in a separate PR? |
CLEAN : rm duplicate code in fit_loop. (skorch-dev#564)
For the third time, I found that I had to modify fit_loop (once for skipping some validation steps when doing few shot learning, an other time for a workaround of #245 ). Every time I'm a bit hesitant because it's a large function so my changes will likely break for new versions of skorch. I think this should be made a little cleaner + in addition there's a large chunk of duplicate code, which is bad practice. I think it should be split in 2 simple functions.
Nothing very important but it's a bit better. I also give
epoch
as argument because this is something I usually need and makes sense to give to a function that computes a single epoch (e.g. for logging).