# mypy: 允许未类型化定义
rRAdam 算法的实现
from 打字
导入
角色,
可选,
联合
导入
火炬
from 火炬
导入
张量
from .优化器
导入 (
_可捕获文档,
默认为融合或遍历,
_可区分文档,
_如果不受支持则禁用 Dynamo,
_foreach_doc,
_获取可捕获的设备,
_获取标量数据类型,
_获取值,
_maximize_doc,
_params_doc,
_use_grad_for_differentiable,
真实查看,
优化器,
参数 T,
)
全部 = ["RAdam",
radam]
[文档]
类 RAdam(
优化器):
# 无需注意:D101
定义 __init__(
我,
参数:
参数 T,
学习率:
并集[float,
张量] =
0.001,
betas: 元组[float, float] = (0.9, 0.999),
eps: 浮点数 = 1e-8,
权重衰减:
浮点数 = 0,
解耦权重衰减:
布尔值 =
错误,
*,
foreach: 可选[
布尔] =
无,
最大化:
布尔值 =
错误,
可捕捉的:
布尔值 =
错误,
可微分的:
布尔值 =
错误,
): # 无需注意:D107
如果 isinstance(
学习率,
张量)
和
学习率.
元素数量() != 1:
提升 ValueError(
"Tensor lr 必须是 1 个元素")
如果
不 0.0 <=
学习率:
提升 ValueError(f
"无效的学习率:"{
学习率}")
如果
不 0.0 <= eps:
提升 ValueError(f
"无效的 epsilon 值:"{eps}")
如果
不 0.0 <= betas[0] < 1.0:
提升 ValueError(f
"在索引 0 处的无效 beta 参数:"{betas[0]}")
如果
不 0.0 <= betas[1] < 1.0:
提升 ValueError(f
"无效的 beta 参数,索引为 1:"{betas[1]}")
如果
不 0.0 <=
权重衰减:
提升 ValueError(f
"无效的 weight_decay 值:"{
权重衰减}")
默认 =
字典(
学习率=
学习率,
betas=betas,
eps=eps,
权重衰减=
权重衰减,
最大化=
最大化,
foreach=foreach,
可捕捉的=
可捕捉的,
解耦权重衰减=
解耦权重衰减,
可微分的=
可微分的,
)
超级().__init__(
参数,
默认值)
定义 __setstate__(
我,
状态):
# 无需注意:D105
超级().__setstate__(
状态)
for 组
在
我.
参数组:
群组.setdefault(
foreach,
无)
群组.setdefault(
最大化,
错误)
群组.setdefault(
可微分的,
错误)
群组.setdefault(
"解耦权重衰减",
错误)
群组.setdefault(
"可捕获的",
错误)
for p 在
群组[
参数]:
状态 =
我.
状态.
获取(p,
[]
如果
长度(
状态) != 0
和
不 torch.is_tensor(
状态[
步骤
)]
步骤值 = float(
状态[
步骤])
p_state[步骤] = (
torch.张量(
步值,
数据类型=
_获取标量数据类型(),
设备=p.
设备
)
如果
群组[
"可捕获的"]
否则 torch.
张量(
步值,
数据类型=
_获取标量数据类型())
)
定义
初始化组(
我,
群组,
带梯度的参数,
梯度,
实验平均值,
实验平均平方值,
状态步骤
):
复杂的参数 =
假
for p 在
群组[
参数]:
如果 p.
梯度
是
不
无:
复杂的参数 |=
火炬.
是复杂的(p)
带梯度的参数.append(p)
如果 p.
研究生.is_sparse:
提升
运行时错误(
"RAdam 不支持稀疏梯度")
梯度.append(p.
研究生)
状态 =
我.
状态[p]
懒惰状态初始化
如果
长度(
状态) == 0:
状态[
步骤] = (
火炬.
零值((),
数据类型=
_获取标量数据类型(),
设备=p.
设备)
如果
群组[
"可捕获的"]
否则
火炬.
张量(0.0,
数据类型=
_获取标量数据类型())
)
指数移动平均的梯度值
状态[
平均值] =
火炬.
等于零的(
p, 内存格式=
火炬.
保留格式
)
平方梯度值的指数移动平均
状态["exp_avg_sq"] =
火炬.
等于零的(
p, 内存格式=
火炬.
保留格式
)
实验平均值.append(
状态[
平均值])
实验平均平方值.append(
状态["exp_avg_sq"])
状态步数.append(
状态[
步骤])
返回
复杂的参数
[文档] @使用梯度进行可微分
def step(self, closure=None):
"""执行单个优化步骤。
Args:
闭包(Callable,可选):一个重新评估模型并返回损失的闭包。
。
"""
self._cuda_graph_capture_health_check()
loss = None
if closure is not None:
with torch.enable_grad():
loss = closure()
for group in self.param_groups:
params_with_grad: list[Tensor] = []
grads: list[Tensor] = []
exp_avgs: 列[Tensor] = []
exp_avg_sqs: 列[Tensor] = []
state_steps: 列[Tensor] = []
beta1, beta2 = cast(tuple[float, float], group["betas"])
has_complex = self._init_group(
group, params_with_grad, grads, exp_avgs, exp_avg_sqs, state_steps
)
radam(
params_with_grad,
grads,
exp_avgs,
exp_avg_sqs,
状态步骤,
beta1=beta1,
beta2=beta2,
lr=group["lr"],
weight_decay=group["weight_decay"],
eps=group["eps"],
maximize=group["maximize"],
foreach=group["foreach"],
capturable=group["capturable"]
differentiable=group["differentiable"]
decoupled_weight_decay=group["decoupled_weight_decay"]
has_complex=has_complex
)
返回损失
RAdam.__doc__ = (
r实现 RAdam 算法。
.. math::
\begin{aligned}
&\rule{110mm}{0.4pt} \\
&textbf{输入} : γ (lr), β1, β2
(betas), θ0 (params), f(θ) (目标函数),
λ (权重衰减), \:\textit{maximize} \\
&\hspace{13mm} ε (epsilon), \textit{decoupled\_weight\_decay} \\
初始化:m_0 ← 0(第一矩)
v_0 ← 0(二阶矩),\\
&\hspace{18mm} \rho_{\infty} \leftarrow \frac{2}{1-\beta_2} -1
&\rule{110mm}{0.4pt} \\
for t=1 到 ... do
如果 \: \textit{最大化}: \\
g_t ← -∇_θ f_t (θ_{t-1}) \\
&\hspace{6mm}else \\
g_t ← ∇_θ f_t (θ_{t-1}) \\
&\hspace{6mm} θ_t ← θ_{t-1} \\
如果 λ 不等于 0
如果 decoupled_weight_decay
&\hspace{18mm} θ_t ← θ_t - γλθ_t \\
否则
&\hspace{18mm} g_t ← g_t + λθ_t
m_t ← β_1 m_{t-1} + (1 - β_1) g_t \\
v_t ← β_2 v_{t-1} + (1-β_2) g^2_t
&\hspace{6mm}\widehat{m_t} \leftarrow m_t/\big(1-\beta_1^t \big) \\
翻译:&\hspace{6mm}\widehat{m_t} \leftarrow m_t/\big(1-\beta_1^t \big) \\
&\hspace{6mm}\rho_t \leftarrow \rho_{\infty} -
2 t β^t_2 /\big(1-β_2^t \big) \\[0.1.ex]
如果 \: \rho_t > 5 \\
l_t ← √((1-β^t_2)) / (√v_t + ε)
r_t ←
\sqrt{\frac{(\rho_t-4)(\rho_t-2)\rho_{\infty}}{(\rho_{\infty}-4)(\rho_{\infty}-2) \rho_t}} \\
\sqrt{\frac{(\rho_t-4)(\rho_t-2)\rho_{\infty}}{(\rho_{\infty}-4)(\rho_{\infty}-2) \rho_t}} \\
&\hspace{12mm}θ_t ← θ_t - γ 估计 m_t r_t l_t \\
否则
&\hspace{12mm}\theta_t \leftarrow \theta_t - \gamma \widehat{m_t} \\
&\规则{110 毫米}{0.4 点} \\[-1.ex]
&\bf{return} \: \theta_t \\[-1.ex]
&\规则{110 毫米}{0.4 点} \\[-1.ex]
\end{aligned}
有关算法的更多详细信息,请参阅《自适应学习率方差及其超越》_。
此实现提供了一种选项,可以使用 Adam 中的原始 weight_decay 实现
(其中 weight_decay 应用于梯度)或 AdamW 中的实现
通过 decoupled_weight_decay 选项调整权重。当 decoupled_weight_decay 设置为 False 时
(默认),它使用原始 Adam 风格的权重衰减,否则,它使用 AdamW 风格
更接近于 RAdam 论文中`作者的实现`。更多信息
关于解耦权重衰减的内容,可以在《解耦权重衰减正则化》_中找到。
"文档"
+ rf""
参数:
{_params_doc}
lr(浮点数,张量,可选):学习率(默认:1e-3)
betas (Tuple[float, float], 可选): 用于计算系数
运行平均梯度及其平方(默认:(0.9, 0.999))
eps (float, 可选): 添加到分母中的项,以改善
数值稳定性(默认:1e-8)
权重衰减(float,可选):权重衰减(L2 惩罚)(默认:0)
decoupled_weight_decay (bool, 可选): 是否解耦权重
类似 AdamW 中的衰减以获得 RAdamW。如果为 True,则算法不会在动量或方差中累积权重衰减。(默认:False)
在动量或方差中累积权重衰减。(默认:False)
{_foreach_doc}
{_maximize_doc}
{_可捕获文档}
{_可区分文档}
.. _On the variance of the adaptive learning rate and beyond:
https://arxiv.org/abs/1908.03265
.. _作者实现:
https://github.com/LiyuanLucasLiu/RAdam
.. _解耦权重衰减正则化:
https://arxiv.org/abs/1711.05101
"文档"
)
定义 _single_tensor_radam(
参数:
列表[
张量
]
梯度:
列表[
张量
]
实验平均值:
列表[
张量
]
实验平均平方值:
列表[
张量
]
状态步数:
列表[
张量
]
*,
beta1: float,
beta2: float,
学习率: float,
权重衰减: float,
eps: float,
解耦权重衰减:
布尔,
可微分的:
布尔,
最大化:
布尔,
可捕捉的:
布尔,
有复杂的:
布尔,
):
for i, 参数
在
列举(
参数):
梯度 =
梯度[i]
如果
不
最大化
否则 -
梯度[i]
指数平均值 =
实验平均值[i]
指数平均值的平方 =
实验平均平方值[i]
步骤_t =
状态步数[i]
如果编译,编译器将处理 cudagraph 检查,参见注释[torch.compile x capturable]
如果
不
火炬.
编译器.is_compiling()
和
可捕捉的:
可捕获支持的设备 =
_获取可捕获的设备()
断言 (
参数.
设备.
类型 ==
步骤_t.
设备.
类型
和
参数.
设备.
类型
在
可捕获支持的设备
), f如果 capturable=True,则 params 和 state_steps 必须在支持的设备上:{
支持捕获的设备}
。
如果
火炬.
是复杂的(
参数):
参数 =
火炬.
真实查看(
参数)
梯度 =
火炬.
真实查看(
研究生)
指数平均值 =
火炬.
真实查看(exp_avg)
指数平均值的平方 =
火炬.
真实查看(
指数平均平方)
# 更新步骤
步骤_t += 1
步骤 =
步骤_t
如果
可捕获的
否则
_获取值(
步骤_t)
如果
权重衰减 != 0:
如果
解耦权重衰减:
参数.mul_(1 -
左右 *
权重衰减)
else:
梯度 =
研究生.
添加(
参数,
阿尔法=
权重衰减)
衰减第一个和第二个矩度的运行平均值系数
exp_avg.线性插值 _(
研究生, 1 - beta1)
指数平均平方.mul_(beta2).addcmul_(
研究生,
研究生,
值=1 - beta2)
偏差校正 1 = 1 - beta1**
步骤
偏差校正 2 = 1 - beta2**
步骤
纠正第一移动矩量的偏差
偏差校正的期望平均值 =
指数平均值 /
偏差校正 1
近似 SMA 的最大长度
比值无穷 = 2 / (1 - beta2) - 1
计算近似 SMA 的长度
ρ_t =
ρ_∞ - 2 *
步骤 * (beta2**
步长) /
偏差校正 2
定义 _compute_rect():
返回 (
(rho_t - 4)
* (rho_t - 2)
* ρ∞
/ ((ρ∞ - 4) * (
ρ∞ - 2) * rho_t)
) ** 0.5
定义 _compute_adaptive_lr():
期望平均平方根 =
指数平均平方.
平方根()
如果
可微分的:
期望平均平方根 =
指数平均平方根.
添加(eps)
else:
期望平均平方根 =
指数平均平方根.
加_(eps)
返回 (
偏差校正 2**0.5) /
期望平均平方根
# 计算方差校正项并相应更新参数
如果
可捕捉的:
更新 =
火炬.
哪里(
rho_t > 5.0, _compute_rect() * _compute_adaptive_lr(), 1.0
)
参数.
加_(bias_corrected_exp_avg *
左右 *
更新,
阿尔法=-1.0)
else:
如果 rho_t > 5.0:
参数.
加_(
偏差校正的指数平均
* 左右
* _compute_adaptive_lr()
* _compute_rect(),
阿尔法=-1.0,
)
else:
参数.
加_(
偏差校正平均 *
学习率,
阿尔法=-1.0)
定义
多张量 RAdam(
参数:
列表[
张量
]
梯度:
列表[
张量
]
实验平均值:
列表[
张量
]
实验平均平方值:
列表[
张量
]
状态步数:
列表[
张量
]
*,
beta1: float,
beta2: float,
学习率: float,
权重衰减: float,
eps: float,
解耦权重衰减:
布尔,
可微分的:
布尔,
最大化:
布尔,
可捕捉的:
布尔,
有复杂的:
布尔,
):
如果
长度(
参数) == 0:
返回
断言
不
可微分的,
"_foreach 操作不支持自动求导"
如果编译,编译器将处理 cudagraph 检查,参见注释[torch.compile x capturable]
如果
不
火炬.
编译器.is_compiling()
和
可捕捉的:
可捕获支持的设备 =
_获取可捕获的设备(
支持 XLA=
假
)
断言
所有(
p.设备.
类型 ==
步长.
设备.
类型
和 p.
设备.
类型
在
可捕获支持的设备
for p, 步骤
在 zip(
参数,
状态步数)
), f如果 capturable=True,则 params 和 state_steps 必须在支持的设备上:{
支持捕获的设备}
。
分组张量 =
优化器.
按设备类型和数据类型分组张量(
[参数,
梯度,
实验平均值,
实验平均平方值,
状态步数] # type: ignore[list-item]
)
for (
分组参数_,
分组梯度_,
分组实验平均值_,
分组实验平均平方,
分组状态步骤_,
), _ 在
分组张量.
值():
分组参数 =
角色(
列表[
张量
]
分组参数_)
分组梯度 =
角色(
列表[
张量
]
分组梯度_)
分组实验平均值 =
角色(
列表[
张量
]
分组实验平均值_)
分组实验平均平方 =
角色(
列表[
张量
]
分组实验平均平方)
分组状态步骤 =
角色(
列表[
张量
]
分组状态步骤_)
# 更新步骤
# 如果步骤在 CPU 上,foreach 将回退到慢速路径,即通过循环调用 t.add(1)
#然后 1 会被反复包裹成 Tensor,这比只包裹一次要慢。
#alpha 是必须的,以确保我们进入正确的重载。
如果
不
火炬.
编译器.is_compiling()
和
分组状态步骤[0].
是 CPU:
火炬._foreach_add_(
分组状态步骤,
火炬.
张量(1.0,
设备="cpu"),
阿尔法=1.0
)
else:
火炬._foreach_add_(
分组状态步骤, 1)
如果
有复杂的:
真实查看(
分组参数,
分组梯度,
分组实验平均值,
分组经验平均平方
)
如果
最大化:
分组梯度 =
火炬._foreach_neg(
分组梯度)
# 类型:忽略[赋值]
近似 SMA 的最大长度
比值无穷 = 2 / (1 - beta2) - 1
计算近似 SMA 的长度
偏差校正 1:
并集[
元组[
张量, ...
]
列表[
张量]]
偏差校正 2:
并集[
元组[
张量, ...
]
列表[
张量]]
rho_t_list: 并集[
元组[
张量, ...
]
列表[
张量]]
如果
可捕捉的:
偏差校正 1 =
火炬._foreach_pow(beta2,
分组状态步骤)
火炬._foreach_neg_(
偏差校正 1)
火炬._foreach_add_(
偏差校正 1, 1)
偏差校正 2 =
火炬._foreach_pow(beta2,
分组状态步骤)
火炬._foreach_mul_(
偏差校正 2,
分组状态步骤)
火炬._foreach_mul_(
偏差校正 2, 2)
火炬._foreach_div_(
偏差校正 2,
偏差校正 1)
火炬._foreach_neg_(
偏差校正 2)
火炬._foreach_add_(
偏差校正 2,
ρ∞)
ρt 列表 =
偏差校正 2
else:
ρt 列表 = [
ρ∞
- 2
* _获取值(
步长)
* (beta2 ** _获取值(
步长))
/ (1 - beta2 ** _获取值(
步长))
for 步骤
在
分组状态步骤
]
如果
权重衰减 != 0:
如果
解耦权重衰减:
火炬._foreach_mul_(
分组参数, 1 -
左右 *
权重衰减)
else:
重复使用已分配给最大化操作的中间内存(grouped_grads)
如果
最大化:
火炬._foreach_add_(
分组梯度,
分组参数,
阿尔法=
权重衰减
)
else:
分组梯度 =
火炬.
_foreach_add_(
# 类型:忽略[赋值]
分组梯度,
分组参数,
阿尔法=
权重衰减
)
衰减第一个和第二个矩度的运行平均值系数
火炬._foreach_lerp_(
分组实验平均值,
分组梯度, 1 - beta1)
火炬._foreach_mul_(
分组经验平均平方, beta2)
火炬._foreach_addcmul_(
分组经验平均平方,
分组梯度,
分组梯度, 1 - beta2
)
删除本地中间文件,因为它将不再使用,以节省峰值内存
删除
分组梯度
如果
可捕捉的:
数字 =
火炬.
遍历子项(rho_t_list, 4)
sub2 = 火炬.
遍历子项(rho_t_list, 2)
火炬._foreach_mul_(
数字, sub2)
删除 sub2
torch._foreach_mul_(数字,
无穷大)
ρ_∞ = (
ρ_∞ - 4) * (
ρ_∞ - 2)
分母 = torch._foreach_mul(
ρ_t_列表, rho_inf)
torch._foreach_div_(数字, denom)
删除
分母
torch._foreach_sqrt__(
数字)
# TODO(mlazos): 我们应该尝试获取 foreach_where 操作 https://github.com/pytorch/pytorch/issues/117884
矩形 = [
torch.哪里(rho_t > 5.0, n, 0.0) for n,
ρ_t
在 zip(
数字,
ρ_t 列表)
]
删除
数字
删除
ρ_t 列表
未校正步长 = [torch.
哪里(
矩形 > 0, 0.0, 1.0) for
矩形
在
矩形]
torch._foreach_mul_(非矩形步长,
学习率)
偏差校正 1 = torch._foreach_pow(beta1,
分组状态步骤)
torch._foreach_neg_(偏差校正 1)
torch._foreach_add_(偏差校正 1, 1)
torch._foreach_div_(不规则步长,
偏差校正 1)
torch._foreach_neg_(不规则步长)
偏差校正 2 = torch._foreach_pow(beta2,
分组状态步骤)
torch._foreach_neg_(偏差校正 2)
torch._foreach_add_(偏差校正 2, 1)
torch._foreach_sqrt__(
偏差校正 2)
torch._foreach_mul_(偏差校正 2,
学习率)
torch._foreach_mul_(偏差校正 2,
矩形)
删除
矩形
torch._foreach_neg_(偏差校正 2)
torch._foreach_div_(偏差校正 2,
偏差校正 1)
删除
偏差校正 1
else:
矩形 = [
( # 类型:忽略[杂项]
(rho_t - 4) # type: ignore[arg-type]
* (ρ_t - 2)
* ρ_∞
/ ((ρ_∞ - 4) * (
ρ_∞ - 2) *
ρ_t)
)
** 0.5
如果
ρ_t > 5
否则 0
for ρ_t
在
ρ_t_list
]
未校正的 = [0
如果
矫正 > 0
否则 1.0 for
矫正
在
矫正]
偏差校正 1 = [
1 - beta1 ** _获取值(
步长) for
步骤
在
分组状态步骤
]
未校正步长 = [
(左右 *
矩形 / bc) * -1 for
矩形, bc
在 zip(
未校正的,
偏差校正 1)
]
偏差校正 2 = [
((1 - beta2 ** _获取值(
步长)) ** 0.5) * (
左右 *
矫正 / bc) * -1
for 步长,
矫正, bc
在 zip(
分组状态步骤,
矩形,
偏差校正 1)
]
缓冲区 = torch.
_foreach 平方根(
分组经验平均平方)
torch._foreach_add_(缓冲区, eps)
torch._foreach_div_(缓冲区,
偏差校正 2)
torch._foreach_reciprocal_(缓冲区)
torch._foreach_add_(缓冲区,
非矩形步长)
# 这里,buffer = sqrt(1 - beta2^t) * 矩形步长 / (sqrt(v) + eps) + 非矩形步长
torch._foreach_addcmul_(分组参数,
分组实验平均值,
缓冲区)
@_disable_dynamo_if_unsupported(单个张量函数=
_单张量 radam)
定义 radam(
参数:
列表[
张量
]
梯度:
列表[
张量
]
实验平均值:
列表[
张量
]
实验平均平方值:
列表[
张量
]
状态步数:
列表[
张量
]
使用 torchscript 编译的函数不支持带默认值的只写关键字参数问题 #70627
现由 torch/distributed/optim 编译的函数 API 参数
解耦权重衰减:
布尔值 =
错误,
foreach: 可选[
布尔] =
无,
可微分的:
布尔值 =
错误,
可捕捉的:
布尔值 =
错误,
有复杂的:
布尔值 =
错误,
最大化:
布尔值 =
错误,
*,
beta1: float,
beta2: float,
学习率: float,
权重衰减: float,
eps: float,
):
r功能性 API,执行 RAdam 算法计算。
详细信息请参阅 :class:`~torch.optim.RAdam`。
"文档"
如果
不
所有(isinstance(t, torch.
张量) for t
在
状态步数):
提升
运行时错误(
"API 已更改,`state_steps`参数必须包含一个单例张量列表"
)
如果 foreach
是
无:
_, foreach = 默认为融合或遍历(
参数,
可微分的,
使用融合的=
假
)
如果 foreach
和 torch.
算子.
是否正在脚本化():
提升
运行时错误(
"torch.jit.script 不支持 foreach 优化器")
如果 foreach
和
不 torch.
算子.
是否正在脚本化():
函数 =
_多张量 RAdam
else:
函数 =
_单张量 RAdam
函数(
参数,
梯度,
实验平均值,
实验平均平方值,
状态步数,
beta1=beta1,
beta2=beta2,
学习率=
学习率,
权重衰减=
权重衰减,
eps=eps,
最大化=
最大化,
解耦权重衰减=
解耦权重衰减,
可微分的=
可微分的,
可捕捉的=
可捕捉的,
有复杂的=
有复杂的,
)