原理优点缺点GAP将多维特征映射降维为一个固定长度的特征向量①减少了模型的参数量;②保留更多的空间位置信息;③可并行计算,计算效率高;④具有一定程度的不变性①可能导致信息的损失;②忽略不同尺度的空间信息CAM利用最后一个卷积层的特征图×权重(用GAP代替全连接层,重新训练,经过GAP分类后概率最大的神经元的权重)效果已经很不错需要修改原模型的结构,导致需要重新训练该模型,大大限制了使用场景,如果模型已经上线了,或着训练的成本非常高,我们几乎是不可能为了它重新训练的。Grad-CAM最后一个卷积层的特征图×权重(通过对特征图梯度的全局平均来计算权重)①解决了CAM的缺点,适用于任何卷积神经网络;②利用特征图的梯度,可视化结果更准确和精细Grad-CAM++1. 定位更准确 2. 更适合同类多目标的情况

目录

GAP全局平均池化

CAM

Grad-CAM 

Grad-CAM++

GAP全局平均池化

论文:Network In Network

GAP (Global Average Pooling,全局平均池化),在上述论文中提出,用于避免全连接层的过拟合问题。全局平均池化就是对整个特征映射应用平均池化。

图1:将原本h × w × d的三维特征图,具体大小为6 × 6 × 3,经过GAP池化为1 × 1 × 3 输出值。也就是每一个channel的h × w 平均池化为一个值。特征图经过 GAP 处理后每一个特征图包含了不同类别的信息。 

GAP平均池化的操作步骤如下:

经过卷积操作和激活函数后,得到最后一个卷积层的特征图。对每个通道的特征图进行平均池化,即计算每个通道上所有元素的平均值。这将每个通道的特征图转化为一个标量值。将每个通道的标量值组合成一个特征向量。这些标量值的顺序与通道的顺序相同。最终得到的特征向量可以作为分类器的输入,用于进行图像分类。

CAM

论文:Learning Deep Features for Discriminative Localization

原理:利用最后一个卷积层的特征图与经过GAP分类后概率最大的神经元权重进行叠加。

图2:解释了在CNN中使用全局平均池化(GAP)生成类激活映射(CAM)的过程:

经过最后一层卷积操作之后,得到的特征图包含多个channel,如图1中的不同颜色的3个channel,也就是在GAP之前所对应的不同的channel特征图,就表示第k个channel的特征图。然后经过GAP处理后每个channel的特征图包含了不同类别的信息,就表示分类概率最大的神经元(图2黑色神经元)所对应连接的第k个神经元的权重。

Grad-CAM 

Grad-CAM: Visual Explanations from Deep Networks via Gradient-based Localization (arxiv.org)

Grad-CAM的前身是 CAM,CAM 的基本的思想是求分类网络某一类别得分对高维特征图 (卷积层的输出) 的偏导数,从而可以得到该高维特征图每个通道对该类别得分的权值;而高维特征图的激活信息 (正值) 又代表了卷积神经网络的所感兴趣的信息,加权后使用热力图呈现得到 CAM。

原理:Grad-CAM的关键思想是将输出类别的梯度(相对于特定卷积层的输出)与该层的输出相乘,然后取平均,得到一个“粗糙”的热力图。这个热力图可以被放大并叠加到原始图像上,以显示模型在分类时最关注的区域。

具体步骤如下:

选择网络的最后一个卷积层,因为它既包含了高级特征,也保留了空间信息。前向传播图像到网络,得到你想解释的类别的得分。计算此得分相对于我们选择的卷积层输出的梯度。对于该卷积层的每个通道,使用上述梯度的全局平均值对该通道进行加权。结果是一个与卷积层的空间维度相同的加权热力图。

因为热力图关心的是对分类有正面影响的特征,所以在线性组合的技术上加上了ReLU,以移除负值 。

第 k 个特征图对应于类别 c 的权重,表示:第 k 个特征图,表示特征图的像素个数,表示: 第c类得分的梯度,表示: 第 k个特征图中坐标( i , j )位置处的像素值;

