aryadomain's picture
Add files using upload-large-folder tool
6d6dbbc verified
Raw
History Blame Contribute Delete
6.9 kB
"""
Learning rate schedulers for gradient ascent optimization.
Provides various LR scheduling strategies for reward-guided gradient ascent,
including cosine annealing, linear decay, and custom schedules.
"""
import math
from typing import Optional, Literal
class LRScheduler:
"""Base class for learning rate schedulers."""
def __init__(self, initial_lr: float, num_steps: int):
"""
Initialize LR scheduler.
Args:
initial_lr: Initial learning rate
num_steps: Total number of optimization steps
"""
self.initial_lr = initial_lr
self.num_steps = num_steps
self.current_step = 0
def get_lr(self) -> float:
"""Get current learning rate."""
raise NotImplementedError
def step(self):
"""Update scheduler state after a step."""
self.current_step += 1
def reset(self):
"""Reset scheduler state."""
self.current_step = 0
class ConstantLR(LRScheduler):
"""Constant learning rate (no scheduling)."""
def get_lr(self) -> float:
return self.initial_lr
class LinearLR(LRScheduler):
"""Linear learning rate decay."""
def __init__(
self,
initial_lr: float,
num_steps: int,
end_lr: float = 0.0,
start_step: int = 0,
):
"""
Initialize linear LR scheduler.
Args:
initial_lr: Starting learning rate
num_steps: Total number of steps
end_lr: Ending learning rate (default: 0.0)
start_step: Step to begin decay (default: 0)
"""
super().__init__(initial_lr, num_steps)
self.end_lr = end_lr
self.start_step = start_step
def get_lr(self) -> float:
if self.current_step < self.start_step:
return self.initial_lr
progress = (self.current_step - self.start_step) / (self.num_steps - self.start_step)
progress = min(1.0, progress)
return self.initial_lr + (self.end_lr - self.initial_lr) * progress
class CosineLR(LRScheduler):
"""Cosine annealing learning rate schedule."""
def __init__(
self,
initial_lr: float,
num_steps: int,
min_lr: float = 0.0,
warmup_steps: int = 0,
):
"""
Initialize cosine LR scheduler.
Args:
initial_lr: Maximum learning rate
num_steps: Total number of steps
min_lr: Minimum learning rate (default: 0.0)
warmup_steps: Number of linear warmup steps (default: 0)
"""
super().__init__(initial_lr, num_steps)
self.min_lr = min_lr
self.warmup_steps = warmup_steps
def get_lr(self) -> float:
if self.current_step < self.warmup_steps:
# Linear warmup
return self.initial_lr * (self.current_step / self.warmup_steps)
# Cosine annealing
progress = (self.current_step - self.warmup_steps) / (self.num_steps - self.warmup_steps)
progress = min(1.0, progress)
cosine_decay = 0.5 * (1 + math.cos(math.pi * progress))
return self.min_lr + (self.initial_lr - self.min_lr) * cosine_decay
class ExponentialLR(LRScheduler):
"""Exponential learning rate decay."""
def __init__(
self,
initial_lr: float,
num_steps: int,
gamma: float = 0.95,
):
"""
Initialize exponential LR scheduler.
Args:
initial_lr: Starting learning rate
num_steps: Total number of steps
gamma: Multiplicative decay factor per step
"""
super().__init__(initial_lr, num_steps)
self.gamma = gamma
def get_lr(self) -> float:
return self.initial_lr * (self.gamma ** self.current_step)
class StepLR(LRScheduler):
"""Step-wise learning rate decay."""
def __init__(
self,
initial_lr: float,
num_steps: int,
step_size: int,
gamma: float = 0.1,
):
"""
Initialize step LR scheduler.
Args:
initial_lr: Starting learning rate
num_steps: Total number of steps
step_size: Number of steps between each decay
gamma: Multiplicative decay factor
"""
super().__init__(initial_lr, num_steps)
self.step_size = step_size
self.gamma = gamma
def get_lr(self) -> float:
num_decays = self.current_step // self.step_size
return self.initial_lr * (self.gamma ** num_decays)
def create_lr_scheduler(
scheduler_type: Literal["constant", "linear", "cosine", "exponential", "step"],
initial_lr: float,
num_steps: int,
**kwargs
) -> LRScheduler:
"""
Factory function to create learning rate schedulers.
Args:
scheduler_type: Type of scheduler ("constant", "linear", "cosine", "exponential", "step")
initial_lr: Initial learning rate
num_steps: Total number of optimization steps
**kwargs: Additional scheduler-specific arguments
For linear: end_lr, start_step
For cosine: min_lr, warmup_steps
For exponential: gamma
For step: step_size, gamma
Returns:
LRScheduler instance
Examples:
# Constant LR
scheduler = create_lr_scheduler("constant", initial_lr=0.1, num_steps=100)
# Linear decay
scheduler = create_lr_scheduler("linear", initial_lr=0.1, num_steps=100, end_lr=0.01)
# Cosine annealing with warmup
scheduler = create_lr_scheduler("cosine", initial_lr=0.1, num_steps=100,
min_lr=0.001, warmup_steps=10)
"""
if scheduler_type == "constant":
return ConstantLR(initial_lr, num_steps)
elif scheduler_type == "linear":
return LinearLR(
initial_lr, num_steps,
end_lr=kwargs.get("end_lr", 0.0),
start_step=kwargs.get("start_step", 0),
)
elif scheduler_type == "cosine":
return CosineLR(
initial_lr, num_steps,
min_lr=kwargs.get("min_lr", 0.0),
warmup_steps=kwargs.get("warmup_steps", 0),
)
elif scheduler_type == "exponential":
return ExponentialLR(
initial_lr, num_steps,
gamma=kwargs.get("gamma", 0.95),
)
elif scheduler_type == "step":
return StepLR(
initial_lr, num_steps,
step_size=kwargs.get("step_size", 10),
gamma=kwargs.get("gamma", 0.1),
)
else:
raise ValueError(f"Unknown scheduler type: {scheduler_type}. "
f"Choose from: constant, linear, cosine, exponential, step")