Linux深入浅出PyTorch(五)PyTorch可视化

在深度学习中,需要可视化的地方有很多:

  • 网络结构
  • 卷积层
  • 数据集
  • 参数
  • 变量的变化过程

等等

PyTorch中也有很多的可视化工具包,如tensorboadnetronVisdomtorchviz等等,本节主要讲的是用torchinfotensorboard可视化以及CNN卷积层的可视化方法。

一、可视化网络结构

1. 使用print打印模型信息

import torchvision.models as models
model = models.resnet18() # 用resnet18作为例子
print(model)

输出结果如下:

ResNet(
  (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
  (layer1): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
    (1): BasicBlock(
      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer2): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer3): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer4): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
  (fc): Linear(in_features=512, out_features=1000, bias=True)
)

更直观的展示网络结构方法——torchinfo(一个由torchsummarytorchsummaryX重构出的库)

2. 使用torchinfo可视化网络信息

(1)安装

pip install torchinfo # 也可用conda:conda install -c conda-forge torchinfo

在这里插入图片描述

(2)使用

import torchvision.models as models
from torchinfo import summary
resnet18 = models.resnet18() # 实例化模型
summary(resnet18, (1, 3, 224, 224)) # 1:batch_size 3:图片的通道数 224: 图片的高宽

输出结果如下:
在这里插入图片描述
可以看到torchinfo提供的信息更加详细且直观,包括模块信息(每一层的类型、输出shape和参数量)、模型整体的参数量、模型大小、一次前向或者反向传播需要的内存大小等。

二、CNN卷积层可视化

  • CNN是一个“黑盒模型”,CNN是如何获得较好表现的并不能确切的证明,由此带来了深度学习的可解释性问题;
  • 理解CNN的一个重要的方式就是可视化。

1. 卷积核可视化

  • 卷积核用于提取特征,可视化卷积核用于理解在提取什么样的特征
  • 靠近输入的层提取的特征是相对简单的结构,而靠近输出的层提取的特征就和图中的实体形状相近(Zeiler和Fergus,2013)
import torch
from torchvision.models import vgg11 # 以vgg11为例

model = vgg11(pretrained=True) # 打印查看预训练好的模型信息
print(dict(model.features.named_children()))
# named_children():返回包含子模块的迭代器,同时产生模块的名称以及模块本身

结果如下:

{
    
    '0': Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), 
'1': ReLU(inplace=True), 
'2': MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False), 
'3': Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), 
'4': ReLU(inplace=True), 
'5': MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False), 
'6': Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), 
'7': ReLU(inplace=True), 
'8': Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), 
'9': ReLU(inplace=True), 
'10': MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False), 
'11': Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), 
'12': ReLU(inplace=True), 
'13': Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), 
'14': ReLU(inplace=True), 
'15': MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False), 
'16': Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), 
'17': ReLU(inplace=True), 
'18': Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), 
'19': ReLU(inplace=True), 
'20': MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)}
import matplotlib.pyplot as plt # 注意matplotlib后面还有 .pyplot,否则会报错
conv1 = dict(model.features.named_children())['3'] # 可视化vgg11中的第3层卷积层
kernel_set = conv1.weight.detach()
num = len(conv1.weight.detach())
print(kernel_set.shape)  # 第3层尺寸,可视化出来的是128组,每组64个卷积核,每个卷积核都是3×3结构
for i in range(0,num):
    i_kernel = kernel_set[i]
    plt.figure(figsize=(20, 17))
    if (len(i_kernel)) > 1:
        for idx, filer in enumerate(i_kernel):
            plt.subplot(9, 9, idx+1) 
            plt.axis('off') # 不设坐标轴
            plt.imshow(filer[ :, :].detach(),cmap='bwr')

可视化部分结果如下:
在这里插入图片描述

2. 特征图可视化

  • 原始图像经过每次卷积层得到的数据称为特征图;
  • 可视化特征图可以看到模型提取到的特征是什么样的
