Score-CAM|用kernel加权解释CNN的预测结果

alt

【写在前面】

最近,人们越来越关注卷积神经网络的内部机制,以及网络做出特定决策的原因。在本文中,作者基于类激活映射开发了一种新颖的事后视觉解释方法,称为Score-CAM。与以前的基于类激活映射的方法不同,Score-CAM通过在目标类上的前向传递分数获得每个激活图的权重来摆脱对梯度的依赖,最终结果是通过权重和激活图的线性组合获得的。作者证明Score-CAM在解释决策过程中具有更好的视觉性能和公平性。本文的方法在识别和定位任务上都优于以前的方法。

1. 论文和代码地址

alt

Score-CAM: Score-Weighted Visual Explanations for Convolutional Neural Networks

论文地址:https://ieeexplore.ieee.org/document/9150840[1]

代码地址:https://github.com/haofanwang/Score-CAM[2]

2. Motivation

对深度神经网络 (DNN) 的解释通过暴露某些可由人类解释的推理方面来提高透明度。在解释中,可视化一定数量的兴趣 (例如输入特征的重要性或学习的权重) 已成为最直接的方法。由于空间卷积是图像和语言处理的最先进模型的常见组成部分,因此许多方法专注于建立卷积和卷积神经网络 (CNNs) 的更好解释。具体而言: 梯度可视化,扰动,类激活图 (CAM) 是三种广泛采用的方法。

基于梯度的方法将目标类的梯度反向传播到输入,以突出显示影响预测的图像区域。Saliency Map用目标类相对于输入图像的分数作为解释。其他作品基于并操纵该梯度,以在视觉上锐化结果。这些图通常质量低且有噪声。基于扰动的方法扰动原始输入,以观察模型预测的变化。为了找到最小区域,这些方法通常需要额外的正则化,并且计算量大。

基于CAM的解释通过卷积层激活图的线性加权组合,为单个输入提供视觉解释。CAM创建局部视觉解释,但对架构敏感;需要全局池层。Grad CAM及其变体,例如Grad CAM++,将CAM推广到没有全局池层的模型。在这项工作中,作者回顾了梯度信息在梯度CAM中的使用,并讨论了为什么梯度可能不是推广CAM的理想方法。此外,为了解决基于梯度的CAM变化的局限性,作者提出了一种新的事后视觉解释方法,称为ScoreCAM,其中激活图的重要性来自其突出显示的输入特征对模型输出的贡献,而不是局部灵敏度测量,即梯度信息。本文的贡献是:

(1) 作者提出了一种新的无梯度视觉解释方法Score-CAM,它弥合了基于扰动的方法和基于CAM的方法之间的差距,并以直观易懂的方式推导了激活图的权重。

(2) 作者使用平均下降/平均增加和删除曲线/插入曲线指标定量评估了分数CAM在识别任务上生成的显著性图,并表明分数CAM更好地发现了重要特征。

(3) 作者定性地评估了可视化和定位性能,并在这两项任务上取得了更好的结果。最后,描述了Score CAM作为分析模型问题的调试工具的有效性。

3. 方法

3.1. Methodology

与以前的方法不同,前者使用流入最后一个卷积层的梯度信息来表示每个激活图的重要性,作者将重要性纳入置信度的增加。

Increase of Confidence

给定一个取输入向量 并输出标量Y的一般函数 。对于已知基线输入 向Y的方向的贡献 是通过将 中的第i项替换为 来改变输出:

其中, 是具有相同形状 的向量。

Channel-wise Increase of Confidence (CIC)

给定一个CNN模型Y=f(X),它接受一个输入X并输出一个标量Y。我们在f中选取一个内部卷积层l,并将相应的激活作为A。用 表示 的第k个通道。对于已知的基线输入 对Y的贡献定义为:

Score-CAM

alt

考虑模型f中的卷积层l,给定一类感兴趣的c,Score-CAM 可以定义为:

其中 表示激活图 的CIC分数。

