# mypy: 允许未类型化定义
本地最优块预条件共轭梯度方法。
# 作者:Pearu Peterson
# 创建时间:2020 年 2 月
from 打字
导入
可选
导入
火炬
from 火炬
导入
_线性代数工具 as _utils,
张量
来自 torch.overrides
导入 handle_torch_function,
有 torch 功能
全部 = [
lobpcg]
定义 _symeig_backward_complete_eigenspace(D_grad,
U_梯度, A, D, U):
# 计算 F,使得 F_ij = (d_j - d_i)^{-1} 对于 i != j,F_ii = 0
F = D.展平(-2) - D.
展平(-1)
F.对角线(dim1=-2, dim2=-1).
填充_(float(
"无穷大"))
F.幂_(-1)
# A.梯度 = U (D.梯度 + (U^T U.梯度 * F)) U^T
Ut = U.mT.连续()
res = 火炬.
矩阵乘法(
U, 火炬.
矩阵乘法(
火炬.
嵌入式诊断(D_grad) +
火炬.
矩阵乘法(Ut,
U 梯度) * F, Ut)
)
返回 res
定义
给定根的多项式系数(
根):
""
给定多项式的根,找出多项式的系数。
如果根为 (r_1, ..., r_n),则该方法返回
系数 (a_0, a_1, ..., a_n (== 1)),使得
p(x) = (x - r_1) * ... * (x - r_n)
= x^n + a_{n-1} * x^{n-1} + ... a_1 * x_1 + a_0
注意:为了更好的性能,需要编写低级内核
"""
多项式阶数 =
根.
形状[-1]
多项式系数形状 =
列表(
根.
形状)
# 假设 p(x) = x^n + a_{n-1} * x^{n-1} + ... + a_1 * x + a_0,
# so poly_coeffs = {a_0, ..., a_n, a_{n+1}(== 1)},
# but we insert one extra coefficient to enable better vectorization below
poly_coeffs_shape[-1] += 2
poly_coeffs = 根.
新零(
多项式系数形状)
多项式系数[..., 0] = 1
多项式系数[..., -1] = 1
执行霍纳法则
对于 i
在
范围(1,
多项式阶数 + 1):
注意,这种方法反向计算在计算上很困难,
因为给定系数后,就需要找到根和/或
基于韦达定理计算灵敏度。
因此下面的代码试图通过一系列在内存副本上的操作来规避显式根的查找。
这些内存副本用于构建计算图中的节点。
计算图中的节点需要这些内存副本来构建。
通过利用显式(非原地,每个步骤单独节点)
霍纳方法的递归。
需要更多内存,O(... * k^2),但只有 O(... * k^2) 的复杂度。
poly_coeffs_new = 多项式系数.
克隆()
如果
根.
需要梯度
否则
多项式系数
外部 =
新多项式系数.
狭窄(-1,
多项式阶数 - i, i + 1)
外部 -=
根.
狭窄(-1, i - 1, 1) *
多项式系数.
狭窄(
-1, 多项式阶数 - i + 1, i + 1
)
多项式系数 =
新多项式系数
返回
多项式系数.
狭窄(-1, 1,
多项式阶数 + 1)
定义
多项式值(
多项式, x,
零次幂,
转换):
""
使用 Horner 法则计算 poly(x)的通用方法。
参数:
poly(张量):表示(可能批量的)1D 张量,代表
多项式系数,使得
poly[..., i] = (a_{i_0}, ..., a{i_n} (==1)),
poly(x) = poly[..., 0] * 零次幂 + ... + poly[..., n] * x^n
x (Tensor):评估多项式 `poly` 的值(可能为批处理)。
zero_power (Tensor):表示 `x^0` 的形式。它是应用特定的。
transition (Callable):接受某些中间结果 `int_val` 的函数,
x 和特定的多项式系数
`poly[..., k]` 对于某个迭代 `k`。
它基本上执行霍纳法则的一次迭代。
定义为 `x * int_val + poly[..., k] * zero_power`。
请注意,`zero_power` 不是一个参数
因为步骤 `+ poly[..., k] * zero_power` 依赖于 `x`
无论是一个向量、一个矩阵还是其他什么,所以这个
功能委托给用户。
"""
res = 零功率.
克隆()
对于 k
在
范围(
多元.
尺寸(-1) - 2, -1, -1):
res = 转换(
资源, x,
多元[..., k])
返回 res
定义
_矩阵多项式值(
多项式, x,
零次幂=
无):
""
评估 `poly(x)` 对于矩阵输入 `x`(批处理)。
查看更多详细信息请参阅 `_polynomial_value` 函数。
"""
矩阵感知霍纳法则迭代
定义
转换(
当前多项式值, x,
多项式系数):
res = x.矩阵乘法(
当前多项式值)
资源.
对角线(dim1=-2, dim2=-1).
加_(
多项式系数.
展平(-1))
返回 res
如果
零功率
是
无:
零功率 =
PyTorch.
眼睛(
x.尺寸(-1), x.
尺寸(-1),
数据类型=x.
数据类型,
设备=x.
设备
).视图(*
[1] *
长度(
列表(x.
形状
[-2
]))), x.
尺寸(-1), x.
尺寸(-1))
返回
多项式值(
多项式, x,
零功率,
转换)
定义
_向量多项式值(
多项式, x,
零功率=
无):
""
评估多项式 poly(x) 对于(批处理的)向量输入 x。
有关更多详细信息,请查看 `_polynomial_value` 函数。
"""
向量感知霍纳法则迭代
定义
转换(
当前多项式值, x,
多项式系数):
res = PyTorch.
添加乘法(
多项式系数.
展平(-1), x,
当前多项式值)
返回 res
如果
零次幂
是
无:
零次幂 = x.
新一(1).
展开(x.
形状)
返回
多项式值(
多项式, x,
零次幂,
转换)
定义 _symeig_backward_partial_eigenspace(D_grad, U_grad, A, D, U,
最大的):
# 计算投影算子到由正交子空间生成的子空间上
U 的列定义为 (I - UU^T)
Ut = U.mT.连续()
proj_U_ortho = -U.矩阵乘法(Ut)
proj_U_正交.
对角线(dim1=-2, dim2=-1).
添加_(1)
# 计算 U_ortho,U 的生成空间的正交补的一个基,
# 通过将一个随机的 [..., m, m - k] 矩阵投影到由 U 的列张成的子空间,
# 上。
#
修复生成器以确保确定性
生成 =
火炬.
生成器(A.
设备)
生成子空间(U)的正交补
U_正交 =
proj_U_正交.
矩阵乘法(
火炬.randn(
(*A.形状
[-1
] A.
尺寸(-1) - D.
尺寸(-1)),
数据类型=A.
数据类型,
设备=A.
设备,
生成器=
生成,
)
)
U_正交_t =
U_正交.mT.
连续()
# 计算张量 D 的特征多项式的系数。
# 注意,D 是对角矩阵,因此对角线元素正好是根。
特征多项式的数量。
chr_poly_D = 根据根给出的多项式系数(D)
下面的代码找到了 Sylvester 方程的显式解
# U_正交^T A U_正交 dX - dX D = -U_正交^T A U
# 并将其合并到存储在 `res` 变量中的整个梯度中。
#
# 等价于以下简单的实现:
# res = A.new_zeros(A.shape)
# p_res = A.new_zeros(*A.shape[:-1], D.size(-1))
# for k in range(1, chr_poly_D.size(-1)):
# p_res.zero_()
# for i in range(0, k):
# p_res += (A.matrix_power(k - 1 - i) @ U_grad) * D.pow(i).unsqueeze(-2)
# res -= chr_poly_D[k] * (U_ortho @ poly_D_at_A.inverse() @ U_ortho_t @ p_res @ U.t())
#
# 注意,dX 是微分,因此梯度贡献来自反向敏感性
# Tr(f(U_grad, D_grad, A, U, D)^T dX) = Tr(g(U_grad, A, U, D)^T dA) 对于某些函数 f 和 g,
# 我们需要计算 g(U_grad, A, U, D)
#
# 基于论文的朴素实现
# 胡庆熙,程大章
# "Sylvester 矩阵方程的多项式解法。"
应用数学信函 19.9 (2006): 859-864.
#
我们可以更高效地修改上述 `p_res` 的计算方式。
p_res = U_grad * (chr_poly_D[1] * D.pow(0) + ... + chr_poly_D[k] * D.pow(k)).unsqueeze(-2)
+ A U_grad * (chr_poly_D[2] * D.pow(0) + ... + chr_poly_D[k] * D.pow(k - 1)).unsqueeze(-2)
# + ...
# + A 的 k-1 次方矩阵乘以 U_grad * chr_poly_D[k]
# 注意这使我们免去了与 A 的冗余矩阵乘法(消除了矩阵幂)
U_grad 投影 =
U 梯度
系列加速度 =
投影 U 梯度.
新零(
投影 U 梯度.
形状)
对于 k
在
范围(1, chr_poly_D.
尺寸(-1)):
poly_D = _vector_polynomial_value(chr_poly_D[..., k, D)
系列加速度 +=
U 梯度投影 *
多项式 D.
展平(-2)
U_grad_projected = A.矩阵乘法(U_grad_projected)
# 计算 chr_poly_D(A),本质上就是:
#
# chr_poly_D_at_A = A.new_zeros(A.shape)
# for k in 范围(chr_poly_D.size(-1)):
# chr_poly_D_at_A += chr_poly_D[k] * A.matrix_power(k)
#
# 注意,然而,为了更好的性能,我们使用 Horner 法则
chr_poly_D_at_A = _矩阵多项式值(chr_poly_D, A)
# 计算`chr_poly_D_at_A`在 U_ortho_t 上的作用
chr_poly_D_at_A_to_U_ortho = 火炬.
矩阵乘法(
U_正交_t,
火炬.
矩阵乘法(chr_poly_D_at_A,
U_正交)
)
# 我们需要反转 'chr_poly_D_at_A_to_U_正交`,为此我们计算其
# Cholesky 分解后使用`torch.cholesky_solve`以获得更好的稳定性。
# Cholesky 分解要求输入必须是正定的。
# 注意,当`largest`等于 False 时,`chr_poly_D_at_A_to_U_ortho`是正定的。
# 1. `largest`为 False,或者
当且仅当 `largest` 等于 True 且 `k` 为偶数时
在假设 `A` 有不同的特征值的情况下
#
检查 `chr_poly_D_at_A_to_U_ortho` 是否为正定或负定
chr_poly_D_at_A_to_U_ortho_sign = -1 如果 (
最大
和 (k % 2 == 1))
否则 +1
chr_poly_D_at_A_to_U_ortho_L = 火炬.
线性代数.
转置分解(
chr_poly_D_at_A_to_U_ortho_sign * chr_poly_D_at_A_to_U_ortho
)
计算 span(U)中的梯度部分
res = _symeig_backward_complete_eigenspace(D_grad, U_grad, A, D, U)
将 Sylvester 方程的解融入完整梯度
它位于 span(U_ortho)中
res -= U_ortho.矩阵乘法(
chr_poly_D_at_A_to_U_ortho_sign
* 火炬.
cholesky 求解(
U_正交_t.
矩阵乘法(
系列加速度),
chr_poly_D_at_A_to_U_正交_L
)
).矩阵乘法(Ut)
返回 res
定义 _symeig_backward(D_grad, U_grad, A, D, U,
最大的):
如果`U`是方阵,那么`U`的列构成一个完整的特征空间
如果 U.
尺寸(-1) == U.
尺寸(-2):
返回 _symeig_backward_complete_eigenspace(D_grad, U_grad, A, D, U)
否则:
返回 _symeig_backward_partial_eigenspace(
D 梯度,
U 梯度, A, D, U,
最大的)
类
LOBPCG 自动微分函数(
PyTorch.
自动微分.
函数):
@staticmethod
定义
前向(
# 类型:忽略[重写]
ctx,
A: 张量,
k: 可选[int] =
无,
B: 可选[
张量] =
无,
X: 可选[
张量] =
无,
n: 可选[int] =
无,
我克:
可选[
张量] =
无,
奈特:
可选[int] =
无,
容差:
可选[float] =
无,
最大的:
可选[bool] =
无,
方法:
可选[str] =
无,
跟踪器:
无 =
无,
正射投影参数:
可选[
字典[str, int]] =
无,
正射函数参数:
可选[
字典[str, float]] =
无,
正射边界参数:
可选[
字典[str, bool]] =
无,
) -> 元组[
张量,
张量
]
确保输入连续以提高效率。
注意:autograd 目前尚不支持稀疏输入的密集梯度。
A = A.连续()
如果 (
不 A.is_sparse)
否则 A
如果 B
是
不
无:
B = B.连续()
如果 (
不 B.is_sparse)
否则 B
D, U = _lobpcg(
A,
k,
B,
X,
n,
我克,
奈特,
容差,
最大的,
方法,
跟踪器,
正射投影参数,
正射函数参数,
正射边界参数,
)
ctx.保存用于回放(A, B, D, U)
ctx.最大的 =
最大
返回 D, U
@staticmethod
定义
反向(ctx,
D 梯度,
U 梯度):
A_梯度 =
B_梯度 =
无
梯度 = [
无] * 14
A, B, D, U = 上下文.
保存的张量
最大的 = ctx.
最大的
# lobpcg.backward 存在一些限制。检查不支持的输入
如果 A.is_sparse
或者 (B
是
不
无
和 B.is_sparse
和 ctx.needs_input_grad[2
)]
抛出
值错误(
"lobpcg.backward 目前还不支持稀疏输入。"
请注意,lobpcg.forward 确实如此。
)
如果 (
A.dtype 在 (
PyTorch.complex64,
火炬.complex128)
或者 B
是
不
无
和 B.dtype
在 (
火炬.complex64,
火炬.complex128)
):
抛出
值错误(
"lobpcg.backward 目前还不支持复数输入。"
"请注意,lobpcg.forward 是支持的。"
)
如果 B
是
不
无:
抛出
值错误(
"lobpcg.backward 目前还不支持 B != I 的反向操作。"
)
如果
最大
是
无:
最大 =
真实
# symeig 反向
如果 B
是
无:
A_梯度 =
_symeig 反向(
D_梯度,
U_梯度, A, D, U,
最大的)
# A 的索引为 0
梯度[0] =
A_梯度
# B 具有索引 2
梯度[2] = B_grad
返回
元组(
梯度)
[文档]
定义 lobpcg(
A: 张量,
k: 可选[int] =
无,
B: 可选[
张量] =
无,
X: 可选[
张量] =
无,
n: 可选[int] =
无,
我克:
可选[
张量] =
无,
奈特:
可选[int] =
无,
容差:
可选[float] =
无,
最大的:
可选[bool] =
无,
方法:
可选[str] =
无,
跟踪器:
无 =
无,
正射投影参数:
可选[
字典[str, int]] =
无,
正射函数参数:
可选[
字典[str, float]] =
无,
正射边界参数:
可选[
字典[str, bool]] =
无,
) -> 元组[
张量,
张量
]
找到最大的(或最小的)k 个特征值及其对应的
对称正定广义特征值和特征向量
使用无矩阵 LOBPCG 方法的特征值问题。
此函数是以下 LOBPCG 算法的前端:
可通过`method`参数选择:
`method="basic"` - 由 Andrew 提出的 LOBPCG 方法。
克尼亚泽夫,参见[Knyazev2001]。一种不太稳健的方法,当 Cholesky 应用于奇异输入时可能会失败。
Cholesky 方法应用于奇异输入时可能会失败。
`方法="ortho"` - 使用正交基的 LOBPCG 方法。
正交选择[StathopoulosEtal2002]。一种稳健的方法。
支持的输入包括密集矩阵、稀疏矩阵和密集矩阵的批次。
.. 注意 :: 通常情况下,基本方法每迭代一次花费的时间最少。
然而,鲁棒的方法收敛速度更快,更稳定。因此,基本方法的使用通常较少。
所以,基本方法的使用通常较少。
不推荐,但确实存在一些情况下使用基本方法可能更合适。
基本方法可能更受欢迎。
.. 警告:: 向后方法不支持稀疏和复杂输入。
仅当不提供 `B` 时(即 `B == None`)才有效。
我们正在积极开发扩展,算法的详细信息将及时发布。
算法详情将及时公布。
..警告::虽然假设`A`是对称的,但`A.grad`不是。
确保`A.grad`是对称的,以便`A - t * A.grad`是对称的。
在第一阶优化例程中,在运行 `lobpcg` 之前
我们执行以下对称映射:`A -> (A + A.t()) / 2`。
地图只在`A`需要梯度时执行。
参数:
一个(张量):大小为 :math:`(*, m, m)` 的输入张量
B(张量,可选):大小为 :math:`(*, m,` 的输入张量
m) 当未指定时,`B` 被解释为
单位矩阵
X(张量,可选):输入张量的大小为 :math:`(*, m, n)`
`k <= n <= m` 的情形下。当指定时,用作
特征向量的初始近似。X 必须是一个
稠密张量。
iK(张量,可选):大小为 :math:`(*, m,`
当指定时,它将被用作预条件器。
k(整数,可选):请求的特征值对的数量。
默认为 :math:`X` 的列数(当指定时)或 `1`。
列(当指定时)或 `1`。
n(整数,可选):如果未指定 :math:`X`,则 `n`
指定生成的随机大小
特征向量的近似。`n` 的默认值
是 `k`。如果指定了 :math:`X`,则 `n` 的值
(当指定时)必须是:math:`X` 的数量
列
tol(浮点数,可选):停止的残差容差
标准。默认值为 `feps ** 0.5`,其中 `feps` 是
给定最小的非零浮点数
输入张量 `A` 数据类型。
largest (bool, 可选): 当为 True 时,求解特征值问题
最大的特征值。否则,求解
特征值最小特征值问题。默认为
`True`
方法 (str, 可选): 选择 LOBPCG 方法。查看
上述函数的描述。默认为
"ortho"
niter(int,可选):最大迭代次数。当达到时,迭代过程会硬停止,并返回当前特征对近似值。
达到时,迭代过程会硬停止,并返回当前特征对近似值。
达到时,迭代过程会硬停止,并返回当前特征对近似值。
无限迭代,直到满足收敛标准
为止,使用 `-1`。
跟踪器(可调用,可选):一个用于跟踪迭代过程的函数。当指定时,它会在
每次迭代时被调用。
每次迭代步骤以 LOBPCG 实例作为参数。
LOBPCG 实例包含以下属性的全状态:迭代过程,`iparams`,`fparams`,`bparams` - 字典。
`iparams`,`fparams`,`bparams` - 字典。
`iparams`,`fparams`,`bparams` - 字典。
整数、浮点数和布尔值输入
分别为参数
`ivars`、`fvars`、`bvars`、`tvars` - 字典
整数、浮点数、布尔数和张量值
迭代变量,分别。
`A`、`B`、`iK` - 输入张量参数。
`E`、`X`、`S`、`R` - 迭代张量变量。
例如:
`ivars["istep"]` - 当前迭代步数
`X` - 当前特征向量的近似值
`E` - 当前特征值的近似值
`R` - 当前残差
`ivars["converged_count"]` - 当前收敛特征对的数量
`tvars["rerr"]` - 当前收敛准则的状态
注意当 `tracker` 存储 LOBPCG 实例的 Tensor 对象时,它必须复制这些对象。
当`tracker`存储 LOBPCG 实例的 Tensor 对象时,它必须对这些对象进行复制。
如果 `tracker` 设置 `bvars["force_stop"] = True`,则
迭代过程将被强制停止。
ortho_iparams, ortho_fparams, ortho_bparams (dict, 可选):
使用 LOBPCG 算法时的各种参数
`method="ortho"`.
返回值:
E (张量):特征值张量,大小为 :math:`(*, k)`
X (张量):特征向量张量,大小为 :math:`(*, m, k)`
参考文献:
[Knyazev2001] 安德烈·V·克尼亚泽夫. (2001) 向最优...
预处理特征值求解器:局部最优块预处理
共轭梯度法。美国工业与应用数学学会科学计算杂志,23(2),
517-541.(25 页)
https://epubs.siam.org/doi/abs/10.1137/S1064827500366124
[StathopoulosEtal2002] 安德烈亚斯·斯塔索普洛斯和凯申
吴. (2002) 具有常量同步要求的块正交化过程。SIAM J. Sci. Comput., 23(6),2165-2182。(18 页)
同步要求。SIAM J. Sci. Comput., 23(6),2165-2182。(18 页)
2165-2182.(18 页)
https://epubs.siam.org/doi/10.1137/S1064827500370883
[DuerschEtal2018] Jed A. Duersch,邵美月,杨超,明
2018 年《一种稳健高效的 LOBPCG 实现》
SIAM J. Sci. Comput., 40(5), C655-C676. (22 页)
https://epubs.siam.org/doi/abs/10.1137/17M1129830
"""
如果
不
火炬.
算子.
是否正在脚本化():
张量操作 = (A, B, X,
我克)
如果
不
设置(
地图(
类型,
张量操作)).
子集(
(火炬.
张量,
类型(
无))
) 和
有 torch 功能(
张量操作):
返回 handle_torch_function(
lobpcg,
张量操作,
A,
k=k,
B=B,
X=X,
n=n,
我克=
我克,
奈特=
奈特,
容差=
容差,
最大的=
最大的,
方法=
方法,
跟踪器=
跟踪器,
正射投影参数=
正射投影参数,
正射函数参数=
正射函数参数,
正射边界参数=
正射边界参数,
)
如果
不
火炬._jit_internal.
是否正在脚本化():
如果 A.
需要梯度
或者 (B
是
不
无
和 B.
需要梯度):
虽然“A”是对称的,但
“A_grad”可能不是。因此我们执行以下技巧,
使得“A_grad”变得对称。
对称化对于一阶优化方法很重要,
确保矩阵 (A - alpha * A_grad) 仍然是对称矩阵。
同样适用于 `B`。
A_sym = (A + A.mT) / 2
B_对称 = (B + B.mT) / 2
如果 (B
是
不
无)
否则
无
返回
LOBPCG 自动微分函数.
应用(
A_符号,
k,
B_符号,
X,
n,
我克,
奈特,
容差,
最大的,
方法,
跟踪器,
正射投影参数,
正射函数参数,
正射边界参数,
)
否则:
如果 A.
需要梯度
或者 (B
是
不
无
和 B.
需要梯度):
抛出
运行时错误(
"当前不支持脚本和 require grad。"
"如果您只想进行正向操作,请使用.detach()。"
"在调用 lobpcg 之前对 A 和 B 进行操作。"
)
返回 _lobpcg(
A,
k,
B,
X,
n,
我克,
奈特,
容差,
最大的,
方法,
跟踪器,
正射投影参数,
正射函数参数,
正射边界参数,
)
定义 _lobpcg(
A: 张量,
k: 可选[int] =
无,
B: 可选[
张量] =
无,
X: 可选[
张量] =
无,
n: 可选[int] =
无,
我克:
可选[
张量] =
无,
奈特:
可选[int] =
无,
容差:
可选[float] =
无,
最大的:
可选[bool] =
无,
方法:
可选[str] =
无,
跟踪器:
无 =
无,
正射投影参数:
可选[
字典[str, int]] =
无,
正射函数参数:
可选[
字典[str, float]] =
无,
正射边界参数:
可选[
字典[str, bool]] =
无,
) -> 元组[
张量,
张量
]
# A 必须是平方的:
断言 A.
形状[-2] == A.
形状[-1
] A.
形状
如果 B
是
不
无:
# A 和 B 必须具有相同的形状:
断言 A.
形状 == B.
形状, (A.
形状, B.
形状)
dtype = _utils.获取浮点数据类型(A)
设备 = A.
设备
如果
容差
是
无:
feps = {PyTorch.float32:
1.2e-7,
PyTorch.float64: 2.23e-16
}
数据类型]
托尔 = feps**0.5
m = A.形状[-1]
k = (1 如果 X
是
无
否则 X.
形状[-1])
如果 k
是
无
否则 k
n = (k 如果 n
是
无
否则 n)
如果 X
是
无
否则 X.
形状[-1]
如果 m < 3 * n:
抛出
值错误(
fLPBPCG 算法在 A 行数(=)小于请求的特征值对数的 3 倍时不可用{m})"
f是小于请求的特征值对数的 3 倍(=){n})"
)
方法 =
"正交"
如果
方法
是
无
否则
方法
iparams = {
"m": m,
"n": n,
"k": k,
niter: 1000
如果
液滴
是
无
否则
奈特,
}
参数 = {
容差:
容差,
}
模型参数 = {
最大:
真实
如果
最大
是
无
否则
最大的}
如果
方法 ==
正交:
如果 ortho_iparams
是
不
无:
iparams.更新(
正射投影参数)
如果 ortho_fparams
是
不
无:
fparams.更新(
正射函数参数)
如果 ortho_bparams
是
不
无:
b 参数.
更新(
正射边界参数)
i 参数[
"正交_i_max"] =
i 参数.
获取(
"正交_i_max", 3)
iparams["正交_j_max"] = iparams.
获取(
"正交_j_max", 3)
fparams["正交容差"] = fparams.
获取(
正交公差,
容差)
fparams["ortho_tol_drop"] = fparams.获取("ortho_tol_drop",
容差)
fparams["ortho_tol_replace"] = fparams.获取("ortho_tol_replace",
容差)
bparams["正交使用删除"] = bparams.
获取(
"正交使用删除", False)
如果
不
PyTorch.
算子.
是否正在脚本化():
LOBPCG.调用跟踪器 =
LOBPCG 调用跟踪器
# 类型:忽略[方法分配]
如果
长度(A.
形状) > 2:
N = int(PyTorch.
生产(
PyTorch.
张量(A.
形状
[-2
)))))
bA = A.重塑((N,) + A.
形状[-2
])
bB = B.重塑((N,) + A.
形状[-2
])
如果 B
是
不
无
否则
无
bX = X.重塑((N,) + X.
形状[-2
])
如果 X
是
不
无
否则
无
bE = PyTorch.
空的((N, k),
数据类型=
数据类型,
设备=
设备)
bXret = PyTorch.
空的((N, m, k),
数据类型=
数据类型,
设备=
设备)
对于 i
在
范围(N):
A_ = bA[i]
B_ = bB[i] 如果 bB
是
不
无
否则
无
X_ = (
PyTorch.randn((m, n),
数据类型=
数据类型,
设备=
设备)
如果 bX
是
无
否则 bX[i]
)
断言
长度(X_.
形状) == 2
和 X_.
形状 == (m, n), (X_.
形状, (m, n))
iparams[批次索引] = i
工作者 = LOBPCG(A_,
B_(未知),
X_(未知),
我克,
输入参数,
输出参数, bparams,
方法,
跟踪器)
worker.run()
bE[i] = 工人.E
[k]
bXret[i] = 工人.X[:, :k]
如果
不
PyTorch.
算子.
是否正在脚本化():
LOBPCG.调用跟踪器 =
LOBPCG_调用跟踪器原始
# 类型:忽略[方法分配]
返回 bE.
重塑(A.
形状
[-2] + (k,)), bXret.
重塑(A.
形状
[-2] + (m, k))
X = PyTorch.randn((m, n),
数据类型=
数据类型,
设备=
设备)
如果 X
是
无
否则 X
断言
长度(X.
形状) == 2
和 X.
形状 == (m, n), (X.
形状, (m, n))
工人 = LOBPCG(A, B, X,
我克,
i 参数,
f 参数, bparams,
方法,
跟踪器)
worker.run()
if 不
PyTorch.
算子.
是否正在脚本化():
LOBPCG.通话跟踪器 =
LOBPCG_通话跟踪器原始
# 类型:忽略[方法分配]
返回
工作者.E
[k
]
工人.X[:, :k]
类 LOBPCG:
"""LOBPCG 方法的 Worker 类。"""
定义 __init__(
self,
A: 可选[
张量
]
B: 可选[
张量
]
X: 张量,
我克:
可选[
张量
]
参数:
字典[str, int
]
函数参数:
字典[str, float
]
基本参数:
字典[str, bool
]
方法: str,
跟踪器:
无,
) -> 无:
# 常量参数
self.A = A
self.B = B
self.我克 =
我克
self.i 参数 =
i 参数
self.fparams = fparams
self.bparams = bparams
self.方法 =
方法
self.跟踪器 =
跟踪器
m = 参数[
m]
n = iparams["n"]
变量参数
self.X = X
self.E = PyTorch.
零值((n,),
数据类型=X.
数据类型,
设备=X.
设备)
self.R = PyTorch.
零值((m, n),
数据类型=X.
数据类型,
设备=X.
设备)
self.S = PyTorch.
零值((m, 3 * n),
数据类型=X.
数据类型,
设备=X.
设备)
self.tvars: 字典[str,
张量] = {}
self.ivars: 字典[str, int] = {
istep: 0}
self.fvars: 字典[str, float] = {
“_”: 0.0}
self.bvars: 字典[str, bool] = {
“_”: False}
定义 __str__(self):
行 = ["LOPBCG:"]
行 += [f
" iparams="{self.iparams}"]
行 += [f
"fparams="{self.fparams}"]
行 += [f
"bparams="{self.bparams}"]
行 += [f
" ivars="{self.ivars}"]
行 += [f
" fvars="{self.fvars}"]
行 += [f
" bvars="{self.bvars}"]
行 += [f
" tvars="{self.tvars}"]
行 += [f
A={self.A}"]
行 += [f
"B="{self.B}"]
行 += [f
"iK="{self.
我克}"]
行 += [f
"X="{self.X}"]
行 += [f
"E="{self.E}"]
r = 请提供需要翻译的文本
对于
行
在
行:
r += 行 + "
输入文本翻译为简体中文为:\n"
返回 r
定义
更新(self):
设置和更新迭代变量。
if self.ivars[istep] == 0:
X_norm = float(PyTorch.
归一化(self.X))
iX_标准 =
X_标准**-1
A_标准 = float(
PyTorch.
归一化(_utils.
矩阵乘法(self.A, self.X))) *
iX_标准
B_标准 = float(
PyTorch.
归一化(_utils.
矩阵乘法(self.B, self.X))) *
iX_标准
self.f 变量["X_norm"] = X_norm
self.fvars["A_norm"] = 标准化 A
self.变量 f[
"标准化 B"] =
标准化 B
self.ivars["剩余迭代次数"] = self.
输入参数["niter"]
self.ivars["收敛计数"] = 0
self.ivars["收敛结束"] = 0
if self.方法 ==
正交:
self._更新正交_()
否则:
self._更新基础_()
self.变量[
"剩余迭代次数"] = self.
实例变量[
"剩余迭代次数"] - 1
self.实例变量[
步骤] = self.
变量[
步骤] + 1
def 更新残差(self):
"更新 A、B、X、E 中的残差 R"
mm = _utils.矩阵乘法
self.R = mm(self.A, self.X) - mm(self.B, self.X) * self.E
def 更新收敛计数(self):
"使用向后稳定方法确定收敛特征对的数量"
收敛准则,参见[DuerschEtal2018]第 4.3 节讨论。
用户可以为自定义收敛准则重新定义此方法。
"""
# (...) -> int
prev_count = self.ivars["收敛计数"]
容差 = self.
f 参数[
"公差"]
标准化 A = self.
变量 f[
"标准化 A"]
标准化 B = self.fvars["B_norm"]
E, X, R = self.E, self.X, self.R
rerr = (
PyTorch.
归一化(R, 2, (0,))
* (PyTorch.
归一化(X, 2, (0,)) * (A_norm + E
[ X.
形状[-1]] * B_norm)) ** -1
)
收敛 =
错误.
真实 <
容差
# 这是一个规范,因此图像为 0.0
计算 = 0
对于 b
在
收敛:
if 不 b:
# 忽略以下对的收敛以确保
# 线性对的自伴性严格排序
断开
计算 += 1
断言
计算
≥ prev_count, (
f"收敛特征值对的数目(曾经){prev_count}
,获得{
数量}
)不能减少
)
self.ivars["汇聚计数"] =
计算
self.变量[
"错误"] =
错误
返回
计算
def 停止迭代(self):
返回 True 以停止迭代。
注意,如果已定义跟踪器,则可以通过设置 `worker.bvars['force_stop'] = True` 来强制停止迭代。
设置 ``worker.bvars['force_stop'] = True`` 可强制停止迭代。
"""
返回 (
self.bvars.获取(
force_stop, False)
或者 self.ivars[
iterations_left] == 0
或者 self.ivars["converged_count"]
≥ self.iparams["k"]
)
def run(self):
运行 LOBPCG 迭代。
将此方法作为实现 LOBPCG 迭代方案的模板。
使用与 TorchScript 兼容的自定义跟踪器实现迭代方案。
支持 TorchScript。
"""
self.更新()
if 不
PyTorch.
算子.
是否正在脚本化()
和 self.
跟踪器
是
不
无:
self.呼叫跟踪器()
当
不 self.
停止迭代():
self.更新()
if 不
PyTorch.
算子.
是否正在脚本化()
和 self.
跟踪器
是
不
无:
self.调用跟踪器()
@torch.算子.
未使用
def 调用跟踪器(self):
Python 模式中跟踪迭代过程的界面。
跟踪迭代过程在 TorchScript 中已禁用
模式。实际上,在 JIT 时应该指定 tracker=None
使用 lobpcg 编译函数。
"""
在 TorchScript 模式下不执行任何操作。
内部方法。
def _update_basic(self):
""
当 `method == "basic"` 时更新或初始化迭代变量。
"""
mm = PyTorch.
矩阵乘法
命名空间 = self.
初始变量[
收敛结束]
纳克 = self.
变量名[
合并计数]
n = self.输入参数["n"]
最大 = self.bparams[
"最大"]
if self.变量[
"步长"] == 0:
里 = self.
_获取瑞利里茨变换_(self.X)
M = _utils.qform(_utils.qform(self.A, self.X), 瑞)
E, Z = _utils.特征值(M,
最大的)
self.X[] = mm(self.X, mm(Ri, Z))
self.E[] = E
np = 0
self.更新残差()
nc = self.更新收敛计数()
self.S[..., :n] = self.X
W = _utils.矩阵乘法(self.
我克, self.R)
self.伊瓦尔[
"收敛端"] =
命名空间 = n + np + W.
形状[-1]
self.S[:, n + np : ns] = W
否则:
S_ = self.S[:, nc:ns]
里 = self.
_获取瑞利里茨变换_(S_)
M = _utils.qform(_utils.qform(self.A, S_), Ri)
E_, Z = _utils.特征值(M,
最大的)
self.X[:, nc] = mm(S_, mm(Ri, Z[:, : n - nc]))
self.E[nc] = E_
[ n - nc]
P = mm(S_, mm(Ri, Z[:, n : 2 * n - nc)) ]
np = P.形状[-1]
self.更新残差()
nc = self.更新收敛计数()
self.S[..., :n] = self.X
self.S空列表 n : n +
numpy] = P
W = _utils.矩阵乘法(self.
我克, self.R
空列表 nc
])
self.ivars["converged_end"] = 命名空间 = n + np + W.
形状[-1]
self.S[:, n + np : ns] = W
def _update_ortho(self):
""
当`method == "ortho"`时更新或初始化迭代变量。
"""
mm = PyTorch.
矩阵乘法
命名空间 = self.
初始变量[
收敛结束]
纳克 = self.
变量[
"汇聚数量"]
n = self.iparams["n"]
最大的 = self.bparams[
"最大的"]
if self.伊瓦尔[
步] == 0:
Ri = self._获取瑞利里茨变换_(self.X)
M = _utils.qform(_utils.qform(self.A, self.X), 里)
_E, Z = _utils.特征值(M,
最大的)
self.X = mm(self.X, mm(里, Z))
self.更新残差()
np = 0
nc = self.更新收敛计数()
self.S[ :n] = self.X
W = self._获取正交(self.R, self.X)
命名空间 = self.ivars["converged_end"] = n + np + W.
形状[-1]
self.S逗号 n + np : ns] = W
否则:
S_ = self.S[:, nc:ns]
雷利-里茨法
E_, Z = _utils.特征值(_utils.qform(self.A, S_),
最大的)
# 更新 E, X, P
self.X[:, nc] = mm(S_, Z[:, : n - nc])
self.E[nc] = E_
[ n - nc]
P = mm(S_, mm(Z[:, n - nc , _utils.
基础(Z
[ n - nc, n -
纽卡斯尔
].mT)))
np = P.形状[-1]
检查收敛
self.更新残差()
nc = self.更新收敛计数()
更新 S
self.S[ :n] = self.X
self.S[ n : n +
numpy] = P
W = self.获取正交(self.R[:, nc
[:, self.S[:, : n +
numpy])
命名空间 = self.ivars["converged_end"] = n + np + W.
形状[-1]
self.S逗号 n + np : ns] = W
def _get_rayleigh_ritz_transform(self, S):
返回用于 Rayleigh-Ritz 的变换矩阵
求解一般特征值问题的步骤:\( (S^TAS) \)
C = (S^TBS) C E` 转换为一个标准特征值问题 :math:`(Ri^T`
S^TAS Ri) Z = Z E` 其中 `C = Ri Z`。
.. 注意:: 在原始的 Rayleight-Ritz 方法中
[DuerschEtal2018],问题被表述如下:
SAS = S^T A S
SBS = S^T B S
D = (SBS 的对角矩阵) ** -1/2
R^T R = Cholesky(D SBS D)
Ri = D R^-1
求解对称特征值问题 Ri^T SAS Ri Z = Theta Z
C = Ri Z
为了减少矩阵乘法(用空格表示)的数量
矩阵之间的空格),这里我们介绍逐元素
产品(用符号 `*` 表示)以便 Rayleight-Ritz
程序变为:
SAS = S^T A S
SBS = S^T B S
d = () ** -1/2 # 这是一个 1-d 列向量
dd = d d^T # 这是 2 维矩阵
R^T R = Cholesky(dd * SBS)
Ri = R^-1 * d # 广播
求解对称特征值问题 Ri^T SAS Ri Z = Theta Z
C = Ri Z
其中 `dd` 是一个 2 维矩阵,它用逐元素乘积 `M * dd` 替代矩阵乘积 `D M`
`D`,并且 `d` 用逐元素乘积 `M * d` 替代矩阵乘积 `D M`
`D M` 用逐元素乘积 `M * d` 替代
d`. 此外,还避免了创建对角矩阵 `D`。
参数:
S (张量):搜索子空间的矩阵基,大小为
(m, n)。
返回值:
Ri(张量):大小为的上三角变换矩阵。
(n, n)。
"""
B = self.B
SBS = _utils.qform(B, S)
d_row = SBS.对角线(0, -2, -1) ** -0.5
d_col = d_row.重塑(d_row.
形状[0
] 1)
# TODO 使用 torch.linalg.cholesky_solve 一旦实现
R = PyTorch.
线性代数.
转置分解((SBS * d_row) * d_col,
上=True)
返回
PyTorch.
线性代数.
解三角形方程(
R, d_row.嵌入式诊断(),
上=True,
左侧=
假
)
def _get_svqb(self, U: 张量, drop: bool,
淘: float) ->
张量:
返回 B-正交 U。
.. 注意:: 当 `drop` 为 `False` 时,`svqb` 基于以下内容
来自[DuerschPhD2015]的第 4 个算法,是对相应算法的轻微修改
对应算法中引入的修改
U(张量):初始近似,大小为(m,n)
参数:
U(张量):初始近似,大小为(m,n)
drop(布尔值):当为 True 时,删除对`span([U])`贡献小的列。
tau(浮点数):正容差。
U(张量):B-正交列(:math:`U^T B U = I`),大小为。
返回值:
U(张量):B-正交列(:math:`U^T B U = I`),大小为。
是 (m, n1),其中 `n1 = n` 如果 `drop` 为 `False`,
否则 `n1 <= n`。
"""
if PyTorch.
元素数量(U) == 0:
返回 U
UBU = _utils.qform(self.B, U)
d = UBU.对角线(0, -2, -1)
在检测和删除 U 中的零列时
对于随机数据,`abs(d) == 0` 很不可能为真,但可以构造输入数据,使其在 lobpcg 中为真,从而导致失败(注意`d ** -0.5`操作)
这可能导致失败(注意`d ** -0.5`操作)
这可能导致失败(注意`d ** -0.5`操作)
在原始算法中)。为了防止失败,我们删除
#此处为精确的零列,然后继续
# 原始算法如下。
新西兰 =
PyTorch.
哪里(
绝对值(d) != 0.0)
断言
长度(
新西兰) == 1,
新西兰
if 长度(
新西兰[0]) <
长度(d):
U = U逗号
新西兰[0]]
if PyTorch.
元素数量(U) == 0:
返回 U
UBU = _utils.qform(self.B, U)
d = UBU.对角线(0, -2, -1)
nz = PyTorch.
哪里(
绝对值(d) != 0.0)
断言
长度(nz[0]) ==
长度(d)
原始算法 4 来自[DuerschPhD2015]。
d_col = (d**-0.5).重塑(d.
形状[0
] 1)
DUBUD = (UBU * d_col) * d_col.mT
E, Z = _utils.特征值(
杜布杜)
t = 塔乌 *
绝对值(E).
最大值()
if drop:
保持 =
PyTorch.
哪里(E > t)
断言
长度(
保持) == 1,
保持
E = E[保持[0]]
Z = Z[:, 保持[0]]
d_col = d_col[保持[0]]
否则:
E[
PyTorch.
哪里(E < t))[0]] = t
返回
PyTorch.
矩阵乘法(U * d_col.mT, Z * E**-0.5)
def _get_ortho(self, U, V):
返回 B-正交 U,其列与 V 正交。
.. note:: 当`bparams["ortho_use_drop"] == False`时,
_get_ortho 基于算法 3
[杜尔施博士 2015] 这是一种轻微的修改
相应算法介绍
[StathopolousWu2002]。否则,该方法
实现了[DuerschPhD2015]中的算法 6
.. 注意:: 如果所有 U 列都与 V B-共线,则
返回的 tensor U 将为空。
参数:
U(张量):初始近似,大小为(m,n)
V (张量) : B-正交外基,大小为 (m, k)
返回值:
U (张量) : B-正交归一列 (:math:`U^T B U = I`)
使得 :math:`V^T B U=0`,大小为 (m, n1),
其中 `n1 = n` 如果 `drop` 是 `False`,否则
n1 ≤ n
"""
mm = PyTorch.
矩阵乘法
mm_B = _utils.矩阵乘法
m = self.iparams["m"]
tau_ortho = self.fparams["ortho_tol"]
tau_下降 = self.fparams["ortho_tol_drop"]
tau_替换 = self.fparams[
正交容差替换]
i_最大值 = self.iparams[
"正交_i_max"]
j_max = self.iparams["ortho_j_max"]
# 当 use_drop==True 时,启用丢弃对 `span([U, V])` 有小贡献的 U 列
# 对 `span([U, V])` 有小贡献的 U 列
use_drop = self.bparams["正交使用删除"]
清理前一次调用的变量
对于 vkey
在
列表(self.fvars.
键()):
if vkey.以...开头(
"正交_")
和 vkey.
以...结尾(
"错误_rerr"):
self.变量 fvars.
流行(
键 vkey)
self.变量 ivars.
流行(
"正交_i", 0)
self.变量.
流行(
"正交_j", 0)
范数_BV =
PyTorch.
归一化(mm_B(self.B, V))
BU = mm_B(self.B, U)
VBU = mm(V.mT, BU)
i = j = 0
对于 i
在
范围(i_max):
U = U - mm(V, VBU)
丢弃 =
假
tau_svqb = 混合翻译
对于 j
在
范围(j_max):
if 使用滴落:
U = self._获取_svqb(U, drop, tau_svqb)
丢弃 =
真实
tau_svqb = tau_replace
否则:
U = self._get_svqb(U, False, tau 替换)
if PyTorch.
元素数量(U) == 0:
所有初始的 U 列都与 V 列 B-共线
self.ivars["ortho_i"] = i
self.伊瓦尔[
正交_j] = j
返回 U
BU = mm_B(self.B, U)
UBU = mm(U.mT, BU)
U_norm = PyTorch.
归一化(U)
BU_norm = PyTorch.
归一化(BU)
R = UBU - PyTorch.
眼睛(UBU.
形状[-1
]
设备=UBU.
设备,
数据类型=UBU.
数据类型)
R_norm = PyTorch.
归一化(R)
# https://github.com/pytorch/pytorch/issues/33810 解决方案:
rerr = float(R_norm) * float(BU_标准 *
U_标准) ** -1
vkey = f"正交_UBUmI_错误["{i}, {j}]"
self.fvars[vkey] = 错误
if 错误 <
tau_正交:
断开
VBU = mm(V.mT, BU)
VBU_norm = PyTorch.
归一化(VBU)
U_norm = PyTorch.
归一化(U)
错误 = float(VBU_norm) * float(
BV 规范 *
U 规范) ** -1
vkey = f"正交_VBU_rerr["{i}]"
self.fvars[vkey] = rerr
if rerr < tau_ortho:
断开
if m < U.形状[-1] + V.
形状[-1
]
# TorchScript 需要将类变量分配给局部变量
# 进行可选类型精炼
B = self.B
断言 B
是
不
无
抛出
值错误(
"U:的过定形状"
f" #B-列(= "{B.
形状[-1]}
) >= #U-列(= {U.
形状[-1]}
) + #V-列(= {V.
形状[-1]}
必须持有
)
self.变量["ortho_i"] = i
self.变量["ortho_j"] = j
返回 U
# 调用跟踪器与 LOBPCG 定义分离,因为
# TorchScript 不支持用户定义的回调参数:
LOBPCG_调用跟踪器原始 = LOBPCG.
调用跟踪器
def LOBPCG 调用跟踪器(self):
self.跟踪器(self)