这是个很现实的问题,因为实际中的图片(呃,这里是说多源途径得到的)大部分都是变尺寸的。
于是每次都要进行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_backward
和acc_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.01的acc时意识到这个问题的)
但是能够work的参数肯定存在,否则,训练中acc就不会持续上升。
解决这个问题的思路,来自于另一个意外发现,mxnet/python/mxnet/module/module.py中update操作,其中有一个对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
从注释中已经可以明白些了。试了一下,可以解决问题。
增加的功能如下:
acc_backward
每次forward后调用,可以对grad进行累加acc_update
多次 acc_backward后使用,接受归一化参数 , 以上两个用以对 bach_size进行扩充save_checkpoint
保存训练所得参数,其他调用途径肯能导致保存错误的参数
TODO
- 需要一个类似与mx.mod.Module.load的接口。 现在程序进行这一步较为混乱。
Appendix
- 修改后的module.py文件在这里。