卷积可视化 (超详细,多图警告!)

 人们常说,深度学习模型是“黑盒”,模型学到的东西很难理解。

1.是什么?

卷积可视化是指通过可视化卷积神经网络中的过滤器和特征图来理解网络的工作原理和学习到的特征。常用的卷积可视化方法包括:
1. 特征图可视化:将网络中某一层的特征图可视化出来,以便观察网络在不同层次上学到的特征。
2. 卷积核可视化:通过最大化某一层的某个过滤器的响应来可视化该过滤器所学到的特征。
3. 热力图可视化:将输入图像的每个像素与网络中某一层的每个过滤器的响应值相乘,并将结果相加,得到一个热力图,用于可视化网络对输入图像的响应。
4. 梯度可视化:通过计算输入图像对网络输出的梯度,可视化哪些区域对网络输出有较大的影响,以及网络对不同特征的敏感度。

2.为什么?

卷积可视化可以帮助深度学习研究者更好地理解卷积的概念和原理,从而更好地设计和优化卷积神经网络。通过可视化,研究者可以更清晰地看到卷积运算中的每一个步骤,包括输入、卷积核、卷积操作和输出,从而更好地理解卷积的本质和作用。此外,卷积可视化还可以帮助研究者更好地理解卷积神经网络中的高级概念,如池化、批量归一化等,从而更好地设计和优化深度学习模型。

3.怎么样?

3.1 特征图可视化

特征图可视化有两类方法,一类是直接将某一层的feature map映射到0-255的范围,变成图像,但这样。另一类是使用一个反卷积网络(反卷积、反池化)将feature map变成图像,从而达到可视化feature map的目的。
特征图的可视化,是指对于给定输入图像,展示模型处理后的各中间层(包括卷积层和池化层等)输出的特征图(各中间层的激活函数的输出代表该层特征图)。这让我们可以看到输入数据在网络中是如何被分解,不同滤波器分别聚焦于原始图像的什么方面的信息。我们希望在三个维度对特征图进行可视化:宽度、高度和深度(通道,channel)。每个通道都对应相对独立的特征。所以将这些特征图可视化的正确方法是将每个通道的内容分别会支持成二维图像。

3.1.1反卷积网络deconvnet

feature map可视化的另一种方式是通过反卷积网络从feature map变成图像。反卷积网络在论文《Visualizing and Understanding Convolutional Networks》中提出,论文中提出图像像素经过神经网络映射到特征空间,而反卷积网络可以将feature map映射回像素空间。

如下图所示,反卷积网络的用途是对一个训练好的神经网络中任意一层feature map经过反卷积网络后重构出像素空间,主要操作是反池化unpooling、修正rectify、滤波filter,换句话说就是反池化,反激活,反卷积。

反卷积网络特征可视化结果

3.1.2 导向反向传播

在论文《Striving for Simplicity:The All Convolutional Net》中提出使用导向反向传播(Guided- backpropagation),导向反向传播与反卷积网络的区别在于对ReLU的处理方式。在反卷积网络中使用ReLU处理梯度,只回传梯度大于0的位置,而在普通反向传播中只回传feature map中大于0的位置,在导向反向传播中结合这两者,只回传输入和梯度都大于0的位置,这相当于在普通反向传播的基础上增加了来自更高层的额外的指导信号,这阻止了负梯度的反传流动,梯度小于0的神经元降低了正对应更高层单元中我们想要可视化的区域的激活值。

使用导向反向传播与反卷积网络的效果对比

分析反卷积网络的对各层feature map可视化的结果可知,CNN中会学到图像中的一些主要特征,如狗头,鼻子眼睛,纹理,轮廓等内容。但对特征图可视化有个明显的不足,即无法可视化图像中哪些区域对识别具体某个类别的作用,这个主要是使用CAM系列的方法

3.2 卷积核可视化

