在 PyTorch 中使用 module.to() 移动成员张量

Moving member tensors with module.to() in PyTorch

我正在 PyTorch 中构建变分自动编码器 (VAE),但在编写与设备无关的代码时遇到了问题。 Autoencoder 是 nn.Module 的子代,具有编码器和解码器网络,它们也是。通过调用 net.to(device).

可以将网络的所有权重从一个设备移动到另一个设备

我遇到的问题是重新参数化技巧:

encoding = mu + noise * sigma

噪声是与musigma大小相同的张量,保存为autoencoder模块的成员变量。它在构造函数中初始化,并在每个训练步骤中就地重新采样。我这样做是为了避免每一步都构建一个新的噪声张量并将其推送到所需的设备。此外,我想修复评估中的噪音。这是代码:

class VariationalGenerator(nn.Module):
    def __init__(self, input_nc, output_nc):
        super(VariationalGenerator, self).__init__()

        self.input_nc = input_nc
        self.output_nc = output_nc
        embedding_size = 128

        self._train_noise = torch.randn(batch_size, embedding_size)
        self._eval_noise = torch.randn(1, embedding_size)
        self.noise = self._train_noise

        # Create encoder
        self.encoder = Encoder(input_nc, embedding_size)
        # Create decoder
        self.decoder = Decoder(output_nc, embedding_size)

    def train(self, mode=True):
        super(VariationalGenerator, self).train(mode)
        self.noise = self._train_noise

    def eval(self):
        super(VariationalGenerator, self).eval()
        self.noise = self._eval_noise

    def forward(self, inputs):
        # Calculate parameters of embedding space
        mu, log_sigma = self.encoder.forward(inputs)
        # Resample noise if training
        if self.training:
            self.noise.normal_()
        # Reparametrize noise to embedding space
        inputs = mu + self.noise * torch.exp(0.5 * log_sigma)
        # Decode to image
        inputs = self.decoder(inputs)

        return inputs, mu, log_sigma

当我现在使用 net.to('cuda:0') 将自动编码器移动到 GPU 时,我在转发时遇到错误,因为噪声张量没有移动。

我不想在构造函数中添加设备参数,因为那样以后仍然无法将其移动到另一个设备。我还尝试将噪音包装到 nn.Parameter 中,以便它受到 net.to() 的影响,但这会导致优化器出错,因为噪音被标记为 requires_grad=False.

谁有解决方案来移动所有带有 net.to() 的模块?

使用这个:

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

现在对于模型和您使用的每个张量

net.to(device)
input = input.to(device)

经过反复试验,我发现了两种方法:

  1. 使用缓冲区:通过将 self._train_noise = torch.randn(batch_size, embedding_size) 替换为 self.register_buffer('_train_noise', torch.randn(batch_size, embedding_size),噪声张量将作为缓冲区添加到模块中。这也让 net.to(device) 影响它。此外,张量现在是 state_dict.
  2. 的一部分
  3. Override net.to(device):使用这个噪音远离state_dict。

    def to(device):
        new_self = super(VariationalGenerator, self).to(device)
        new_self._train_noise = new_self._train_noise.to(device)
        new_self._eval_noise = new_self._eval_noise.to(device)
    
        return new_self
    

is probably to override _apply, rather than to. That way net.cuda(), net.float(), etc will all work as well, since those all call _apply rather than to (as can be seen in the source 的更好版本,它比您想象的要简单):

def _apply(self, fn):
    super(VariationalGenerator, self)._apply(fn)
    self._train_noise = fn(self._train_noise)
    self._eval_noise = fn(self._eval_noise)
    return self

通过使用它,您可以将相同的参数应用于您的张量和模块

def to(self, **kwargs):
    module = super(VariationalGenerator, self).to(**kwargs)
    module._train_noise = self._train_noise.to(**kwargs)
    module._eval_noise = self._eval_noise.to(**kwargs)

    return module

您可以使用 nn.Module 缓冲区和参数 - 两者在调用 .to(device) 时都会被考虑并移至 device。 优化器正在更新参数(因此它们需要 requires_grad=True),缓冲区不是。

所以在你的情况下,我会把构造函数写成:

    def __init__(self, input_nc, output_nc):
        super(VariationalGenerator, self).__init__()

        self.input_nc = input_nc
        self.output_nc = output_nc
        embedding_size = 128

        # --- CHANGED LINES ---
        self.register_buffer('_train_noise', torch.randn(batch_size, embedding_size))
        self.register_buffer('_eval_noise', torch.randn(1, embedding_size))
        # --- CHANGED LINES ---

        self.noise = self._train_noise

        # Create encoder
        self.encoder = Encoder(input_nc, embedding_size)
        # Create decoder
        self.decoder = Decoder(output_nc, embedding_size)