mxnet 变尺寸输入

这是个很现实的问题,因为实际中的图片(呃,这里是说多源途径得到的)大部分都是变尺寸的。
于是每次都要进行bind。然而(hehe),单纯的bind会带来一些一想不到的效果 \huixiao。

重复 bind 使update无效

import mxnet as mx

M,N=3,3
num_filter=1
kernel=mx.nd.array([ [1,2,3],[1,2,3],[1,2,3] ])
kernel1=mx.nd.array([ [1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4] ])

d=mx.sym.Variable('data')

sym=mx.sym.Convolution(data=d,kernel=(3,3),num_filter=num_filter,no_bias=True,name='conv1')
sym=mx.sym.sum(sym)
sym=mx.sym.MakeLoss(data=sym)
bch_kernel=kernel.reshape((1,1,M,N))
bch_kernel1=kernel1.reshape((1,1,M+1,N+1))

arg_params={'conv1_weight': bch_kernel}
mod=mx.mod.Module(symbol=sym,data_names=('data',),label_names=None)

mod.bind(data_shapes=[ ('data',[1,1,M,N]),])
mod.init_params()  
mod.set_params(arg_params=arg_params, aux_params=None,allow_missing=True)  
mod.init_optimizer()  
######################   test whether re_bind  will effect weight   ########### 
mod.forward(mx.io.DataBatch([bch_kernel],[],provide_data=[('data',bch_kernel.shape),] ) )
mod.get_outputs()[0].asnumpy() 
#array([[[[ 42.]]]], dtype=float32)
mod.backward()
mod.update()
mod.forward(mx.io.DataBatch([bch_kernel],[],provide_data=[('data',bch_kernel.shape),] ) )
mod.get_outputs()[0].asnumpy() 
#array([ 41.57999802], dtype=float32)

mod.bind(data_shapes=[ ('data',[1,1,M,N]),],force_rebind=True)  # 重新 bind 一下

mod.forward(mx.io.DataBatch([bch_kernel],[],provide_data=[('data',bch_kernel.shape),] ) )
mod.get_outputs()[0].asnumpy() 
# array([ 42.], dtype=float32)                       # update 无效了

多次 backward 与一次 等价

由于一次只能输入一个样本,所以backward最好能不间断进行几轮后,然后再进行update。但原始的backward也不支持这种操作:

import mxnet as mx

M,N=3,3
num_filter=1
kernel=mx.nd.array([ [1,2,3],[1,2,3],[1,2,3] ])
kernel1=mx.nd.array([ [1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4] ])
d=mx.sym.Variable('data')
sym=mx.sym.Convolution(data=d,kernel=(3,3),num_filter=num_filter,no_bias=True,name='conv1')
sym=mx.sym.sum(sym)
sym=mx.sym.MakeLoss(data=sym)
bch_kernel=kernel.reshape((1,1,M,N))
bch_kernel1=kernel1.reshape((1,1,M+1,N+1))
arg_params={'conv1_weight': bch_kernel}

mod=mx.mod.Module(symbol=sym,data_names=('data',),label_names=None)


mod.bind(data_shapes=[ ('data',[1,1,M,N]),])

mod.init_params()  
mod.set_params(arg_params=arg_params, aux_params=None,allow_missing=True)  
mod.init_optimizer() 


############################################   test whether continue using backward is ok (acc the grad)  ### NOT ok!
mod.forward(mx.io.DataBatch([bch_kernel],[],provide_data=[('data',bch_kernel.shape),] ) )
mod.get_outputs()[0].asnumpy() 
#array([[[[ 42.]]]], dtype=float32)
mod.backward()

mod.forward(mx.io.DataBatch([bch_kernel],[],provide_data=[('data',bch_kernel.shape),] ) )
mod.backward()                   # 两次 backward

mod.update()
mod.forward(mx.io.DataBatch([bch_kernel],[],provide_data=[('data',bch_kernel.shape),] ) )
mod.get_outputs()[0].asnumpy() 
# array([ 41.57999802], dtype=float32)    这实际是一次 backward 就能达到的效果

Solution

想起之前看 example/rcnn时留意到一个关于变尺寸输入的module,翻出来试了下,可以解决第一个问题,稍微改进下,搞定第二个问题。


Jul 31, 2017 记
文件较长,移到github上,module.py见后文链接。


增加了acc_backwardacc_update用以进行累计操作。

测试


import mxnet as mx
from module import MutableModule  # 上文的 module 文件

M,N=3,3
num_filter=1
kernel=mx.nd.array([ [1,2,3],[1,2,3],[1,2,3] ])
kernel1=mx.nd.array([ [1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4] ])

d=mx.sym.Variable('data')