class Hook(object): # pytorch中提供的一个专用接口,让网络在前向传播过程中能够获取到特征图
    def __init__(self):
        self.module_name = []
        self.features_in_hook = []
        self.features_out_hook = []

    def __call__(self,module, fea_in, fea_out):
        print("hooker working", self)
        self.module_name.append(module.__class__)
        self.features_in_hook.append(fea_in)
        self.features_out_hook.append(fea_out) # 是一个list,每前向传播一次,长度增加1
        return None
    

def plot_feature(model, idx, inputs):
    hh = Hook()
    model.features[idx].register_forward_hook(hh) # 将hook对象注册到需要可视化网络的某层
    
    # forward_model(model,False)
    model.eval()
    _ = model(inputs)
    print(hh.module_name)
    print((hh.features_in_hook[0][0].shape))
    print((hh.features_out_hook[0].shape))
    
    out1 = hh.features_out_hook[0]

    total_ft  = out1.shape[1]
    first_item = out1[0].cpu().clone()    

    plt.figure(figsize=(20, 17))
    

    for ftidx in range(total_ft):
        if ftidx > 99:
            break
        ft = first_item[ftidx]
        plt.subplot(10, 10, ftidx+1) 
        
        plt.axis('off')
        #plt.imshow(ft[ :, :].detach(),cmap='gray')
        plt.imshow(ft[ :, :].detach())

3. class activation map(CAM)可视化方法

  • 判断哪些变量对模型来说是重要的
  • 判断图像中哪些像素点对预测结果是重要的
  • 更为直观,能够一目了然地确定重要区域,进而进行可解释性分析或模型优化改进
  • 除了可视化重要区域,还有其他变种如Grad-CAM(可视化重要区域的梯度)等

(1)安装

pip install grad-cam

在这里插入图片描述

(2)使用

import torch
from torchvision.models import vgg11,resnet18,resnet101,resnext101_32x8d
import matplotlib.pyplot as plt
from PIL import Image
import numpy as np

model = vgg11(pretrained=True)
img_path = '/home/cloris/Downloads/dog.png'
# resize操作是为了和传入神经网络训练图片大小一致
img = Image.open(img_path).resize((224,224))
# 需要将原始图片转为np.float32格式并且在0-1之间 
rgb_img = np.float32(img)/255
plt.imshow(img)

在这里插入图片描述

from pytorch_grad_cam import GradCAM,ScoreCAM,GradCAMPlusPlus,AblationCAM,XGradCAM,EigenCAM,FullGrad
from pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget
from pytorch_grad_cam.utils.image import show_cam_on_image

img_tensor = torch.Tensor(rgb_img) # 这里需要先把图像转换成tensor格式,上面PIL.Image读取的图片格式不能直接放入模型训练
# print(img_tensor.shape) # 打印一下图片的shape是224×224×3,模型需要的是1×3×224×224形状
img_tensor = img_tensor.unsqueeze(0) # 这里先用unsequeeze将图片增加1维,变成1×224×224×3
img_tensor = img_tensor.permute(0, 3, 1, 2) # 用permute将图片shape调成 1×3×224×224形状
# print(img_tensor.shape)

target_layers = [model.features[-1]] # 选取合适的类激活图,但是ScoreCAM和AblationCAM需要batch_size
cam = GradCAM(model=model,target_layers=target_layers)
targets = [ClassifierOutputTarget(200)] # preds需要设定,比如ImageNet有1000类,这里可以直接设为200
grayscale_cam = cam(input_tensor=img_tensor, targets=targets)
grayscale_cam = grayscale_cam[0, :]
cam_img = show_cam_on_image(rgb_img, grayscale_cam, use_rgb=True)
print(type(cam_img))
Image.fromarray(cam_img)

在这里插入图片描述
注:运行时代码报错的点:

  • 读取图片的格式问题:PIL.Image读取的图片不能直接放入模型训练,需要转化成Tensor格式;
  • 图片的shape问题:模型需要的是4维1×3×224×224形状的输入,因为这里只输入一张图片,所以是3维,需要用unsqueeze手动增加1个维度;然后用permute更改一下形状,才能正确输入;
  • 原始图片的问题:如果用已经处理过的图片再拿来训练,可能出现图片shape是224×224×4的情况,换成原始图片问题即可解决。

