摘要:
要把自己的模型进行移植,之前是后端的移植,最近前端也提了需求,前端一般都是用海思芯片(海思HI3516DV300),只支持caffe,所以为了先测试时间得把tf的模型转成caffemodel。这里是将tf1.x转为caffemode,后续补全darknet转为caffemode
一、转换环节的注意点:
BN层由于tensoflow和caffe有差异
在转化的时候记住caffe的bn+scale层等于tensorflow的bn层次
-
卷积
tensorflow卷积参数的存储方式是:
(kernel_h, kernel_w, input_channel, output_channel)
caffe卷积参数的存储方式是:
(output_channel, input_channel, kernel_h, kernel_w)
卷积的存储权重和偏差的位置:
权重存储的位置为:conv_params[conv][0]
偏差存储的位置为:conv_params[conv][1] -
全连接
tensorflow全连接参数的存储方式是:
(input_dim, output_dim)
caffe全连接参数存储方式是:
(output_dim, input_dim)
全连接的存储权重和偏差的位置:
权重存储的位置为:conv_params[fc][0]
偏差存储的位置为:conv_params[fc][1] -
TF和caffe的卷积和池化操作在针对某些特征图尺寸取整或者填充机制上有所不同;因此,会导致输出的特征值不同;
-
TF的conv和pooling为均是向下取整,而caffe的Conv是向下取整,而pooling为向上取整;
在做tensorflow模型转caffe模型时,遇到了几个坑。其中之一就是caffe的padding方式和tensorflow的padding方式有很大的区别,导致 每一层的输出都无法对齐,让我一度怀疑转换模型的代码是错的。
卷积操作输出的形状计算公式是这样的:
output_shape = (image_shape-fileter_shape+2padding)/stride+1
因为padding前面的系数是2,所以在padding时,一般是对称地补,左/右各padding一列 或者 上下各padding一行。
那么问题来了,如果stride是2,而括号里算出来的值刚好是奇数怎么办?那就再偷偷摸摸补一列padding或者补一行padding。
解决办法:
tensorflow/core/kernels/ops_util.cc
把60行的pad_top 改成 pad_bottom,把61行的pad_bottom改成pad_top
把65行的pad_left 改成 pad_right ,把66行的pad_right 改成 *pad_left
然后重新编译一下,就可以让tensorflow和caffe的padding方式保持一致了。
除了padding方式外,卷积层和fc层的通道顺序也需要注意一下:
卷积层的通道顺序:在caffe里是[N,C,H,W],而tensorflow是[H,W,C,N]
fc层的通道顺序:在caffe 里是[c_in,c_out],而tensorflow是[c_out,c_in] -
TF和caffe在遇到非整除情况下,其进行卷积遍历的位置不一样,当计算需遍历一行一列满足尺寸要求时,TF优先遍历填充方向在右下角的一行一列,而忽略左上的填充,而caffe是优先遍历填充方向在左上角的一行一列,pytorch和caffe是一致
-
使用训练好的tensorflow模型,转换成caffemodel的时候,需要对tensorflow中接口设置和caffe中接口设置都非常了解,才能对其参数进行对应设置,举个简单的例子:在我们使用slim库的时候,卷积层默认relu激活,caffe,并没有默认relu层
二、验证环节的注意点
这里还有一个问题需要注意的是tensorflow的数据格式一般都是(N, H,W, C)的方式,而在caffe中是(N, C, H, W),opencv读入图片的格式为(H, W, C),所以在传入数据时需要使用transpose进行转换。
img = cv2.imread(test_dir) # (H, W, C)
img = preprocess(img)
img = img.transpose((2, 0, 1)) # (C, H, W)