Keras中可以调用一个叫做model.summary()
的API来很方便地可视化,调用后就会显示我们的模型参数,输入大小,输出大小,模型的整体参数等,在PyTorch中可以使用print
(单纯的print,只能得出基础构件的信息,既不能显示出每一层的shape,也不能显示对应参数量的大小)和torchinfo
来可视化
# 安装方法一
pip install torchinfo
# 安装方法二
conda install -c conda-forge torchinfo
# 使用方法,当然也可以print(torchinfo.summary(...))
torchinfo.summary(<model>, <input_size[batch_size,channel,h,w]>)
torchinfo
的输出结果包括模块信息(每一层的类型、输出shape和参数量)、模型整体的参数量、模型大小、一次前向或者反向传播需要的内存大小等
torchinfo
的输出结果和之前获取中间层特征输出的net.named_parameters()
得到的结果完全不一样,有没有办法联动起来呢?还有
理解CNN的重要一步是可视化,包括可视化特征是如何提取的、提取到的特征的形式以及模型在输入数据上的关注点等
卷积核在CNN中负责提取特征,可视化卷积核能够帮助人们理解CNN各个层在提取什么样的特征,进而理解模型的工作原理。
Zeiler和Fergus 2013年的paper中就研究了CNN各个层的卷积核的不同,他们发现靠近输入的层提取的特征是相对简单的结构,而靠近输出的层提取的特征就和图中的实体形状相近
可视化卷积核的核心在于特定层的卷积核即特定层的模型权重,可视化卷积核等价于可视化对应的权重矩阵
# 首先加载模型vgg11
model = vgg11(pretrained=True)
# 将model.features.named_children()返回的对象存为字典
# dict(model.features.named_children())['3']就是名为'3'的层
print(dict(model.features.named_children()))
# 把第三层提取出来
# 但是resnet18没有features属性
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')
第“3”层的特征图由64维变为128维,因此共有128*64个卷积核
输入的原始图像经过每次卷积层得到的数据称为特征图,可视化特征图则是为了看模型提取到的特征是什么样子的(更加直观)
可以从输入开始,逐层做前向传播,直到想要的特征图处将其返回
使用hook:pytorch在每一次运算结束后,会将中间变量释放,以节省内存空间,这些会被释放的变量包括非叶子张量的梯度,中间层的特征图等。如果需要提取这些数据,就可以用到hook
。pytorch中一共有四个hook,一个应用于tensor
,另外三个是针对nn.Module
的。
(hook)相当于插件,可以实现一些额外的功能,而又不用修改主体代码。把这些额外功能实现了挂在主代码上,所以叫钩子,很形象。
在pytorch中,主题是forward和backward,额外的功能就是对模型的变量进行操作,如“提取”特征图,“提取”非叶子张量的梯度,修改张量梯度等等。
torch.Tensor.register_hook(hook)
功能:注册一个反向传播hook函数,计算tensor梯度时自动运行;在hook函数中可对梯度grad进行in-place操作,即可修改tensor的grad值
形式:.register_hook()
def grad_hook(grad):
y_grad.append(grad)
y_grad = list()
x = torch.tensor([[1., 2.], [3., 4.]], requires_grad=True)
y = x+1
y.register_hook(grad_hook) # 提取非叶子张量y的梯度
z = torch.mean(y*y)
z.backward()
print("type(y): ", type(y)) # >>> ('type(y): ', )
print("y.grad: ", y.grad) # >>> ('y.grad: ', None)
# y是非叶子结点张量,在z.backward()完成之后,y的梯度被释放掉以节省内存
print("y_grad[0]: ", y_grad[0])
# >>> ('y_grad[0]: ', tensor([[1.0000, 1.5000],[2.0000, 2.5000]]))
y.remove()
def grad_hook(grad):
grad *= 2
x = torch.tensor([2., 2., 2., 2.], requires_grad=True)
y = torch.pow(x, 2)
z = torch.mean(y)
h = x.register_hook(grad_hook)
z.backward()
print(x.grad)
h.remove() # removes the hook
# >>> tensor([2., 2., 2., 2.])
torch.nn.Module.register_forward_hook
功能:提取特征图
数据通过网络向前传播,网络某一层我们预先设置了一个钩子,数据传播过后钩子上会留下数据在这一层的样子,读取钩子的信息就是这一层的特征图
class Hook(object):
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):
hh = Hook()
model.features[idx].register_forward_hook(hh)
forward_model(model,False)
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())
首先实现了一个hook
类,之后在plot_feature
函数中,将该hook
类的对象注册到要进行可视化的网络的某层中。model在进行前向传播的时候会调用hook
的__call__
函数,我们也就是在那里存储了当前层的输入和输出。这里的features_out_hook
是一个list,每次前向传播一次,都是调用一次,也就是features_out_hook
长度会增加1。
torch.nn.Module.register_forward_pre_hook
torch.nn.Module.register_backward_hook
class activation map(CAM)的作用是判断哪些变量对模型来说是重要的,在CNN可视化的场景下,即判断图像中哪些像素点对预测结果是重要的。除了确定重要的像素点,人们也会对重要区域的梯度感兴趣,因此在CAM的基础上也进一步改进得到了Grad-CAM(以及诸多变种)
通过pip install grad-cam
进行安装
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.jpg'
# 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
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)
通过pip install flashtorch
进行安装
可视化梯度
可视化卷积核
联合阅读((20220204193821-stopmsu ‘tensorboard’))
一般来说,我们希望可视化训练集的损失函数和验证集的损失函数,绘制两条损失函数的曲线来确定训练的终点;此外,我们也希望可视化其他内容,如输入数据(尤其是图片)、模型结构、参数分布等,这些对于我们在debug中查找问题来源非常重要(比如输入数据和我们想象的是否一致)
安装:pip install tensorboard
X,from tensorboardX import SummaryWriter
或者也可以使用pytorch自带的tensorboard,则使用from torch.utils.tensorboard import SummaryWriter
在使用TensorBoard前,我们需要先指定一个文件夹供TensorBoard保存记录下来的数据。然后调用tensorboard中的SummaryWriter作为上述"记录员"
from tensorboardX import SummaryWriter
# from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter('./runs')
上面的操作实例化SummaryWritter为变量writer,并指定writer的输出目录为当前目录下的"runs"目录,可以手动往runs文件夹里添加数据用于可视化,或者把runs文件夹里的数据放到其他机器上可视化。
在命令行输入tensorboard --logdir=/path/to/logs/ --port=xxxx
以启动tensorboard
其中"path/to/logs/"
是指定的保存tensorboard记录结果的文件路径,port是外部访问TensorBoard的端口号,可以通过访问ip:port
访问tensorboard
为了tensorboard能够不断地在后台运行,也可以使用nohup
命令或者tmux
工具来运行tensorboard
writer.add_graph(model, input_to_model = torch.rand(<input_size>))
writer.close()
对于单张图片的显示使用add_image
,对于多张图片的显示使用add_images
,也可以使用torchvision.utils.make_grid
将多张图片拼成一张图片后,用writer.add_iamge
显示
以CIFAR10数据集为例:
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()
除了可视化原始图像,TensorBoard提供的可视化方案也适用于我们在Python中用matplotlib等工具绘制的其他图像,用于展示分析结果等内容
可视化连续变量(或时序变量)的变化过程,通过add_scalar
实现
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()
如果需要在同一张图显示多个曲线,则:
add_scalar
中使曲线的tag保持一致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()