MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《10》(尾)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《1》:论文源地址克隆MXNet版本的源码安装环境与测试以及对下载的源码的每个目录做什么用的做个解释。

MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《2》:对论文中的区域提议、平移不变锚、多尺度预测等概念的了解对损失函数、边界框回归的公式的了解以及共享特征的训练网络的方法。

MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《3》:加载模型参数对参数文件的了解以及感兴趣区域ROI和泛洪填充的方法FLOODFILL_FIXED_RANGEFLOODFILL_MASK_ONLY

MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《4》:下载与熟悉Pascal VOC20072012语义分割数据集明白实例分割除了分类之外还可以细分到像素级别的所属类别。

MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《5》:主要就是熟悉转置卷积与大家所熟知的卷积有什么区别作用是什么以及双线性插值等相关知识

MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《6》:主要讲解关于参数解析的安全执行(ast.literal_eval)ROI池化以及计算图的可视化的处理

MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《7》:打印内容(比如参数文件里的东西)的三种方式以及对奇异值分解SVDSingular Value Decomposition的熟悉了解SVD的作用和运用

MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《8》:主要是通过参数的设置进一步熟悉模型以及对于符号式编程的复习另外关于损失函数之类这里用到了自定义评价函数然后通过自带的mx.metric来做有示例让大家熟悉。

MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《9》:从测试模型了解有哪些知识点张量的垂直叠加然后从单个文件细读有哪些关键点交并比的计算、裁剪掉超出图像部分区域的锚框、边界框回归方法以及非极大值抑制的实现(并画出边界框图形)

这是第十篇继续来拆解学习Faster RCNN也是最后一篇知识点比较多另外在文章末尾附上最终加上了注释的源码有兴趣的伙伴们可以Clone一个看看。

我们继续对每个文件的代码进行阅读有些我就直接在源码中做了注释没有贴代码了另外coco与restnet相关的代码跟voc的都差不多只是数据集与网络结构不一样整体的思路是一样的。这里还是以voc数据集为主来熟悉来到这个symimdb目录主要是图像数据的处理。

错误与断言assert

在代码中可以看到很多地方有断言与错误等处理我们来熟悉下:

def _load_gt_roidb(self):
        raise NotImplementedError

出现raise NotImplementedError这个错误就是说如果这个方法没有被重写就报错我们看下这个方法是属于哪个类的class IMDB(object) 然后我们搜索IMDB查看相关调用我们发现在pascal_voc.py中有调用:class PascalVOC(IMDB)

那么在这个PascalVOC类里面肯定需要重写_load_gt_roidb这个方法我们往下查看

def _load_gt_roidb(self):
    image_index = self._load_image_index()
    gt_roidb = [self._load_annotation(index) for index in image_index]
    return gt_roidb

没错代码中确实做了重写如果没有重写这个方法那就会触发NotImplementedError这个错误

另外断言出现的频率也很高比如下面:

assert ex_rois.shape[0] == gt_rois.shape[0], 'inconsistent rois number'

如果两者的样本数不一样那就会报inconsistent rois number这样的错误当然这个错误的信息显示是自定义的,比如:

assert 1==2,'1不等于2'

源码中还有一种值错误处理比如:

networks = {
        'vgg16': get_vgg16_train,
        'resnet50': get_resnet50_train,
        'resnet101': get_resnet101_train
    }
    if network not in networks:
        raise ValueError("network {} not supported".format(network))

这样的处理就是说网络只能是指定的这三者中的一种如果是其他的就会报错比如如果输入不存在的get_vgg18_train将出现如下错误:

ValueError: network get_vgg18_train not supported

这些错误与断言只要出现都将终止程序不会继续往下执行这个在平时自己写代码的时候需要注意提高代码的严谨性。

推断形状infer_shape

我们来到symnet/model.py文件其中关于推断形状有必要介绍下因为形状在神经网络中是非常非常重要的概念先来看下源码中实现的方法infer_param_shape

