# mypy: 允许未类型化定义
from 打字
导入
角色,
可选,
联合
导入
火炬
from 火炬
导入
张量
from .优化器
导入 (
_capturable_doc,
默认为融合或遍历,
_可微文档,
_如果不受支持则禁用 Dynamo,
_foreach_doc,
_获取可捕获的设备,
获取标量数据类型,
_获取值,
_maximize_doc,
_params_doc,
_use_grad_for_differentiable,
真实查看,
优化器,
参数 T,
)
全部 = ["ASGD",
asgd]
[文档]
类 ASGD(
优化器):
定义 __init__(
自身,
参数:
参数 T,
学习率:
并集[float,
张量] =
0.01,
拉姆达:
浮点数 =
0.0001,
阿尔法:
浮点数 = 0.75,
t0: 浮点数 =
10 万,
权重衰减:
浮点数 = 0,
foreach: 可选[
布尔] =
无,
最大化:
布尔值 =
错误,
可微:
布尔值 =
错误,
可捕捉的:
布尔值 =
错误,
):
如果 isinstance(
学习率,
张量)
和
学习率.
元素数量() != 1:
提升 ValueError(
"Tensor lr 必须是 1 个元素")
如果
不 0.0 <=
学习率:
提升 ValueError(f
"无效的学习率:"{
学习率}")
如果
不 0.0 <=
权重衰减:
提升 ValueError(f
"无效的 weight_decay 值:"{
权重衰减}")
默认 =
字典(
学习率=
学习率,
拉姆达=
拉姆达,
阿尔法=
阿尔法,
t0=t0,
权重衰减=
权重衰减,
foreach=foreach,
最大化=
最大化,
可微=
可微,
可捕捉的=
可捕捉的,
)
超级().__init__(
参数,
默认值)
定义 __setstate__(
自身,
状态):
超级().__setstate__(
状态)
为
群组
在
自身.
参数组:
群组.setdefault("foreach",
无)
群组.setdefault(
最大化,
错误)
群组.setdefault(
"可区分的",
错误)
群组.setdefault(
"可捕获的",
错误)
为 p
在
群组[
参数]:
状态 =
自身.
状态.
获取(p,
[]
如果
长度(
状态) != 0:
如果
不
火炬.is_tensor(
状态[
步骤
)]
步骤值 = float(
状态[
步骤])
状态[
步骤] =
火炬.
张量(
步值,
数据类型=
获取标量数据类型(),
设备=p.
设备
)
如果
不
火炬.is_tensor(
状态["eta"
)]
状态["eta"] =
火炬.
张量(
状态["eta"
]
数据类型=
获取标量数据类型(),
设备=p.
设备
)
如果
不
火炬.is_tensor(
状态["mu"
)]
状态[
“穆”] =
火炬.
张量(
状态[
“穆”
]
数据类型=
获取标量数据类型(),
设备=p.
设备
)
定义
初始化组(
自身,
群组,
带梯度的参数,
梯度,
肌,
“axs”,
“etas”, state_steps):
复杂的参数 =
假
为 p
在
群组[
参数]:
如果 p.
梯度 is
不
无:
复杂的参数 |=
火炬.
是复杂的(p)
带梯度的参数.append(p)
如果 p.
研究生.is_sparse:
提升
运行时错误(
"ASGD 不支持稀疏梯度")
梯度.append(p.
研究生)
状态 =
自身.
状态[p]
# 状态初始化
如果
长度(
状态) == 0:
状态[
步骤] =
火炬.
零值(
(), 设备=p.
设备,
数据类型=
获取标量数据类型()
)
状态[
"η"] = (
火炬.as_tensor(
群组["lr"
]
设备=p.
设备,
数据类型=
获取标量数据类型()
)
.克隆()
.detach()
)
状态["mu"] =
火炬.
一(
(), 设备=p.
设备,
数据类型=
获取标量数据类型()
)
状态["ax"] =
火炬.
等于零的(
p, 内存格式=
火炬.
保留格式
)
肌.append(
状态["mu"])
axs.append(状态["ax"])
etas.append(状态["eta"])
state_steps.append(状态[
步骤])
返回
复杂的参数
[文档] @_use_grad_for_differentiable
def step(self, closure=None):
"""执行单个优化步骤。
Args:
closure (Callable, 可选): 一个重新评估模型的闭包
返回损失。
"""
self._cuda_graph_capture_health_check()
损失 = None
如果闭包不为空:
使用 torch.enable_grad():
损失 = 闭包()
对于 self.param_groups 中的每个组:
params_with_grad: 列表[Tensor] = []
grads: 列表[Tensor] = []
mus: 列表[Tensor] = []
axs: 列表[Tensor] = []
etas: 列[Tensor] = []
state_steps: 列[Tensor] = []
has_complex = self._init_group(
group, params_with_grad, grads, mus, axs, etas, state_steps
)
asgd(
带梯度的参数,
梯度,
axs,
mus,
etas,
state_steps,
lambd=group["lambd"]
lr=group["lr"]
t0=group["t0"]
alpha=group["alpha"]
weight_decay=group["weight_decay"],
foreach=group["foreach"],
maximize=group["maximize"],
differentiable=group["differentiable"],
capturable=group["capturable"]
has_complex=has_complex
)
返回损耗
ASGD.__doc__ = rf实现平均随机梯度下降。
它在《通过平均加速随机逼近》中提出。
_。
参数:
{_params_doc}
lr (float, Tensor, 可选): 学习率(默认:1e-2)
lambd (float, 可选): 衰减项(默认:1e-4)
alpha (float, 可选): eta 更新时的幂(默认:0.75)
t0 (float, 可选): 开始平均的点(默认:1e6)
weight_decay (float, 可选): 权重衰减(L2 惩罚)(默认:0)
{_foreach_doc}
{_maximize_doc}
{_可微文档}
{_capturable_doc}
.. _加速随机逼近的平均法:
https://dl.acm.org/citation.cfm?id=131098
"文档"
定义
单个张量 ASGD(
参数:
列表[
张量
]
梯度:
列表[
张量
]
axs: 列表[
张量
]
肌:
列表[
张量
]
etas: 列表[
张量
]
state_steps: 列表[
张量
]
*,
拉姆达: float,
学习率: float,
t0: float,
阿尔法: float,
权重衰减: float,
最大化:
布尔,
可微:
布尔,
可捕捉的:
布尔,
有复杂的:
布尔,
):
为 i,
参数
在
列举(
参数):
梯度 =
梯度[i]
梯度 =
梯度
如果
不
最大化
否则 -
梯度
mu = 肌[i]
ax = axs[i]
时代 =
时代们[i]
步骤_t = state_steps[i]
如果编译,编译器将处理 cudagraph 检查,参见注释[torch.compile x capturable]
如果
不
火炬.
编译器.is_compiling()
和
可捕捉的:
可捕获支持的设备 =
_获取可捕获的设备()
断言 (
参数.
设备.
类型
== mu.设备.
类型
== 埃塔.
设备.
类型
== 步骤_t.
设备.
类型
和
参数.
设备.
类型
在
可捕获支持的设备
), (
f"如果 capturable=True,则 params、mus、etas 和 state_steps 必须在 "
f"支持设备上:"{
支持捕获的设备}
。
)
如果
火炬.
是复杂的(
参数):
梯度 =
火炬.
真实查看(
研究生)
参数 =
火炬.
真实查看(
参数)
ax = 火炬.
真实查看(ax)
# 更新步骤
步骤_t += 1
如果
权重衰减 != 0:
梯度 =
研究生.
添加(
参数,
阿尔法=
权重衰减)
如果
可捕捉的:
参数.mul_(1 - lambd * eta)
参数.addcmul_(
研究生,
埃塔,
值=-1)
更新参数
else:
估计值 =
_获取值(
估计)
参数.mul_(1 -
拉姆达 *
期望值)
# 衰减项
参数.
加_(
研究生,
阿尔法=-
期望值)
# 更新参数
平均
如果
可捕获的
或者
穆.
项目() != 1:
ax.加_(
参数.
子(ax).mul_(mu))
else:
ax.复制_(
参数)
如果
可捕捉的:
埃塔.
复制_(
左右 / ((1 +
λ *
左右 *
步骤_t) **
阿尔法))
μ.
复制_(1 /
火炬.
最大值(
步骤_t - t0,
火炬.
喜欢的(
步骤_t)))
else:
步骤 =
_获取值(
步骤_t)
新ητα =
火炬.as_tensor(
左右 / ((1 +
λ *
左右 *
步长) **
阿尔法))
估计时间.
复制_(
新的估计时间)
新的μ =
火炬.as_tensor(1 /
最大值(1,
步骤 - t0))
mu.复制_(
新_mu)
定义
_多张量_ASGD(
参数:
列表[
张量
]
梯度:
列表[
张量
]
axs: 列表[
张量
]
肌:
列表[
张量
]
埃塔斯:
列表[
张量
]
state_steps: 列表[
张量
]
*,
拉姆达: float,
学习率: float,
t0: float,
阿尔法: float,
权重衰减: float,
最大化:
布尔,
可微:
布尔,
可捕捉的:
布尔,
有复杂的:
布尔,
):
如果
长度(
参数) == 0:
返回
断言
不
可微,
"_foreach 操作不支持自动求导"
如果编译,编译器将处理 cudagraph 检查,参见注释[torch.compile x capturable]
如果
不
火炬.
编译器.is_compiling()
和
可捕捉的:
可捕获支持的设备 =
_获取可捕获的设备(
支持 XLA=
假
)
断言
所有(
p.设备.
类型 ==
穆.
设备.
类型 ==
俄塔.
设备.
类型 ==
步长.
设备.
类型
和 p.
设备.
类型
在
可捕获支持的设备
为 p,
穆,
俄塔,
步骤
在 zip(
参数,
肌, etas, state_steps)
), f如果 capturable=True,则 params、mus、etas 和 state_steps 必须在支持的设备上:{
支持捕获的设备}
。
分组张量 =
优化器.
按设备类型和数据类型分组张量(
[参数,
梯度, axs,
肌, etas, state_steps] # type: ignore[list-item]
)
为 (
设备, _), (
(
分组参数_,
分组梯度_,
分组_axs_,
分组_mus_,
分组_etas_,
分组状态步骤_,
),
_,
) 在
分组张量.
项目():
分组参数 =
角色(
列表[
张量
]
分组参数_)
分组梯度 =
角色(
列表[
张量
]
分组梯度_)
分组_axs =
角色(
列表[
张量
]
分组_axs_)
分组_mus =
角色(
列表[
张量
]
分组_mus_)
分组_etas =
角色(
列表[
张量
]
分组_etas_)
分组状态步骤 =
角色(
列表[
张量
]
分组状态步骤_)
如果
有复杂的:
真实查看(
分组参数,
分组梯度,
分组_axs)
如果
最大化:
分组梯度 =
火炬._foreach_neg(
分组梯度)
# 类型:忽略[赋值]
# 更新步骤
# 如果步骤在 CPU 上,foreach 将回退到慢速路径,即通过循环调用 t.add(1)
#然后 1 会被反复包裹成 Tensor,这比只包裹一次要慢。
#alpha 是必须的,以确保我们进入正确的重载。
如果
不
火炬.
编译器.is_compiling()
和
分组状态步骤[0].
是 CPU:
火炬._foreach_add_(
分组状态步骤,
火炬.
张量(1.0,
设备="cpu"),
阿尔法=1.0
)
else:
火炬._foreach_add_(
分组状态步骤, 1)
# 中间值 = 梯度 + 参数 * 拉姆达
intermediate: 并集[
元组[
张量, ...
]
列表[
张量]]
如果
权重衰减 != 0:
如果
最大化:
火炬._foreach_add_(
分组梯度,
分组参数,
阿尔法=
权重衰减)
intermediate = 分组梯度
else:
中级 =
火炬.
_foreach_add_(
分组梯度,
分组参数,
阿尔法=
权重衰减
)
火炬._foreach_add_(
中级,
分组参数,
阿尔法=
拉姆达)
else:
中级 =
火炬.
_foreach_add_(
分组梯度,
分组参数,
阿尔法=
羊驼
)
更新参数
# param * (1 - lambd * eta) - eta * grad
# => 参数 - 参数 * lambd * eta - eta * grad
# => 参数 - eta * 中间值
火炬._foreach_addcmul_(
分组参数,
中级,
分组_etas,
值=-1)
删除
中级
# 更新分组_axs
平均值:ax = ax + mu * (参数 - ax)
注释(mlazos):在这里我们不能使用 lerp,因为它需要权重为 float64
并且我们的分组代码要求组内所有张量的数据类型匹配(它应该如此,因为我们
在其他地方使用了 mus)
# 所有数据类型需要匹配,因此我们可以在循环中引入类型转换
# 但这只会增加一个额外的内核启动,看起来这是一个更简洁的
# 并且更快的解决方案
中间 =
火炬.
遍历子项(
分组参数,
分组轴)
火炬._foreach_addcmul_(
分组轴,
中间状态,
分组音乐)
删除
中间阶段
新的 etas:
并集[
元组[
张量, ...
]
列表[
张量]]
新音乐:
并集[
元组[
张量, ...
]
列表[
张量]]
如果
可捕捉的:
更新分组音乐
新音乐 =
火炬.
遍历子项(
分组状态步骤, t0)
火炬._foreach_maximum_(
新音乐, 1.0)
火炬._foreach_reciprocal_(
新音乐)
火炬._foreach_copy_(
分组音乐,
新音乐)
删除
新音乐
# 更新 eta = lr / ((1 + lambd * lr * step)^alpha)
新 eta 值 =
火炬._foreach_mul(
分组状态步骤,
拉姆达)
火炬._foreach_mul_(
新的 etas,
学习率)
火炬._foreach_add_(
新的 etas, 1)
火炬._foreach_pow_(
新的 etas,
阿尔法)
火炬._foreach_reciprocal_(
新的 etas)
火炬._foreach_mul_(
新的 etas,
学习率)
火炬._foreach_copy_(
分组 etas,
新的 etas)
else:
新的 etas = [
火炬.as_tensor(
左右 / ((1 + lambd *
左右 *
步长) **
阿尔法),
设备=
设备)
为
步骤
在
分组状态步骤
]
新的 mus = [
火炬.as_tensor(1 /
最大值(1,
_获取值(
步长) - t0),
设备=
设备)
为
步骤
在
分组状态步骤
]
火炬._foreach_copy_(
分组_etas,
新_etas)
火炬._foreach_copy_(
分组音乐,
新音乐)
@_disable_dynamo_if_unsupported(单个张量函数=
单个张量 ASGD)
定义 asgd(
参数:
列表[
张量
]
梯度:
列表[
张量
]
axs: 列表[
张量
]
肌:
列表[
张量
]
etas: 列表[
张量
]
state_steps: 列表[
张量
]
使用 torchscript 编译的函数不支持带默认值的只写关键字参数问题 #70627
现由 torch/distributed/optim 编译的函数 API 参数
foreach: 可选[
布尔] =
无,
最大化:
布尔值 =
错误,
可微:
布尔值 =
错误,
可捕捉的:
布尔值 =
错误,
有复杂的:
布尔值 =
错误,
*,
拉姆达: float,
学习率: float,
t0: float,
阿尔法: float,
权重衰减: float,
):
r执行 asgd 算法计算的函数式 API。
详细信息请参阅 :class:`~torch.optim.ASGD`。
"文档"
如果 foreach is
无:
_, foreach = 默认为融合或遍历(
参数,
可微,
使用融合的=
假
)
如果 foreach
和
火炬.
算子.
是否正在脚本化():
提升
运行时错误(
"torch.jit.script 不支持 foreach 优化器")
如果 foreach
和
不
火炬.
算子.
是否正在脚本化():
函数 = _multi_tensor_asgd
else:
函数 = _single_tensor_asgd
函数(
参数,
梯度,
axs,
肌,
etas,
state_steps,
拉姆达=
拉姆达,
学习率=
学习率,
t0=t0,
阿尔法=
阿尔法,
权重衰减=
权重衰减,
最大化=
最大化,
可微=
可微,
可捕捉的=
可捕捉的,
有复杂的=
有复杂的,
)