• 文档 >
  • 模块代码 >
  • torch >
  • torch.nn.utils.spectral_norm
快捷键

torch.nn.utils.spectral_norm 的源代码

# mypy: 允许未类型化定义
来自 https://arxiv.org/abs/1802.05957 的谱归一化。
来自 打字 导入 任意, 可选, 类型变量

导入 火炬
导入 torch.nn.functional 作为 F
来自 torch.nn.modules 导入 模块


__all__ = [
    谱范数,
    谱范数加载状态字典预处理钩子,
    谱范数状态字典钩子,
    谱范数,
    移除谱范数,
]


 谱范数:
    # 在每次前向调用前后保持不变:
    #   u = F.normalize(W @ v)
    # 注意:初始化时,此不变性不强制执行

    _版本: 整型 = 1
    # 版本 1:
    #   将 `W` 不再作为缓冲区处理,
    #   添加 `v` 作为缓冲区,并且
    #   评估模式使用 `W = u @ W_orig @ v` 而不是存储的 `W`。
    名称: 字符串
    暗淡: 整型
    n 次幂迭代次数: 整型
    eps: 浮点数

    def 初始化(
        self,
        名称: 字符串 = 权重,
        n 次幂迭代次数: 整型 = 1,
        暗淡: 整型 = 0,
        eps: 浮点数 = 1e-12,
    ) -> :
        self.名称 = 名称
        self.维度 = 维度
        如果 n_power_iterations  0:
            提升 ValueError(
                预期 n_power_iterations 为正数,但
                f获取 n_power_iterations={n 次幂迭代次数}"
            )
        self.n_power_iterations = n_power_iterations
        self.eps = eps

    def 重新塑形权重为矩阵(self, 重量: 火炬.张量) -> 火炬.张量:
        weight_mat = 权重
        如果 self.维度 != 0:
            # 将维度排列到前面
            weight_mat = 权重矩阵.排列(
                self.暗淡, *[d  d  范围(权重矩阵.暗淡()) 如果 d != self.暗淡]
            )
        高度 = 权重矩阵.尺寸(0)
        返回 权重矩阵.重塑(高度, -1)

    def 计算权重(self, 模块: 模块, 执行幂迭代: 布尔) -> 火炬.张量:
        # NB: 如果设置 `do_power_iteration`,则 `u` 和 `v` 向量将在 **原地** 更新。这非常重要
        #     因为在 `DataParallel` 前向传播中,向量(作为缓冲区)是从并行化模块广播到每个模块副本的
        #     因为在 `DataParallel` 前向传播中,向量(作为缓冲区)是从并行化模块广播到每个模块副本的
        #     因为在 `DataParallel` 前向传播中,向量(作为缓冲区)是从并行化模块广播到每个模块副本的
        #     这是一个动态创建的新模块对象。每个副本
        #     都运行自己的谱范数幂迭代。所以简单地将
        #     更新后的向量分配给该函数运行的模块,将导致
        #     更新永远丢失。下次并行化
        #     模块被复制,使用相同的随机初始化的向量!
        #     广播并使用!
        #
        因此,为了使更改回传,我们依赖于两个
        重要行为(也通过测试强制执行):
        1. `DataParallel` 不克隆存储的广播张量
        #          已在正确设备上;并且它确保
        #          并行模块已经在`device[0]`上。
        #       2. 如果`out=`参数中的输出张量具有正确的形状,它将
        #          只需填充值即可。
        #     因此,由于对所有元素都执行了相同的幂迭代,
        仅更新张量而不改变设备,将确保设备上的模块副本更新_u 向量
        在`device[0]`上的模块副本将通过共享存储来并行化模块
        然而,在原地更新`u`和`v`之后,我们需要**克隆**
        #
        然而,在原地更新`u`和`v`之后,我们需要**克隆**
        在使用之前先对它们进行归一化处理。这是为了支持
        通过两次前向传递进行反向传播,例如,GAN 训练中的常见模式:
        GAN 训练中的损失函数:损失 = D(real) - D(fake)。否则,引擎将
        抱怨需要进行第一次前向传播的变量
        (即第二个前向中的 u 和 v 向量)在第二个前向中发生变化。
        权重 = getattr(模块, self.名称 + "_原")
        u = getattr(模块, self.名称 + "_u")
        v = getattr(模块, self.名称 + "_v")
        weight_mat = self.将权重重塑为矩阵(重量)

        如果 执行幂迭代:
             火炬.不梯度():
                 _  范围(self.n 次幂迭代次数):
                    权重的谱范数等于 `u^T W v`,其中 `u` 和 `v`
                    是第一左和右奇异向量。
                    这种幂迭代产生 `u` 和 `v` 的近似值。
                    v = F.normalize(
                        火炬.mv(权重矩阵.t(), u), 暗淡=0, eps=self.eps, 输出=v
                    )
                    u = F.normalize(火炬.mv(权重矩阵, v), 暗淡=0, eps=self.eps, 输出=u)
                如果 self.n_power_iterations > 0:
                    # 请参阅上方,了解为何需要克隆
                    u = u.克隆(内存格式=火炬.连续格式)
                    v = v.克隆(内存格式=火炬.连续格式)

        sigma

