This commit is contained in:
240
ppocr/optimizer/lr_scheduler.py
Normal file
240
ppocr/optimizer/lr_scheduler.py
Normal file
@@ -0,0 +1,240 @@
|
||||
# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import math
|
||||
from paddle.optimizer.lr import LRScheduler
|
||||
|
||||
|
||||
class CyclicalCosineDecay(LRScheduler):
|
||||
def __init__(
|
||||
self, learning_rate, T_max, cycle=1, last_epoch=-1, eta_min=0.0, verbose=False
|
||||
):
|
||||
"""
|
||||
Cyclical cosine learning rate decay
|
||||
A learning rate which can be referred in https://arxiv.org/pdf/2012.12645.pdf
|
||||
Args:
|
||||
learning rate(float): learning rate
|
||||
T_max(int): maximum epoch num
|
||||
cycle(int): period of the cosine decay
|
||||
last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate.
|
||||
eta_min(float): minimum learning rate during training
|
||||
verbose(bool): whether to print learning rate for each epoch
|
||||
"""
|
||||
super(CyclicalCosineDecay, self).__init__(learning_rate, last_epoch, verbose)
|
||||
self.cycle = cycle
|
||||
self.eta_min = eta_min
|
||||
|
||||
def get_lr(self):
|
||||
if self.last_epoch == 0:
|
||||
return self.base_lr
|
||||
reletive_epoch = self.last_epoch % self.cycle
|
||||
lr = self.eta_min + 0.5 * (self.base_lr - self.eta_min) * (
|
||||
1 + math.cos(math.pi * reletive_epoch / self.cycle)
|
||||
)
|
||||
return lr
|
||||
|
||||
|
||||
class OneCycleDecay(LRScheduler):
|
||||
"""
|
||||
One Cycle learning rate decay
|
||||
A learning rate which can be referred in https://arxiv.org/abs/1708.07120
|
||||
Code referred in https://pytorch.org/docs/stable/_modules/torch/optim/lr_scheduler.html#OneCycleLR
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
max_lr,
|
||||
epochs=None,
|
||||
steps_per_epoch=None,
|
||||
pct_start=0.3,
|
||||
anneal_strategy="cos",
|
||||
div_factor=25.0,
|
||||
final_div_factor=1e4,
|
||||
three_phase=False,
|
||||
last_epoch=-1,
|
||||
verbose=False,
|
||||
):
|
||||
# Validate total_steps
|
||||
if epochs <= 0 or not isinstance(epochs, int):
|
||||
raise ValueError(
|
||||
"Expected positive integer epochs, but got {}".format(epochs)
|
||||
)
|
||||
if steps_per_epoch <= 0 or not isinstance(steps_per_epoch, int):
|
||||
raise ValueError(
|
||||
"Expected positive integer steps_per_epoch, but got {}".format(
|
||||
steps_per_epoch
|
||||
)
|
||||
)
|
||||
self.total_steps = epochs * steps_per_epoch
|
||||
|
||||
self.max_lr = max_lr
|
||||
self.initial_lr = self.max_lr / div_factor
|
||||
self.min_lr = self.initial_lr / final_div_factor
|
||||
|
||||
if three_phase:
|
||||
self._schedule_phases = [
|
||||
{
|
||||
"end_step": float(pct_start * self.total_steps) - 1,
|
||||
"start_lr": self.initial_lr,
|
||||
"end_lr": self.max_lr,
|
||||
},
|
||||
{
|
||||
"end_step": float(2 * pct_start * self.total_steps) - 2,
|
||||
"start_lr": self.max_lr,
|
||||
"end_lr": self.initial_lr,
|
||||
},
|
||||
{
|
||||
"end_step": self.total_steps - 1,
|
||||
"start_lr": self.initial_lr,
|
||||
"end_lr": self.min_lr,
|
||||
},
|
||||
]
|
||||
else:
|
||||
self._schedule_phases = [
|
||||
{
|
||||
"end_step": float(pct_start * self.total_steps) - 1,
|
||||
"start_lr": self.initial_lr,
|
||||
"end_lr": self.max_lr,
|
||||
},
|
||||
{
|
||||
"end_step": self.total_steps - 1,
|
||||
"start_lr": self.max_lr,
|
||||
"end_lr": self.min_lr,
|
||||
},
|
||||
]
|
||||
|
||||
# Validate pct_start
|
||||
if pct_start < 0 or pct_start > 1 or not isinstance(pct_start, float):
|
||||
raise ValueError(
|
||||
"Expected float between 0 and 1 pct_start, but got {}".format(pct_start)
|
||||
)
|
||||
|
||||
# Validate anneal_strategy
|
||||
if anneal_strategy not in ["cos", "linear"]:
|
||||
raise ValueError(
|
||||
"anneal_strategy must by one of 'cos' or 'linear', instead got {}".format(
|
||||
anneal_strategy
|
||||
)
|
||||
)
|
||||
elif anneal_strategy == "cos":
|
||||
self.anneal_func = self._annealing_cos
|
||||
elif anneal_strategy == "linear":
|
||||
self.anneal_func = self._annealing_linear
|
||||
|
||||
super(OneCycleDecay, self).__init__(max_lr, last_epoch, verbose)
|
||||
|
||||
def _annealing_cos(self, 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(self, start, end, pct):
|
||||
"Linearly anneal from `start` to `end` as pct goes from 0.0 to 1.0."
|
||||
return (end - start) * pct + start
|
||||
|
||||
def get_lr(self):
|
||||
computed_lr = 0.0
|
||||
step_num = self.last_epoch
|
||||
|
||||
if step_num > self.total_steps:
|
||||
raise ValueError(
|
||||
"Tried to step {} times. The specified number of total steps is {}".format(
|
||||
step_num + 1, self.total_steps
|
||||
)
|
||||
)
|
||||
start_step = 0
|
||||
for i, phase in enumerate(self._schedule_phases):
|
||||
end_step = phase["end_step"]
|
||||
if step_num <= end_step or i == len(self._schedule_phases) - 1:
|
||||
pct = (step_num - start_step) / (end_step - start_step)
|
||||
computed_lr = self.anneal_func(phase["start_lr"], phase["end_lr"], pct)
|
||||
break
|
||||
start_step = phase["end_step"]
|
||||
|
||||
return computed_lr
|
||||
|
||||
|
||||
class TwoStepCosineDecay(LRScheduler):
|
||||
def __init__(
|
||||
self, learning_rate, T_max1, T_max2, eta_min=0, last_epoch=-1, verbose=False
|
||||
):
|
||||
if not isinstance(T_max1, int):
|
||||
raise TypeError(
|
||||
"The type of 'T_max1' in 'CosineAnnealingDecay' must be 'int', but received %s."
|
||||
% type(T_max1)
|
||||
)
|
||||
if not isinstance(T_max2, int):
|
||||
raise TypeError(
|
||||
"The type of 'T_max2' in 'CosineAnnealingDecay' must be 'int', but received %s."
|
||||
% type(T_max2)
|
||||
)
|
||||
if not isinstance(eta_min, (float, int)):
|
||||
raise TypeError(
|
||||
"The type of 'eta_min' in 'CosineAnnealingDecay' must be 'float, int', but received %s."
|
||||
% type(eta_min)
|
||||
)
|
||||
assert T_max1 > 0 and isinstance(
|
||||
T_max1, int
|
||||
), " 'T_max1' must be a positive integer."
|
||||
assert T_max2 > 0 and isinstance(
|
||||
T_max2, int
|
||||
), " 'T_max1' must be a positive integer."
|
||||
self.T_max1 = T_max1
|
||||
self.T_max2 = T_max2
|
||||
self.eta_min = float(eta_min)
|
||||
super(TwoStepCosineDecay, self).__init__(learning_rate, last_epoch, verbose)
|
||||
|
||||
def get_lr(self):
|
||||
if self.last_epoch <= self.T_max1:
|
||||
if self.last_epoch == 0:
|
||||
return self.base_lr
|
||||
elif (self.last_epoch - 1 - self.T_max1) % (2 * self.T_max1) == 0:
|
||||
return (
|
||||
self.last_lr
|
||||
+ (self.base_lr - self.eta_min)
|
||||
* (1 - math.cos(math.pi / self.T_max1))
|
||||
/ 2
|
||||
)
|
||||
|
||||
return (1 + math.cos(math.pi * self.last_epoch / self.T_max1)) / (
|
||||
1 + math.cos(math.pi * (self.last_epoch - 1) / self.T_max1)
|
||||
) * (self.last_lr - self.eta_min) + self.eta_min
|
||||
else:
|
||||
if (self.last_epoch - 1 - self.T_max2) % (2 * self.T_max2) == 0:
|
||||
return (
|
||||
self.last_lr
|
||||
+ (self.base_lr - self.eta_min)
|
||||
* (1 - math.cos(math.pi / self.T_max2))
|
||||
/ 2
|
||||
)
|
||||
|
||||
return (1 + math.cos(math.pi * self.last_epoch / self.T_max2)) / (
|
||||
1 + math.cos(math.pi * (self.last_epoch - 1) / self.T_max2)
|
||||
) * (self.last_lr - self.eta_min) + self.eta_min
|
||||
|
||||
def _get_closed_form_lr(self):
|
||||
if self.last_epoch <= self.T_max1:
|
||||
return (
|
||||
self.eta_min
|
||||
+ (self.base_lr - self.eta_min)
|
||||
* (1 + math.cos(math.pi * self.last_epoch / self.T_max1))
|
||||
/ 2
|
||||
)
|
||||
else:
|
||||
return (
|
||||
self.eta_min
|
||||
+ (self.base_lr - self.eta_min)
|
||||
* (1 + math.cos(math.pi * self.last_epoch / self.T_max2))
|
||||
/ 2
|
||||
)
|
||||
Reference in New Issue
Block a user