作者还将ReLU应用于map的线性组合,因为只对对感兴趣的类别有积极影响的特征感兴趣。由于权重来自与目标类上的激活映射对应的CIC分数,分数CAM摆脱了对梯度的依赖。虽然最后一个卷积层是更可取的选择,因为它是特征提取的终点,但在本文的框架中可以选择任何中间卷积层。

alt

3.2. Normalization on Score

每个前向传递神经网络是独立的,每个前向传播的分数幅度是不可预测且不固定的。归一化后的相对输出值(后softmax)比绝对输出值(前softmax)更适合测量相关性。因此,在分数CAM中,作者将权重表示为后softmax值,以便分数可以重新缩放到固定范围。

alt

由于每次预测的范围不同,是否使用softmax会产生差异。上图显示了一个有趣的发现。模型预测输入图像为“狗”,无论采用哪种类型的分数,都可以正确突出显示。但对于目标类“cat”,如果使用pre-softmax logit作为权重,则分数CAM同时突出显示“dog”和“cat”区域。相反,尽管“猫”的预测概率低于“狗”的预测概率,但使用softmax的分数CAM可以很好地区分两个不同的类别。归一化操作使分数CAM具有良好的类判别能力。

4.实验

alt

不同可解释方法的可视化结果

alt

类判别结果。

alt

识别结果。

alt

多目标结果。

alt

基于能量的定点博弈的比较评价。

alt

不同方法的显著性map。

alt

检测结果。

alt

随机抽样的检查结果。

alt

左侧由未微调VGG16生成,分类精度为22.0%,右侧由微调VGG16生成,分类精度为90.1%。结果表明,随着分类精度的提高,显著性图变得更加集中。

alt

左栏是输入示例,中间是显著图-预测类(person),右栏是显著图-目标类(bicycle)。

5. 总结

作者提出了一种新的Cam变体Score Cam,用于视觉解释。分数CAM使用每个激活图权重的置信度增加,消除了对梯度的依赖,并具有更合理的权重表示。本文对动机、实施、定性和定量评估进行了深入分析。本文的方法在识别和定位评估指标方面优于所有以前基于CAM的方法和其他最先进的方法。


已建立深度学习公众号——FightingCV,欢迎大家关注!!!

ICCV、CVPR、NeurIPS、ICML论文解析汇总:https://github.com/xmu-xiaoma666/FightingCV-Paper-Reading

面向小白的Attention、重参数、MLP、卷积核心代码学习:https://github.com/xmu-xiaoma666/External-Attention-pytorch

加入交流群,请添加小助手wx:FightngCV666

参考资料

[1]

https://ieeexplore.ieee.org/document/9150840: https://ieeexplore.ieee.org/document/9150840

[2]

https://github.com/haofanwang/Score-CAM: https://github.com/haofanwang/Score-CAM

本文由 mdnice 多平台发布