卷积核,在网络中起到将图像从像素空间映射到特征空间的作用,可认为是一个映射函数,像素空间中的值经过卷积核后得到响应值,在特征提取网络中,基本都是使用最大池化来选择最大响应值进入下一层继续卷积,其余响应值低的都进入待定。也就是说,我们认定只有响应值大的才会对最终的识别任务起作用。

根据这个思路,给定一个已经训练好的网络,现在想要可视化某一层的某一个卷积核,我们随机初始化生成一张图(指的是对像素值随机取值,不是数据集中随机选一张图),然后经过前向传播到该层,我们希望这个随机生成的图在经过这一层卷积核时,它的响应值能尽可能的大,换句话说,响应值比较大的图像是这个卷积核比较认可的,是与识别任务更相关的。然后不断调整图像像素值,直到响应值足够大,我们就可以认为此时的图像就是这个卷积核所认可的,从而达到可视化该卷积核的目的。

3.2.1可视化各层输出的feature map
import os
import torch
import torchvision as tv
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim
import argparse
import skimage.data
import skimage.io
import skimage.transform
import numpy as np
import matplotlib.pyplot as plt
import torchvision.models as models
from PIL import Image
import cv2
 
#提取某一层网络特征图
class FeatureExtractor(nn.Module):
    def __init__(self, submodule, extracted_layers):
        super(FeatureExtractor, self).__init__()
        self.submodule = submodule
        self.extracted_layers = extracted_layers
 
    def forward(self, x):
        outputs = {}
        for name, module in self.submodule._modules.items():
            if "fc" in name:
                x = x.view(x.size(0), -1)
            x = module(x)
            print(name)
            if (self.extracted_layers is None) or (name in self.extracted_layers and 'fc' not in name):
                outputs[name] = x
        # print(outputs)
        return outputs
 
 
def get_picture(pic_name, transform):
    img = skimage.io.imread(pic_name)
    img = skimage.transform.resize(img, (256, 256)) #读入图片时将图片resize成(256,256)的
    img = np.asarray(img, dtype=np.float32)
    return transform(img)
 
 
def make_dirs(path):
    if os.path.exists(path) is False:
        os.makedirs(path)
 
pic_dir = 'dataset/dogsvscats/train/cat.1700.jpg'
transform = transforms.ToTensor()
img = get_picture(pic_dir, transform)
# 插入维度
img = img.unsqueeze(0)
 
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
img = img.to(device)
 
net = models.resnet101(pretrained=True).to(device)
 
dst = './feautures'
therd_size = 256
 
myexactor = FeatureExtractor(submodule=net, extracted_layers=None)
output = myexactor(img)
#output是dict
#dict_keys(['conv1', 'bn1', 'relu', 'maxpool', 'layer1', 'layer2', 'layer3', 'layer4', 'avgpool', 'fc'])
 
for idx,val in enumerate(output.items()):
    k,v = val
    features = v[0]
    iter_range = features.shape[0]
    for i in range(iter_range):
        # plt.imshow(features.data.cpu().numpy()[i,:,:],cmap='jet')
        if 'fc' in k:  #不可视化fc层
            continue
 
        feature = features.data.cpu().numpy()
        feature_img = feature[i, :, :]
        feature_img = np.asarray(feature_img * 255, dtype=np.uint8)
 
        dst_path = os.path.join(dst, str(idx)+'-'+k)
 
        make_dirs(dst_path)
        feature_img = cv2.applyColorMap(feature_img, cv2.COLORMAP_JET)
        if feature_img.shape[0] < therd_size:
            tmp_file = os.path.join(dst_path, str(i) + '_' + str(therd_size) + '.png')
            tmp_img = feature_img.copy()
            tmp_img = cv2.resize(tmp_img, (therd_size, therd_size), interpolation=cv2.INTER_NEAREST)
            cv2.imwrite(tmp_file, tmp_img)
 
        dst_file = os.path.join(dst_path, str(i) + '.png')
        cv2.imwrite(dst_file, feature_img)


输入原图

因为到后期的图片会越来越小,所以我们有一个缩放操作,每张图片有一个输出的原图,还有一个放大后的图片