(由于 sigma 是数学和工程领域的通用符号,通常不进行翻译,因此保持原文不变。) = 火炬.(u, 火炬.mv(权重矩阵, v))
        权重 = 权重 / sigma

(由于 sigma 是数学和工程领域的通用符号,通常不进行翻译,因此保持原文不变。)
        返回 权重

    def 删除(self, 模块: 模块) -> :
         火炬.不梯度():
            权重 = self.计算权重(模块, 执行幂迭代=错误)
        delattr(模块, self.名称)
        delattr(模块, self.名称 + "_u")
        delattr(模块, self.名称 + "_v")
        delattr(模块, self.名称 + "_orig")
        模块.注册参数(self.名称, 火炬.nn.参数(重量.detach()))

    def __调用__(self, 模块: 模块, 输入: 任意) -> :
        setattr(
            模块,
            self.名称,
            self.计算权重(模块, 执行幂迭代=模块.训练),
        )

    def _解_v_并缩放(self, 权重矩阵, u, 目标方差):
        尝试返回一个向量 `v`,使得 `u = F.normalize(W @ v)`
        (此类的顶部不变量)且 `u @ W @ v = sigma`。
        当 `W^T W` 不可逆时,使用 pinverse。
        v = 火炬.线性代数.多点(
            [权重矩阵.t().mm(权重矩阵).p 逆(), 权重矩阵.t(), u.展平(1]
        ).挤压(1)
        返回 v.mul_(目标 sigma / 火炬.(u, 火炬.mv(权重矩阵, v)))

    @staticmethod
    def 应用(
        模块: 模块, 名称: 字符串, n 次幂迭代次数: int, 暗淡: int, eps: 浮点数
    ) -> "谱归一化":
         钩子  模块._前向预处理钩子.():
            如果 isinstance(hook, 谱归一化)  hook.名称 == 名称:
                提升 运行时错误(
                    f"不能在同一个参数上注册两个 spectral_norm 钩子"{名称}"
                )

        fn = 谱归一化(名称, n 次幂迭代次数, 暗淡, eps)
        权重 = 模块.参数[名称]
        如果 权重  :
            提升 ValueError(
                f"无法将 `SpectralNorm` 应用为参数"{名称}参数为空
            )
        如果 isinstance(重量, 火炬.nn.参数.未初始化参数):
            提升 ValueError(
                "传递给 `SpectralNorm` 的模块不能有未初始化的参数。"
                "在应用谱归一化之前,请确保运行模拟前向传播"
            )

         火炬.不梯度():
            weight_mat = 函数.重新塑形权重为矩阵(重量)

            h, w = 权重矩阵.尺寸()
            随机初始化 `u` 和 `v`
            u = F.normalize(重量.新空(h).正常的(0, 1), 暗淡=0, eps=函数.eps)
            v = F.normalize(重量.新空(w).正常的(0, 1), 暗淡=0, eps=函数.eps)

        delattr(模块, 函数.名称)
        模块.注册参数(函数.名称 + "_原", 重量)
        # 我们仍然需要将权重作为 fn.name 返回,因为所有各种
        # things may assume that it exists, e.g., when initializing weights.
        # However, we can't directly assign as it could be an nn.Parameter and
        # gets added as a parameter. Instead, we register weight.data as a plain
        # attribute.
        setattr(模块, 函数.名称, 重量.数据)
        模块.注册缓冲区(函数.名称 + "_u", u)
        模块.注册缓冲区(函数.名称 + "_v", v)

        模块.注册前向钩子(函数)
        模块._register_state_dict_hook(SpectralNormStateDictHook(函数))
        模块._register_load_state_dict_pre_hook(SpectralNormLoadStateDictPreHook(函数))
        返回 fn


# 这是一个顶级类,因为 Py2 的 pickle 不喜欢内部类也不喜欢实例方法。
# 实例方法。
 谱归一化加载状态字典预钩子:
    请参阅 SpectralNorm._version 的文档字符串,了解 spectral_norm 的变化。
    def 初始化(self, 函数) -> :
        self.fn = fn

    对于版本为 None 的状态字典(假设它已经至少经过一次训练正向传播),我们有
    对于版本为 None 的状态字典(假设它已经至少经过一次训练正向传播),我们有
    #
    #    u = F.normalize(W_orig @ v)
    #    W = W_orig / sigma, 其中 sigma = u @ W_orig @ v
    #
    # To compute `v`, we solve `W_orig @ x = u`, and let
    #    v = x / (u @ W_orig @ x) * (W / W_orig).
    def __调用__(
        self,
        state_dict,
        前缀,
        本地元数据,
        严格的,
        缺少键,
        预期之外的键,
        错误信息,
    ) -> :
        fn = self.fn
        版本 = 本地元数据.获取(谱范数, {}).获取(
            函数.名称 + ".版本", 
        )
        如果 版本    版本 < 1:
            权重键 = 前缀 + 函数.名称
            如果 (
                版本  
                 所有(权重键 + s  状态字典  s  ("_原始", "_u", "_v"))
                 权重键 not  状态字典
            ):
                # 检测是否是缺少元数据的更新状态字典。
                # 这可能发生在用户自己构建状态字典的情况下,
                # 因此我们假设这是最新的。
                返回
            缺少键 = 
             后缀  ("_原始", "", "_u"):
                key = 重量键 + 后缀
                如果 key not  state_dict:
                    缺少键 = 真实
                    如果 严格的:
                        缺少键.追加()
            如果 缺少键:
                返回
             火炬.不梯度():
                原始权重 = state_dict[权重键 + "_原始"]
                权重 = state_dict.弹出(重量键)
                sigma

(由于 sigma 是数学和工程领域的通用符号,通常不进行翻译,因此保持原文不变。) = (原始权重 / 重量).均值()
                weight_mat = 函数.将权重重塑为矩阵(原始权重)
                u = state_dict[重量键 + "_u"]
                v = 函数._解决_v_并缩放(权重矩阵, u, 西格玛)
                state_dict[重量键 + "_v"] = v


# 这是一个顶级类,因为 Py2 pickle 不喜欢内部类也不喜欢嵌套类
实例方法
 SpectralNormStateDictHook:
    请参阅 SpectralNorm._version 的文档字符串,了解 spectral_norm 的更改。
    def 初始化(self, 函数) -> :
        self.fn = fn

    def __调用__(self, 模块, state_dict, 前缀, 本地元数据) -> :
        如果 "光谱范数" not  本地元数据:
            本地元数据[谱范数] = {}
        key = self.函数.名称 + ".版本"
        如果 key  本地元数据[谱范数]:
            提升 运行时错误(f"在元数据['spectral_norm']中意外键"{}")
        本地元数据[谱范数]
[] = self.函数._version


T 模块 = 类型变量("T 模块", 绑定=模块)


[文档]def 谱范数( 模块: 模块 T, 名称: 字符串 = 权重, n 次幂迭代次数: 整型 = 1, eps: 浮点数 = 1e-12, 暗淡: 可选[int] = , ) -> 模块 T: r将频谱归一化应用于给定模块中的参数。 .. math:: \mathbf{W}_{SN} = \dfrac{\mathbf{W}}{\sigma(\mathbf{W})}, \sigma(\mathbf{W}) = \max_{\mathbf{h}: \mathbf{h} \ne 0} \dfrac{\|\mathbf{W} \mathbf{h}\|_2}{\|\mathbf{h}\|_2}, 光谱归一化稳定了判别器(评论家)的训练 生成对抗网络(GANs)通过缩放权重张量 使用权重矩阵的谱范数:σ计算 幂迭代法。如果权重张量的维度大于 比 2 大时,在幂迭代法中将其重塑为 2D 以获取谱 规范。这是通过一个钩子实现的,该钩子计算谱范数 在每次调用:meth:`~Module.forward`之前重新缩放权重。 请参阅《用于生成对抗网络的谱归一化》_ 。 参考链接:https://arxiv.org/abs/1802.05957 Args: 模块(nn.Module):包含模块。 名称(str,可选):权重参数的名称 n_power_iterations(int,可选):幂迭代次数。 计算谱范数 eps (float, 可选): ε用于数值稳定性 计算范数 dim (int, 可选): 对应输出数量的维度, 默认为 ``0``,除非是实例化的模块, ConvTranspose{1,2,3}d,当其为``1``时 返回: 原始模块带有谱范数钩子 .. 注意:: 此函数已被重新实现为 :func:`torch.nn.utils.parametrizations.spectral_norm` 使用新的 参数化功能 :func:`torch.nn.utils.parametrize.register_parametrization`. 请使用 新版本。此功能将在未来的版本中弃用 的 PyTorch。 示例:: >>> m = spectral_norm(nn.Linear(20, 40)) >>> m Linear(in_features=20, out_features=40, bias=True) >>> m.weight_u.size() torch.Size([40]) "源代码" 如果 维度 : 如果 isinstance( 模块, ( 火炬.nn.ConvTranspose1d, 火炬.nn.ConvTranspose2d, 火炬.nn.ConvTranspose3d, ), ): 维度 = 1 否则: 维度 = 0 频谱归一化.应用(模块, 名称, n 次幂迭代次数, 暗淡, eps) 返回 模块
[文档]def remove_spectral_norm(module: T_module, name: str = "weight") -> T_module: r"""从模块中移除频谱归一化的重新参数化。 Args: 模块 (Module): 包含的模块 名称 (str, 可选): 权重参数的名称 示例: >>> m = spectral_norm(nn.Linear(40, 10)) >>> remove_spectral_norm(m) """ for k, hook in module._forward_pre_hooks.items(): 如果 isinstance(hook, SpectralNorm) 且 hook.name 等于 name: hook.remove(module) del module._forward_pre_hooks[k] break else: 抛出 ValueError 异常(f"spectral_norm of '{name}' not found in {module} ") for k, hook in module._state_dict_hooks.items(): if isinstance(hook, SpectralNormStateDictHook) and hook.fn.name == name: 删除 module._state_dict_hooks[k] 终止循环 for k, hook in module._load_state_dict_pre_hooks.items(): 如果 isinstance(hook, SpectralNormLoadStateDictPreHook) 且 hook.fn.name == name: del module._load_state_dict_pre_hooks[k] break return module

© 版权所有 PyTorch 贡献者。

使用 Sphinx 构建,并使用 Read the Docs 提供的主题。

文档

查看 PyTorch 的全面开发者文档

查看文档

教程

深入了解初学者和高级开发者的教程

查看教程

资源

查找开发资源,获取您的疑问解答

查看资源