,笔者向大家介绍了caffemodel文件类型下的参数架构和如何取出tensorflow框架下训练参数。在本期中,笔者将向大家阐述,如何去将tensorflow框架下训练得到的参数转化为caffe框架下的规定格式的参数。首先,我们来捋一捋目前我们手里面已经有了哪些东西:
1. 我们有自己的tensorflow训练程序,也就是说我们知道训练的网络架构。
2. 我们能够得到tensorflow架构训练得到的参数,并且我们知道我们的主要目的是得到一个caffemodel。
那么,请读者朋友们想一想,我们现在还缺少什么东西呢?
首先我们需要一个caffemodel,其次,在模型测试的时候,我们需要一个.prototxt文件,该文件记录了网络前传的逻辑顺序。
写到这里,知其然不如知其所以然,笔者不妨多说两句。各位读者朋友知道,当使用caffe框架训练模型的时候,我们会使用一个prototxt文件,姑且就叫他train.prototxt吧。那么,在测试模型的时候,我们同样使用了一个prototxt文件,姑且将该文件称为test.prototxt。那么,如何将train.prototxt、训练得到的caffemodel文件还有test.prototxt文件关联起来呢?答案是这样的:caffemodel里面包含了绝大部分train.prototxt的内容,就如tensorflow2caffe(1)中所述。为什么要这么做,是因为train.prototxt除了约定了训练网络架构与参数配置,更重要的是规定了键名,这个键名就是layer中的"name"参数,而该键名也会记录在caffemodel中。在我们训练完毕模型并使用test.prototxt结合caffemodel对模型进行测试时,相当于是根据test.prortotxt中的layer的"name"参数去取得键名,然后根据这个键名在caffemodel中取得参数,然后才能进行网络的前向传播。到这里,请读者朋友们明白,test.prototxt是根据键名去caffemodel中取参数的,也就是说,如果提供的键名在caffemodel中找寻不到,那么也就无从取值。这其实和我们使用caffe框架训练模型时需要去finetune成熟模型的部分层的参数,于是我们就将我们的模型中需要finetune的layer的"name"参数改成finetune的caffemodel中对应layer的"name"一样是同一个道理。
言归正传,经过上面一段话的阐述,我们明白了,我们目前还缺少什么东西。
(1) 我们需要一个test.prototxt。
(2) 我们需要将tensorflow训练出来的参数转化成文本,并且写在test.prototxt里面。
网络架构应该按照tensorflow训练程序的网络架构来。也就是说,在写作test.prototxt的时候,需要对tensorflow框架下面的训练网络架构相当熟悉,并且明了tensorflow和caffe下面的框架协议规范。举个栗子,在写tensorflow卷积层时候,有很多读者朋友可能会使用padding = "SAME" 这个参数,可是在caffe,没有那么智能的操作,因此在写test.prototxt框架下面卷积层的定义参数的时候,需要人为地去pad,再比如说,tensorflow下面的卷积实现的时候有时没有在权重中加上bias,只有weight,那么在撰写test.prototxt的时候,就需要在该卷积层convolution_param的大括号中,加上"bias_term: false"的定义,这就需要对网络中数据流向和数据维度有相当程度的了解。除了这一点,还需要读者朋友们注意的是,需要按需去重写某些前传层。比如说,tensorflow下面实现了一个激活函数
[python] view plain copy
1. def lrelu(x, leak=0.2, name = "lrelu"):
2. return tf.maximum(x, leak*x)
需要读者朋友们自己去写作caffe框架下的前传代码(不需反传函数)。并且,对于tensorflow和caffe两个框架,对于某些层的实现机制是不一样的。也就是说,在进行这一步的时候,请大家务必对训练代码和caffe编程了解深刻,这样才能够在caffe框架下实现tensorflow的某些自定义层的逻辑。最后,形成一个完整的test.prototxt。
接下来,笔者详细地介绍一下(2)。
在tensorflow2caffe(2)中,我们已经能够打印出tensorflow下训练得到的权重参数的名字了,也就是说可以得到权重。以卷积层为例,tesorflow框架下卷积层的权重shape是[kernel_height, kernel_width, input_channels, output_channels](相反,反卷积层的权重shape是[kernel_height, kernel_width, input_channels, output_channels])。
那么,caffe下面的参数规格在哪里定义的呢?笔者提醒读者朋友们,在tensorflow2caffe(1)中笔者有贴出读出的caffemodel转化得到的文本文件截图,读者朋友们仔细观察可以发现,caffemodel在每一个layer中,记录参数的blobs大括号尾部,有一个shape属性,里面的dims就记录了caffe框架下的参数格式,在caffe框架中,卷积层权重参数shape是[output_channels, input_channels, kernel_height, kernel_width](相反地,反卷积层权重参数是[input_channels, output_channels, kernel_height, kernel_width])。因此,我们需要将参数的维度加以变换。
到这里的时候,笔者的困惑就来了,举个栗子,某个卷积层输入channel是128,输出channel是256,二维卷积核长宽都是4,没有bias参数,那么,该卷积层的权重参数数量是多少呢?
答案是128×256×4×4 = 524288个,这个时候,用什么函数或者工具能够轻而易举地转化权重参数的维度呢?
笔者最开始在解决这个问题的时候也一筹莫展,甚至使用c语言进行过数组处理,可是事实证明,这样处理的方式是低效的。那么如何取得参数维度转化时的高效率呢?笔者的同事@feiyang想出了解决方案,使用numpy的swapaxes函数,几行代码就可解决问题。
笔者使用的代码如下:
[python] view plain copy
1. #!/usr/bin/python
2.
3. import tensorflow as tf
4. import numpy as np
5.
6. with tf.Session() as sess:
7. '.model.meta')
8. for var in tf.trainable_variables():
9. print var.name
10. './checkpoint_dir/'))
11. all_vars = tf.trainable_variables()
12. for v in all_vars:
13. name = v.name
14. '.prototxt'
15. '/','_')
16. print fname
17. v_4d = np.array(sess.run(v))
18. if v_4d.ndim == 4:
19. #v_4d.shape [ H, W, I, O ]
20. 0, 2) # swap H, I
21. 1, 3) # swap W, O
22. 0, 1) # swap I, O
23. #v_4d.shape [ O, I, H, W ]
24. 'w')
25. vshape = v_4d.shape[:]
26. 0]*v_4d.shape[1]*v_4d.shape[2]*v_4d.shape[3])
27. ' blobs {\n')
28. for vv in v_1d:
29. ' data: %8f' % vv)
30. '\n')
31. ' shape {\n')
32. for s in vshape:
33. ' dim: ' + str(s))#print dims
34. '\n')
35. ' }\n')
36. ' }\n')
37. elif v_4d.ndim == 1 :#do not swap
38. 'w')
39. ' blobs {\n')
40. for vv in v_4d:
41. ' data: %.8f' % vv)
42. '\n')
43. ' shape {\n')
44. ' dim: ' + str(v_4d.shape[0]))#print dims
45. '\n')
46. ' }\n')
47. ' }\n')
48. f.close()
首先,代码的上半部分和tensorflow2caffe(2)中的一样,至于代码的下半部分,就涉及到参数维度的转换了,笔者将每一层对应的权重参数逐一取出来,并相应地新建了.prototxt文件并按照caffemodel下面的参数格式写入了文件中。
值的读者朋友们注意的是,由于笔者模型下面只涉及到四维(卷积层,反卷积层)和一维(batch_norm的乘数(scale),偏置(offset))的参数,因此笔者在写参数文件中只对这两类参数做了处理,读者朋友们在使用的时候可以按需作出处理。
运行一下上述程序。
可以看到,转化为caffe框架格式的各层权重参数文件已经保存在了路径下:
随便点开一个卷积层的参数文件,头尾部分如下两图所示:
读者朋友们可以看到,权重参数的shape是不是变成了caffe框架下的[output_channels, input_channels, kernel_height, kernel_width]了呢?
很简单,直接将我们得到的参数文件写入对应层的大括号内就好辣!ヾ(✿゚▽゚)ノ,如下代码示意:
[python] view plain copy
1. layer {
2. "conv_layer_name"
3. "Convolution"
4. "bottom_blob"
5. "top_blob"
6. param { lr_mult: ... }
7. convolution_param {
8. num_output: output_dims
9. kernel_size: kernel_size
10. pad: padding_size
11. stride: stride
12. bias_term: false
13. }
14. #add params
15. blobs: {
16. data: ...
17. ...
18. shape {
19. dim: ...
20. dim: ...
21. dim: ...
22. dim: ...
23. }
24. }
25. }
按照添加断点拆成若干部分,然后制作一个.sh脚本,就可以将各层参数添加进test.prototxt文件中了哦,姑且称这个添加过权重参数的文件为model.prototxt文件吧。
笔者就是将test.prototxt按照拼接断点拆开:
然后再将上图中的文件和转化得到的.prototxt格式的众多参数文件放在同一目录下,并且使用一个index.txt文件从上到下记录了拼接顺序:
然后使用一个名为ss.sh的脚本文件作拼接:
[python] view plain copy
然后我们运行ss.sh文件:
1. #!/bin/bash
2.
3. cat index.txt |while read line
4. do
5. cat $line >>model.prototxt
6. done
可以看到生成了model.prototxt文件。那么,这个文件有多大呢?
大家可以看到,该文件是相当大的,因此,强烈推荐大家使用脚本对文件进行拼接得到最终的模型文件。
可是,最终的模型文件有什么用呢?最终的模型文件将被转化为.caffemodel的模型文件并在测试程序中被调用。那么,如何将最终的.ptototxt模型文件转化为.caffemodel文件呢?预知后事如何,请看下篇分解!
本篇描述的是最难的部分,也是笔者阐述相当认真的一部分,希望能对各位读者朋友有帮助和有启发,对于博客中的疏漏,万望各位读者朋友在评论区指出,笔者不胜感激!