卷积神经网络的特征图可视化秘籍——PyTorch实现

2023-05-16

卷积神经网络的特征图可视化秘籍——PyTorch实现

  • 可视化的定义及步骤
  • PyTorch实现
    • 以预训练好的VGG16为例进行可视化
    • 关键代码剖析
    • 如果是自行搭建的网络,如何索引网络层?
      • 继续使用序号索引
      • 不使用序号,直接索引模型内部网络层的属性

可视化的定义及步骤

  这里所说的可视化是指对卷积神经网络中间层的输出特征图进行可视化,比如将网络第八层的输出特征图保存为图像显示出来。那么,我们实际上要做的事情非常简单,分为如下两步:
【1】搭建网络模型,并将数据输入到网络之中
【2】提取想可视化层的输出特征图,并将其按每个channel都保存为一张图像的方式进行可视化,其原因在于有几个channel就代表了该层输出有几张特征图(也代表了该层的卷积核数量)

PyTorch实现

以预训练好的VGG16为例进行可视化

  下面提供了PyTorch实现的从在ImageNet上预训练好的VGG16中可视化第一层输出的代码,该代码参考了PyTorch|提取神经网络中间层特征进行可视化的实现,实现思路及所做的修改如下:

  1. 在基于ImageNet预训练的VGG16网络上,处理单张图像作为网络的输入,对该图像进行的归一化处理以ImageNet图像的标准处理方式进行。
  2. 根据给定的可视化层序号,获取该层的输出特征图,注意返回的是一个[1,channels,width,height]的四维张量。
  3. 将每一个channel的结果即[width,height]的二维张量都保存为一张图像,那么该层的输出特征图一共有channels[width,height]灰度图像(单通道图像)
  4. 在对输入图像的处理上,采用了ImageNet图像的归一化方式,得到的图像像素值分布区间为[-2.7,2.1]之间,而不是熟悉的[-1,1]或是[0,1]。在保存单个channel的特征图时,保存函数又需要输出特征图像素分布为[0,255]或是[0,1]。那么在这里,采用了最大最小比例放缩的方法将输出特征图的像素值分布区间转化到了[0,1],而没有像上述链接一样使用Sigmoid来将像素值分布区间转化为[0,1]。笔者认为采用最大最小比例放缩的方法更加合理,另外需要注意添加一个1e-5来防止分母为0的情况。
import cv2
import numpy as np
import torch
from torch.autograd import Variable
from torchvision import models
import os

# 该函数创建保存特征图的文件目录,以网络层号命名文件夹,如feature\\1\\..文件夹中保存的是模型第二层的输出特征图
def mkdir(path):

    isExists = os.path.exists(path) # 判断路径是否存在,若存在则返回True,若不存在则返回False
    if not isExists: # 如果不存在则创建目录
        os.makedirs(path)
        return True
    else:
        return False

# 图像预处理函数,将图像转换成[224,224]大小,并进行Normalize,返回[1,3,224,224]的四维张量
def preprocess_image(cv2im, resize_im=True):

    # 在ImageNet100万张图像上计算得到的图像的均值和标准差,它会使图像像素值大小在[-2.7,2.1]之间,但是整体图像像素值的分布会是标准正态分布(均值为0,方差为1)
    # 之所以使用这种方法,是因为这是基于ImageNet的预训练VGG16对输入图像的要求
    mean = [0.485, 0.456, 0.406]
    std = [0.229, 0.224, 0.225]

    # 改变图像大小并进行Normalize
    if resize_im:
        cv2im = cv2.resize(cv2im, dsize=(224,224),interpolation=cv2.INTER_CUBIC)
    im_as_arr = np.float32(cv2im)
    im_as_arr = np.ascontiguousarray(im_as_arr[..., ::-1])
    im_as_arr = im_as_arr.transpose(2, 0, 1)  # 将[W,H,C]的次序改变为[C,W,H]

    for channel, _ in enumerate(im_as_arr): # 进行在ImageNet上预训练的VGG16要求的ImageNet输入图像的Normalize
        im_as_arr[channel] /= 255
        im_as_arr[channel] -= mean[channel]
        im_as_arr[channel] /= std[channel]

    # 转变为三维Tensor,[C,W,H]
    im_as_ten = torch.from_numpy(im_as_arr).float()
    im_as_ten = im_as_ten.unsqueeze_(0) # 扩充为四维Tensor,变为[1,C,W,H]

    return im_as_ten # 返回处理好的[1,3,224,224]四维Tensor


