我们在看Faster R-CNN源码(MXNet版本)的时候,除了下面这些我们遇到的常见的参数解析
import argparse
import ast
parser = argparse.ArgumentParser(description='Demonstrate a Faster R-CNN network',formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--network', type=str,default='vgg16', help='base network')
之外,我们发现如下,有一个不一样的代码:
args = parser.parse_args()
args.img_pixel_means = ast.literal_eval(args.img_pixel_means)
出现ast.literal_eval这样的一个执行方法,看起来跟我们学到的eval方法很类似,确实如此,用法到没什么区别,主要区别体现在安全方面,我们先来熟悉下eval方法,再比较下就明白了。
eval方法
# 列表
a = '[2,4,6,7]'
print(type(a), a) # <class 'str'> [2,4,6,7]
a1 = eval(a)
print(type(a1), a1) # <class 'list'> [2, 4, 6, 7]
# 元组
b = '(1,22,33)'
print(type(b), b) # <class 'str'> (1,22,33)
b1 = eval(b)
print(type(b1), b1) # <class 'tuple'> (1, 22, 33)
# 字典
c = '{"object":"image","width":300,"height":400}'
print(type(c), c) # <class 'str'> {"object":"image","width":300,"height":400}
c1 = eval(c)
# <class 'dict'> {'object': 'image', 'width': 300, 'height': 400}
print(type(c1), c1)
v=eval(input("输入操作符也能执行:"))
#输入操作符也能执行:1+2
print(v)#3
既然eval可以执行很多操作,那么下面这样的系统命令也是可以执行的__import__('os').system('dir')
然后再输入查看文件内容的命令:open("t.py",r).read()
这样可以查看到想要查看的东西了,更关键的是这样的输入如果是删除命令或其他恶意代码呢?照样执行不误!那就很不安全了!!
literal_eval方法
为了防止出现这样的恶意代码执行,在源码中我们看到的就是用literal_eval方法,如下:
import argparse
import ast
args = parser.parse_args()
args.img_pixel_means = ast.literal_eval(args.img_pixel_means)
args.img_pixel_stds = ast.literal_eval(args.img_pixel_stds)
args.rpn_anchor_scales = ast.literal_eval(args.rpn_anchor_scales)
args.rpn_anchor_ratios = ast.literal_eval(args.rpn_anchor_ratios)
args.rcnn_pooled_size = ast.literal_eval(args.rcnn_pooled_size)
args.rcnn_bbox_stds = ast.literal_eval(args.rcnn_bbox_stds)
这样的ast.literal_eval一种执行里面操作的方法,是做了安全处理的,这个方法的具体代码实现,大家可以转到定义去看下。
我们来看下执行上面1+2和一些命令看会发生什么情况。
import ast
ast.literal_eval('1+2')
ast.literal_eval("__import__('os').system('dir')")
Exception has occurred: ValueError
malformed node or string: <_ast.Call object at 0x0000020ECE612D88>
这样的形式涉及到安全问题,都不会执行,会报错,也就是说只能是Python类型才会执行,其余的都会引发错误。
a = ast.literal_eval('[2,3,4,5]')
print(type(a), a)#<class 'list'> [2, 3, 4, 5]
像上面那些属于Python类型的都是安全的,可以执行,没有问题。
所以在选择这两种的情况时,需要注意它们的安全问题,尤其是开放性的执行,最好就是用ast.literal_eval,不得已用eval执行的,就要控制好输入了。
ROIPooling
感兴趣区域(ROI,region of interest)的池化,其目的是对输入执行最大池化以获得固定大小的特征图,大多使用在对象检测的Fast R-CNN网络。输入是4维的数组,将区域提议作为roi,然后它在输入的子区域上汇集,并生成固定大小的输出,无论roi大小如何。
换句话说就是经过卷积层之后的特征图变小多少倍(下采样,这个跟我们上一篇讲的转置卷积是上采样,刚好相反),ROI也要缩小这么多倍(没被整除,取整操作就存在量化误差)映射回特征图上对应位置,这种ROI是各种尺寸不一的,然后划分成N x N的区域,每个区域做最大池化的操作,这样最终就获得了N x N的输出。
通过改变参数"rois"和"spatial_scale",可以调整边界框坐标的大小,通过标准最大池化操作将裁剪的特征图合并为固定大小的输出由"pooled_size"参数指示。batch_size将更改为区域数"ROIPooling"之后的边界框。
先来看下MXNet中的ROI池化的原函数:
#对输入数组执行感兴趣区域(ROI)池化
(function) ROIPooling(data: Any | None = None, rois: Any | None = None, pooled_size: _NullType | Any = _Null, spatial_scale: _NullType | Any = _Null, name: Any | None = None, attr: Any | None = None, out: Any | None = None, **kwargs: Any) -> tuple[Literal[0]]
data(Symbol类型):输入数组到池化操作符,一个4维的特征映射
rois(Symbol类型):[[batch_index, x1, y1, x2, y2]]的2维数组,其中(x1, y1)和(x2, y2)是指定感兴趣区域的左上角和右下角的坐标。'batch_index'表示输入数组中对应图像的索引
我们具体来看下这个池化操作与池化之后的形状会怎么样,由于这里使用的是符号式编程,所以我们需要定义的是变量与设计出计算图,然后我们使用一个工具将其可视化看下:
import mxnet as mx
from mxnet import nd
x = mx.sym.var('inputarr')
y = mx.sym.var('rois')
'''
x1 = nd.array([[[[ 0., 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., 43., 44., 45., 46., 47.]]]])
y1 = nd.array([[0,0,0,4,4]])
'''
net = mx.sym.ROIPooling(data=x, rois=y, pooled_size=(2, 2), spatial_scale=1.0, name="roi_pool_2x2")
digraph = mx.viz.plot_network(net, shape={'inputarr': (1, 1, 8, 6), 'rois': (1, 5)})
digraph.view()
print(net.list_arguments(), net.list_outputs())# ['inputarr', 'rois'] ['roi_pool_2x2_output']
arg_shape, out_shape, _ = net.infer_shape(inputarr=(1, 1, 8, 6), rois=(1, 5))
print(arg_shape, out_shape) # [(1, 1, 8, 6), (1, 5)] [(1, 1, 2, 2)]
如果报下面的错误:
failed to execute 'dot', make sure the Graphviz executables are on your systems' PATH
就是说在运行plot_network这个函数需要先安装这个Graphviz可视化计算图的程序,应该是没有安装,下载地址:Graphviz可视化计算图
安装完毕之后重启下VSCode然后再次执行,将会生成一个pdf的文件,里面展示的就是一张"计算图"的可视化的有向图。
我们可以看到通过推断形状,输入(inputarr)的是4维的形状,感兴趣区域rois的是2维形状,最终的输出形状是4维的2x2尺寸(由pooled_size决定),然后你将pooled_size的大小修改成其他形状看下,输出的尺寸是不是也更改了。
另外需要注意的是,如果将spatial_scale修改成其他,比如0.7,那么这个输出的形状不变,里面的值有变化,从自带的示例中,将会由[[[[ 14., 16.],[ 26., 28.]]]]变成[[[[ 7., 9.],[ 19., 21.]]]]
可视化计算图
我们再来熟悉下这个Graphviz可视化计算图的工具,接着上面的来看下,这个计算图设计好了,我们想得到具体的输出结果,如何做呢?
好的,去掉x1和y1的注释,我们来看下经过ROI池化之后的结果是不是示例中的结果:
ex = net.bind(ctx=mx.cpu(), args={'inputarr' : x1, 'rois' : y1})
ex.forward()
print(ex.outputs)
'''
[
[[[[14. 16.]
[26. 28.]]]]
<NDArray 1x1x2x2 @cpu(0)>]
'''
计算结果是正确的,将spatial_scale修改成0.7,运行结果也是正确的。
这个就是计算图做好之后,做个绑定,然后做前向计算可以看到具体的输出结果。
接着来看几个例子,再次巩固下这个计算图的计算与可视化的操作
1、普通计算
import mxnet as mx
a = mx.sym.Variable('a_data')
b = mx.sym.Variable('b_data')
c = a + b
d = a * b
e = mx.sym.dot(a, b)
f = mx.sym.Reshape(d+e, shape=(1, 4))
g = mx.sym.broadcast_to(f, shape=(2, 4))
mx.viz.plot_network(symbol=g).view()
2、多层神经网络
既然是MXNet的特色,那肯定最常见的就是对神经网络的计算,我们来看下几个层的连接的运行,全连接层+激活函数层+全连接层+Softmax输出层
import mxnet as mx
net = mx.sym.Variable('input_data')
net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=128)
net = mx.sym.Activation(data=net, name='relu1', act_type="relu")
net = mx.sym.FullyConnected(data=net, name='fc2', num_hidden=10)
net = mx.sym.SoftmaxOutput(data=net, name='out')
digraph = mx.viz.plot_network(net, shape={'input_data':(100,200)},node_attrs={"fixedsize":"false"})
digraph.view()#有向图的显示
3、Group组合
还可以将多个输出做一个组合,通过mx.sym.Group结合起来,如下:
import mxnet as mx
net = mx.sym.Variable('input_data')
fc1 = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=128)
net = mx.sym.Activation(data=fc1, name='relu1', act_type="relu")
#这里是两个输出,将其组合
out1 = mx.sym.SoftmaxOutput(data=net, name='softmax')
out2 = mx.sym.LinearRegressionOutput(data=net, name='regression')
group = mx.sym.Group([out1, out2])
print(group.list_arguments())#['input_data', 'fc1_weight', 'fc1_bias', 'softmax_label', 'regression_label']
print(group.list_outputs())#['softmax_output', 'regression_output']
digraph = mx.viz.plot_network(group)
digraph.view()
有兴趣的伙伴们可以回过来看下本人以前写的关于符号式编程的两篇文章:
MXNet中的命令式编程和符号式编程的优缺点https://blog.csdn.net/weixin_41896770/article/details/125370472
计算性能的提升之混合式编程(MXNet)https://blog.csdn.net/weixin_41896770/article/details/127400770