CNN特征提取结果可视化——hooks简单应用
在神经网络搭建时可能出现各式各样的错误,使用hook而非print或者简单的断点调试有助于你更清晰的意识到错误所在。
hook的使用场景多种多样,本文将使用hooks来简单可视化卷积神经网络的特征提取。用到的神经网络框架为Pytorch
Hooks简单介绍
每个hook都是预先定义好的可调用对象,在pytorch框架中,每个nn.Module对象都能够方便地注册(定义)一个hook。当一些trigger方法调用(如forward()和backward())后,注册了hook的nn.Module对象会将相关信息传递到hook里面去。
在PyTorch中,可以注册三种hook:
forward prehook (在forward之前执行)
forward hook (在forward之后执行)
backward hook (在backward之后执行)
具体理解每种hook的使用不是本文讨论的范围,我们将通过一个生动的卷积神经网络可视化例子来介绍hook的使用
可视化准备工作
我们将要进行的工作包括:
创建CNN特征提取器,本文使用PyTorch自带的resnet34
创建一个保存hook内容的对象
为每个卷积层创建hook
导入需要使用的库
本文将对下图进行特征提取并可视化
用到的python库
import numpy as np
import torch
import torchvision
from PIL import Image
from torchvision import transforms as T
import matplotlib.pyplot as plt
1
2
3
4
5
6
7
8
创建CNN特征提取器
import torch
import torchvision
feature_extractor = torchvision.models.resnet34(pretrained=True)
if torch.cuda.is_available():
feature_extractor.cuda()
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')
1
2
3
4
5
6
7
8
创建保存hook内容的对象
class SaveOutput:
def __init__(self):
self.outputs = []
def __call__(self, module, module_in, module_out):
self.outputs.append(module_out)
def clear(self):
self.outputs=[]
save_output = SaveOutput()
1
2
3
4
5
6
7
8
9
为卷积层注册hook
hook_handles = []
for layer in feature_extractor.modules():
if isinstance(layer, torch.nn.Conv2d):
handle = layer.register_forward_hook(save_output)
hook_handles.append(handle)
1
2
3
4
5
6
读取图像并进行特征提取
cat.jpg地址
from PIL import Image
from torchvision import transforms as T
image = Image.open('cat.jpg')
transform = T.Compose([T.Resize((224, 224)), T.ToTensor()])
X = transform(image).unsqueeze(dim=0).to(device)
out = feature_extractor(X)
1
2
3
4
5
6
7
8
查看卷积层特征提取效果
对于resnet来说,其具体结构如下:
卷积层共有1+6+(4*2+1)+(6*2+1)+(3*2+1)=36个,对conv3_x层有4*2+1卷积层的原因是(1)四个basicblock本身有4*2个卷积层(2)其中一个basicblock进行了downsample,又多了一个卷积层
可视化哪些卷积层?
对于resnet34来说,我们计划可视化其第1、2、15、28个卷积层,为何如此?
第一个卷积层是conv1_x的输出,图片轮廓较为清楚
第二、七个卷积层是conv2_x首个和末尾卷积层的输出,我们将其与第一个卷积层输出对比可以得到特征逐渐高层化的结论
第十五个卷积层是conv3_x的输出
第二十八个卷积层是conv4_x的输出
为何不可视化最后一个卷积层?
对于最后一个卷积层,其每个通道的像素仅仅为7x7,可视化也看不出什么东西。或者说我们可视化第二十八个卷积层后,就发现继续可视化没有必要了。
提取计划可视化的卷积层结果
每个卷积层的结果都通过hook保存到了save_output.outputs里面,我们查看是否为36个结果
我们创建一个拼接卷积结果的函数。对每个卷积层来说,其结果都是由许多单通道图片组成(比如第一个卷积层的通道为64,因此有64张单通道图片),因此我们首先需要将这些单通道图片进行拼接一张单通道大图。
这里的单通道仅仅指图片通道数目为1,是否为灰度图片本人并不清楚,只是可视化时只好使用灰度图的办法显示,如果有清楚概念的大哥希望不吝赐教
提取计划可视化的卷积层结果
临时查看
我们查看是否为36个结果并查看计划可视化的层的shape
print(len(save_output.outputs))
a_list = [0, 1, 6, 15, 28, 35]
for i in a_list:
print(save_output.outputs[i].cpu().detach().squeeze(0).shape)
1
2
3
4
可见确为36个卷积层,我们预计的可视化层的size也都正确
拼接函数
def grid_gray_image(imgs, each_row: int):
'''
imgs shape: batch * size (e.g., 64x32x32, 64 is the number of the gray images, and (32, 32) is the size of each gray image)
'''
row_num = imgs.shape[0]//each_row
for i in range(row_num):
img = imgs[i*each_row]
img = (img - img.min()) / (img.max() - img.min())
for j in range(1, each_row):
tmp_img = imgs[i*each_row+j]
tmp_img = (tmp_img - tmp_img.min()) / (tmp_img.max() - tmp_img.min())
img = np.hstack((img, tmp_img))
if i == 0:
ans = img
else:
ans = np.vstack((ans, img))
return ans
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
提取计划可视化的卷积层结果
img0 = save_output.outputs[0].cpu().detach().squeeze(0)
img0 = grid_gray_image(img0.numpy(), 8)
img1 = save_output.outputs[1].cpu().detach().squeeze(0)
img1 = grid_gray_image(img1.numpy(), 8)
img6 = save_output.outputs[6].cpu().detach().squeeze(0)
img6 = grid_gray_image(img6.numpy(), 8)
img15 = save_output.outputs[15].cpu().detach().squeeze(0)
img15 = grid_gray_image(img15.numpy(), 16)
img29 = save_output.outputs[28].cpu().detach().squeeze(0)
img29 = grid_gray_image(img29.numpy(), 16)
1
2
3
4
5
6
7
8
9
10
可视化第一个卷积层
对resnet34来说,首个卷积层的卷积核为7*7,将输入的三通道彩色图像通道增加至64,尺寸从224*224对折为112*112,tensor的shape为1x64x112x112
我们对首个卷积层的提取结果进行可视化:
plt.figure(figsize=(15, 15))
plt.imshow(img0, cmap='gray')
1
2
下面是第一个卷积层的提取结果,显然每个卷积层的不同通道的侧重点不同:
可视化第二个卷积层
对resnet34来说,第2-7个卷积层tensor的shape为64x1x56x56,我们对其第二个卷积层输出进行可视化:
plt.figure(figsize=(15, 15))
plt.imshow(img1, cmap='gray')
1
2
第二个卷积层的特征相较第一个更加高级
可视化第七个卷积层
第2-7个卷积层tensor的shape为64x1x56x56,我们对第七个卷积层也可视化:
plt.figure(figsize=(15, 15))
plt.imshow(img6, cmap='gray')
1
2
特征提取逐渐高层化,不同通道的侧重点更加明显
可视化第16个卷积层
第16个卷积层对应的是conv3_x的结果,其shape为1x128x28x28,可视化如下
plt.figure(figsize=(30, 15))
plt.imshow(img15, cmap='gray')
1
2
可见图像经过多层特征提取,提取到的特征变得更加高层,不同通道的侧重点更加明显
可视化第29个卷积层
plt.figure(figsize=(15, 15))
plt.imshow(img29, cmap='gray')
1
2
结语
对神经网络提取结果进行可视化有助于理解其特征提取逐渐高层化的过程。
hook的使用场景还有很多,希望小伙伴们继续探索。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_34769162/article/details/115567093
文章
10.56W+人气
19粉丝
1关注
©Copyrights 2016-2022 杭州易知微科技有限公司 浙ICP备2021017017号-3 浙公网安备33011002011932号
互联网信息服务业务 合字B2-20220090