• Pytorch ——特征图的可视化



    前言

    Pytroch中间层的特征图可视化,网上已经有很多教程,比如用hook钩子函数,但是代码都写得不是很清楚,所以还是自己去摸索一下。


    更新一下


    目前这种方法有很大的缺陷,最近看一篇国外的blog时,发现了Pytorch官方文档有一个Torch FX,基于这个实现了特征提取,更新一下Pytorch官方实现的特征图提取,参考我新写的记录:六行代码实现:特征图提取与特征图可视化


    一、torchvision.models._utils.IntermediateLayerGetter

    IntermediateLayerGetter这个函数是在看DETR源码时发现的,它的作用很简单,记录我们想要的中间层的输出。看个官方给出的例子:

    import torch
    import torchvision
    import torch.nn as nn
    import torchvision.transforms as transforms
    import torchvision.models as models
    from matplotlib import pyplot as plt
    
    model = torchvision.models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
    new_model = torchvision.models._utils.IntermediateLayerGetter(model, {'layer1': 'feat1', 'layer3': 'feat2'})
    out = new_model(torch.rand(1, 3, 224, 224))
    
    print([(k, v.shape) for k, v in out.items()])  # 其中v是对应层的输出,也就是我们要得到的特征图Tensor
    
    #输出
    "[('feat1', torch.Size([1, 64, 56, 56])), ('feat2', torch.Size([1, 256, 14, 14]))]"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    注意:torcvision的最新版本0.13,已经取消了pretrained=True这个参数,并且打算在0.15版正式移除,如果用pretrained这个参数会出现warring警告。现在加载与训练权重的参数改成了weights,这样可以加载不同版本的预训练权重,比如models.ResNet18_Weights.DEFAULT,就加载默认最新的ResNet18权重文件,还有其他参数形式,具体参考官网

    这里详细说一下

    #首先定义一个模型,这里直接加载models里的预训练模型
    model = torchvision.models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
    #查看模型的各个层,
    for name in model.named_children():
        print(name[0])
    #输出,相当于把ResNet的分成了10个层
    """
    conv1
    bn1
    relu
    maxpool
    layer1
    layer2
    layer3
    layer4
    avgpool
    fc"""
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    可以看到ResNet18的结构被分为了10个部分,和下图的网络结构是一一对应的,conv1、bn1、relu、maxpool这四个对应第一层的卷积conv1,layer1对应图中的conv2_x,也就是一个残差结构,同理layer2对应conv3_x,以此类推。

    在这里插入图片描述
    比如,我想要layer1(conv2_x)和layer2(conv3_x)的输出,那么只需要构建一个字典,{‘layer1’: ‘feat1’, ‘layer2’: ‘feat2’},feat1、feat2是我们的重命名,可以随意输入自己想要的名字。

    #现在我们把model传进IntermediateLayerGetter
    new_model = torchvision.models._utils.IntermediateLayerGetter(model, {'layer1': 'feat1', 'layer2': 'feat2'})
    out = new_model(torch.rand(1, 3, 224, 224))
    print([(k,v.shape) for  k,v in out.items()])
    
    #输出
    """
    [('feat1', torch.Size([1, 64, 56, 56])), ('feat2', torch.Size([1, 128, 28, 28]))]
    """
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    二、示例

    1.ResNet50特征图可视化

    代码如下:

    # 返回输出结果
    import cv2
    import torchvision
    import torch
    from matplotlib import pyplot as plt
    import numpy as np
    
    
    #定义函数,随机从0-end的一个序列中抽取size个不同的数
    def random_num(size,end):
        range_ls=[i for i in range(end)]
        num_ls=[]
        for i in range(size):
            num=random.choice(range_ls)
            range_ls.remove(num)
            num_ls.append(num)
        return num_ls
        
    
    
    path = "test.jpg"
    transformss = transforms.Compose(
        [transforms.ToTensor(),
         transforms.Resize((224, 224)),
         transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
    
    #注意如果有中文路径需要先解码,最好不要用中文
    img = cv2.imread(path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    
    #转换维度
    img = transformss(img).unsqueeze(0)
    
    model = torchvision.models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
    new_model = torchvision.models._utils.IntermediateLayerGetter(model, {'layer1': '1', 'layer2': '2',"layer3":"3"})
    out = new_model(img)
    
    tensor_ls=[(k,v) for  k,v in out.items()]
    
    #这里选取layer2的输出画特征图
    v=tensor_ls[1][1]
    
    """
    如果要选layer3的输出特征图只需把第一个索引值改为2,即:
    v=tensor_ls[2][1]
    只需把第一个索引更换为需要输出的特征层对应的位置索引即可
    """
    #取消Tensor的梯度并转成三维tensor,否则无法绘图
    v=v.data.squeeze(0)
    
    print(v.shape)  # torch.Size([512, 28, 28])
    
    
    #随机选取25个通道的特征图
    channel_num = random_num(25,v.shape[0])
    plt.figure(figsize=(10, 10))
    for index, channel in enumerate(channel_num):
        ax = plt.subplot(5, 5, index+1,)
        plt.imshow(v[channel, :, :])
    plt.savefig("feature.jpg",dpi=300)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    原图

    请添加图片描述

    特征图

    从特征图中可以看到,layer2确实已经学习到了某些特征,比如第二行第二列的特征图已经把狗的形状勾勒出来了,说明这个卷积核学习的可能是狗的颜色。

    请添加图片描述
    这里再展示一下ResNet第一部分(conv1)卷积层的特征图(灰度图):
    在这里插入图片描述

    2.AlexNet可视化

    上面的ResNet用的是预训练模型,这里我们自己构建AlexNet。
    代码如下:

    class AlexNet(nn.Module):
        def __init__(self):
            super(AlexNet, self).__init__()
    
            self.conv1 = nn.Sequential(nn.Conv2d(3, 96, 11, 4, 2),
                                       nn.ReLU(),
                                       nn.MaxPool2d(3, 2),
                                       )
    
            self.conv2 = nn.Sequential(nn.Conv2d(96, 256, 5, 1, 2),
                                       nn.ReLU(),
                                       nn.MaxPool2d(3, 2),
                                       )
    
            self.conv3 = nn.Sequential(nn.Conv2d(256, 384, 3, 1, 1),
                                       nn.ReLU(),
                                       nn.Conv2d(384, 384, 3, 1, 1),
                                       nn.ReLU(),
                                       nn.Conv2d(384, 256, 3, 1, 1),
                                       nn.ReLU(),
                                       nn.MaxPool2d(3, 2))
    
    
            self.fc=nn.Sequential(nn.Linear(256*6*6, 4096),
                                    nn.ReLU(),
                                    nn.Dropout(0.5),
                                    nn.Linear(4096, 4096),
                                    nn.ReLU(),
                                    nn.Dropout(0.5),
                                    nn.Linear(4096, 100),
                                    )
    
        def forward(self, x):
            x=self.conv1(x)
            x=self.conv2(x)
            x=self.conv3(x)
            output=self.fc(x.view(-1, 256*6*6))
            return output
    
    model=AlexNet()
    for name in model.named_children():
        print(name[0])
    #同理先看网络结构
    #输出
    """
    conv1
    conv2
    conv3
    fc
    """
    
    
        
    path = "test.jpg"
    transformss = transforms.Compose(
        [transforms.ToTensor(),
         transforms.Resize((224, 224)),
         transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
    
    #注意如果有中文路径需要先解码,最好不要用中文
    img = cv2.imread(path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    
    #转换维度
    img = transformss(img).unsqueeze(0)
    
    model = AlexNet()
    
    
    ## 修改这里传入的字典即可
    
    new_model = torchvision.models._utils.IntermediateLayerGetter(model, {"conv1":1,"conv2":2,"conv3":3})
    out = new_model(img)
    
    tensor_ls=[(k,v) for  k,v in out.items()]
    
    #选取conv2的输出
    v=tensor_ls[1][1]
    
    #取消Tensor的梯度并转成三维tensor,否则无法绘图
    v=v.data.squeeze(0)
    
    print(v.shape)  # torch.Size([512, 28, 28])
    
    
    #随机选取25个通道的特征图
    channel_num = random_num(25,v.shape[0])
    plt.figure(figsize=(10, 10))
    for index, channel in enumerate(channel_num):
        ax = plt.subplot(5, 5, index+1,)
        plt.imshow(v[channel, :, :])  # 灰度图参数cmap="gray"
    plt.savefig("feature.jpg",dpi=300)
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    也就是说AlexNet这里分为了4部分,三个卷积和一个全连接(其实就是我们自己定义的foward前向传播),我们想要哪层的输出改个字典就好了,new_model = torchvision.models._utils.IntermediateLayerGetter(model, {“conv1”:1,“conv2”:2,“conv3”:3}),得到的特征图如下。

    在这里插入图片描述
    plt.imshow(v[channel, :, :],cmap="gray") 加上cmap参数就可以显示灰度图
    在这里插入图片描述


    总结

    IntermediateLayerGetter有一个不足就是它不能获取二级层的输出,比如ResNet的layer2,他不能获取layer2里面的卷积的输出。

  • 相关阅读:
    操作系统备考学习 day3 (2.1.1 - 2.1.6)
    Linux 常见面试题 Day6
    OpenCV 4.10 发布
    Opencv项目实战:06 文档扫描仪
    最新基于Citespace、vosviewer、R语言的文献计量学可视化分析技术及全流程文献可视化SCI论文高效写作方法
    扩散模型训练太难?来看看Meta AI最新提出的KNN-Diffusion
    Postman的接口测试和持续集成——接口测试方法论
    HCNP Routing&Switching之组播技术PIM-SM 稀疏模式
    交通物流模型 | 基于双向时空自适应Transformer的城市交通流预测
    数据结构算法之——时间复杂度和空间复杂度
  • 原文地址:https://blog.csdn.net/m0_46412065/article/details/127882492