``` # Import necessary libraries import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms import matplotlib.pyplot as plt from torch.utils.data import DataLoader, random_split import torch.nn.functional as F # Define data transformations transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize((0.5,0.5,0.5), (0.5,0.5,0.5)) ]) # 使用原始字符串处理Windows路径和空格 dataset_path = r"C:\Users\29930\Desktop\IOS图像\结构参数图" # 加载完整数据集 full_dataset = datasets.ImageFolder( root=dataset_path, transform=transform ) # 按8:2比例划分 train_size = int(0.8 * len(full_dataset)) test_size = len(full_dataset) - train_size train_dataset, test_dataset = random_split( full_dataset, [train_size, test_size] ) # Create data loaders for batching and shuffling train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True) test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=64, shuffle=False) # Define the CNN Model class CNN(nn.Module): def __init__(self, input_channels=3, img_size=224): # 添加尺寸参数 super().__init__() self.conv1 = nn.Conv2d(input_channels,32,kernel_size=3,stride=1,padding=1) self.pool = nn.MaxPool2d(2,2) self.conv2 = nn.Conv2d(32,64,3,1,1) # 自动计算展平维度 with torch.no_grad(): self.flatten_size = 64 * (img_size//4) * (img_size//4) self.fc1 = nn.Linear(self.flatten_size,128) self.fc2 = nn.Linear(128,10) def forward(self, x): x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = x.view(-1, self.flatten_size) x = F.relu(self.fc1(x)) x = self.fc2(x) return x # Instantiate the model model = CNN() print(model) # Define the loss function and optimizer criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # Train the model num_epochs = 10 for epoch in range(num_epochs): running_loss =0.0 for i, data in enumerate(train_loader,0): inputs, labels = data optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() if i % 100==99: print(f'Epoch {epoch + 1}, Batch {i + 1}, Loss: {running_loss /100:.3f}') running_loss = 0.0 print('Finished Training') correct =0 total =0 with torch.no_grad(): for data in test_loader: images, labels = data outputs = model(images) _, predicted = torch.max(outputs.data,1) total += labels.size(0) correct += (predicted == labels).sum().item() print(f'Accuracy of the model on the test set: {100 * correct / total:.2f}%')```增加AUC可视化和Grad-CAM+++可视化
03-30
<think>好的,我现在需要帮助用户在他们现有的PyTorch代码中增加AUC可视化和Grad-CAM++可视化。首先,我需要理解他们提供的代码结构,然后确定在哪里插入新的功能。 首先,用户现有的代码做了以下事情: 1. 导入必要的库,包括torch、torchvision等。 2. 定义数据预处理和加载数据集,划分训练集和测试集。 3. 定义一个简单的CNN模型,包含两个卷积层和两个全连接层。 4. 训练模型,并在测试集上计算准确率。 用户现在想要增加AUC可视化和Grad-CAM++可视化。我需要分别考虑这两个部分如何实现。 首先,关于AUC(ROC曲线下面积)的计算。由于模型输出的是10个类别的logits,而AUC通常用于二分类问题,所以需要确认用户的问题是否是二分类还是多分类。如果数据是多分类的,可能需要用One-vs-Rest的方式计算每个类别的AUC,或者用户可能希望处理的是二分类问题。但根据代码中的数据集结构,ImageFolder通常对应多类别,而最后的全连接层输出是10个节点,说明是10分类问题。因此,可能需要调整AUC的计算方式,比如使用多类别的处理方式,比如macro-average或micro-average的AUC。不过,常见的做法是对每个类别单独计算AUC,然后取平均。这里可能需要使用sklearn的roc_auc_score函数,并设置multi_class参数为'ovr'(One-vs-Rest)。 接下来,实现AUC的步骤可能包括: - 在测试阶段收集所有样本的真实标签和预测概率。 - 使用这些数据计算AUC,并绘制ROC曲线。 - 需要将模型的输出通过softmax转换为概率,然后传入roc_auc_score函数。 然后是Grad-CAM++的可视化。Grad-CAM++是对Grad-CAM的改进,通过更高的阶梯度来获取更精确的激活图。实现Grad-CAM++需要: 1. 获取目标卷积层的输出和梯度。 2. 计算梯度权重时,不仅考虑一阶梯度,还考虑二阶和三阶梯度。 3. 生成激活图,并将其叠加到原始图像上显示。 具体步骤可能包括: - 在模型的前向传播中注册钩子(hook)来捕获目标层的输出和梯度。 - 在反向传播时计算这些梯度,并根据Grad-CAM++的公式计算权重。 - 将激活图与原始图像结合,进行可视化。 需要注意的是,现有的CNN模型中的卷积层是conv1和conv2,用户可能需要选择最后一个卷积层作为目标层,通常是conv2。需要修改CNN类,使得在前向传播时可以获取中间层的输出,或者在hook中捕获这些值。 此外,可视化部分需要使用matplotlib来显示图像和热力图。还需要确保输入的图像被正确反标准化,以便显示原始图像。 现在需要考虑如何整合这些功能到现有代码中,而不会破坏原有的训练和测试流程。可能的修改点包括: 1. 在测试阶段,除了计算准确率,还需要收集所有样本的概率和真实标签。 2. 在模型中添加钩子来捕获梯度,或者在forward方法中返回中间层的输出。 3. 添加新的函数来计算Grad-CAM++并生成可视化结果。 接下来,需要验证这些修改是否会影响模型的训练过程。由于在测试阶段使用torch.no_grad(),所以在计算梯度时可能需要临时启用梯度计算,或者在特定部分允许梯度保留。 可能的错误点: - 在多分类情况下,AUC的计算是否正确处理。 - 钩子是否正确注册和释放,避免内存泄漏。 - 图像的反标准化是否正确,以恢复原始图像的显示。 - 确保Grad-CAM++的计算公式正确,特别是关于梯度的权重计算。 针对AUC部分,假设是10分类问题,需要计算每个类别的AUC,然后取平均。可能需要使用sklearn的roc_auc_score,并设置multi_class='ovr',同时需要将标签进行二值化处理。不过,roc_auc_score在multi_class='ovr'时,会自动处理多分类情况,但需要确保预测概率是正确的形状。 对于Grad-CAM++的实现,需要参考相关论文或可靠的实现代码。例如,计算激活图的权重时,需要计算每个神经元的梯度,以及梯度平方和梯度立方,然后进行加权组合。 最后,需要将这两个可视化部分的结果展示出来,可能需要选择几个样本图像进行展示,而不是全部,以避免内存问题和可视化过于密集。 总结步骤: 1. 修改测试部分,收集所有预测概率和真实标签,计算AUC并绘制ROC曲线。 2. 实现Grad-CAM++的类或函数,在指定层获取梯度和激活,生成热力图并叠加到图像上显示。 3. 在代码中调用这些新增的可视化函数,可能放在测试阶段之后。 现在需要将这些思考转化为具体的代码修改,并确保语法正确,特别是PyTorch的梯度处理和钩子使用部分。</think>以下是为您的代码添加AUC可视化和Grad-CAM++可视化的分步解决方案: ```python # 新增库导入 from sklearn.metrics import roc_curve, auc, roc_auc_score from sklearn.preprocessing import label_binarize import numpy as np import cv2 # ====== 第一部分:AUC可视化 ====== # 在测试阶段收集预测概率和真实标签 all_probs = [] all_labels = [] with torch.no_grad(): for data in test_loader: images, labels = data outputs = model(images) probs = F.softmax(outputs, dim=1) all_probs.append(probs.numpy()) all_labels.append(labels.numpy()) all_probs = np.concatenate(all_probs) all_labels = np.concatenate(all_labels) # 计算多分类AUC(使用One-vs-Rest方法) n_classes = len(full_dataset.classes) fpr = dict() tpr = dict() roc_auc = dict() # 将标签转换为二值化形式 y_test = label_binarize(all_labels, classes=range(n_classes)) for i in range(n_classes): fpr[i], tpr[i], _ = roc_curve(y_test[:, i], all_probs[:, i]) roc_auc[i] = auc(fpr[i], tpr[i]) # 计算宏观平均AUC fpr["macro"], tpr["macro"], _ = roc_curve(y_test.ravel(), all_probs.ravel()) roc_auc["macro"] = auc(fpr["macro"], tpr["macro"]) # 绘制ROC曲线 plt.figure() plt.plot(fpr["macro"], tpr["macro"], label=f'Macro-average ROC (AUC = {roc_auc["macro"]:.2f})', color='deeppink', linestyle=':') for i in range(n_classes): plt.plot(fpr[i], tpr[i], lw=1, label=f'Class {full_dataset.classes[i]} (AUC = {roc_auc[i]:.2f})') plt.plot([0, 1], [0, 1], 'k--', lw=1) plt.xlim([0.0, 1.0]) plt.ylim([0.0, 1.05]) plt.xlabel('False Positive Rate') plt.ylabel('True Positive Rate') plt.title('Multi-class ROC Curve') plt.legend(loc="lower right") plt.show() # ====== 第二部分:Grad-CAM++可视化 ====== class GradCAMpp: def __init__(self, model, target_layer): self.model = model self.target_layer = target_layer self.activations = None self.gradients = None # 注册前向传播和反向传播钩子 target_layer.register_forward_hook(self.save_activation) target_layer.register_backward_hook(self.save_gradient) def save_activation(self, module, input, output): self.activations = output.detach() def save_gradient(self, module, grad_input, grad_output): self.gradients = grad_output[0].detach() def __call__(self, input_tensor, class_idx=None): # 前向传播 self.model.eval() output = self.model(input_tensor) if class_idx is None: class_idx = output.argmax(dim=1) # 反向传播 self.model.zero_grad() one_hot = torch.zeros_like(output) one_hot[0][class_idx] = 1.0 output.backward(gradient=one_hot) # 计算权重 grad = self.gradients[0].numpy() act = self.activations[0].numpy() # Grad-CAM++核心计算 grad_power = grad ** 2 grad_power_times_act = grad_power * act weights = grad_power_times_act / (2 * grad_power_times_act + np.sum(act * grad_power_times_act, axis=(1,2), keepdims=True)) weights = np.sum(weights, axis=(1,2)) # 生成热力图 cam = np.zeros(act.shape[1:], dtype=np.float32) for i, w in enumerate(weights): cam += w * act[i] cam = np.maximum(cam, 0) cam = cv2.resize(cam, (224, 224)) cam = cam - np.min(cam) cam = cam / np.max(cam) return cam # 获取目标层(使用最后一个卷积层) target_layer = model.conv2 # 创建Grad-CAM++实例 gradcam_pp = GradCAMpp(model, target_layer) # 反标准化函数 def denormalize(tensor): mean = torch.tensor([0.5, 0.5, 0.5]) std = torch.tensor([0.5, 0.5, 0.5]) return tensor * std.view(3, 1, 1) + mean.view(3, 1, 1) # 可视化示例 num_samples = 3 # 可视化样本数量 fig, axes = plt.subplots(num_samples, 3, figsize=(15, 10)) for idx in range(num_samples): # 获取测试样本 img, label = test_dataset[idx] input_tensor = img.unsqueeze(0) # 获取预测结果 with torch.no_grad(): output = model(input_tensor) pred_class = output.argmax().item() # 生成热力图 cam = gradcam_pp(input_tensor, pred_class) # 处理原始图像 img_denorm = denormalize(img).permute(1, 2, 0).numpy() # 绘制结果 axes[idx, 0].imshow(img_denorm) axes[idx, 0].axis('off') axes[idx, 0].set_title(f'Original (True: {full_dataset.classes[label]})') axes[idx, 1].imshow(cam, cmap='jet') axes[idx, 1].axis('off') axes[idx, 1].set_title(f'Grad-CAM++ Heatmap (Pred: {full_dataset.classes[pred_class]})') axes[idx, 2].imshow(img_denorm) axes[idx, 2].imshow(cam, cmap='jet', alpha=0.5) axes[idx, 2].axis('off') axes[idx, 2].set_title('Overlay') plt.tight_layout() plt.show() ``` 主要改进说明: 1. **AUC可视化**: - 使用$roc\_auc\_score$计算多分类AUC - 通过$label\_binarize$处理标签 - 绘制每个类别的ROC曲线和宏观平均曲线 - 最终显示AUC数值和曲线形状 2. **Grad-CAM++可视化**: - 实现标准的Grad-CAM++算法公式: $$ \alpha_{ij}^kc = \frac{\partial^2 S^c}{(\partial A_{ij}^k)^2} \Big/ \left(2\frac{\partial^2 S^c}{(\partial A_{ij}^k)^2} + \sum_{a,b} A_{ab}^k \frac{\partial^3 S^c}{(\partial A_{ij}^k)^3}\right) $$ - 使用钩子(hook)技术捕获中间层激活值和梯度 - 实现热力图与原始图像叠加显示 - 包含样本原始图像、热力图和叠加效果的三列对比 3. **代码优化**: - 添加图像反标准化处理 - 自动获取类别数量 - 动态选择最后一个卷积层作为目标层 - 支持自定义可视化样本数量 使用注意事项: 1. 确保测试集足够大以获取可靠的AUC评估 2. 可以根据需要调整显示样本数量(num_samples) 3. 热力图强度可以通过调整alpha参数控制 4. 如果显存不足,可以减小测试时的batch_size
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值