4. 使用FlashTorch快速实现CNN可视化

不需要一步步去写代码,通过FlashTorch可以快速实现CNN可视化。

(1)安装

pip install flashtorch

在这里插入图片描述
注:

  • 可能会出现安装环境问题,看这里:flashtorch
  • 我的环境是conda虚拟环境:在python3.7下安装的各种包,可以正确安装flashtorch

(2)实例

1)可视化梯度

import matplotlib.pyplot as plt
import torchvision.models as models
from flashtorch.utils import apply_transforms, load_image
from flashtorch.saliency import Backprop

model = models.alexnet(pretrained=True)
backprop = Backprop(model)

image = load_image('/home/cloris/Downloads/grey_owl.png')
owl = apply_transforms(image)

target_class = 24
backprop.visualize(owl, target_class, guided=True, use_gpu=True)

注意:经过前面的实现,再跑这里的代码时,可能会出现 RunTimeError:CUDA error:out of memory的错误 ,这里就需要查看GPU占用并 kill 前面没有释放掉的进程了,如下:

nvidia-smi # 在终端输入

在这里插入图片描述
发现2G的显存,PID=8739的进程占用了1483M没有释放,这时需要手动释放:

sudo kill -9 PID # 我这里PID是8739

再继续运行代码即可。

2)可视化卷积核

import torchvision.models as models
from flashtorch.activmax import GradientAscent

model = models.vgg16(pretrained=True)
g_ascent = GradientAscent(model.features)

# specify layer and filter info
conv5_1 = model.features[24]
conv5_1_filters = [45, 271, 363, 489]

g_ascent.visualize(conv5_1, conv5_1_filters, title="VGG16: conv5_1")

在这里插入图片描述

三、使用TensorBoard可视化训练过程

  • 深度学习的过程是一个优化的过程,需要找到最优的点作为训练过程的输出产物
  • 一般深度学习训练中,会结合训练集的损失函数和验证集的损失函数,绘制两条损失函数的曲线来确定训练的终点,找到对应的模型用于测试,实时观察损失函数曲线的变化,及时捕捉模型的变化
  • 也可以用来观察其他内容如输入数据(尤其是图片)、模型结构、参数分布等
  • tensorboard像一个记录员,可以记录指定的数据,包括模型每一层的feature map,权重,以及训练loss等等;记录下的内容保存在指定文件夹,可以通过网页的形式可视化

1. 安装

pip install tensorboardX

在这里插入图片描述

2. 配置与启动

from tensorboardX import SummaryWriter # 调用summarywriter当记录员

writer = SummaryWriter('./runs') # 指定文件夹保存记录的数据

也可以使用pytorch自带的tensorboard

from torch.utils.tensorboard import SummaryWriter

启动tensorboard

tensorboard --logdir="/path/to/logs/:" # 在命令行中输入
# path/to/logs/:指定的保存tensorboard记录结果的文件路径
# port是外部访问TensorBoard的端口号,可以通过访问ip:port访问tensorboard

注意:这里可能报错:tensorboard :command not found ,解决办法是:没有安装tensorflow,安装即可:

pip install tensorflow

3. tensorboard模型结构可视化

# 定义模型
import torch.nn as nn

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=3,out_channels=32,kernel_size = 3)
        self.pool = nn.MaxPool2d(kernel_size = 2,stride = 2)
        self.conv2 = nn.Conv2d(in_channels=32,out_channels=64,kernel_size = 5)
        self.adaptive_pool = nn.AdaptiveMaxPool2d((1,1))
        self.flatten = nn.Flatten()
        self.linear1 = nn.Linear(64,32)
        self.relu = nn.ReLU()
        self.linear2 = nn.Linear(32,1)
        self.sigmoid = nn.Sigmoid()

    def forward(self,x):
        x = self.conv1(x)
        x = self.pool(x)
        x = self.conv2(x)
        x = self.pool(x)
        x = self.adaptive_pool(x)
        x = self.flatten(x)
        x = self.linear1(x)
        x = self.relu(x)
        x = self.linear2(x)
        y = self.sigmoid(x)
        return y