class FeatureVisualization():

    def __init__(self,img_path,selected_layer):
        '''
        :param img_path:  输入图像的路径
        :param selected_layer: 待可视化的网络层的序号
        '''
        self.img_path = img_path
        self.selected_layer = selected_layer
        self.pretrained_model = models.vgg16(pretrained=True).features # 调用预训练好的vgg16模型

    def process_image(self):
        img = cv2.imread(self.img_path)
        img = preprocess_image(img)
        return img

    def get_feature(self):

        input=self.process_image() # 读取输入图像
        # 以下是关键代码:根据给定的层序号,返回该层的输出
        x = input
        for index, layer in enumerate(self.pretrained_model):
            x = layer(x) # 将输入给到模型各层,注意第一层的输出要作为第二层的输入,所以才会复用x

            if (index == self.selected_layer): # 如果模型各层的索引序号等于期望可视化的选定层号
                return x # 返回模型当前层的输出四维特征图

    def get_single_feature(self):
        features = self.get_feature() # 得到期望模型层的输出四维特征图
        return features

    def save_feature_to_img(self):

        features=self.get_single_feature() # 返回一个指定层输出的特征图,属于四维张量[batch,channel,width,height]
        for i in range(features.shape[1]):
            feature = features[:, i, :, :] # 在channel维度上,每个channel代表了一个卷积核的输出特征图,所以对每个channel的图像分别进行处理和保存
            feature = feature.view(feature.shape[1], feature.shape[2]) # batch为1,所以可以直接view成二维张量
            feature = feature.data.numpy() # 转为numpy

            # 根据图像的像素值中最大最小值,将特征图的像素值归一化到了[0,1];
            feature = (feature - np.amin(feature))/(np.amax(feature) - np.amin(feature) + 1e-5) # 注意要防止分母为0! 
            feature = np.round(feature * 255) # [0, 1]——[0, 255],为cv2.imwrite()函数而进行

            mkdir('C:\\Users\\hu\\Desktop\\fea\\' + str(self.selected_layer))  # 创建保存文件夹,以选定可视化层的序号命名
            cv2.imwrite('C:\\Users\\hu\\Desktop\\fea\\' + str(self.selected_layer) + '\\' + str(i) + '.jpg',feature)  # 保存当前层输出的每个channel上的特征图为一张图像


if __name__=='__main__':

    for k in range(1): # k代表选定的可视化的层的序号
        myClass = FeatureVisualization('C:\\Users\\hu\\Desktop\\TRP.jpg', k) # 实例化类
        print (myClass.pretrained_model)
        myClass.save_feature_to_img() # 开始可视化,并将特征图保存成图像

关键代码剖析

        self.pretrained_model = models.vgg16(pretrained=True).features # 调用预训练好的vgg16模型
        ...
        x = input
        for index, layer in enumerate(self.pretrained_model):
            x = layer(x) # 将输入给到模型各层,注意第一层的输出要作为第二层的输入,所以才会复用x

            if (index == self.selected_layer): # 如果模型各层的索引序号等于期望可视化的选定层号
                return x # 返回模型当前层的输出四维特征图

  首先,self.pretrained_model实际上就是网络层按顺序排列组成的列表,展示了网络层的顺序排列,我们打印出self.pretrained_model来看:
在这里插入图片描述
  for index, layer in enumerate(self.pretrained_model):中的enumerate(与打印图中的(1)、(2)、(3)等一致),目的在于得知期望输出的层在模型中的的序号index,这样就可以将它与对应的选定序号相比对,从而将期望层的输出特征图返回了
  另一个需要注意的地方是这里复用了x,这是因为模型第二层的输入要求是第一层的输出,而不再是原始输入了,所以需要复用x