def infer_param_shape(symbol, data_shapes):
    arg_shape, _, aux_shape = symbol.infer_shape(**dict(data_shapes))
    arg_shape_dict = dict(zip(symbol.list_arguments(), arg_shape))
    aux_shape_dict = dict(zip(symbol.list_auxiliary_states(), aux_shape))
    return arg_shape_dict, aux_shape_dict

主要关注infer_shape这个方法其中的参数data_shapes假如类似下面这样的形状由元组组成的列表:

data_shapes = [('data', (1, 3, 800, 800)), ('im_info', (1, 3))]

这里通过字典的类型转换:dict(data_shapes)变成字典类型:

{'data': (1, 3, 800, 800), 'im_info': (1, 3)}

对于前面两个星号**的用法如果不了解的伙伴们可以查阅:Python中*args和**kwargs的解释

如果有符号式编程的经验那对于形状的推断就会很熟悉如果是第一次接触没关系这里再次复习一遍。

通俗简单来说符号式编程就是先将整个计算流程给设计出来最后需要使用的时候进行绑定与计算操作就好比建房子之前先将图纸画好然后我们只需要按照图纸来执行相关操作。

为了快速熟悉它这里我用一个特别简单的示例来说明:

a = mx.sym.Variable('A')
b = mx.sym.Variable('B')
c = (a + b) / 10

这里定义了两个SymbolA和B然后将两者相加再除以10这个时候的A和B是个未知变量或说是个符号变量。那如果场景是在深度卷积网络中呢?整个流程我们需要正确执行形状是关键不然会因为形状不符合就没法计算所以这里就出现了推断形状的方法然后可以做一些前面介绍的断言确保形状一样这样就可以确保顺畅的向下执行了。

我们来看个具体的示例假如输入的形状如下:

input_shapes = {'A':(10,2), 'B':(10,2)}#这里我们就直接使用字典类型
c.infer_shape(**input_shapes)#这里会返回三个形状arg_shapesout_shapesaux_shapes

接收返回值打印看下结果:

arg_shapes,out_shapes,aux_shapes=c.infer_shape(**input_shapes)
#arg_shapes:[(10, 2), (10, 2)]
#out_shapes:[(10, 2)]
#aux_shapes:[]

也就是说在符号式编程中只需要指定形状我们就可以通过“计算图”推断出每层输出的形状。

如何在实践中得到实际的结果通过bind绑定和forward计算即可。

executor=c.bind(ctx=mx.cpu(),args={'A':nd.array([[2,3],[4,5]]),'B':nd.array([[11,10],[1,8]])})
executor.arg_dict
'''
{'A':
[[2. 3.]
 [4. 5.]]
<NDArray 2x2 @cpu(0)>, 'B':
[[11. 10.]
 [ 1.  8.]]
<NDArray 2x2 @cpu(0)>}
'''
executor.forward()
executor.outputs[0]
'''
[[1.3 1.3]
 [0.5 1.3]]
<NDArray 2x2 @cpu(0)>
'''

计算结果没有问题两者相加之后除以10想了解更多关于符号式编程的伙伴们可以查阅:

MXNet中的命令式编程和符号式编程的优缺点

计算性能的提升之混合式编程MXNet

MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《6》

MakeLoss计算损失函数

我们在symnet/symbol_vgg.py的边界框回归的源码中使用了平滑L1损失函数

在命令式编程中我们知道nd有自带的L1平滑损失直接可以求出:

