神经网络输出可视化
CNN缺乏可分解性,无法直接理解为独立组件。可解释性很重要。通常要在准确性和简洁性或可解释性之间进行权衡。本文方法是CAM的推广,适用于各类CNN模型。
图像分类模型中,用于证明任何目标类别合理的“良好”视觉解释应是区分类别(在图像中定位该类别)和高分辨率(捕获细粒度细节)。像素空间梯度可视化具有高分辨率,突出显示了图像中的细粒度细节,但没有类别差异。而诸如CAM或grad-CAM之类的定位方法具有高度的类别区分。
为了结合两个方面的优势,可以将现有的像素空间渐变可视化与Grad-CAM融合,以创建高分辨率和区分类别的Guided Grad-CAM可视化。结果是,即使图像包含多个可能的概念,也可以在高分辨率细节中可视化对应于任何感兴趣决策的图像重要区域。
本文贡献:
相关研究有:CNN可视化、模型信任评估、对齐基于梯度的重要性、弱监督定位。
CAM的一个缺点是它要求特征图直接位于softmax层之前,因此仅适用于在预测之前立即对卷积图执行全局平均池化的特定类型的CNN架构(即,conv特征图→全局平均池化→softmax层)。 与某些任务上的通用网络相比,此类体系结构的精度可能较低。
本文介绍了一种使用梯度信号组合特征图的方法,无需对网络结构中做修改。 可以应用于基于CNN的现成架构。 对于全卷积架构,CAM是Grad-CAM的特例。
本文方法可以一次性实现定位,只需要每个图像一次前向传播和部分反向通过,因此通常效率要高一个数量级。
CNN中的深层表示代表了高级视觉结构。卷积层会自然保持空间信息,因此最后的卷积层在高级语义和详细的空间信息之间具有最佳的承诺。这些层中的神经元在图像中寻找特定于语义类的信息。Grad-CAM使用最后卷积层输入的梯度信息为每个神经元分配重要性,以进行特定的关注决策。
以分类为例,下面是解释:
直到在可视化期间标准化的比例常数 1 Z \frac{1}{Z} Z1为止, w k c w^c_k wkc的表达式都与Grad-CAM使用的 α k c \alpha^c_k αkc相同。因此,Grad-CAM是CAM的严格概括。这种概括能够从基于CNN的模型中生成可视化的解释,该模型将卷积层与更复杂的交互作用层叠在一起。
虽然Grad-CAM是类别区分的,并且可以定位相关的图像区域,但是它缺乏突出显示细微细节的能力。
指导式反向传播可以对相对于图像的梯度进行可视化,在通过ReLU层反向传播时抑制负梯度。直观上讲,这旨在捕获神经元检测到的像素,而不是抑制神经元检测到的像素。
指导式反向传播 + Grad-CAM可视化 => Guided Grad-CAM,通过逐元素相乘融合( L G r a d − C A M C L^C_{Grad-CAM} LGrad−CAMC先经双线性插值上采样到输入图像分辨率)。
这种可视化既具有高分辨率,又具有类别区分性。
用反卷积代替指导式反向传播可得到相似的结果,但反卷积可视化具有伪影,而引导式反向传播通常噪音较小。
通过对Grad-CAM稍加修改,可以得到关于对区域支持对网络预测变更的解释。 那么,删除那些区域中出现的概念将使模型对其预测更有信心,称为反事实解释。
针对卷积层的特征图 A A A否定了 y c y^c yc(c类得分)的梯度。因此,重要性权重 α k c \alpha^c_k αkc变为:
就是掩去负类。
弱监督定位,在图像分类环境下
给定图像,根据网络获取类别预测,然后为每个预测的类别生成Grad-CAM图,并使用最大强度的15%的阈值对它们进行二值化。 这将导致连接的像素段,并在单个最大段周围绘制一个边界框。(最大连通分支)
弱监督语义分割
不得不说,这篇论文实验部分是真的多
pytorch可以使用**钩子函数(hook)**来获得层的输入和输出。
import cv2
import numpy as np
import torchvision.transforms as transforms
from torchvision import models
from PIL import Image
def show_with_grad_cam(path_img, out_img):
# 准备图片,用于前向传播
img_raw = Image.open(path_img).convert('RGB')
img_raw = transforms.ToTensor()(img_raw)
_, H, W = img_raw.size()
# 定义模型,以resnet18为例
model = models.resnet18(pretrained=True)
model.eval()
features_map = [] # 后面用于存放特征图
grads = [] # 后面用于存放梯度
def get_feature_map(module, ip, op):
features_map.append(op.data.numpy())
def get_grad(module, grad_in, grad_out):
grads.append(grad_out[0].detach().data.numpy())
model.layer4[-1].register_forward_hook(get_feature_map)
model.layer4[-1].register_backward_hook(get_grad)
# 计算grad-CAM,前向传播和反向传播
data = img_raw.unsqueeze(0) # 增加一个batch size维度
output = model(data).squeeze() # 最后再消去batch size维度
idx = output.sort(descending=True)[1].data.numpy()[0] # 最可能的类别序号
model.zero_grad()
loss = output[idx]
loss.backward()
fm = features_map[0][0] # 取出特征图,第一个0是从列表取,第二个0是从batch size取
grad = grads[0][0]
weight = np.mean(grad.reshape([grad.shape[0], -1]), axis=1) # 权重是通道上梯度的平均
channel, h, w = fm.shape
cam = weight.dot(fm.reshape(channel, h * w)).reshape((h, w)) # 权值进行矩阵乘
# 热力图和原始图相加
cam_img = (cam - cam.min()) / (cam.max() - cam.min())
cam_img = np.uint8(255 * cam_img)
cam_img = cv2.resize(cam_img, (W, H))
heatmap = cv2.applyColorMap(cam_img, cv2.COLORMAP_JET)
img = cv2.imread(path_img, 1)
cam_img = 0.3 * heatmap + 0.7 * img
cv2.imwrite(out_img, cam_img)
if __name__ == '__main__':
show_with_grad_cam(r'E:\Downloads\a.jpg', r'E:\Downloads\grad-cam.jpg')