-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Easier way to configure optimizers and schedulers in the CLI #7576
Comments
The CLI should not modify or override any code inside the from enum import Enum
import torch
import pytorch_lightning as pl
from pytorch_lightning.utilities.cli import LightningCLI
# could be just strings but enum forces the set of choices
class OptimizerEnum(str, Enum):
Adam = "Adam"
SGD = "SGD"
LBFGS = "LBFGS"
class LRSchedulerEnum(str, Enum):
...
class MyModel(pl.LightningModule):
def configure_optimizer(self, optimizer: OptimizerEnum, learning_rate: float = 1e-3, weight_decay: float = 0.0):
if optimizer == "Adam":
return torch.optim.Adam(
self.parameters(),
lr=learning_rate,
weight_decay=weight_decay,
)
elif optimizer == "SGD":
return torch.optim.SGD(
self.parameters(),
lr=learning_rate,
weight_decay=weight_decay,
)
elif optimizer == "LBFGS":
return torch.optim.LBFGS(self.parameters(), lr=1)
raise ValueError(f"Invalid optimizer {optimizer}. See --help")
def configure_scheduler(self, optimizer: torch.optim.Optimizer, lr_scheduler: LRSchedulerEnum):
# same structure as `configure_optimizer`
...
def configure_optimizers(self):
optimizer = self.configure_optimizer(
self.hparams.optimizer,
learning_rate=self.hparams.learning_rate,
weight_decay=self.hparams.weight_decay,
)
if self.hparams.lr_scheduler is None:
return optimizer
scheduler = self.configure_scheduler(optimizer, self.hparams.lr_scheduler)
return [optimizer], [scheduler]
class MyLightningCLI(LightningCLI):
def add_arguments_to_parser(self, parser):
# add the `configure_optimizer` arguments under the key optimizer
parser.add_method_arguments(self.model_class, "configure_optimizer", nested_key="optimizer")
# add the `configure_scheduler` arguments under the key lr_scheduler
parser.add_method_arguments(self.model_class, "configure_scheduler", nested_key="lr_scheduler", skip={"optimizer"})
MyLightningCLI(MyModel)
Now all those defaults and types are part of the function signatures |
I know this is "working as intended" but I also think you see missing a key feature. The code given doesnt tackle the bigger issue of configuring the learning rate schedulers with their different parameters, etc. I am not even sure how much wrapper code that would take or how to write it. Also, the optimizers don't always have the same options so I think architecture for your code example just wouldn't work with more generality. Not to mention, how are new optimizers that lightning adds going to be supported? It would require basically hardcoding something in user code for the dispatching rather than an OO design within lightning or lightning CLI code. Something is missing. @zippeurfou was there a structure that works for this with hydra? This is very un-lightning in the quantity of copy paste boilerplate required for something almost everyone would want the same features work. I am positive there is something which would require zero code for users who want the CLI to use config for standard optimizer and scheduler usage |
@jlperla I agree this is a missing feature. One of the objectives of LightningCLI is that it can be used without people having to learn about a configuration framework. This at least for most common cases since the possibilities of what people might need are endless. I have some ideas and will describe them here in a bit. Though I will not manage to do this today. |
Thanks. Yeah, the other consideration is that if you can move stuff into your libraries in one form or another, it makes user code far less fragile to any refactoring or additional pytorch optimizer options. Otherwise there will be a half dozen, almost identical versions of the same code out there...none of them really customized for the user |
I give here some initial ideas to start the discussion. Some objectives could be:
My initial idea is the following (not yet fully thought through). There would be a method in |
I think I would have to get a sense of what you mean by that, but sounds like boilerplate to me :-) The source of the difficulty in all of this stuff is writing the code to configure the class and store the parameters. I still don't understand why the user would need to write a single line of code for this since it is always the same stuff (except for weird cases where you need two optimizers/etc. but users can then do what they want for that). Why can't PL and the CLI could do everything internally and have things nice and consistent? But... the stuff in the parser groups seem to make sense to me and the grouping "dicts" as constructors for the optimizers and scheudlers also make plenty of sense. Now I think it just needs to be taken to its inevitable conclusion of an option which formalizes that stuff inside of PL itself :-) |
You are right. It wouldn't be difficult to automatically implement the model's parser.add_optimizer_args(Adam) # 'optimizer' key would be default
parser.add_scheduler_args(StepLR) # 'scheduler' key would be default In the case of multiple optimizers/schedulers it becomes manual. The parser could be configured like parser.add_optimizer_args(Adam, 'optimizer1', link_to='model.optim1')
parser.add_optimizer_args(Adam, 'optimizer2', link_to='model.optim2') Then the user needs to store the parameters and implement |
I am not sure I completely follow, but if it means I can have a configurable and swappable single optimizer and single scheduler in my code without any manual boilerplate, then I am happy. But just to confirm, the key here is that it is all swappable and configurable in the CLI wihtout me needing to manually write a bunch of That is, if you are saying that I have to go
Then if the user doesn't provide a model:
decoder_layers:
- 2
- 4
encoder_layers: 12
optimizer:
Adam:
weight_decay: 1e-5
lr_scheduler:
ReduceLROnPlateau:
factor: 0.1
trainer:
accelerator: null
accumulate_grad_batches: 1
amp_backend: native
amp_level: O2 And if they wanted to try a different optimizer, they just pass in a different YAML or CLI.
etc. Then you could either use dispatching on the type (e.g. the |
This is why I said a single optimizer class or multiple. If it is configured to allow multiple classes then it would follow the same pattern that LightningCLI already uses. So in the config it would be like optimizer:
class_path: torch.optim.Adam
init_args:
weight_decay: 1e-5
scheduler:
class_path: torch.optim.lr_scheduler.ReduceLROnPlateau
init_args:
factor: 0.1 |
Yes, for a single optimizer/scheduler it would be automatic and it shouldn't depend on the use of |
This issue has been automatically marked as stale because it hasn't had any recent activity. This issue will be closed in 7 days if no further activity occurs. Thank you for your contributions, Pytorch Lightning Team! |
def configure_optimizers(self):
optimizer = getattr(torch.optim, self.hparams.optimizer)(
self.parameters()
lr=self.hparams.lr,
weight_decay=self.hparams.weight_decay,
)
scheduler = self.configure_scheduler(optimizer, self.hparams.lr_scheduler)
return [optimizer], [scheduler] |
🚀 Feature
Right now if you want to work with multiple optimizers and/or learning rate schedulers you need to write a whole bunch of ugly boilerplate. I suggest that the LightningCLI in one way or another to enable that sort of optimizer configuration code in a clean way, which would take care of most use cases.
Motivation
Right now I have the following code in
configure_optimizers
callback:And the commandline configuration looks like
or maybe even more....
Pitch
Not exactly sure how you are planning to do recursive configuration stuff, but I can imagine the CLI having a baseline
configure_optimizers
on its own so that the user doesn't need to write it for simple patterns. Then maybe have defaults in the CLI arguments to have things likeand
or whatever. And the user just wouldn't implement a
configure_optimizers
or at least would have something simple to call.Writing this out as a configuration, I have in mind that the user could have something like
or maybe instead:
etc.
Alternatives
See the above monstrosity of what is done now.
The text was updated successfully, but these errors were encountered: