第七章:PyTorch可视化

第七章:PyTorch可视化 — 深入浅出PyTorch (datawhalechina.github.io)

7.1 可视化网络结构

使用torchinfo来可视化网络结构

  • torchinfo的安装
1
2
3
4
# 安装方法一
pip install torchinfo
# 安装方法二
conda install -c conda-forge torchinfo
  • torchinfo的使用

只需使用torchinfo.summary()

必需的参数分别是model,input_size[batch_size,channel,h,w]

更多参数可以参考documentation

1
2
3
4
5
# 例
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和参数量)、模型整体的参数量、模型大小、一次前向或者反向传播需要的内存大小等。

7.2 CNN可视化

7.2.1 卷积核可视化

卷积核在CNN中负责提取特征,可视化卷积核能够帮助人们理解CNN各个层在提取什么样的特征,进而理解模型的工作原理。

在PyTorch中可视化卷积核也非常方便,核心在于特定层的卷积核即特定层的模型权重,可视化卷积核就等价于可视化对应的权重矩阵。

首先加载模型,并确定模型的层信息:

1
2
3
4
5
import torch
from torchvision.models import vgg11

model = vgg11(pretrained=True)
print(dict(model.features.named_children()))

卷积核对应的应为卷积层(Conv2d),这里以第“3”层为例,可视化对应的参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
conv1 = dict(model.features.named_children())['3']
kernel_set = conv1.weight.detach()
num = len(conv1.weight.detach())
print(kernel_set.shape)
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')
torch.Size([128, 64, 3, 3])

由于第“3”层的特征图由64维变为128维,因此共有128*64个卷积核,其中部分卷积核可视化效果如下图所示:

kernel

7.2.2 特征图可视化

输入的原始图像经过每次卷积层得到的数据称为特征图,可视化即查看模型提取到的特征是什么样的。

hook:PyTorch提供的使得网络在前向传播过程中能够获取到特征图的一个专用接口。

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
class Hook(object): #定义Hook类
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) #存储当前层的输入和输出
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())

7.2.3 class activation map可视化

class activation map (CAM)的作用是判断哪些变量(可视化场景下为像素点)对模型来说是重要的。

同时为了判断重要区域的梯度等信息,衍生出了Grad-CAM等诸多变种。

相较于上两条,CAM能一目了然地确定重要区域,进而进行可解释性分析或模型优化改进。

cam

实现方法:

pytorch-grad-cam

  • 安装
1
pip install grad-cam
  • 一个简单的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
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 = './dog.png'
# resize操作是为了和传入神经网络训练图片大小一致
img = Image.open(img_path).resize((224,224))
# 需要将原始图片转为np.float32格式并且在0-1之间
rgb_img = np.float32(img)/255
plt.imshow(img)

dog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

target_layers = [model.features[-1]]
# 选取合适的类激活图,但是ScoreCAM和AblationCAM需要batch_size
cam = GradCAM(model=model,target_layers=target_layers)
targets = [ClassifierOutputTarget(preds)]
# 上方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)

grad_cam

7.2.4 FlashTorch快速可视化

对环境有要求:https://github.com/MisaOgura/flashtorch/issues/39

  • 安装
1
pip install flashtorch
  • 可视化梯度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Download example images
# !mkdir -p images
# !wget -nv \
# https://github.com/MisaOgura/flashtorch/raw/master/examples/images/great_grey_owl.jpg \
# https://github.com/MisaOgura/flashtorch/raw/master/examples/images/peacock.jpg \
# https://github.com/MisaOgura/flashtorch/raw/master/examples/images/toucan.jpg \
# -P /content/images

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('/content/images/great_grey_owl.jpg')
owl = apply_transforms(image)

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

ft-gradient

  • 可视化卷积核
1
2
3
4
5
6
7
8
9
10
11
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")

ft-activate

7.3 使用TensorBoard可视化训练过程

可视化你所想可视化的所有内容。

7.3.1安装

1
pip install tensorboardX

7.3.2可视化的基本逻辑

TensorBoard会将模型每一层的数据保存在指定位置 并以网页的形式可视化。

7.3.3 配置与启动

①首先指定一个文件夹供TensorBoard保存记录下来的数据,然后调用tensorboard中的SummaryWriter。

1
2
3
from tensorboardX import SummaryWriter

writer = SummaryWriter('./指定位置') #可手动往文件夹里添加数据,也可以提取到其他机器

※如果使用PyTorch自带的tensorboard,则采用如下方式import:

1
from torch.utils.tensorboard import SummaryWriter

②启动tensorboard