print(nd.smooth_l1(nd.array([0.5, 0.9, 1, 2, 3]), scalar=1))
'''
[0.125      0.40499997 0.5        1.5        2.5       ]
<NDArray 5 @cpu(0)>

这里顺带将L1平滑损失函数的公式再次贴出来:

那么在符号式编程中所以如何使用呢?我们看下源码中是怎么样的:

bbox_pred = mx.symbol.FullyConnected(name='bbox_pred', data=top_feat, num_hidden=num_classes * 4)
bbox_loss_ = bbox_weight * mx.symbol.smooth_l1(name='bbox_loss_', scalar=1.0, data=(bbox_pred - bbox_target))
bbox_loss = mx.sym.MakeLoss(name='bbox_loss', data=bbox_loss_, grad_scale=1.0 / rcnn_batch_rois)

照葫芦画瓢这个symbol模块也自带有smooth_l1(平滑L1损失函数)指定需要的参数data(Symbol类型)然后通过MakeLoss去执行即可

data = mx.sym.Variable('data')
sl1_loss_ = mx.sym.smooth_l1(data=data, name='bbox_loss_', scalar=1)
m_loss = mx.sym.MakeLoss(data=sl1_loss_)
EX = m_loss.bind(ctx=mx.cpu(), args={'data': nd.array([0.5, 0.9, 1, 2, 3])})
EX.forward()
'''
[0.125      0.40499997 0.5        1.5        2.5       ]
<NDArray 5 @cpu(0)>]
'''

跟前面命令行编程的结果是一样的当然自己使用公式计算也是这样的结果其中0.40499997本来是0.41这个属浮点数的误差。

这里需要注意的是参数是scalar而不是源码中示例的sigma如果写成sigma在VSCode中就会报错命令行却没有问题这里比较奇怪:

Exception has occurred: OSError
[WinError -529697949] Windows Error 0xe06d7363

以方法的参数为准:

def smooth_l1(data=None, scalar=_Null, name=None, attr=None, out=None, **kwargs)

示例中的公式是sigma所以例子中的参数误写成了sigma这里的细节需要注意下。

BlockGrad阻塞梯度的反向传播

Block是阻塞的意思Grad是表示梯度Gradient含义就是阻止梯度的反向传播。

我们在symnet/symbol_vgg.py的代码get_vgg_train训练方法中看到有这个方法的调用:

group = mx.symbol.Group([rpn_cls_prob, rpn_bbox_loss, cls_prob, bbox_loss, mx.symbol.BlockGrad(label)])

mx.symbol.BlockGrad(label)那意思就是说label在反向传播时的梯度在这里终止

我们来看个具体的示例:

from mxnet import nd
import mxnet as mx

x1 = nd.array([[1, 2]])
x2 = nd.array([[3, 4]])

a = mx.sym.Variable('a')
b = mx.sym.Variable('b')
a_grad = 5*a
b_grad = 10*b
m_loss = mx.sym.MakeLoss(a_grad+b_grad)
EX = m_loss.simple_bind(ctx=mx.cpu(), a=(1, 2), b=(1, 2))
print(EX.forward(a=x1, b=x2)[0])  # [[35. 50.]]

EX.backward()
print(EX.grad_arrays)
'''
[
[[5. 5.]]
<NDArray 1x2 @cpu(0)>,
[[10. 10.]]
<NDArray 1x2 @cpu(0)>]
'''

我们可以看到前向传播的结果是正确的再观察这个5a和10b的梯度分别是5和10反向传播的结果也没有问题。

现在我们阻止这个10b的梯度传播看下会是什么情况:

x1 = nd.array([[1, 2]])
x2 = nd.array([[3, 4]])

a = mx.sym.Variable('a')
b = mx.sym.Variable('b')
a_grad = 5*a
b_grad = 10*b
# 这个位置我们添加一个阻止b_grad的反向传播
b_grad_stop = mx.sym.BlockGrad(b_grad)

m_loss = mx.sym.MakeLoss(a_grad+b_grad_stop)
EX = m_loss.simple_bind(ctx=mx.cpu(), a=(1, 2), b=(1, 2))
print(EX.forward(a=x1, b=x2)[0])  # [[35. 50.]]

EX.backward()
print(EX.grad_arrays)
'''
[
[[5. 5.]]
<NDArray 1x2 @cpu(0)>,
[[0. 0.]]
<NDArray 1x2 @cpu(0)>]
'''

5a的反向传播是正常的求出的梯度是5没有问题10b的梯度全部变为了0说明成功阻止了它的反向传播我们也可以可视化下这个小型的“计算图”:

digraph = mx.viz.plot_network(m_loss)
digraph.view()

如图:

可以看到a和b的区别b这个分支多了一个blockgrad0然后再相加这里需要注意的是前向传播是不影响的还是正常的相乘之后两者相加只是在反向传播求梯度的时候做一个阻塞终止10b的传播。

自定义操作符operator

我们在symnet/proposal_target.py源码发现在类的上面出现这样一个装饰@mx.operator.register('proposal_target')它的作用就是将名称proposal_target注册到自定义操作符中。

@mx.operator.register('proposal_target')
class ProposalTargetProp(mx.operator.CustomOpProp):
    def __init__(self, num_classes='21', batch_images='1', batch_rois='128', fg_fraction='0.25',
                 fg_overlap='0.5', box_stds='(0.1, 0.1, 0.2, 0.2)'):
        super(ProposalTargetProp, self).__init__(need_top_grad=False)#False:此层不需要传来的梯度
        self._num_classes = int(num_classes)
        self._batch_images = int(batch_images)
        self._batch_rois = int(batch_rois)
        self._fg_fraction = float(fg_fraction)
        self._fg_overlap = float(fg_overlap)
        self._box_stds = tuple(np.fromstring(box_stds[1:-1], dtype=float, sep=','))

先看下这个类的基类mx.operator.CustomOpProp我们可以转到定义可以知道这个是自定义操作符属性类的基类。CustomOpProp最后创建操作符是返回CustomOp()然后转到定义发现这个是python中实现的操作符的真正基类了其他还有NumpyOpNDArrayOp这样的操作都在operator.py文件定义

symnet/symbol_vgg.py中的调用:

group = mx.symbol.Custom(rois=rois, gt_boxes=gt_boxes, op_type='proposal_target',
                         num_classes=num_classes, batch_images=rcnn_batch_size,
                         batch_rois=rcnn_batch_rois, fg_fraction=rcnn_fg_fraction,
                         fg_overlap=rcnn_fg_overlap, box_stds=rcnn_bbox_stds)

op_type='proposal_target'这个操作符的名称就是来自前面声明的装饰@mx.operator.register('proposal_target')注册中的名称。

这样注册了之后在这个类里面可以重写方法比如说

def list_arguments(self):
    return ['rois', 'gt_boxes']

def list_outputs(self):
    return ['rois_output', 'label', 'bbox_target', 'bbox_weight']

这个list_arguments输入的参数就是由op_type绑定的自定义操作符决定了同样的list_outputs输出参数也是的这里其实是后缀形式输出形式是name_后缀这样的输出。

当然这里还是围绕着这个源码来熟悉这个知识点我们来看个具体的示例(输出是Softmax层对官方示例有所改动)一个多层感知机的网络:

import mxnet as mx
from mxnet import nd
import numpy as np

class TestLayer(mx.operator.CustomOp):
    def forward(self, is_train, req, in_data, out_data, aux):
        x = in_data[0].asnumpy()
        y = np.exp(x - x.max(axis=1).reshape((x.shape[0], 1)))
        y /= y.sum(axis=1).reshape((x.shape[0], 1))
        self.assign(out_data[0], req[0], mx.nd.array(y))

    def backward(self, req, out_grad, in_data, out_data, in_grad, aux):
        l = in_data[1].asnumpy().ravel().astype(np.int32)
        y = out_data[0].asnumpy()
        y[np.arange(l.shape[0]), l] -= 1.0
        self.assign(in_grad[0], req[0], mx.nd.array(y))


@mx.operator.register('Tony')  # 注册名称将在调用的时候作为操作符名称
class TestProp(mx.operator.CustomOpProp):
    def __init__(self):
        super(TestProp, self).__init__(need_top_grad=False)

    def list_arguments(self):
        return ['data', 'label']

    def list_outputs(self):
        return ['output']

    def infer_shape(self, in_shape):
        data_shape = in_shape[0]
        label_shape = (in_shape[0][0],)
        output_shape = in_shape[0]
        return [data_shape, label_shape], [output_shape], []

    def infer_type(self, in_type):
        return in_type, [in_type[0]], []

    def create_operator(self, ctx, shapes, dtypes):
        return TestLayer()


net = mx.sym.Variable('data')
net = mx.sym.FullyConnected(net, name='fc', num_hidden=10)
net = mx.sym.Activation(net, name='relu', act_type="relu")
mlp = mx.sym.Custom(data=net, name='MySoftmax', op_type='Tony')

print(mlp.list_arguments(), mlp.list_outputs())
#['data', 'fc_weight', 'fc_bias', 'MySoftmax_label'] ['MySoftmax_output']

# 推断形状
input_shapes = {'data': (5, 28*28)}
print(mlp.infer_shape(**input_shapes))
#([(5, 784), (10, 784), (10,), (5,)], [(5, 10)], [])

#绑定做反向传播
args = {'data': nd.ones((1, 4)), 'fc_weight': nd.ones((10, 4)),
        'fc_bias': nd.ones((10,)), 'MySoftmax_label': nd.ones((1))}
args_grad = {'fc_weight': nd.zeros((10, 4)), 'fc_bias': nd.zeros((10))}

executor = mlp.bind(ctx=mx.cpu(0), args=args,args_grad=args_grad, grad_req='write')
print("executor.arg_dict 初始值\n", executor.arg_dict)
'''
executor.arg_dict 初始值
 {'data':
[[1. 1. 1. 1.]]
<NDArray 1x4 @cpu(0)>, 'fc_weight':
[[1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]]
<NDArray 10x4 @cpu(0)>, 'fc_bias':
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
<NDArray 10 @cpu(0)>, 'MySoftmax_label':
[1.]
<NDArray 1 @cpu(0)>}
'''

print("executor.grad_dict 初始值\n", executor.grad_dict)
'''
executor.grad_dict 初始值
 {'data': None, 'fc_weight':
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]
<NDArray 10x4 @cpu(0)>, 'fc_bias':
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
<NDArray 10 @cpu(0)>, 'MySoftmax_label': None}
'''
executor.backward()
print(executor.grad_arrays)
'''
[None,
[[-2.144862e-15 -2.144862e-15 -2.144862e-15 -2.144862e-15]
 [-1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00]
 [ 0.000000e+00  0.000000e+00  0.000000e+00  0.000000e+00]
 [ 4.591214e-41  4.591214e-41  4.591214e-41  4.591214e-41]
 [ 0.000000e+00  0.000000e+00  0.000000e+00  0.000000e+00]
 [ 0.000000e+00  0.000000e+00  0.000000e+00  0.000000e+00]
 [ 4.203895e-45  4.203895e-45  4.203895e-45  4.203895e-45]
 [ 0.000000e+00  0.000000e+00  0.000000e+00  0.000000e+00]
 [ 0.000000e+00  0.000000e+00  0.000000e+00  0.000000e+00]
 [ 5.044674e-43  5.044674e-43  5.044674e-43  5.044674e-43]]
<NDArray 10x4 @cpu(0)>,
[-2.144862e-15 -1.000000e+00  0.000000e+00  4.591214e-41  0.000000e+00
  0.000000e+00  4.203895e-45  0.000000e+00  0.000000e+00  5.044674e-43]
<NDArray 10 @cpu(0)>, None]
'''

十连载将Faster-RCNN全部梳理了一遍尤其是对于源码中出现的知识点都单独挑出来进行了示例演示希望能够帮助到大家更快的理解这个网络模型由于水平有限错误难免还望留言指正

其中对于源码的理解做了一些注释有兴趣的可以clone一个来看看github地址如下:

https://github.com/yihangzhao/NewMXRCNN

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

“MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《10》(尾)” 的相关文章