AFR-DFV-v2 / dinov3 /eval /segmentation /schedulers.py
Addax-Data-Science's picture
Upload 162 files
d9bb75c verified
Raw
History Blame Contribute Delete
7.1 kB
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This software may be used and distributed in accordance with
# the terms of the DINOv3 License Agreement.
from inspect import signature
import math
from typing import Any, Literal
import torch
from packaging.version import Version
from torch.optim import lr_scheduler as torch_schedulers
from torch.optim.optimizer import Optimizer
TORCH_VERSION = Version(torch.__version__)
def annealing_cos(start, end, pct):
"Cosine anneal from `start` to `end` as pct goes from 0.0 to 1.0."
cos_out = math.cos(math.pi * pct) + 1
return end + (start - end) / 2.0 * cos_out
def annealing_linear(start, end, pct):
"Linearly anneal from `start` to `end` as pct goes from 0.0 to 1.0."
return (end - start) * pct + start
class WarmupOneCycleLR(torch_schedulers.LRScheduler):
def __init__(
self,
optimizer: Optimizer,
max_lr: float | None = None,
total_steps: int = 0,
warmup_iters: int = 0,
warmup_ratio: float = 0.0,
pct_start: float = 0.295,
anneal_strategy: Literal["cos", "linear"] = "cos",
base_momentum: float = 0.85,
max_momentum: float = 0.95,
div_factor: float = 25.0,
final_div_factor: float = 1000.0,
use_beta1: bool = True,
update_momentum: bool = True,
last_epoch: int = -1,
):
"""
A variant of OneCycleLR with a warmup on top which potentially
replaces the first phase of the original OneCycleLR.
"""
self.warmup_iters = warmup_iters
self.warmup_ratio = warmup_ratio
self.max_lr = max_lr
self.min_point = float(pct_start * total_steps)
self.base_momentum = base_momentum
self.max_momentum = max_momentum
self.total_steps = total_steps
self.use_beta1 = use_beta1
self.anneal_strategy = anneal_strategy
self.final_div_factor = final_div_factor
self.update_momentum = update_momentum
assert self.anneal_strategy in [
"cos",
"linear",
], f"Only cosine and linear-annealing strategy supported, got {self.anneal_strategy}"
assert total_steps > 0
# Initialize learning rate variables and momentum
for group in optimizer.param_groups:
if "initial_lr" not in group:
assert last_epoch == -1
ml = group["lr"]
assert isinstance(ml, float) # makes sure that the variable is well updated
group["initial_lr"] = ml / div_factor
group["max_lr"] = ml
group["min_lr"] = group["initial_lr"] / final_div_factor
# initialize learning rate
group["lr"] = ml / final_div_factor if self.warmup_iters > 0 else group["initial_lr"]
if self.use_beta1:
group["betas"] = (self.max_momentum, *group["betas"][1:])
elif self.update_momentum:
group["momentum"] = self.max_momentum
super().__init__(optimizer, last_epoch)
def _anneal_func(self, *args, **kwargs):
if self.anneal_strategy == "cos":
return annealing_cos(*args, **kwargs)
elif self.anneal_strategy == "linear":
return annealing_linear(*args, **kwargs)
def _compute_lr_momentum(self, optimizer_param_group):
# torch.optim.lr_scheduler.LRScheduler does an initial
# step that sets self._step_count = 1
step_num = (self._step_count - 1) if self.last_epoch != -1 else 0
momentum = 0
if step_num < self.warmup_iters:
if self.warmup_ratio:
k = (1 - step_num / self.warmup_iters) * (1 - self.warmup_ratio)
warmup_lr = optimizer_param_group["max_lr"] * (1 - k)
thelr = warmup_lr * (1 - step_num / self.total_steps)
else:
gmax = (
optimizer_param_group["max_lr"] * (1 + math.cos(math.pi * step_num / float(self.total_steps))) / 2
)
thelr = optimizer_param_group["max_lr"] / self.final_div_factor + gmax * step_num / float(
self.warmup_iters
)
else:
pct = (step_num - self.warmup_iters) / float(self.total_steps - self.warmup_iters)
step_num_to_use = step_num
momentum = self._anneal_func(
self.base_momentum,
self.max_momentum,
pct,
)
if self.anneal_strategy == "cos":
step_num_to_use += 1
thelr = self._anneal_func(
optimizer_param_group["max_lr"],
optimizer_param_group["min_lr"],
step_num_to_use / float(self.total_steps),
)
return thelr, momentum
def get_lr(self):
"""Compute the learning rate of each parameter group."""
if TORCH_VERSION >= Version("2.4.0"):
torch_schedulers._warn_get_lr_called_within_step(self)
lrs = []
step_num = self.last_epoch
if step_num > self.total_steps:
raise ValueError(
f"Tried to step {step_num} times. The specified number of total steps is {self.total_steps}" # noqa: UP032
)
for group in self.optimizer.param_groups:
computed_lr, computed_momentum = self._compute_lr_momentum(group)
lrs.append(computed_lr) # type: ignore[possibly-undefined]
if self.use_beta1:
group["betas"] = (computed_momentum, *group["betas"][1:]) # type: ignore[possibly-undefined]
elif self.update_momentum:
group["momentum"] = computed_momentum # type: ignore[possibly-undefined]
return lrs
def build_scheduler(
scheduler_type: str,
optimizer: Optimizer,
lr: float,
total_iter: int,
constructor_kwargs: dict[str, Any],
):
_kwargs = {}
_kwargs.update(**constructor_kwargs)
constructor_fn = SCHEDULERS_DICT[scheduler_type]
accepted_kwargs = signature(constructor_fn).parameters.keys()
keywords = list(constructor_kwargs.keys())
for key in keywords:
if key not in accepted_kwargs:
# ignore arguments that are not part of kwargs
_kwargs.pop(key)
if scheduler_type in ["OneCycleLR", "WarmupOneCycleLR", "WarmupMultiStepLR"]:
_kwargs.update(
dict(
max_lr=lr,
total_steps=total_iter,
)
)
elif scheduler_type in [
"ConstantLR",
"LinearLR",
"PolynomialLR",
]:
constructor_kwargs.update(dict(total_iters=total_iter))
return constructor_fn(optimizer, **_kwargs)
SCHEDULERS_DICT = {
"ConstantLR": torch_schedulers.ConstantLR,
"LinearLR": torch_schedulers.LinearLR,
"MultiStepLR": torch_schedulers.MultiStepLR,
"PolynomialLR": torch_schedulers.PolynomialLR,
"StepLR": torch_schedulers.StepLR,
"OneCycleLR": torch_schedulers.OneCycleLR,
"WarmupOneCycleLR": WarmupOneCycleLR,
}