0-conv1

1-bn1

2-relu

3-maxpool

4-layer1

5-layer2

6-layer3

7-layer4

8-avgpool

可以看出,第一层的卷积层输出,特征图里面还可以看出猫的形状,到了后面卷积网络的输出特征图,看着有点像热力图,并且完全没有猫的样子,是更加抽象的图片表达 

3.2.2 卷积核权重可视化

我们一个conv层,比如有64个filter,每个filter又是个三维的,要扫过R,G,B通道,这里可视化的时候只选择了每个filter的第一个channel来显示

import torch
import torchvision.models as models
import matplotlib.pyplot as plt
from PIL import Image
from torchvision import transforms
 
input_image = Image.open('dataset/dogsvscats/train/cat.1700.jpg')
preprocess = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
input_tensor = preprocess(input_image)
input_batch = input_tensor.unsqueeze(0) # create a mini-batch as expected by the model
 
 
model = models.alexnet(pretrained=True)
if torch.cuda.is_available():
    input_batch = input_batch.to('cuda')
    model.to('cuda')
with torch.no_grad():
    output = model(input_batch)
 
#卷积可视化
#将数据灌入模型后,pytorch框架会进行对应的前向传播,要对卷积核可视化,我们需要把卷积核从框架中提取出来。多谢torch提供的接口,我们可以直接把对应层的权重取出
for layer in dict(model.features.named_children()).keys():
    if layer not in ['0','3','6','8','10']: #只有conv层可以可视化,maxpooling层和relu层不能可视化
        continue
    filter = dict(model.features.named_children())[layer]
    filter = filter.weight.cpu().clone()
    print("total of number of filter : ", len(filter))
    num = len(filter)
    plt.figure(figsize=(20, 17))
    for i in range(1,64):
        plt.subplot(9, 9, i)
        plt.axis('off')
        plt.imshow(filter[i][0, :, :].detach(),cmap='gray')
    plt.show()

conv1

conv2

conv3

conv4

conv5 

可以看出第一层卷积核 人类还是可以比较容易理解,有些提取的是边缘,有些提取的是圆形,有些提取的是斑点等。
最后一层卷积层的卷积核就已经看不出来是提取的什么东西了,即卷积核提取的是更加抽象的特征。

3.3 热力图可视化

3.3.1 CAM

 论文地址:https://arxiv.org/pdf/1512.04150.pdf 

实现原理:一张图片在经过CNN特征提取网络后得到feature maps, 再对每一个feature map进行全局平均池化,变成一维向量,再经过全连接层与softmax得到类的概率。

假定在GAP前是n个通道,则经过GAP后得到的是一个长度为1x n的向量,假定类别数为m,则全连接层的权值为一个n x m的张量。(注:这里先忽视batch-size)

对于某一个类别C, 现在想要可视化这个模型对于识别类别C,原图像的哪些区域起主要作用,换句话说模型是根据哪些信息得到该图像就是类别C。

做法是取出全连接层中得到类别C的概率的那一维权值,用W表示,即上图的下半部分。然后对GAP前的feature map进行加权求和,由于此时feature map不是原图像大小,在加权求和后还需要进行上采样,即可得到Class Activation Map。

用公式表示如下:(k表示通道,c表示类别,fk(x,y)表示feature map)
 

效果图: 

 

CAM的分析

CAM有个很致命的缺陷,它的结构是由CNN + GAP + FC + Softmax组成,也就是说如果想要可视化某个现有的模型,但大部分现有的模型没有GAP这个操作,此时想要可视化便需要修改原模型结构,并重新训练,相当麻烦,且如果模型很大,在修改后重新训练不一定能达到原效果,可视化也就没有意义了。因此,针对这个缺陷,其后续有了改进版Grad-CAM。

3.3.2  Grad-CAM

论文地址:https://arxiv.org/pdf/1610.02391v1.pdf

