前言
在工业界混的人,每个人都有自己习惯的框架,有时为了部署不得不在框架之间进行模型转换。踩了几个坑之后,终于转换成功,这里记录一下分享一下。gayhub链接:
xggIoU/tensorflow_keras_to_caffegithub.comenvs/tools
tensorflow1.13.2、keras2.2.4、caffe1.0、python3.6、pycharm、Anoconda、MMdnn0.2.4、ubuntu18.04
准备工作
- 如果你像我一样caffe只是转换过程需要,而平常不用它来train,你可以简单的利用conda安装编译好了的caffe,命令行:
python conda install caffe-gpu
- 如果你的模型本身现在本来就是keras model,那么上述环境中所述的MMdnn就不需要了。
- 如果你的模型现在是tensorflow model,例如ckpt model,那么就需要微软的MMdnn进行转换,虽然这个工具目前还不能无缝转换所有不同框架模型,但是tf与keras的互相转换还是无痛的,毕竟可以说是同一个框架。命令行转换示例:
python mmconvert -sf tensorflow -in your_model_name.ckpt.meta -iw your_model_name.ckpt --inNodeName input_name --inputShape 224,224,3 --dstNodeName output1 ouput2 -df keras -om your_keras_model.h5
开始转换
这里不讲具体的转换过程,你可以自己去看转换脚本,很容易就能看懂。并且对照着已有的脚本代码也容易就能实现自己用到的layer的转换。使用很简单,指定好你的keras模型路径,指定好你想要的caffe模型的输出prototxt、caffemodel名字,最后run就ok了。像这样:
if __name__=='__main__':
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.Session(config=config)
K.set_session(sess)
kModel=load_keras_model('../your_keras_model.h5')
prototxt='../your_caffe_model.prototxt'
caffemodel='../your_caffe_model.caffemodel'
generate_caffe_model(kModel, prototxt, caffemodel)
目前实现的可转换layer如下:
- InputLayer
- Dense
- Dropout
- ZeroPadding2D
- Multiply、Concatenate、Maximum、Add
- Conv2D、Conv2DTranspose
- BatchNormalization
- MaxPooling2D、AveragePooling2D、GlobalAveragePooling2D
- relu、prelu、elu、softmax、sigmoid、tanh
这样就已经能转换绝大部分模型了,如果没实现你用到的layer,你可自行在函数generate_layer(blobs, layer, n, net_params)
添加你的layer即可。
Tips(坑)
- 如果你的模型是tensorflow模型,并且使用了转置卷积
conv2d_transpose
(caffe中为Deconvolution
),那么千万避免使用高级api,例如slim.conv2d_transpose
,而是需要使用更低级的tf.nn.conv2d_transpose
接口。否则会转换失败(看了下,貌似实现是不一样的)。 - 另外,tf/keras中卷积的padding与caffe的padding方式不一样,tf/keras在padding方式为
same
情况下存在只pad右下的情况,而caffe是上下左右都pad,会导致结果有误差。tf中的same
pad具体计算如下,以宽度为例:
width_padd=(new_width-1)*stride+kernel_size-ori_width
padd_left=width_padd//2
padd_right=width_padd-padd_left
为了解决这个问题,在tf/keras中建议对卷积改造一下,手动加pad,就像这样:
def conv2d(inputs, filters, kernel_size, strides=1,rate=1,biases_initializer=tf.zeros_initializer, activation_fn=tf.nn.relu):#stride>1时padding,valid卷积实现same
def _fixed_padding(inputs, kernel_size):
pad_total = kernel_size - 1
pad_beg = pad_total // 2
pad_end = pad_total - pad_beg
padded_inputs = tf.pad(inputs, [[0, 0], [pad_beg, pad_end],
[pad_beg, pad_end], [0, 0]], mode='CONSTANT')
return padded_inputs
if strides > 1:
inputs = _fixed_padding(inputs, kernel_size)
inputs = slim.conv2d(inputs, filters, kernel_size, stride=strides,rate=rate,biases_initializer=biases_initializer,
activation_fn=activation_fn,padding=('SAME' if strides == 1 else 'VALID'),weights_initializer=tf.initializers.he_normal())
return inputs
如此一来,在转换过程中去掉pad层,(去掉keras中的ZeroPadding2D层),就能直接只使用caffe中的卷积层,实现一致的输出。
希望对你有帮助!
reference
keras2caffe,我修改了其中的bug,简化了代码,只转换为inference model。