# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. from bisect import bisect_right import math import torch # FIXME ideally this would be achieved with a CombinedLRScheduler, # separating MultiStepLR with WarmupLR # but the current LRScheduler design doesn't allow it class WarmupMultiStepLR(torch.optim.lr_scheduler._LRScheduler): def __init__( self, optimizer, milestones, gamma=0.1, warmup_factor=1.0 / 3, warmup_iters=500, warmup_method="linear", last_epoch=-1, ): if not list(milestones) == sorted(milestones): raise ValueError( "Milestones should be a list of" " increasing integers. Got {}", milestones, ) if warmup_method not in ("constant", "linear"): raise ValueError("Only 'constant' or 'linear' warmup_method accepted" "got {}".format(warmup_method)) self.milestones = milestones self.gamma = gamma self.warmup_factor = warmup_factor self.warmup_iters = warmup_iters self.warmup_method = warmup_method super(WarmupMultiStepLR, self).__init__(optimizer, last_epoch) def get_lr(self): warmup_factor = 1 if self.last_epoch < self.warmup_iters: if self.warmup_method == "constant": warmup_factor = self.warmup_factor elif self.warmup_method == "linear": alpha = float(self.last_epoch) / self.warmup_iters warmup_factor = self.warmup_factor * (1 - alpha) + alpha return [ base_lr * warmup_factor * self.gamma ** bisect_right(self.milestones, self.last_epoch) for base_lr in self.base_lrs ] class WarmupCosineAnnealingLR(torch.optim.lr_scheduler._LRScheduler): def __init__( self, optimizer, max_iters, gamma=0.1, warmup_factor=1.0 / 3, warmup_iters=500, warmup_method="linear", eta_min=0, last_epoch=-1, ): if warmup_method not in ("constant", "linear"): raise ValueError("Only 'constant' or 'linear' warmup_method accepted" "got {}".format(warmup_method)) self.max_iters = max_iters self.gamma = gamma self.warmup_factor = warmup_factor self.warmup_iters = warmup_iters self.warmup_method = warmup_method self.eta_min = eta_min super(WarmupCosineAnnealingLR, self).__init__(optimizer, last_epoch) def get_lr(self): warmup_factor = 1 if self.last_epoch < self.warmup_iters: if self.warmup_method == "constant": warmup_factor = self.warmup_factor elif self.warmup_method == "linear": alpha = float(self.last_epoch) / self.warmup_iters warmup_factor = self.warmup_factor * (1 - alpha) + alpha return [base_lr * warmup_factor for base_lr in self.base_lrs] else: return [ self.eta_min + (base_lr - self.eta_min) * (1 + math.cos(math.pi * (self.last_epoch - self.warmup_iters) / self.max_iters)) / 2 for base_lr in self.base_lrs ] class WarmupReduceLROnPlateau(torch.optim.lr_scheduler.ReduceLROnPlateau): def __init__( self, optimizer, max_iters, gamma=0.1, warmup_factor=1.0 / 3, warmup_iters=500, warmup_method="linear", eta_min=0, last_epoch=-1, patience=5, verbose=False, ): if warmup_method not in ("constant", "linear"): raise ValueError("Only 'constant' or 'linear' warmup_method accepted" "got {}".format(warmup_method)) self.warmup_factor = warmup_factor self.warmup_iters = warmup_iters self.warmup_method = warmup_method self.eta_min = eta_min if last_epoch == -1: for group in optimizer.param_groups: group.setdefault("initial_lr", group["lr"]) else: for i, group in enumerate(optimizer.param_groups): if "initial_lr" not in group: raise KeyError( "param 'initial_lr' is not specified " "in param_groups[{}] when resuming an optimizer".format(i) ) self.base_lrs = list(map(lambda group: group["initial_lr"], optimizer.param_groups)) super(WarmupReduceLROnPlateau, self).__init__( optimizer, factor=gamma, patience=patience, mode="max", min_lr=eta_min, verbose=verbose ) def step(self, metrics=None): warmup_factor = 1 if self.last_epoch < self.warmup_iters: if self.warmup_method == "constant": warmup_factor = self.warmup_factor elif self.warmup_method == "linear": alpha = float(self.last_epoch) / self.warmup_iters warmup_factor = self.warmup_factor * (1 - alpha) + alpha if self.last_epoch >= self.warmup_iters - 1: warmup_factor = 1.0 warmup_lrs = [base_lr * warmup_factor for base_lr in self.base_lrs] for param_group, lr in zip(self.optimizer.param_groups, warmup_lrs): param_group["lr"] = lr self.last_epoch += 1 elif metrics: super().step(metrics)