如果是自行搭建的网络,如何索引网络层?

  在上面给出的示例中,基于预训练的模型VGG16,通过self.pretrained_model = models.vgg16(pretrained=True).features就可以返回一个包含所有网络层的Sequential,然后通过for index, layer in enumerate(self.pretrained_model):中的enumerate来实现得知期望输出层在模型中的序号。通过模型输出层的序号与选定的序号是否匹配,来判断是否是我们想要的进行可视化的层。
  那么,对于我们自己定义和训练的网络来说,一般是没有features这个属性的,我们如何来索引想要的网络层,从而得到它的输出特征图呢?在这里介绍两种方法。

继续使用序号索引

  既然自行搭建的网络没有features属性,那我们就人为构建一个,反正它的本质是个列表或Sequential(Sequential本身也可以理解成一个列表,可以通过net[3]这种索引访问其中元素)然后把网络层依次添加到里面,就可以照上述方法接着使用了,代码如下:

import torch as t
from torch import nn

# 整个网络由四层网络搭建,神经元个数分别为4——128——64——32——2
class ClassNet(nn.Module):
    def __init__(self):
        # 初始化模型
        nn.Module.__init__(self)
        self.features = []
        # 搭建多层感知机网络模型
        self.net_1 = nn.Linear(in_features=4, out_features=128)  # 网络第一层:输入神经元为4个,输出神经元为128个
        self.features.append(self.net_1)
        self.net_2 = nn.Linear(in_features=128, out_features=64) # 网络第二层:输入神经元为128个,输出神经元为64个
        self.features.append(self.net_2)
        self.net_3 = nn.Linear(in_features=64, out_features=32)  # 网络第三层:输入神经元为64个,输出神经元为32个
        self.features.append(self.net_3)
        self.net_4 = nn.Linear(in_features=32, out_features=2)   # 网络第四层:输入神经元为32个,输出神经元为2个
        self.features.append(self.net_4)

    def forward(self, x): # 取得网络输入x
        y_1 = self.net_1(x)   # 将输入x输入第一层,得到第一层的输出结果
        y_2 = self.net_2(y_1) # 将第一层的输出结果作为第二层的输入
        y_3 = self.net_3(y_2) # 将第二层的输出结果作为第三层的输入
        y_4 = self.net_4(y_3) # 将第三层的输出结果作为第四层的输入
        return y_4 # 得到的第四层的输出结果即为模型最终输出,两个神经元分别代表土壤流失量和径流深的数值

input = t.zeros(4)
input = t.unsqueeze(input,dim=0)
net = ClassNet() # 搭建模型
k = 0 # 可视化第一层的输出特征图
x = input
for index, layer in enumerate(net.features):
    print(index,layer)
    x = layer(x) # 复用x使得第一层的输出作为第二层输入
    if index == k:
        print(x)

不使用序号,直接索引模型内部网络层的属性

  从本质上来说,使用序号的索引就是为了确定期望的中间层处于模型的什么位置。那么如果我们在构建模型时定义了想要的模型中间层输出(前提),那么就可以直接访问该属性:
  比如在下面模型中,我们想要模型第二层的输出结果可视化,由于我们在forward()函数中定义了第二层的输出为self.y_2,那么就只需要通过访问net.y_2,就能直接将该输出保存出来,进行后面的逐channel保存操作。
  关于获取模型中间层的输出,这篇博客Pytorch学习(十六)----获取网络的任意一层的输出中关于【1】如何从Sequential中提取单独网络层以及通过Sequential在forward中定义中间层输出;【2】通过hook函数(钩子函数)获取中间层输出;这两部分有点意思。

import torch as t
from torch import nn