model = Net()
print(model)

在这里插入图片描述

# 给定一个输入数据,前向传播后得到模型的结构,再通过TensorBoard进行可视化
writer.add_graph(model, input_to_model = torch.rand(1, 3, 224, 224))
writer.close()

打开tensorboard,展示结果如下:
在这里插入图片描述

4. tensorboard 图像可视化

import torchvision
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

transform_train = transforms.Compose(
    [transforms.ToTensor()])
transform_test = transforms.Compose(
    [transforms.ToTensor()])

train_data = datasets.CIFAR10(".", train=True, download=True, transform=transform_train)
test_data = datasets.CIFAR10(".", train=False, download=True, transform=transform_test)
train_loader = DataLoader(train_data, batch_size=64, shuffle=True)
test_loader = DataLoader(test_data, batch_size=64)

images, labels = next(iter(train_loader))
 
# 仅查看一张图片
writer = SummaryWriter('./pytorch_tb')
writer.add_image('images[0]', images[0])
writer.close()
 
# 将多张图片拼接成一张图片,中间用黑色网格分割
# create grid of images
writer = SummaryWriter('./pytorch_tb')
img_grid = torchvision.utils.make_grid(images)
writer.add_image('image_grid', img_grid)
writer.close()
 
# 将多张图片直接写入
writer = SummaryWriter('./pytorch_tb')
writer.add_images("images",images,global_step = 0)
writer.close()

在这里插入图片描述

5. tensorboard连续变量可视化

writer = SummaryWriter('./pytorch_tb')
for i in range(500):
    x = i
    y = x**2
    writer.add_scalar("x", x, i) #日志中记录x在第step i 的值
    writer.add_scalar("y", y, i) #日志中记录y在第step i 的值
writer.close()

在这里插入图片描述

# 在同一张图中显示多个曲线
writer1 = SummaryWriter('./pytorch_tb/x') # 分别建立存放子路径
writer2 = SummaryWriter('./pytorch_tb/y')
for i in range(500):
    x = i
    y = x*2
    # 同时在add_scalar中修改曲线的标签使其一致
    writer1.add_scalar("same", x, i) #日志中记录x在第step i 的值
    writer2.add_scalar("same", y, i) #日志中记录y在第step i 的值
writer1.close()
writer2.close()
# 也可以用一个writer,但for循环中不断创建SummaryWriter不是一个好选项

在这里插入图片描述
注:

  • 左下角红框圈出来这里,可以选择指定的曲线显示
  • 这种方法非常适合损失函数的可视化,可以更加直观地了解模型的训练情况

6. tensorboard参数分布可视化

对参数(或者向量)的变化或分布进行研究

import torch
import numpy as np

# 创建正态分布的张量模拟参数矩阵
def norm(mean, std):
    t = std * torch.randn((100, 20)) + mean
    return t
 
writer = SummaryWriter('./pytorch_tb/')
for step, mean in enumerate(range(-10, 10, 1)):
    w = norm(mean, 1)
    writer.add_histogram("w", w, step)
    writer.flush()
writer.close()

在这里插入图片描述
在这里插入图片描述

7. 总结

  • TensorBoard可视化主要的实现方案是构建一个SummaryWriter,然后通过add_XXX()函数来实现;
  • TensorBoard的逻辑很简单,基本逻辑就是文件的读写逻辑:写入想要可视化的数据,然后TensorBoard自己会读出来。

参考:
[1] datawhalechina/thorough-pytorch: https://github.com/datawhalechina/thorough-pytorch

猜你喜欢

转载自blog.csdn.net/weixin_41794514/article/details/127004698