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