# 整个网络由四层网络搭建,神经元个数分别为4——128——64——32——2
class ClassNet(nn.Module):
    def __init__(self):
        # 初始化模型
        nn.Module.__init__(self)
        self.features = []
        # 搭建多层感知机网络模型
        self.net_1 = nn.Linear(in_features=4, out_features=128)  # 网络第一层:输入神经元为4个,输出神经元为128个
        self.features.append(self.net_1)
        self.net_2 = nn.Linear(in_features=128, out_features=64) # 网络第二层:输入神经元为128个,输出神经元为64个
        self.features.append(self.net_2)
        self.net_3 = nn.Linear(in_features=64, out_features=32)  # 网络第三层:输入神经元为64个,输出神经元为32个
        self.features.append(self.net_3)
        self.net_4 = nn.Linear(in_features=32, out_features=2)   # 网络第四层:输入神经元为32个,输出神经元为2个
        self.features.append(self.net_4)

    def forward(self, x): # 取得网络输入x
        self.y_1 = self.net_1(x)   # 将输入x输入第一层,得到第一层的输出结果
        self.y_2 = self.net_2(self.y_1) # 将第一层的输出结果作为第二层的输入
        self.y_3 = self.net_3(self.y_2) # 将第二层的输出结果作为第三层的输入
        self.y_4 = self.net_4(self.y_3) # 将第三层的输出结果作为第四层的输入
        return self.y_4 # 得到的第四层的输出结果即为模型最终输出,两个神经元分别代表土壤流失量和径流深的数值

input = t.zeros(4)
input = t.unsqueeze(input,dim=0)
net = ClassNet() # 搭建模型
output = net(input) # 将输入给到模型
print(net.y_2) # 由于在forward时定义了想要的中间层输出,直接索引该属性即可得到期望可视化的特征图
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

卷积神经网络的特征图可视化秘籍——PyTorch实现 的相关文章

  • 阿里云服务器盘镜像备份恢复到本地VMware

    步骤如下 xff1a 1 阿里云创建镜像并下载镜像文件到本地 xff1b 2 使用qemu img工具转换镜像文件为vmdk 格式 3 将转换后的文件挂载到VMware上然后运行 xff1b 4 运行后修改账号密码 IP等信息 xff1b
  • 树莓派系统入门教程(一)—— 烧录系统镜像,配置系统信息

    烧录系统镜像 一 准备工作二 烧录镜像2 1 格式化内存卡2 2 烧录镜像 xff08 Raspberry Pi Imager xff09 2 3 烧录镜像 xff08 Win32DiskImager xff09 一 准备工作 1 材料准备
  • LCD1602按下复位后乱码的问题

    1602按下后会有乱码的问题 xff0c 其实根本上是硬件引起的 某些最小系统板的复位电路没做好 xff0c 按下复位键后 xff0c 就会产生乱码 但是出现这种现象 xff0c 是可以用软件后期修正的 由于LCD复位后默认是8线输入 xf
  • Linux VNC server 安装配置

    一 服务端操作 0 打开终端 xff0c 切换到目标账户 su xie 1 打开终端使用yum命令安装vnc sever yum install tigervnc server y 2 设置 vnc server 开机启动 chkconfi
  • IDEA出现 java.lang.NoSuchMethodError 错误的原因及解决方法

    出现 java lang NoSuchMethodError 错误的原因及解决方法 问题分析 xff1a 出现这种情况 xff0c 一般是存在jar冲突 xff0c 简单的说就是导入了两个相同名称的jar xff0c 系统不知道用哪个 解决
  • VNC 桌面没有图标解决办法

    VNC 桌面没有图标解决办法 安装 sudo apt get install gnome core xfce4 打开xstartup文件 xff08 前提已安装VNC xff09 命令 xff1a vim vnc xstartup 操作 x
  • DELL R730服务器安装centos7.3教程

    服务器安装centos7 3系统分为两步 第一步 xff0c 对磁盘进行分组并部署raid 第二步 xff0c 通过系统安装程序安装操作系统 xff0c 并进行系统安装配置 第一步 xff0c RADI的分组部署 将服务器开机 xff0c
  • 访问控制ACL

    ACL访问控制列表可以对具体的用户或组设置权限 root 64 localhost useradd tony tom root 64 localhost passwd tony root 64 localhost passwd tom ro
  • NFS与自动挂载

    1 NFS共享文件server端的配置 xff08 server ip xff1a 172 16 8 11 xff09 root 64 localhost getenforce Enforcing root 64 localhost set
  • SAP主题改变方法

    背景 xff1a SAP7 6有很多主题 xff0c 然后按以下的方法可以改变 xff08 我比较习惯blue这个主题目 xff0c 7 6版本现在默认不是这个主题目 xff09 文章目录 方法一 通过登录后设置步骤1步骤2 方法二 通过程
  • SMB文件共享

    1 windows server端配置 C Users wll gt net share 共享名 资源 注解 C C 默认共享 D D 默认共享 IPC
  • swap空间扩容

    1 查看现有swap空间大小 root 64 centos7 free total used free shared buff cache available Mem 2028116 389108 1266584 12644 372424
  • Linux 卸载及删除磁盘分区

    1 卸载分区 root 64 centos7 df Th Filesystem Type Size Used Avail Use Mounted on dev mapper centos root xfs 17G 4 0G 14G 24 d
  • 磁盘阵列raid5的创建及管理

    1 在虚拟机上添加4个磁盘 xff0c 其中主用盘3个 xff0c 热备盘1个 root 64 centos7 fdisk l Disk dev sda 21 5 GB 21474836480 bytes 41943040 sectors
  • Keil关于.axf文件报错

    项目场景 xff1a 熟悉Keil C51的同学在使用Keil MDK编译STM32系列单片机时会更容易上手 Keil C51是美国Keil Software公司出品的51系列兼容单片机C语言软件开发系统 xff0c 与汇编相比 xff0c
  • 《java核心技术卷1》部分章节读书笔记

    目录 3 java基本的程序设计结构3 1 命名规范3 2 数据类型3 3 运算符3 4 枚举类型 3 5 字符串3 6 大数值 BigInteger和BigDecimal3 7 数组 4 对象与类4 1 识别类4 2 类之间的关系4 3
  • linux 问题-——退出vi编辑器 wq失效

    退出linux的vi编辑器时 xff0c 先按左上角的esc按键 xff0c 再输出输入命令 保存不退出w强制保存但不退出w 保存并退出wq强制保存并退出wq xff01 不保存退出q不保存并强制退出q 出现以下问题 xff1a esc退出
  • SUMO中车辆类型的定义及路由文件的写法

    车辆类型的定义 在SUMO中 xff0c 通过vType标签来定义车辆的类型 xff0c 一般可以写成如下形式 xff1a span class token operator lt span vType id span class toke
  • 为什么c语言允许直接访问物理地址?

    C语言允许直接访问物理地址 xff0c 是因为它是一种面向底层的语言 xff0c 提供了底层的硬件操作和系统调用的能力 在C语言中 xff0c 可以通过指针变量直接访问内存地址 xff0c 从而对硬件进行底层控制和操作 在一些应用场景下 x
  • TortoiseGit 安装和使用

    TortoiseGit是一款适用于Windows系统的开源的git版本控制客户端 xff0c 其优点是使用图像化界面操作 xff0c 而不需要记住或使用Git命令 xff0c 从而增加开发效率 xff0c 对不熟悉命令的使用者比较友善 下面