Grad-CAM代码:

import torch

import cv2

import torch.nn.functional as F

import torchvision.transforms as transforms

import matplotlib.pyplot as plt

from PIL import Image

class GradCAM:

def __init__(self, model, target_layer):

self.model = model # 要进行Grad-CAM处理的模型

self.target_layer = target_layer # 要进行特征可视化的目标层

self.feature_maps = None # 存储特征图

self.gradients = None # 存储梯度

# 为目标层添加钩子,以保存输出和梯度

target_layer.register_forward_hook(self.save_feature_maps)

target_layer.register_backward_hook(self.save_gradients)

def save_feature_maps(self, module, input, output):

"""保存特征图"""

self.feature_maps = output.detach()

def save_gradients(self, module, grad_input, grad_output):

"""保存梯度"""

self.gradients = grad_output[0].detach()

def generate_cam(self, image, class_idx=None):

"""生成CAM热力图"""

# 将模型设置为评估模式

self.model.eval()

# 正向传播

output = self.model(image)

if class_idx is None:

class_idx = torch.argmax(output).item()

# 清空所有梯度

self.model.zero_grad()

# 对目标类进行反向传播

one_hot = torch.zeros((1, output.size()[-1]), dtype=torch.float32)

one_hot[0][class_idx] = 1

output.backward(gradient=one_hot.cuda(), retain_graph=True)

# 获取平均梯度和特征图

pooled_gradients = torch.mean(self.gradients, dim=[0, 2, 3])

activation = self.feature_maps.squeeze(0)

for i in range(activation.size(0)):

activation[i, :, :] *= pooled_gradients[i]

# 创建热力图

heatmap = torch.mean(activation, dim=0).squeeze().cpu().numpy()

heatmap = np.maximum(heatmap, 0)

heatmap /= torch.max(heatmap)

heatmap = cv2.resize(heatmap, (image.size(3), image.size(2)))

heatmap = np.uint8(255 * heatmap)

heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)

# 将热力图叠加到原始图像上

original_image = self.unprocess_image(image.squeeze().cpu().numpy())

superimposed_img = heatmap * 0.4 + original_image

superimposed_img = np.clip(superimposed_img, 0, 255).astype(np.uint8)

return heatmap, superimposed_img

def unprocess_image(self, image):

"""反预处理图像,将其转回原始图像"""

mean = np.array([0.485, 0.456, 0.406])

std = np.array([0.229, 0.224, 0.225])

image = (((image.transpose(1, 2, 0) * std) + mean) * 255).astype(np.uint8)

return image

def visualize_gradcam(model, input_image_path, target_layer):

"""可视化Grad-CAM热力图"""

# 加载图像

img = Image.open(input_image_path)

preprocess = transforms.Compose([

transforms.Resize((224, 224)),

transforms.ToTensor(),

transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])

])

input_tensor = preprocess(img).unsqueeze(0).cuda()

# 创建GradCAM

gradcam = GradCAM(model, target_layer)

heatmap, result = gradcam.generate_cam(input_tensor)

# 显示图像和热力图

plt.figure(figsize=(10,10))

plt.subplot(1,2,1)

plt.imshow(heatmap)

plt.title('热力图')

plt.axis('off')

plt.subplot(1,2,2)

plt.imshow(result)

plt.title('叠加后的图像')

plt.axis('off')

plt.show()

# 以下是示例代码,显示如何使用上述代码。

# 首先,你需要加载你的模型和权重。

# model = resnet20()

# model.load_state_dict(torch.load("path_to_your_weights.pth"))

# model.to('cuda')

# 然后,调用`visualize_gradcam`函数来查看结果。

# visualize_gradcam(model, "path_to_your_input_image.jpg", model.layer3[-1])

 Grad-CAM++

Grad-CAM++: Improved Visual Explanations for Deep Convolutional Networks (arxiv.org) 

精彩文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: