【问题标题】:PyTorch gradients have different shape for CUDA and CPUPyTorch 梯度对于 CUDA 和 CPU 有不同的形状
【发布时间】:2020-11-09 15:57:50
【问题描述】:

我正在处理一个奇怪的问题,即反向传递后的渐变具有不同的形状,具体取决于使用的是 CUDA 还是 CPU。使用的模型比较简单:

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool1 = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.pool2 = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
        self.relu1 = nn.ReLU()
        self.relu2 = nn.ReLU()
        self.relu3 = nn.ReLU()
        self.relu4 = nn.ReLU()

    def forward(self, x):
        x = self.pool1(self.relu1(self.conv1(x)))
        x = self.pool2(self.relu2(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = self.relu3(self.fc1(x))
        x = self.relu4(self.fc2(x))
        x = self.fc3(x)
        return x

输入张量的形状为(1, 3, 32, 32),相关部分代码如下,其中generate_gradients方法尤为重要:

class VanillaBackprop():
    """
        Produces gradients generated with vanilla back propagation from the image
    """
    def __init__(self, model):
        self.model = model
        self.gradients = None
        # Put model in evaluation mode
        self.model.eval()
        # Hook the first layer to get the gradient
        self.hook_layers()
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        self.model.to(self.device)


    def hook_layers(self):
        def hook_function(module, grad_in, grad_out):
            self.gradients = grad_in[0]

        # Register hook to the first layer
        try:
            first_layer = list(self.model.features._modules.items())[0][1]
        except:
            first_layer = list(self.model._modules.items())[0][1]
        first_layer.register_backward_hook(hook_function)

    def generate_gradients(self, input_image, target_class):
        # Forward
        model_output = self.model(input_image.to(self.device))
        # Zero grads
        self.model.zero_grad()
        # Target for backprop
        one_hot_output = torch.FloatTensor(1, model_output.size()[-1]).zero_()
        one_hot_output[0][target_class] = 1
        # Backward pass
        model_output.backward(gradient=one_hot_output.to(self.device))
        # Convert Pytorch variable to numpy array
        gradients_as_arr = self.gradients.data.cpu().numpy()[0]
        return gradients_as_arr

在 CPU 上,self.gradients 的形状为 (1, 3, 32, 32),而在 CUDA 上,它的形状为 (1, 6, 28, 28)。这怎么可能,我该如何解决?非常感谢任何帮助。

【问题讨论】:

  • hook_layers 中的 try-except 看起来很可疑。它应该处理什么异常?
  • try-except 用于处理非torchvision 模型。大多数torchvision模型由model.features(conv + activation + pooling blocks)和model.classifier(最后的全连接层)组成。你会注意到一个基本的卷积网络(比如我创建的那个或任何 nn.Sequential 模型)缺少这两个子组件。本质上,我正在迭代层,对于 torchvision 模型,您需要迭代 model.features._modules,而对于非 Torchvision 模型,您需要 self.model._modules。这不是万无一失的,但对于我的目的来说已经足够了。
  • 好的。 (1, 6, 28, 28) 看起来像第一个卷积层的输出。你能检查一下第一层实际上是两种情况下的第一层吗?
  • 确实,在这两种情况下,第一层实际上是第一层,并且通过 forward 方法迭代会导致两者在每一层之后的形状相同。例如,x = self.pool1(self.relu1(self.conv1(x))) 在 CUDA 和 CPU 上输出 x,形状为 (1, 6, 14, 14),这是预期的行为。

标签: python pytorch autograd


【解决方案1】:

看起来问题源于register_backward_hook() 函数。正如the PyTorch forums中指出的那样:

您可能需要仔细检查register_backward_hook() 文档。但 众所周知,它目前有点坏,可以有这个 行为。

我建议您为此使用autograd.grad()。那将 使其比向后+访问.grad 字段更简单。

然而,我选择使用register_hook() 而不是register_backward_hook()(而不是建议的autograd.grad()),这似乎也有效:

class VanillaBackprop():
    """
        Produces gradients generated with vanilla back propagation from the image
    """
    def __init__(self, model):
        self.model = model
        self.gradients = None
        # Put model in evaluation mode
        self.model.eval()
        # Hook the first layer to get the gradient

    def hook_input(self, input_tensor):
        def hook_function(grad_in):
            self.gradients = grad_in
        input_tensor.register_hook(hook_function)

    def generate_gradients(self, input_image, target_class):
        # Register input hook
        self.hook_input(input_image)
        # Forward
        model_output = self.model(input_image)
        # Zero grads
        self.model.zero_grad()
        # Target for backprop
        device = next(self.model.parameters()).device
        one_hot_output = torch.FloatTensor(1, model_output.size()[-1]).zero_()
        one_hot_output[0][target_class] = 1
        one_hot_output = one_hot_output.to(device)
        # Backward pass
        device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        model_output.backward(gradient=one_hot_output.to(device))
        # Convert Pytorch variable to numpy array
        # [0] to get rid of the first channel (1,3,224,224)
        gradients_as_arr = self.gradients.data.cpu().numpy()[0]
        return gradients_as_arr

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-26
    • 2019-04-29
    • 2021-01-13
    • 1970-01-01
    • 2021-08-28
    • 2020-02-10
    • 1970-01-01
    相关资源
    最近更新 更多