随机推荐

  • 解放拖动屏幕的双手——用xrandr配置多屏显示

    操作系统 xff1a Arch Linux 桌面系统 xff1a Xfce 又是一次会议的结束 xff0c 待我回办公室把大屏幕接上 xff0c 准备继续干 等等 xff0c 这是怎么回事 xff1f 打开系统的Display设置一看 xf
  • 深入浅出BGP

    文章目录 深入浅出BGP说明一 BGP的产生1 1 动态路由的分类1 2 BGP概述 二 与IGP的区别三 BGP核心3 1 属性3 1 1 属性特点 3 2 选路规则3 3 对等体 深入浅出BGP 说明 此篇主要对BGP的产生 与IGP的
  • LVM跨主机迁移

    LVM跨主机迁移 虚拟机 实验环境 两台Centos 7 8虚拟机 xff0c IP xff1a 192 168 221 199 200 查看lv test中的数据 使用命令lsblk xff0c 确认逻辑卷vg下挂有两个分区均为sdb磁盘
  • CodeBlocks 20.03安装&汉语化&找不到编译器

    一 CodeBlocks下载 下载地址 xff1a https www codeblocks org downloads binaries 到如下界面选择下载codeblocks 20 03mingw setup exe xff0c 这个里
  • 【FreeRTOS】从0写简易RTOS实现任务切换

    1 RTOS引入 单片机性能越来越强 xff0c 很多Linux程序在单片机上也可以运行了 xff1a 这需要RTOS 我们要开发的单片机产品 xff0c 功能也越来越丰富 xff1a 这也需要RTOS 就个人技术发展来说 xff0c 单片
  • java中的抽象类与接口(面试常考,重要)!!

    目录 抽象类语法规则注意事项 重要 xff0c 全部掌握 抽象类的作用 接口语法规则注意事项提示类实现多个接口 接口使用实例 Comparable 接口与Comparator接口 xff09 Comparable接口Comparator接口
  • Tensorflow2.0学习(十) — 基础张量、微分操作及自定义层

    因为再后面一些分享的章节的内容很多是基于经典论文的复现了 xff0c 里面会牵扯到很多自定义的模型及其变换 而这些内容有些是我们的Keras API 无法完成的 例如Resnet的residual block 因此这一节课我们有必要去学习一
  • 数据加解密时Base64异常:Illegal base64 character 3a

    现象 用base64工具类对中文进行处理时出现异常 xff0c 在数据加解密场景中经常使用 java lang IllegalArgumentException Illegal base64 character 3a at java uti
  • HTML5学习资料

    xff08 一 xff09 HTML5的原型设计模板平台 http www zzfriend com xiazai moban http www ke01 com html5cmsmuban http www ui92 com down 2
  • centOS7安装图形化界面

    三步 1 yum groupinstall y 34 Server with GUI 34 这一步是下载图形化界面 2 reboot 重启一下 3 init 5 这一步是进入图形化界面模式 xff0c 如果配置文件里已经默认的是init 5
  • PHPExcel下载excel文件正常,打开报错:'break' not in the 'loop' or 'switch' context

    其实 xff0c thinkphp的报错已经很明显了 39 break 39 not in the 39 loop 39 or 39 switch 39 context错误位置FILE private var www backend Thi
  • ubuntu 系统出现 仓库 “http://ppa.launchpad.net/fcitx-team/nightly/ubuntu xenial Release“ 没有Release文件

    Ubuntu系统在执行sudo apt get update 后出现仓库 http ppa launchpad net fcitx team nightly ubuntu xenial Release 没有Release文件的错误 解决办法
  • Docker bridge网络模式搭建

    提示 xff1a 在 docker 中新建网络后 xff0c 容器绑定在新建网络上的容器可以用容器名ping其他的容器 xff0c 但是在默认网络上的容器是无法直接使用容器名ping通的 bridge网络搭建 1 安装brctl yum i
  • 【Python】python读取excel超链接

    前言 表格存在两种格式 xls 和 xlsx xff0c 所以python存在两种库分别读取这两种格式表格超链接 openpyl针对 xlsx格式 xff0c xlrd针对 xls文件 示例如下 xff1a 1 openpyl coding
  • 基于51单片机的12864液晶演示器驱动

    span class token macro property span class token directive keyword include span span class token string lt reg52 h gt sp
  • Ubuntu安装VNC4Server遇到的问题与解决方法

    在Ubuntu安装VNC4Server时 xff0c 报错 xff1a Package 39 vnc4server 39 has no installation candidate 这是由于没有找到apt get的source 可以按照以下
  • sumo中随机产生车流

    在sumo中绘制地图 xff0c 然后利用sumo中的randomTrips py工具产生随机的车流量 一 首先绘制路网 xff0c 本文绘制了一个九宫格路网 xff08 myroad net xml xff09 xff0c 如下图 xff
  • 计算机网络第一章概述总结知识点

    第1章 概述 本章最重要的内容 xff1a 互联网边缘部分和核心部分的作用 xff0c 什么是分组交换 xff1f 计算机网络的性能指标有哪些计算机网络分层次的体系结构是怎样的 xff1f 什么是协议和服务 xff1f 本章的重要概念 互联
  • pip安装git

    解决 xff1a Cannot find command git do you have git installed and in your PATH 和系统找不到指定的文件 while executing command git http
  • 卷积神经网络的特征图可视化秘籍——PyTorch实现

    卷积神经网络的特征图可视化秘籍 PyTorch实现 可视化的定义及步骤PyTorch实现以预训练好的VGG16为例进行可视化关键代码剖析如果是自行搭建的网络 xff0c 如何索引网络层 xff1f 继续使用序号索引不使用序号 xff0c 直