我写了一个pretrained vgg16图像分类模型及其层为
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)
)
)
经过一些最初的小问题,现在工作正常了。我想使用这个模型进行类激活映射 (CAM),以可视化 CNN 输出。我知道,为了做到这一点,我们首先必须获得 vgg16 中最后一个卷积层的激活,然后是最后一个全连接层的权重矩阵,最后取两者的点积。
首先,我使用此代码获取了查询图像的类索引
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 分类器的全连接层的权重,其形状为 torch.Size([1000, 4096])
model.classifier[6].weight.shape
从这个权重矩阵中,我恢复了相关类别索引的权重参数
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,有两种选择:
- 简单的一个:切断 VGG 的最后三个(线性)层,用 AveragePooling 和单个线性层替换它们,并针对 ImageNet 或您正在使用的任何数据集进行微调。
- 通过将 VGG 的最后三层转换为卷积层(即无填充的 4096x512x7x7,然后是 4096x4096x1x1 和 1000x4096x1x1)来近似 CAM,并重新组织参数。整个东西现在只有卷积层,你可以像一个巨大的卷积滤波器一样操作它。唯一的问题:它的输出大小仍然是 1x1。因此,您需要放大图像(尝试放大 2 倍),然后将其与新创建的全卷积网络进行卷积。这将为您提供一个近似的 CAM。
关于tensorflow - 如何在pytorch vgg16模型中进行类激活映射?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62494963/