可视化网络模型
Caffe 目前有两种常用的可视化模型方式:
使用 Netscope 在线可视化
Caffe 代码包内置的 draw_net.py 文件可以可视化网络模型
Netscope
Netscope 能可视化神经网络体系结构(或技术上说, Netscope 能可视化任何有向无环图). 目前 Netscope 能可视化 Caffe 的 prototxt 文件. 网址为: http://ethereon.github.io/netscope/#/editor Netscope 的使用非常简单, 只需要将 prototxt 的文件复制到 Netscope 的编辑框, 再按快捷键 Shift+Enter 即可得到网络模型的可视化结构. Netscope 的优点是显示的网络模型简洁, 而且将鼠标放在右侧可视化的网络模型的任意模块上, 会显示该模块的具体参数. 图 1 以 Faster R-CNN 中 ZF 模型的 train.prototxt 文件为例
图 1 Netscope 可视化 ZF 网络模
draw_net.py
draw_net.py 同样是将 prototxt 绘制成网络模型, 在绘制之前, 需要安装两个依赖库:
1, 安装GraphViz # sudo apt-get install GraphViz 注意, 这里用的是 apt-get 来安装, 而不是 pip. 2 , 安装 pydot # sudo pip install pydot 用的是 pip 来安装, 而不是 apt-get
安装完毕后, 即可调用 draw_net.py 绘制网络模型, 如绘制 caffe 自带的 LeNet 网络模型:
sudo python python/draw_net.py examples/mnist/lenet_train_test.prototxt netImage/lenet.png --rankdir=TB
其中有三个参数, 各自的含义为:
第一个参数: 网络模型的 prototxt 文件 第二个参数: 保存的图片路径及名字 第二个参数:-rankdir=x , x 有四种选项, 分别是 LR, RL, TB, BT . 用来表示网络的方向, 分别是从左到右, 从右到左, 从上到小, 从下到上. 默认为LR.
可视化结果如下图所示:
图 2 draw_net.py 可视化 LeNet 网络模型
可视化图像特征
关于图像的可视化, 我也使用过两种两种方式:
修改 demo.py 代码输出中间层结果
使用可视化工具 deep-visualization-toolbox
修改 demo.py
该部分是参考薛开宇的caffe 学习笔记中的
逐层特征可视化
部分, 还是以 ZFNet 网络训练 Pascal VOC 为例, 修改 demo.py 文件后, 代码如下:
- #!/usr/bin/env python
- #-*-coding:utf-8-*-
- import matplotlib
- matplotlib.use('Agg')
- import _init_paths
- from fast_rcnn.config import cfg
- from fast_rcnn.test import im_detect
- from fast_rcnn.nms_wrapper import nms
- from utils.timer import Timer
- import matplotlib.pyplot as plt
- import numpy as np
- import scipy.io as sio
- import caffe, os, sys, cv2
- import argparse
- CLASSES = ('__background__',
'aeroplane', 'bicycle', 'bird', 'boat',
'bottle', 'bus', 'car', 'cat', 'chair',
'cow', 'diningtable', 'dog', 'horse',
'motorbike', 'person', 'pottedplant',
'sheep', 'sofa', 'train', 'tvmonitor')
- NETS = {'vgg16': ('VGG16',
- 'VGG16_faster_rcnn_final.caffemodel'),
- 'zf': ('ZF',
- 'zf_faster_rcnn_iter_2000.caffemodel')}
- def vis_detections(im, class_name, dets, thresh=0.5):
- """Draw detected bounding boxes."""
- inds = np.where(dets[:, -1]>= thresh)[0]
- if len(inds) == 0:
- return
- im = im[:, :, (2, 1, 0)]
- fig, ax = plt.subplots(figsize=(12, 12))
- ax.imshow(im, aspect='equal')
- for i in inds:
- bbox = dets[i, :4]
- score = dets[i, -1]
- ax.add_patch(
- plt.Rectangle((bbox[0], bbox[1]),
- bbox[2] - bbox[0],
- bbox[3] - bbox[1], fill=False,
- edgecolor='red', linewidth=3.5)
- )
- ax.text(bbox[0], bbox[1] - 2,
- '{:s} {:.3f}'.format(class_name, score),
- bbox=dict(facecolor='blue', alpha=0.5),
- fontsize=14, color='white')
- ax.set_title(('{} detections with'
- 'p({} | box)>= {:.1f}').format(class_name, class_name,
- thresh),
- fontsize=14)
- plt.axis('off')
- plt.tight_layout()
- plt.draw()
- def demo(net, image_name):
- """Detect object classes in an image using pre-computed object proposals."""
- # Load the demo image
- im_file = os.path.join(cfg.DATA_DIR, 'demo', image_name)
- im = cv2.imread(im_file)
- # Detect all object classes and regress object bounds
- timer = Timer()
- timer.tic()
- scores, boxes = im_detect(net, im)
- timer.toc()
- print ('Detection took {:.3f}s for'
- '{:d} object proposals').format(timer.total_time, boxes.shape[0])
- # Visualize detections for each class
- CONF_THRESH = 0.8
- NMS_THRESH = 0.3
- for cls_ind, cls in enumerate(CLASSES[1:]):
- cls_ind += 1 # because we skipped background
- cls_boxes = boxes[:, 4*cls_ind:4*(cls_ind + 1)]
- cls_scores = scores[:, cls_ind]
- dets = np.hstack((cls_boxes,
- cls_scores[:, np.newaxis])).astype(np.float32)
- keep = nms(dets, NMS_THRESH)
- dets = dets[keep, :]
vis_detections(im, cls, dets, thresh=CONF_THRESH)
- def parse_args():
- """Parse input arguments."""
- parser = argparse.ArgumentParser(description='Faster R-CNN demo')
- parser.add_argument('--gpu', dest='gpu_id', help='GPU device id to use [0]',
- default=0, type=int)
- parser.add_argument('--cpu', dest='cpu_mode',
- help='Use CPU mode (overrides --gpu)',
- action='store_true')
- parser.add_argument('--net', dest='demo_net', help='Network to use [zf]',
- choices=NETS.keys(), default='zf')
- args = parser.parse_args()
- return args
- if __name__ == '__main__':
- cfg.TEST.HAS_RPN = True # Use RPN for proposals
- args = parse_args()
- prototxt = os.path.join(cfg.MODELS_DIR, NETS[args.demo_net][0],
- 'faster_rcnn_alt_opt', 'faster_rcnn_test.pt')
- caffemodel = os.path.join(cfg.DATA_DIR, 'faster_rcnn_models',
- NETS[args.demo_net][1])
- if not os.path.isfile(caffemodel):
- raise IOError(('{:s} not found.\nDid you run ./data/script/'
- 'fetch_faster_rcnn_models.sh?').format(caffemodel))
- if args.cpu_mode:
- caffe.set_mode_cpu()
- else:
- caffe.set_mode_gpu()
- caffe.set_device(args.gpu_id)
- cfg.GPU_ID = args.gpu_id
- net = caffe.Net(prototxt, caffemodel, caffe.TEST)
- # 指定 caffe 路径, 以下是我的 caffe 路径
- caffe_root='/home/ouyang/GitRepository/py-faster-rcnn/caffe-fast-rcnn/'
- # import sys
- sys.path.insert(0, caffe_root+'python')
- # import caffe
- # #显示的图表大小为 10, 图形的插值是以最近为原则, 图像颜色是灰色
- plt.rcParams['figure.figsize'] = (10, 10)
- plt.rcParams['image.interpolation'] = 'nearest'
- plt.rcParams['image.cmap'] = 'gray'
- image_file = caffe_root+'examples/images/vehicle_0000015.jpg'
- # 载入模型
- npload = caffe_root+ 'python/caffe/imagenet/ilsvrc_2012_mean.npy'
- transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape})
- transformer.set_transpose('data', (2,0,1))
- transformer.set_mean('data', np.load(npload).mean(1).mean(1))
- # 参考模型的灰度为 0~255, 而不是 0~1
- transformer.set_raw_scale('data', 255)
- # 由于参考模型色彩是 BGR, 需要将其转换为 RGB
- transformer.set_channel_swap('data', (2,1,0))
- im=caffe.io.load_image(image_file)
- net.blobs['data'].reshape(1,3,224,224)
- net.blobs['data'].data[...] = transformer.preprocess('data',im)
- # 显示出各层的参数和形状, 第一个是批次, 第二个是 feature map 数目, 第三和第四是每个神经元中图片的长和宽
- print [(k,v.data.shape) for k,v in net.blobs.items()]
- #输出网络参数
- print [(k,v[0].data.shape) for k,v in net.params.items()]
- def show_image(im):
- if im.ndim==3:
- m=im[:,:,::-1]
- plt.imshow(im)
- #显示图片的方法
- plt.axis('off') # 不显示坐标轴
- plt.show()
- # 每个可视化的都是在一个由一个个网格组成
- def vis_square(data,padsize=1,padval=0):
- data-=data.min()
- data/=data.max()
- # force the number of filters to be square
- n=int(np.ceil(np.sqrt(data.shape[0])))
- padding=((0,n**2-data.shape[0]),(0,padsize),(0,padsize))+((0,0),)*(data.ndim-3)
- data=np.pad(data,padding,mode='constant',constant_values=(padval,padval))
- # 对图像使用滤波器
- data=data.reshape((n,n)+data.shape[1:]).transpose((0,2,1,3)+tuple(range( 4,data.ndim+1)))
- data=data.reshape((n*data.shape[1],n*data.shape[3])+data.shape[4:])
- #show_image(data)
- plt.imshow(data)
- plt.show()
- # 设置图片的保存路径, 此处是我的路径
- plt.savefig("./tools/Vehicle_2000/fc6.jpg")
- out = net.forward()
- image=net.blobs['data'].data[4].copy()
- image-=image.min()
- image/=image.max()
- # 显示原始图像
- show_image(image.transpose(1,2,0))
- #网络提取 conv1 的卷积核
- filters = net.params['conv1'][0].data
vis_square(filters.transpose(0, 2, 3, 1))
- #过滤后的输出, 96 张 featuremap
- feat =net.blobs['conv1'].data[0,:96]
- vis_square(feat,padval=1)
- #第二个卷积层, 显示全部的 96 个滤波器, 每一个滤波器为一行.
- filters = net.params['conv2'][0].data
- vis_square(filters[:96].reshape(96**2, 5, 5))
- # #第二层输出 256 张 featuremap
- feat = net.blobs['conv2'].data[0]
- vis_square(feat, padval=1)
- filters = net.params['conv3'][0].data
- vis_square(filters[:256].reshape(256**2, 3, 3))
- # 第三个卷积层: 全部 384 个 feature map
- feat = net.blobs['conv3'].data[0]
- vis_square(feat, padval=0.5)
- #第四个卷积层, 我们只显示前面 48 个滤波器, 每一个滤波器为一行.
- filters = net.params['conv4'][0].data
- vis_square(filters[:384].reshape(384**2, 3, 3))
- # 第四个卷积层: 全部 384 个 feature map
- feat = net.blobs['conv4'].data[0]
- vis_square(feat, padval=0.5)
- # 第五个卷积层: 全部 256 个 feature map
- filters = net.params['conv5'][0].data
- vis_square(filters[:384].reshape(384**2, 3, 3))
- feat = net.blobs['conv5'].data[0]
- vis_square(feat, padval=0.5)
- #第五个 pooling 层
- feat = net.blobs['fc6'].data[0]
- vis_square(feat, padval=1)
第六层输出后的直方分布
- feat=net.blobs['fc6'].data[0]
- plt.subplot(2,1,1)
- plt.plot(feat.flat)
- plt.subplot(2,1,2)
- _=plt.hist(feat.flat[feat.flat>0],bins=100)
- # #显示图片的方法
- #plt.axis('off') # 不显示坐标轴
- plt.show()
- plt.savefig("fc6_zhifangtu.jpg")
- # 第七层输出后的直方分布
- feat=net.blobs['fc7'].data[0]
- plt.subplot(2,1,1)
- plt.plot(feat.flat)
- plt.subplot(2,1,2)
- _=plt.hist(feat.flat[feat.flat>0],bins=100)
- plt.show()
- plt.savefig("fc7_zhifangtu.jpg")
- #看标签
- #执行测试
- image_labels_filename=caffe_root+'data/ilsvrc12/synset_words.txt'
- #try:
- labels=np.loadtxt(image_labels_filename,str,delimiter='\t')
- top_k=net.blobs['prob'].data[0].flatten().argsort()[-1:-6:-1]
- #print labels[top_k]
- for i in np.arange(top_k.size):
- print top_k[i], labels[top_k[i]]
下面贴几张检测结果
图 3 原始检测图片
图 4 conv1 参数可视化
图 5 conv1 特征可视化
deep-visualization-toolbox
deep-visualization-toolbox 是 Jason Yosinsk 出版在 Computer Science 上的一篇论文的源代码, 改论文主要讲述的是卷积神经网络的可视化, 感兴趣的朋友可以看看这篇论文(论文地址 https://arxiv.org/pdf/1506.06579.pdf ).B 站上有个讲怎么使用该工具的视频, 这里附上链接 https://www.bilibili.com/video/av7405645/ . 该工具的源码在 github: https://github.com/yosinski/deep-visualization-toolbox . 该 github 下有完整的安装配置步骤, 还是以图 2 中的马为例, 贴几张检测结果图.
图 6 ToolBox conv1 特征可视化
图 7 ToolBox conv2 特征可视化
从检测效果上看, 还是挺简洁的. 图片左侧的一列图片左上角是输入图片, 中间部分是图片经过网络前向传播得到的特征图可视化, 左下角是其特征可视化.
Loss 可视化
网络训练过程中 Loss 值的可视化可以帮助分析该网络模型的参数是否合适. 在使用 Faster R-CNN 网络训练模型时, 训练完成后的日志文件中保存了网络训练各个阶段的 loss 值, 如图 8 所示. 只用写简单的 python 程序, 读取日志文件中的迭代次数, 以及需要的损失值, 再画图即可完成 Loss 的可视化.
图 8 模型的训练日志
在下面贴出 Loss 可视化的代码:
- #!/usr/bin/env python
- import os
- import sys
- import numpy as np
- import matplotlib.pyplot as plt
- import math
- import re
- import pylab
- from pylab import figure, show, legend
- from mpl_toolkits.axes_grid1 import host_subplot
- # 日志文件名
- fp = open('faster_rcnn_end2end_ZF_.txt.2018-04-13_19-46-23', 'r',encoding='UTF-8')
- train_iterations = []
- train_loss = []
- test_iterations = []
- #test_accuracy = []
- for ln in fp:
- # get train_iterations and train_loss
- if '] Iteration' in ln and 'loss =' in ln:
- arr = re.findall(r'ion \b\d+\b,',ln)
- train_iterations.append(int(arr[0].strip(',')[4:]))
- train_loss.append(float(ln.strip().split('=')[-1]))
- fp.close()
- host = host_subplot(111)
- plt.subplots_adjust(right=0.8) # ajust the right boundary of the plot window
- #par1 = host.twinx()
- # set labels
- host.set_xlabel("iterations")
- host.set_ylabel("RPN loss")
- #par1.set_ylabel("validation accuracy")
- # plot curves
p1, = host.plot(train_iterations, train_loss, label="train RPN loss")
- .
- host.legend(loc=1)
- # set label color
- host.axis["left"].label.set_color(p1.get_color())
- host.set_xlim([-1000, 60000])
- host.set_ylim([0., 3.5])
- plt.draw()
- plt.show()
可视化效果如下图所示
图 9 Loss 可视化
画 PR 图
Faster R-CNN 训练网络在输出网络模型的同级文件夹里有每一类检测目标每张图片的准确率和召回率, 可以绘制准确率召回率 (Precision-recall, PR) 曲线, PR 曲线的面积即准确率的值. 该文件存储在 ==output\faster_rcnn_end2end\voc_2007_test\zf_faster_rcnn_iter== 下的. pkl 文件下, 需要将其转换为. txt 文件. 代码如下:
- #-*-coding:utf-8-*-
- import cPickle as pickle
- import numpy as np
- np.set_printoptions(threshold=np.NaN)
- fr = open('./aeroplane_pr.pkl') #open 的参数是 pkl 文件的路径
- inf = pickle.load(fr) #读取 pkl 文件的内容
- print inf
- fo = open("aeroplane_pr.txt", "wb")
- fo.write(str(inf))
- fo.close()
- fr.close() #关闭文件
执行完这个程序后, 会将. pkl 文件转换为. txt 文件保存..txt 文件能直观看到每张图片的检测准确率与召回率. 用与画 loss 图相似的方法, 即可完成 PR 曲线的绘制. 效果图如图 10 所示.
图 10 PR 曲线
参考文献
[1] 薛开宇, caffe 学习笔记
[2] Yosinski J, Clune J, Nguyen A, et al. Understanding Neural Networks Through Deep Visualization[J]. Computer Science, 2015.
来源: https://juejin.im/post/5ae2a7b3f265da0b9f40011a