Grad-CAM的最大特点就是不再需要修改现有的模型结构了,也不需要重新训练了,直接在原模型上即可可视化。

原理:同样是处理CNN特征提取网络的最后一层feature maps。Grad-CAM对于想要可视化的类别C,使最后输出的类别C的概率值通过反向传播到最后一层feature maps,得到类别C对该feature maps的每个像素的梯度值,对每个像素的梯度值取全局平均池化,即可得到对feature maps的加权系数alpha,论文中提到这样获取的加权系数跟CAM中的系数几乎是等价的。接下来对特征图加权求和,使用ReLU进行修正,再进行上采样。

使用ReLU的原因是对于那些负值,可认为与识别类别C无关,这些负值可能是与其他类别有关,而正值才是对识别C有正面影响的。

用公式表示如下:

 

效果图如下: 

3.3.3 Grad-CAM++

论文地址:https://arxiv.org/pdf/1710.11063.pdf

Grad-CAM后续还有改进版Grad-CAM++,其主要的改进效果是定位更准确,更适合同类多目标的情况,所谓同类多目标是指一张图像中对于某个类出现多个目标,例如七八个人。

改进方法是对加权系数的获取提出新的方法,该方法复杂到不忍直视。因此这里就不介绍了,感兴趣的读者可通过文章末尾的链接获取该论文。

效果图:

 

3.4 梯度可视化

在一个表面上动画演示5个梯度下降法: 梯度下降(青色) ,momentum(洋红色) ,AdaGrad (白色) ,RMSProp (绿色) ,Adam (蓝色)。左坑是全局极小值,右坑是局部极小值

具体内容可看这篇参考资料 https://www.ruder.io/optimizing-gradient-descent/

3.5 一些可视化工具

3.5.1 CNN-Explainer

链接:https://github.com/poloclub/cnn-explainer

这是一个中国博士发布的名叫CNN解释器的在线交互可视化工具。主要对于那些初学深度学习的小白们 理解关于神经网络是如何工作很有帮助,如卷积过程,ReLU过程,平均池化过程,中间每一层的特征图的样子,都可以看到,相当于给了一个显微镜,可以随意对任意一层,任何一项操作的前后变化,观察得清清楚楚。

显示卷积的过程中前后特征图的变化,中间的操作。

CNN是如何输出预测的 

 

3.5.2  一些可视化特征图、卷积核、热力图的代码 

可视化特征图: https://github.com/waallf/Viusal-feature-map

可视化卷积核:https://blog.keras.io/how-convolutional-neural-networks-see-the-world.html

Grad-CAM:https://github.com/ramprs/grad-cam

热力图:https://github.com/heuritech/convnets-keras

下面这个项目是同时包含特征图可视化,卷积核可视化和热力图的一个链接:

https://github.com/raghakot/keras-vis

3.5.3 结构可视化工具

Netscope :https://github.com/ethereon/netscope

用于可视化模型结构的在线工具,仅支持caffe的prototxt文件可视化。需要自己写prototxt格式的文件。

 

PlotNeuralNet:https://github.com/HarisIqbal88/PlotNeuralNet

  这个稍微麻烦一点点,效果图如下:

3.5.4 网络结构手动画图工具

很多新手会问的一个问题,论文中那些网络结构图是如何画的。 这里解答一下,我所了解的主要是用PPT, VISIO。当然也可以使用上面那几个。  再补充一个在线工具,NN-SVG

https://alexlenail.me/NN-SVG/

 

参考:

https://poloclub.github.io/cnn-explainer/

CNN可视化技术总结(一)--特征图可视化

Pytorch之经典神经网络CNN(Extra-1) —— CNN可视化(查看中间层feature_map)

pytorch 提取卷积神经网络的特征图可视化

DL:卷积神经网络(CNN)的一些学习网址

深度学习(二):网站整理

https://blog.keras.io/how-convolutional-neural-networks-see-the-world.html

https://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf

https://www.ruder.io/optimizing-gradient-descent/

  • 44
    点赞
  • 120
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值