tensorflow 模型转 caffe 模型的相关细节和部分代码

    tensorflow 和 caffe 都是常见的深度学习框架,有时候前端部署会因为平台的要求只能用其中的某种框架,这个时候则需要进行框架间的转换。本博客会介绍 tensorflow 转 caffe 模型的相关细节和部分相应的代码。

1 有哪些转换方法

    我做了简单的查阅,发现主要是下面三种:

1) 利用 net.params 逐层添加参数

2) prototxt 中逐层添加参数后编译成 caffemodel

    我阅读了大佬的专栏,学习良多(地址:tensorflow2caffe),但我发现第一种方法似乎更加方便,所以这种方法未尝试。

3) 微软的 MMdnn

    没有深入了解,就不介绍了。

2 tensorflow 和 caffe 模型的差异性

2.1 tensorflow 的前向部署

    需要 .meta 文件(得到图结构)、.model文件(得到参数)和 checkpoint 文件(一般和.model 文件在一个目录下,得到最新的那个模型)。

2.2 caffe 前向部署

    需要 prototxt 文件(网络结构)和 caffemodel文件(网络结构和参数),两者通过网络结构的 index 匹配。

2.3 两个框架差异性

    我在转换的时候主要用到卷积和反卷积,其他结构差异性没有比较。差异如下表:

类型 tensorflow caffe
卷积 h, w, in, out out, in ,h, w
反卷积 h, w, out, in  in, out, h, w
输入数据 n, h, w, c n, c ,h ,w
卷积(反卷积)是否包含激活函数

    两个框架中的卷积和反卷积的 in 和 out 是相反的,但是在矩阵变换的时候和卷积的通道设置是一样的。

3 具体变换细节及代码

    我采用 1) 中的方法。一般要求 同时部署 tensorflow 和 caffe 环境。由于我的笔记本只部署了 caffe 环境,tf 在服务器上,所以我会先把 tf 的参数提取出来并保存。

3.1 提取 tf 中的参数

    对于卷积和反卷积,参数均包括权重和偏置。提取后,将其保存为一维矩阵的文本(注意参数矩阵的形状变换),代码如下:

import tensorflow as tf
import numpy as np

with tf.Session() as sess:
    new_saver = tf.train.import_meta_graph('/xxx.model.meta')
    for var in tf.trainable_variables():
        print(var.name)
    new_saver.restore(sess, tf.train.latest_checkpoint('/checkpoints57/'))
    all_vars = tf.trainable_variables()
    for v in all_vars:
        name = v.name
        fname = name + '.prototxt'
        fname = fname.replace('/','_')
        print(fname)
        v_4d = np.array(sess.run(v))
        if v_4d.ndim == 4:
            #v_4d.shape [ H, W, I, O ]		
            v_4d = np.transpose(v_4d, [3,2,0,1]) 
            #v_4d.shape [ O, I, H, W ]
            f = open('./prototxt/' + fname, 'w')
            v_1d = v_4d.reshape(v_4d.shape[0]*v_4d.shape[1]*v_4d.shape[2]*v_4d.shape[3])
            for vv in v_1d:
                f.write('%8f' % vv)
                f.write('\n')
        elif v_4d.ndim == 1 :#do not swap
            f = open('./prototxt/' + '_' + fname, 'w')
            for vv in v_4d:
                f.write('%.8f' % vv)
                f.write('\n')

        f.close()
        

  3.2 构建 caffenet

    caffe 的网络结构可以用 pycaffe 写,也可以自己在网页端构建。我选择在网页端构建:http://ethereon.github.io/netscope/#/editor。打开这个网页,左边可以自己写结构,shift+enter 在右边生成可视化结构,方便排查结构问题。

    可能用到的 caffe 层(注意,caffe 中的命名一定要和 tf 中完全一致,便于参数 index 匹配):

输入数据层:

layer{
  name: "data"
  type: "Input"
  top:  "data"
  input_param {
  shape:{dim:1     # 输出
         dim:6     # 输入
         dim:512   # 输入尺寸 h
         dim:512   # 输入尺寸 w
  }
 }
}

卷积层:

layer{
  name: "enc1_1"  
  type: "Convolution"
  bottom: "data"
  top: "enc1_1"
  convolution_param {
    num_output: 32
    pad: 2
    kernel_size: 5
    stride: 1
  }
}

激活函数层紧紧跟在卷积层和反卷积层后面,这个很容易遗漏!!):

# 你可以用 inplace 结构, 即 bottom 和 top 的名字一致,这样可以省内存,但是我写成不一样的方便中间查看 relu 后的结果。
layer {
  name: "relu_enc1_1"
  type: "ReLU"
  bottom: "enc1_1"
  top: "relu_enc1_1"
}

跳跃连接层(一般为两个矩阵逐像素相加):

layer{
  name: "eltwise_layer1"
  type: "Eltwise"
  bottom: "enc1_1"
  bottom: "enc1_2_conv2"
  top: "eltwise_layer1"
  eltwise_param {
    operation: SUM
  }
}

    如果有分支,caffe 内部会 split 成两个通道:

relu_enc1_1_relu_enc1_1_0_split_1      
enc1_2_conv1  

    tensorflow 模型转 caffe 模型的相关细节和部分代码_第1张图片

    最后构建完 caffe 模型后,将结构代码保存为 prototxt 文件。

3.3 将保存的 tf 参数添加到 prototxt 文件中

    注意权重参数在前,偏置参数在后。

import numpy as np
import caffe
import pandas as pd

# 生成 net
prototxt = './caffe_model.prototxt'
model = './mymodel.caffemodel'
net = caffe.Net(prototxt, caffe.TEST)

# 添加参数
# 这里的 index 是所有网络层的名字的索引文件(txt)
# 这里的 dic 是 index 层对应的参数矩阵形状的字典,pandas 读取的是之前保存的一维数据,需要转换
weight_file = np.array(pd.read_csv(path + '/prototxt5/' + weight, header=None))
weight_file1 = weight_file.reshape(dic[i])
biase_file  = np.array(pd.read_csv(path + '/prototxt5/' + biase,  header=None))
biase_file1 = biase_file.reshape(len(biase_file))
net.params[net_index[i]][0].data[...] = weight_file1
net.params[net_index[i]][1].data[...] = biase_file1

# 保存 caffe 模型
net.save(model)

4 效果验证

import cv2
import numpy

# 读取模型
prototxt = './caffe_model.prototxt'
model = './mymodel.caffemodel'         # 上一步保存的模型
net = caffe.Net(prototxt, caffe.TEST)

# 查看模型结构
for layer, blob in net.blobs.iteritems():
    print(layer + '\t' + str(blob.data.shape))

# 读取测试图片
img = cv2.imread('./test.jpg')
img1 = np.transpose(img, [2, 0, 1])   
# cv2读取的数据为(h, w, c), 转为(c, h, w); (1, c, h, w)输入和(c, h, w)输入一样

# 预处理
......

# 前向传播输出某层
net.blobs['data'].data[0] = img1
out = net.forward('dec1_0')
# 前向最后结果:out = net.forward()
res = out['dec1_0'] 

    对比 tf 模型的时候如果出现最后结果差异非常大,则需要进行逐层排查。tf 打印中间变量有点麻烦,要在前向传播的 session 中设置打印的变量。一般两者的结果差异性不大,像素均值之差在小数点后好几位。

    我没有详细对比过两者框架造成的精度差异原因,有大佬精通欢迎交流!

你可能感兴趣的:(Tensorflow,深度学习,深度学习,tensorflow,caffe,模型转换,python)