如何在 pytorch vgg16 模型中进行 Class 激活映射?

How to do Class Activation Mapping in pytorch vgg16 model?

我为图像 class 写了一个 模型,它的层是

VGG(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU(inplace=True)
    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU(inplace=True)
    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (6): ReLU(inplace=True)
    (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (8): ReLU(inplace=True)
    (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (13): ReLU(inplace=True)
    (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (15): ReLU(inplace=True)
    (16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (18): ReLU(inplace=True)
    (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (20): ReLU(inplace=True)
    (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (22): ReLU(inplace=True)
    (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (25): ReLU(inplace=True)
    (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (27): ReLU(inplace=True)
    (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (29): ReLU(inplace=True)
    (30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
  (classifier): Sequential(
    (0): Linear(in_features=25088, out_features=4096, bias=True)
    (1): ReLU(inplace=True)
    (2): Dropout(p=0.5, inplace=False)
    (3): Linear(in_features=4096, out_features=4096, bias=True)
    (4): ReLU(inplace=True)
    (5): Dropout(p=0.5, inplace=False)
    (6): Linear(in_features=4096, out_features=1000, bias=True)
  )
)

经过一些初步的小故障后,它现在可以正常工作了。我想将此模型用于 class 激活映射 (CAM) 以可视化 CNN 输出。我知道为了做到这一点,首先我们必须获得 vgg16 中最后一个卷积层的激活值,然后是最后一个全连接层的权重矩阵,最后取两者的点积。

首先,我使用这段代码class 获得了查询图像的索引

model.eval()
pred = model(img1.float())
class_idx = torch.argmax(pred).detach().numpy().tolist()
classes[class_idx]

然后我获取了大小为torch.Size([1, 512, 14, 14])

的最后一个卷积层激活的输入图像
last_conv_feat = torch.nn.Sequential(*list(model.features)[:30])
pred_a = last_conv_feat(img1.float())
print(pred_a.shape)

在此之后,我提取了 vgg16 classifier 的全连接层的权重,它的形状为 torch.Size([1000, 4096])

model.classifier[6].weight.shape

然后我从这个权重矩阵中恢复了相关 class 索引的权重参数

w_idx = model.classifier[6].weight[class_idx] # torch.Size([4096])

问题是卷积激活矩阵的形状和全连接层不匹配,一个是[1, 512, 14, 14],一个是[4096]。如何对这两个矩阵进行点积并获得 CAM 输出?

此特定模型不适合您指出的简单方法。你说的CAM是从最后只有一个线性层的模型中提取出来的,前面是一个全局平均池化层,像这样

features = MyConvolutions(x)
pooled_features = AveragePool(features)
predictions = Linear(pooled_features)

这通常适用于 ResNet 架构或其众多衍生产品之一。因此,我的建议是,除非有使用 VGG 的特定原因,否则您采用 ResNet 架构。

-------- 编辑 ------

如果你想使用 VGG,有两种选择:

  1. 简单的方法:切断 VGG 的最后三个(线性)层,将它们替换为 AveragePooling 和一个线性层,并为 ImageNet 或您正在使用的任何数据集微调。
  2. 通过将 VGG 的最后三层转换为卷积层(即无填充的 4096x512x7x7,然后是 4096x4096x1x1 和 1000x4096x1x1)来近似 CAM,并重组参数。整个事情现在只有卷积层,你可以像一个巨大的卷积过滤器一样操作它。唯一的问题:它的输出仍然是 1x1 大小。因此,您需要放大图像(可能尝试放大 2 倍),然后将其与新创建的全卷积网络进行卷积。这为您提供了一个近似的 CAM。