1
2
3
tensorboard --logdir=/path/to/logs/ --port=xxxx
#“path/to/logs/"是指定的保存tensorboard记录结果的文件路径
#port是外部访问TensorBoard的端口号,可以通过访问ip:port访问tensorboard

7.3.4 模型结构可视化

首先定义模型:

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
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)

输出如下:

1
2
3
4
5
6
7
8
9
10
11
Net(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1))
(pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(conv2): Conv2d(32, 64, kernel_size=(5, 5), stride=(1, 1))
(adaptive_pool): AdaptiveMaxPool2d(output_size=(1, 1))
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear1): Linear(in_features=64, out_features=32, bias=True)
(relu): ReLU()
(linear2): Linear(in_features=32, out_features=1, bias=True)
(sigmoid): Sigmoid()
)

可视化模型的思路和7.1中介绍的方法一样,都是给定一个输入数据,前向传播后得到模型的结构,再通过TensorBoard进行可视化,使用add_graph:

1
2
writer.add_graph(model, input_to_model = torch.rand(1, 3, 224, 224))
writer.close()

展示结果如下(其中框内部分初始会显示为“Net”,需要双击后才会展开):

tb_model

7.3.5 TensorBoard图像可视化

  • 对于单张图片的显示使用add_image
  • 对于多张图片的显示使用add_images
  • 有时需要使用torchvision.utils.make_grid将多张图片拼成一张图片后,用writer.add_image显示

以torchvision的CIFAR10数据集为例:

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
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()

tb_image

image-20221125220114712

tb_images

7.3.6 TensorBoard连续变量可视化

适合损失函数的可视化,可以更加直观地了解模型的训练情况,从而确定最佳的checkpoint。

1
2
3
4
5
6
7
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()

可视化结果如下:

tb_scalar

如果想在同一张图中显示多个曲线,则需要分别建立存放子路径(使用SummaryWriter指定路径即可自动创建,但需要在tensorboard运行目录下),同时在add_scalar中修改曲线的标签使其一致即可:

1
2
3
4
5
6
7
8
9
writer1 = SummaryWriter('./pytorch_tb/x')
writer2 = SummaryWriter('./pytorch_tb/y')
for i in range(500):
x = i
y = x*2
writer1.add_scalar("same", x, i) #日志中记录x在第step i 的值
writer2.add_scalar("same", y, i) #日志中记录y在第step i 的值
writer1.close()
writer2.close()

tb_scalar_twolines

7.3.7 TensorBoard参数分布可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#举例
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.3.8 服务器端使用TensorBoard

(1)MAC端

打开终端,输入的命令依次如下:

打开tensorflow的运行环境:source activate tensorflow
进入log的目录文件夹:cd desktop/tensorflow/
输入tensorboard命令:tensorboard —logdir=”log”

在浏览器中输入网址:http:localhost:6006

(2)MobaXterm

  1. 在MobaXterm点击Tunneling
  2. 选择New SSH tunnel,我们会出现以下界面。

image-20221125225501168

image-20221125230142013

  1. 对新建的SSH通道做以下设置,第一栏我们选择Local port forwarding<Remote Server>我们填写localhost< Remote port>填写6006,tensorboard默认会在6006端口进行显示,我们也可以根据 tensorboard —logdir=/path/to/logs/ —port=xxxx的命令中的port进行修改,< SSH server> 填写我们连接服务器的ip地址,<SSH login>填写我们连接的服务器的用户名,<SSH port>填写端口号(通常为22),< forwarded port>填写的是本地的一个端口号,以便我们后面可以对其进行访问。
  2. 设定好之后,点击Save,然后Start。在启动tensorboard,这样我们就可以在本地的浏览器输入http://localhost:6006/对其进行访问了

(3)Xshell

  1. Xshell的连接方法与MobaXterm的连接方式本质上是一样的,具体操作如下:
  2. 连接上服务器后,打开当前会话属性,会出现下图,我们选择隧道,点击添加 xhell_ui
  3. 按照下方图进行选择,其中目标主机代表的是服务器,源主机代表的是本地,端口的选择根据实际情况而定。 xhell_set
  4. 启动tensorboard,在本地127.0.0.1:6006 或者 localhost:6006进行访问。

(4)SSL

该方法是将服务器的6006端口重定向到自己机器上来,我们可以在本地的终端里输入以下代码:其中16006代表映射到本地的端口,6006代表的是服务器上的端口。

1
ssh -L 16006:127.0.0.1:6006 username@remote_server_ip

在服务上使用默认的6006端口正常启动tensorboard

1
tensorboard --logdir=xxx --port=6006

在本地的浏览器输入地址

1
127.0.0.1:16006 或者 localhost:16006