# mypy: 允许未类型化定义
r实现鲁棒反向传播。
from 打字
导入
角色,
可选,
联合
导入
火炬
from 火炬
导入
张量
from .优化器
导入 (
_可捕获文档,
默认为融合或遍历,
_可区分文档,
_如果不受支持则禁用 Dynamo,
_foreach_doc,
_获取可捕获的设备,
_获取标量数据类型,
_maximize_doc,
_params_doc,
_use_grad_for_differentiable,
真实查看,
优化器,
参数 T,
)
全部 = [
Rprop,
rprop]
[文档]
类 Rprop(
优化器):
# 无需注意:D101
定义 __init__(
我,
参数:
参数 T,
学习率:
并集[float,
张量] =
0.01,
etas: 元组[float, float] = (0.5, 1.2),
步长大小:
元组[float, float] = (1e-6, 50),
*,
可捕捉的:
布尔值 =
错误,
foreach: 可选[
布尔] =
无,
最大化:
布尔值 =
错误,
可微分的:
布尔值 =
错误,
): # 无需注意:D107
如果 isinstance(
学习率,
张量)
和
学习率.
元素数量() != 1:
提升 ValueError(
"Tensor lr 必须是 1 个元素")
如果
不 0.0 <=
学习率:
提升 ValueError(f
"无效的学习率:"{
学习率}")
如果
不 0.0 < etas[0] < 1.0 < etas[1]:
提升 ValueError(f
无效的预计到达时间值:{etas[0]}, {etas[1]}")
默认 =
字典(
学习率=
学习率,
etas=etas,
步长大小=
步长大小,
foreach=foreach,
最大化=
最大化,
可微分的=
可微分的,
可捕捉的=
可捕捉的,
)
超级().__init__(
参数,
默认值)
定义 __setstate__(
我,
状态):
# 无需注意:D105
超级().__setstate__(
状态)
for 组
在
我.
参数组:
群组.setdefault(
foreach,
无)
群组.setdefault(
最大化,
错误)
群组.setdefault(
可微分的,
错误)
群组.setdefault(
"可捕获的",
错误)
for p 在
群组[
参数]:
状态 =
我.
状态.
获取(p,
[]
如果
长度(
状态) != 0
和
不 torch.is_tensor(
状态[
步骤
)]
步骤值 = float(
状态[
步骤])
状态[
步骤] = (
torch.张量(
步值,
数据类型=
_获取标量数据类型(),
设备=p.
设备
)
如果
群组[
"可捕获的"]
否则 torch.
张量(
步值,
数据类型=
_获取标量数据类型())
)
定义
初始化组(
我,
群组,
参数,
梯度,
前者,
步长大小,
状态步数):
复杂的参数 =
假
for p 在
群组[
参数]:
如果 p.
梯度
是
无:
continue
复杂的参数 |= torch.
是复杂的(p)
参数.append(p)
梯度 = p.
梯度
如果
研究生.is_sparse:
提升
运行时错误(
Rprop 不支持稀疏梯度)
梯度.append(
研究生)
状态 =
我.
状态[p]
# 状态初始化
如果
长度(
状态) == 0:
状态[
步骤] = (
torch.零值((),
数据类型=
_获取标量数据类型(),
设备=p.
设备)
如果
群组[
"可捕获的"]
否则 torch.
零值((),
数据类型=
_获取标量数据类型())
)
状态[
prev] = torch.
等于零的(p,
内存格式=torch.
保留格式)
如果 p.
数据类型.
是复杂的:
复数应该像两个独立的实数一样处理。
因此,步长不应该为零,对于虚部来说。
状态[
步长] = torch.
完全一样(
研究生,
复杂(
群组["lr"
]
群组["lr"])
)
else:
状态[
步长] = torch.
完全一样(
研究生,
群组["lr"])
前面.append(
状态[
前一个])
步长大小.append(
状态[
步长])
状态步数.append(
状态[
步骤])
返回
复杂的参数
[文档] @_use_grad_for_differentiable
def 步(self, closure=None):
执行单次优化步骤。
参数:
闭包(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: list[Tensor] = []
grads: 列表[Tensor] = []
prevs: 列表[Tensor] = []
step_sizes: 列表[Tensor] = []
state_steps: 列表[Tensor] = []
etaminus, etaplus = group["etas"]
step_size_min, step_size_max = group["step_sizes"]
foreach = group["foreach"]
maximize = group["maximize"]
has_complex = self._init_group(
group, params, grads, prevs, step_sizes, state_steps
)
rprop(
params,
grads,
prevs,
step_sizes,
state_steps,
step_size_min=step_size_min,
step_size_max=step_size_max,
etaminus=etaminus,
etaplus=etaplus,
foreach=foreach,
maximize=maximize,
differentiable=group["differentiable"],
capturable=group["capturable"]
has_complex=has_complex
)
返回损耗
Rprop.__doc__ = (
r实现了鲁棒反向传播算法。
.. math::
\begin{aligned}
&\rule{110mm}{0.4pt} \\
输入 &\textbf{input} : \theta_0 \in \mathbf{R}^d \text{ (params)},f(\theta)
\text{ (目标函数)}, \\
&\hspace{13mm} \eta_{+/-} \text{ (etaplus, etaminus)}, \Gamma_{max/min}
&\hspace{13mm} \eta_{+/-}(etaplus,etaminus),\Gamma_{max/min}
(步长)
初始化:g^0_{prev} ← 0
\eta_0 \leftarrow \text{学习率 (lr)}
&\rule{110mm}{0.4pt} \\
for t=1 到 ... do
g_t ← ∇_θ f_t (θ_{t-1}) \\
for i = 0, 1, ..., d-1 do
如果 g^i_{prev} g^i_t 大于 0
&\hspace{15mm} \eta^i_t \leftarrow \mathrm{min}(\eta^i_{t-1} \eta_{+},
Γ_{max}) \\
&\hspace{10mm} \textbf{else if} \: g^i_{prev} g^i_t < 0 \\
&\hspace{15mm} \eta^i_t \leftarrow \mathrm{max}(\eta^i_{t-1} \eta_{-},
\Gamma_{min}) \\
&\hspace{15mm} g^i_t \leftarrow 0 \\
&\hspace{10mm} 否则 \: \\
&\hspace{15mm} \eta^i_t \leftarrow \eta^i_{t-1} \\
&\hspace{5mm}\theta_t \leftarrow \theta_{t-1}- \eta_t \mathrm{sign}(g_t) \\
&\hspace{5mm}g_{prev} \leftarrow g_t \\
&\规则{110 毫米}{0.4 点} \\[-1.ex]
&\bf{return} \: \theta_t \\[-1.ex]
&\规则{110 毫米}{0.4 点} \\[-1.ex]
\end{aligned}
关于算法的更多细节,请参阅论文
一种用于更快反向传播学习的直接自适应方法:RPROP 算法
"文档"
+ rf""
参数:
{_params_doc}
lr(float,可选):学习率(默认:1e-2)
etas(元组[浮点数,浮点数],可选):一对(etaminus,etaplus),表示
可乘性增加和减少因子
(默认:(0.5, 1.2))
step_sizes(元组[浮点数,浮点数],可选):一对最小和
最大允许的步长(默认:(1e-6, 50))
{_可捕获文档}
{_foreach_doc}
{_maximize_doc}
{_可区分文档}
"文档"
)
定义
_单张张量_rprop(
参数:
列表[
张量
]
梯度:
列表[
张量
]
前值:
列表[
张量
]
步长大小:
列表[
张量
]
状态步数:
列表[
张量
]
*,
步长最小值: float,
步长最大值: float,
eta 减: float,
eta 加: float,
最大化:
布尔,
可捕捉的:
布尔,
可微分的:
布尔,
有复杂的:
布尔,
):
for i, 参数
在
列举(
参数):
梯度 =
梯度[i]
梯度 =
梯度
如果
不
最大化
否则 -
梯度
上一个 =
前置[i]
步长 =
步长[i]
步骤 =
状态步数[i]
如果编译,编译器将处理 cudagraph 检查,参见注释[torch.compile x capturable]
如果
不 torch.
编译器.is_compiling()
和
可捕捉的:
可捕获支持的设备 =
_获取可捕获的设备()
断言 (
参数.
设备.
类型 ==
步长.
设备.
类型
和
参数.
设备.
类型
在
可捕获支持的设备
), f如果 capturable=True,则 params 和 state_steps 必须在支持的设备上:{
支持捕获的设备}
。
步骤 += 1
如果 torch.
是复杂的(
参数):
梯度 = torch.
真实查看(
研究生)
上一个 = torch.
真实查看(
上一页)
参数 = torch.
真实查看(
参数)
步长 = torch.
真实查看(
步长)
如果
可微分的:
符号 =
研究生.
多(
上一页.
克隆()).
符号()
else:
符号 =
研究生.
多(
上一页).
符号()
如果
可捕捉的:
符号.
复制_(torch.
哪里(
符号.gt(0), etaplus,
符号))
符号.
复制_(torch.
哪里(
符号.lt(0), etaminus,
符号))
符号.
复制_(torch.
哪里(
符号.eq(0), 1,
符号))
else:
符号[
符号.gt(0)] = etaplus
符号[
符号.lt(0)] = etaminus
符号[
符号.eq(0)] = 1
使用步长更新更新步长
步长.mul_(
符号).clamp_(
步长最小值,
步长最大值)
# 对于 dir<0,dfdx=0
# 对于 dir>=0 dfdx=dfdx
梯度 =
研究生.
克隆(
内存格式=torch.
保留格式)
如果
可捕捉的:
研究生.
复制_(torch.
哪里(
符号.eq(etaminus), 0,
研究生))
else:
研究生[
符号.eq(etaminus)] = 0
更新参数
参数.addcmul_(
研究生.
符号(),
步长,
值=-1)
上一页.
复制_(
研究生)
定义
_多张量 Rprop(
参数:
列表[
张量
]
梯度:
列表[
张量
]
前置:
列表[
张量
]
步长大小:
列表[
张量
]
状态步数:
列表[
张量
]
*,
步长最小值: float,
步长最大值: float,
乙胺负: float,
乙胺正: float,
最大化:
布尔,
可捕捉的:
布尔,
可微分的:
布尔,
有复杂的:
布尔,
):
如果
长度(
参数) == 0:
返回
断言
不
可微分的,
"_foreach 操作不支持自动求导"
如果编译,编译器将处理 cudagraph 检查,参见注释[torch.compile x capturable]
如果
不 torch.
编译器.is_compiling()
和
可捕捉的:
可捕获支持的设备 =
_获取可捕获的设备()
断言
所有(
p.设备.
类型 ==
步长.
设备.
类型
和 p.
设备.
类型
在
可捕获支持的设备
for p, 步骤
在 zip(
参数,
状态步数)
), f如果 capturable=True,则 params 和 state_steps 必须在支持的设备上:{
支持捕获的设备}
。
分组张量 =
优化器.
按设备类型和数据类型分组张量(
[参数,
梯度,
前值,
步长大小,
状态步数] # type: ignore[list-item]
)
for (
分组参数_,
分组梯度_,
分组前值_,
分组步长大小_,
分组状态步骤_,
), _ 在
分组张量.
值():
分组参数 =
角色(
列表[
张量
]
分组参数_)
分组梯度 =
角色(
列表[
张量
]
分组梯度_)
分组前项 =
角色(
列表[
张量
]
分组前项_)
分组步长 =
角色(
列表[
张量
]
分组步长_)
分组状态步骤 =
角色(
列表[
张量
]
分组状态步骤_)
# 更新步骤
# 如果步骤在 CPU 上,foreach 将回退到慢速路径,即通过循环调用 t.add(1)
#然后 1 会被反复包裹成 Tensor,这比只包裹一次要慢。
#alpha 是必须的,以确保我们进入正确的重载。
如果
不 torch.
编译器.is_compiling()
和
分组状态步骤[0].
是 CPU:
torch._foreach_add_(
分组状态步骤, torch.
张量(1.0,
设备="cpu"),
阿尔法=1.0
)
else:
torch._foreach_add_(分组状态步骤, 1)
处理复杂参数
如果
有复杂的:
真实查看(
分组参数,
分组梯度,
分组前值,
分组步长
)
符号 = torch._foreach_mul(
分组梯度,
分组前项)
如果
最大化:
torch._foreach_neg_(标记)
# 步骤结束时,分组前项将包含当前梯度,因此我们重用
# 分组前项的内存而不是创建一个新的缓冲区,但为了清晰起见,我们重新分配
# 保持对缓冲区的引用,称为 grouped_grads。
torch._foreach_copy_(grouped_prevs, 分组梯度)
如果
最大化:
torch._foreach_neg_(grouped_prevs)
分组梯度 = grouped_prevs
torch.每个符号(
符号)
如果
可捕捉的:
for 符号
在
符号:
符号.
复制_(torch.
哪里(
符号.gt(0), etaplus,
符号))
符号.
复制_(torch.
哪里(
符号.lt(0), etaminus,
符号))
符号.
复制_(torch.
哪里(
符号.eq(0), 1,
符号))
else:
for 符号
在
标记:
符号[
符号.gt(0)] = etaplus
符号[
符号.lt(0)] = etaminus
符号[
符号.eq(0)] = 1
使用步长更新更新步长
torch._foreach_mul_(分组步长,
符号)
for 步长
在
分组步长:
步长.clamp_(
步长最小值,
最大步长)
# 对于 dir<0,dfdx=0
# 对于 dir>=0 dfdx=dfdx
分组梯度 =
列表(
分组梯度)
for i 在
范围(
长度(
分组梯度)):
分组梯度[i].
复制_(
torch.哪里(
标志[i].eq(
乙胺), 0,
分组梯度[i])
)
明确删除符号,因为它在这里之后不再使用以节省内存
删除
符号
更新参数
梯度符号 = [
研究生.
符号() for
梯度
在
分组梯度]
torch._foreach_addcmul_(
分组参数,
梯度符号,
分组步长,
值=-1
)
# 从逻辑上讲,你可能期望 grouped_prevs 被更新为 grouped_grads,但实际上
# 已经发生了,因为我们一直在使用 grouped_prevs 的内存来存储
# 更新了分组梯度!
@_disable_dynamo_if_unsupported(单个张量函数=
_单个张量 Rprop)
定义 rprop(
参数:
列表[
张量
]
梯度:
列表[
张量
]
前置:
列表[
张量
]
步长大小:
列表[
张量
]
状态步数:
列表[
张量
]
使用 torchscript 编译的函数不支持带默认值的只写关键字参数问题 #70627
现由 torch/distributed/optim 编译的函数 API 参数
foreach: 可选[
布尔] =
无,
可捕捉的:
布尔值 =
错误,
最大化:
布尔值 =
错误,
可微分的:
布尔值 =
错误,
有复杂的:
布尔值 =
错误,
*,
步长最小值: float,
最大步长: float,
etaminus: float,
etaplus: float,
):
r执行 rprop 算法计算的函数式 API。
查看 :class:`~torch.optim.Rprop` 以获取详细信息。
"文档"
此检查在编译期间较慢,因此我们跳过它。
如果确实需要,我们可以在 dynamo 中添加此检查。
如果
不 torch.
编译器.is_compiling()
和
不
所有(
isinstance(t, torch.张量) for t
在
状态步骤
):
提升
运行时错误(
"API 已更改,`state_steps`参数必须包含一个单例张量列表"
)
如果 foreach
是
无:
_, foreach = 默认为融合或遍历(
参数,
可微分的,
使用融合的=
假
)
如果 foreach
和 torch.
算子.
是否正在脚本化():
提升
运行时错误(
"torch.jit.script 不支持 foreach 优化器")
如果 foreach
和
不 torch.
算子.
是否正在脚本化():
函数 =
多张张量_rprop
else:
函数 =
单张张量_rprop
函数(
参数,
梯度,
前面,
步长大小,
状态步数,
步长最小值=
步长最小值,
步长最大值=
步长最大值,
乙胺负=
乙胺负,
乙胺正=
乙胺正,
可捕捉的=
可捕捉的,
最大化=
最大化,
可微分的=
可微分的,
有复杂的=
有复杂的,
)