sym=mx.sym.Convolution(data=d,kernel=(3,3),num_filter=num_filter,no_bias=True,name='conv1')
sym=mx.sym.sum(sym)
sym=mx.sym.MakeLoss(data=sym)
bch_kernel=kernel.reshape((1,1,M,N))
bch_kernel1=kernel1.reshape((1,1,M+1,N+1))

arg_params={'conv1_weight': bch_kernel}


#mod=mx.mod.Module(symbol=sym,data_names=('data',),label_names=None)
mod=MutableModule(symbol=sym,data_names=('data',),label_names=None)

mod.bind(data_shapes=[ ('data',[1,1,M,N]),])

mod.init_params()  
mod.set_params(arg_params=arg_params, aux_params=None,allow_missing=True)  
mod.init_optimizer()  



mod.forward(mx.io.DataBatch([bch_kernel],[],provide_data=[('data',bch_kernel.shape),] ) )
mod.get_outputs()[0].asnumpy() 
#array([[[[ 42.]]]], dtype=float32)
mod.acc_backward()


mod.forward(mx.io.DataBatch([bch_kernel],[],provide_data=[('data',bch_kernel.shape),] ) )
mod.acc_backward()


mod.acc_update()
mod.forward(mx.io.DataBatch([bch_kernel],[],provide_data=[('data',bch_kernel.shape),] ) )
mod.get_outputs()[0].asnumpy() 
# array([ 41.57999802], dtype=float32)

#############   more backward

mod.forward(mx.io.DataBatch([bch_kernel],[],provide_data=[('data',bch_kernel.shape),] ) )
mod.get_outputs()[0].asnumpy() 
#array([[[[ 42.]]]], dtype=float32)
mod.acc_backward()


mod.forward(mx.io.DataBatch([bch_kernel],[],provide_data=[('data',bch_kernel.shape),] ) )
mod.acc_backward()

mod.forward(mx.io.DataBatch([bch_kernel],[],provide_data=[('data',bch_kernel.shape),] ) )
mod.acc_backward()

mod.forward(mx.io.DataBatch([bch_kernel],[],provide_data=[('data',bch_kernel.shape),] ) )
mod.acc_backward()


mod.acc_update()
mod.forward(mx.io.DataBatch([bch_kernel],[],provide_data=[('data',bch_kernel.shape),] ) )
mod.get_outputs()[0].asnumpy() 
# array([ 40.73999786], dtype=float32)         YES !

搞定!


Jul 31, 2017 记

save_checkpoint 的问题

之前的程序存在一个潜在的问题,如果后续的训练过程中尺寸发生改变,最终需要保存训练的参数时,就会产生问题。从一些测试结果来看,如果只是调用self._curr_module.save_checkpoint, 保存的参数仅仅只是第一次发生输入数据尺寸扩张(也可能是缩小,但此处不是重点)之前的参数。从原始支持变形版的module.py来看,问题应该产生与forward接近结束前的bind中,这个调用使用了shared_module功能,但doc上对这一功能描述难以确定其具体机制。
在查看mxnet/python/mxnet/module时意外发现bucketing_module.py是变形版module.py的原型,主要是发现其实现原理都是在forward中进行使用shared_module功能的bind操作,但在这个相当于官方的程序中却没有发现save_checkpoint的定义(这令人费解,根据测试的结果,通过这种方式的实现,简单的svae_checkpoint会导致以后的噩梦--比如我就是在发现load之前的参数,得到0.01acc时意识到这个问题的)
但是能够work的参数肯定存在,否则,训练中acc就不会持续上升。

解决这个问题的思路,来自于另一个意外发现,mxnet/python/mxnet/module/module.pyupdate操作,其中有一个对self._params_dirty的赋值操作,这个名字看上去很有意思。追踪发现_sync_params_from_devices是个有意思的调用:

#mxnet/python/mxnet/module/module.py
    def _sync_params_from_devices(self):
        """Synchronizes parameters from devices to CPU. This function should be called after
        calling `update` that updates the parameters on the devices, before one can read the
        latest parameters from ``self._arg_params`` and ``self._aux_params``.
        """
        self._exec_group.get_params(self._arg_params, self._aux_params)
        self._params_dirty = False

从注释中已经可以明白些了。试了一下,可以解决问题。

增加的功能如下:

  1. acc_backward 每次forward后调用,可以对grad进行累加
  2. acc_update 多次 acc_backward后使用,接受归一化参数 , 以上两个用以对 bach_size进行扩充
  3. save_checkpoint 保存训练所得参数,其他调用途径肯能导致保存错误的参数

TODO

  1. 需要一个类似与mx.mod.Module.load的接口。 现在程序进行这一步较为混乱。

Appendix

  1. 修改后的module.py文件在这里

posted @ 2017-07-21 16:24  rotxin  阅读(1244)  评论(2编辑  收藏  举报