模型融合

有的时候我们手头可能有了若干个已经训练好的模型,这些模型可能是同样的结构,也可能是不同的结构,训练模型的数据可能是同一批,也可能不同。无论是出于要通过ensemble提升性能的目的,还是要设计特殊作用的网络,在用Caffe做工程时,融合都是一个常见的步骤。

比如考虑下面的场景,我们有两个模型,都是基于resnet-101,分别在两拨数据上训练出来的。我们希望把这两个模型的倒数第二层拿出来,接一个fc层然后训练这个fc层进行融合。那么有两个问题需要解决:1)两个模型中的层的名字都是相同的,但是不同模型对应的权重不同;2)如何同时在一个融合好的模型中把两个训练好的权重都读取进来。

Caffe中并没有直接用于融合的官方工具,本文介绍一个简单有效的土办法,用融合模型进行ensemble的例子,一步步实现模型融合。

完整例子

模型定义和脚本:

https://github.com/frombeijingwithlove/dlcv_for_beginners/tree/master/random_bonus/multiple_models_fusion_caffe

预训练模型:

https://github.com/frombeijingwithlove/dlcv_book_pretrained_caffe_models/blob/master/mnist_lenet_odd_iter_30000.caffemodel

https://github.com/frombeijingwithlove/dlcv_book_pretrained_caffe_models/blob/master/mnist_lenet_even_iter_30000.caffemodel

虽然模型只是简单的LeNet-5,但是方法是可以拓展到其他大模型上的。

模型(及数据)准备:直接采用预训练好的模型

本文的例子要融合的是两个不同任务的模型:

对偶数0, 2, 4, 6, 8分类的模型

对奇数1, 3, 5, 7, 9分类的模型

采用的网络都是LeNet-5

直接从上节中提到的本文例子的repo下载预定义的模型和权重。

上一部分第一个链接中已经写好了用来训练的LeNet-5结构和solver,用的是ImageData层,以训练奇数分类的模型为例:

  1. name: "LeNet"
  2. layer {
  3. name: "mnist"
  4. type: "ImageData"
  5. top: "data"
  6. top: "label"
  7. include {
  8. phase: TRAIN
  9. }
  10. transform_param {
  11. mean_value: 128
  12. scale: 0.00390625
  13. }
  14. image_data_param {
  15. source: "train_odd.txt"
  16. is_color: false
  17. batch_size: 25
  18. }
  19. }
  20. layer {
  21. name: "mnist"
  22. type: "ImageData"
  23. top: "data"
  24. top: "label"
  25. include {
  26. phase: TEST
  27. }
  28. transform_param {
  29. mean_value: 128
  30. scale: 0.00390625
  31. }
  32. image_data_param {
  33. source: "val_odd.txt"
  34. is_color: false
  35. batch_size: 20
  36. }
  37. }
  38. layer {
  39. name: "conv1"
  40. type: "Convolution"
  41. bottom: "data"
  42. top: "conv1"
  43. param {
  44. lr_mult: 1
  45. }
  46. param {
  47. lr_mult: 2
  48. }
  49. convolution_param {
  50. num_output: 20
  51. kernel_size: 5
  52. stride: 1
  53. weight_filler {
  54. type: "xavier"
  55. }
  56. bias_filler {
  57. type: "constant"
  58. }
  59. }
  60. }
  61. layer {
  62. name: "pool1"
  63. type: "Pooling"
  64. bottom: "conv1"
  65. top: "pool1"
  66. pooling_param {
  67. pool: MAX
  68. kernel_size: 2
  69. stride: 2
  70. }
  71. }
  72. layer {
  73. name: "conv2"
  74. type: "Convolution"
  75. bottom: "pool1"
  76. top: "conv2"
  77. param {
  78. lr_mult: 1
  79. }
  80. param {
  81. lr_mult: 2
  82. }
  83. convolution_param {
  84. num_output: 50
  85. kernel_size: 5
  86. stride: 1
  87. weight_filler {
  88. type: "xavier"
  89. }
  90. bias_filler {
  91. type: "constant"
  92. }
  93. }
  94. }
  95. layer {
  96. name: "pool2"
  97. type: "Pooling"
  98. bottom: "conv2"
  99. top: "pool2"
  100. pooling_param {
  101. pool: MAX
  102. kernel_size: 2
  103. stride: 2
  104. }
  105. }
  106. layer {
  107. name: "ip1"
  108. type: "InnerProduct"
  109. bottom: "pool2"
  110. top: "ip1"
  111. param {
  112. lr_mult: 1
  113. }
  114. param {
  115. lr_mult: 2
  116. }
  117. inner_product_param {
  118. num_output: 500
  119. weight_filler {
  120. type: "xavier"
  121. }
  122. bias_filler {
  123. type: "constant"
  124. }
  125. }
  126. }
  127. layer {
  128. name: "relu1"
  129. type: "ReLU"
  130. bottom: "ip1"
  131. top: "ip1"
  132. }
  133. layer {
  134. name: "ip2"
  135. type: "InnerProduct"
  136. bottom: "ip1"
  137. top: "ip2"
  138. param {
  139. lr_mult: 1
  140. }
  141. param {
  142. lr_mult: 2
  143. }
  144. inner_product_param {
  145. num_output: 5
  146. weight_filler {
  147. type: "xavier"
  148. }
  149. bias_filler {
  150. type: "constant"
  151. }
  152. }
  153. }
  154. layer {
  155. name: "accuracy"
  156. type: "Accuracy"
  157. bottom: "ip2"
  158. bottom: "label"
  159. top: "accuracy"
  160. include {
  161. phase: TEST
  162. }
  163. }
  164. layer {
  165. name: "loss"
  166. type: "SoftmaxWithLoss"
  167. bottom: "ip2"
  168. bottom: "label"
  169. top: "loss"
  170. }

训练偶数分类的prototxt的唯一区别就是ImageData层中数据的来源不一样。

模型(及数据)准备:Start From Scratch

当然也可以自行训练这两个模型,毕竟只是个用于演示的小例子,很简单。方法如下:

第一步 下载MNIST数据

直接运行download_mnist.sh这个脚本

第二步 转换MNIST数据为图片

运行convert_mnist.py,可以从mnist.pkl.gz中提取所有图片为jpg

  1. import os
  2. import pickle, gzip
  3. from matplotlib import pyplot
  4. # Load the dataset
  5. print('Loading data from mnist.pkl.gz ...')
  6. with gzip.open('mnist.pkl.gz', 'rb') as f:
  7. train_set, valid_set, test_set = pickle.load(f)
  8. imgs_dir = 'mnist'
  9. os.system('mkdir -p {}'.format(imgs_dir))
  10. datasets = {'train': train_set, 'val': valid_set, 'test': test_set}
  11. for dataname, dataset in datasets.items():
  12. print('Converting {} dataset ...'.format(dataname))
  13. data_dir = os.sep.join([imgs_dir, dataname])
  14. os.system('mkdir -p {}'.format(data_dir))
  15. for i, (img, label) in enumerate(zip(*dataset)):
  16. filename = '{:0>6d}_{}.jpg'.format(i, label)
  17. filepath = os.sep.join([data_dir, filename])
  18. img = img.reshape((28, 28))
  19. pyplot.imsave(filepath, img, cmap='gray')
  20. if (i+1) % 10000 == 0:
  21. print('{} images converted!'.format(i+1))

第三步 生成奇数、偶数和全部数据的列表

运行gen_img_list.py,可以分别生成奇数、偶数和全部数据的训练及验证列表:

  1. import os
  2. import sys
  3. mnist_path = 'mnist'
  4. data_sets = ['train', 'val']
  5. for data_set in data_sets:
  6. odd_list = '{}_odd.txt'.format(data_set)
  7. even_list = '{}_even.txt'.format(data_set)
  8. all_list = '{}_all.txt'.format(data_set)
  9. root = os.sep.join([mnist_path, data_set])
  10. filenames = os.listdir(root)
  11. with open(odd_list, 'w') as f_odd, open(even_list, 'w') as f_even, open(all_list, 'w') as f_all:
  12. for filename in filenames:
  13. filepath = os.sep.join([root, filename])
  14. label = int(filename[:filename.rfind('.')].split('_')[1])
  15. line = '{} {}\n'.format(filepath, label)
  16. f_all.write(line)
  17. line = '{} {}\n'.format(filepath, int(label/2))
  18. if label % 2:
  19. f_odd.write(line)
  20. else:
  21. f_even.write(line)

第四步 训练两个不同的模型

就直接训练就行了。Solver的例子如下:

  1. net: "lenet_odd_train_val.prototxt"
  2. test_iter: 253
  3. test_initialization: false
  4. test_interval: 1000
  5. base_lr: 0.01
  6. momentum: 0.9
  7. weight_decay: 0.0005
  8. lr_policy: "step"
  9. gamma: 0.707
  10. stepsize: 1000
  11. display: 200
  12. max_iter: 30000
  13. snapshot: 30000
  14. snapshot_prefix: "mnist_lenet_odd"
  15. solver_mode: GPU

注意到test_iter是个奇怪的253,这是因为MNIST的验证集中奇数样本多一些,一共是5060个,训练随便取个30个epoch,应该是够了。

制作融合后模型的网络定义

前面提到了模型融合的难题之一在于层的名字可能是相同的,解决这个问题非常简单,只要把名字改成不同就可以,加个前缀就行。按照这个思路,我们给奇数分类和偶数分类的模型的每层前分别加上odd/和even/作为前缀,同时我们给每层的学习率置为0,这样融合的时候就可以只训练融合的全连接层就可以了。实现就是用Python自带的正则表达式匹配,然后进行字符串替换,代码就是第一部分第一个链接中的rename_n_freeze_layers.py:

  1. import sys
  2. import re
  3. layer_name_regex = re.compile('name:\s*"(.*?)"')
  4. lr_mult_regex = re.compile('lr_mult:\s*\d+\.*\d*')
  5. input_filepath = sys.argv[1]
  6. output_filepath = sys.argv[2]
  7. prefix = sys.argv[3]
  8. with open(input_filepath, 'r') as fr, open(output_filepath, 'w') as fw:
  9. prototxt = fr.read()
  10. layer_names = set(layer_name_regex.findall(prototxt))
  11. for layer_name in layer_names:
  12. prototxt = prototxt.replace(layer_name, '{}/{}'.format(prefix, layer_name))
  13. lr_mult_statements = set(lr_mult_regex.findall(prototxt))
  14. for lr_mult_statement in lr_mult_statements:
  15. prototxt = prototxt.replace(lr_mult_statement, 'lr_mult: 0')
  16. fw.write(prototxt)

这个方法虽然土,不过有效,另外需要注意的是如果确定不需要动最后一层以外的参数,或者原始的训练prototxt中就没有lr_mult的话,可以考虑用Caffe的propagate_down这个参数。把这个脚本分别对奇数和偶数模型执行,并记住自己设定的前缀even和odd,然后把数据层到ip1层的定义复制并粘贴到一个文件中,然后把ImageData层和融合层的定义也写入到这个文件,注意融合前需要先用Concat层把特征拼接一下:

  1. name: "LeNet"
  2. layer {
  3. name: "mnist"
  4. type: "ImageData"
  5. top: "data"
  6. top: "label"
  7. include {
  8. phase: TRAIN
  9. }
  10. transform_param {
  11. mean_value: 128
  12. scale: 0.00390625
  13. }
  14. image_data_param {
  15. source: "train_all.txt"
  16. is_color: false
  17. batch_size: 50
  18. }
  19. }
  20. layer {
  21. name: "mnist"
  22. type: "ImageData"
  23. top: "data"
  24. top: "label"
  25. include {
  26. phase: TEST
  27. }
  28. transform_param {
  29. mean_value: 128
  30. scale: 0.00390625
  31. }
  32. image_data_param {
  33. source: "val_all.txt"
  34. is_color: false
  35. batch_size: 20
  36. }
  37. }
  38. ...
  39. ### rename_n_freeze_layers.py 生成的网络结构部分 ###
  40. ...
  41. layer {
  42. name: "concat"
  43. bottom: "odd/ip1"
  44. bottom: "even/ip1"
  45. top: "ip1_fused"
  46. type: "Concat"
  47. concat_param {
  48. axis: 1
  49. }
  50. }
  51. layer {
  52. name: "ip2"
  53. type: "InnerProduct"
  54. bottom: "ip1_fused"
  55. top: "ip2"
  56. param {
  57. lr_mult: 1
  58. }
  59. param {
  60. lr_mult: 2
  61. }
  62. inner_product_param {
  63. num_output: 10
  64. weight_filler {
  65. type: "xavier"
  66. }
  67. bias_filler {
  68. type: "constant"
  69. }
  70. }
  71. }
  72. layer {
  73. name: "accuracy"
  74. type: "Accuracy"
  75. bottom: "ip2"
  76. bottom: "label"
  77. top: "accuracy"
  78. include {
  79. phase: TEST
  80. }
  81. }
  82. layer {
  83. name: "loss"
  84. type: "SoftmaxWithLoss"
  85. bottom: "ip2"
  86. bottom: "label"
  87. top: "loss"
  88. }

分别读取每个模型的权重并生成融合模型的权重

这个思路就是用pycaffe进行读取,然后按照层名字的对应关系进行值拷贝,最后再存一下就可以,代码如下:

  1. import sys
  2. sys.path.append('/path/to/caffe/python')
  3. import caffe
  4. fusion_net = caffe.Net('lenet_fusion_train_val.prototxt', caffe.TEST)
  5. model_list = [
  6. ('even', 'lenet_even_train_val.prototxt', 'mnist_lenet_even_iter_30000.caffemodel'),
  7. ('odd', 'lenet_odd_train_val.prototxt', 'mnist_lenet_odd_iter_30000.caffemodel')
  8. ]
  9. for prefix, model_def, model_weight in model_list:
  10. net = caffe.Net(model_def, model_weight, caffe.TEST)
  11. for layer_name, param in net.params.iteritems():
  12. n_params = len(param)
  13. try:
  14. for i in range(n_params):
  15. net.params['{}/{}'.format(prefix, layer_name)][i].data[...] = param[i].data[...]
  16. except Exception as e:
  17. print(e)
  18. fusion_net.save('init_fusion.caffemodel')

训练融合后的模型

这个也没什么好说的了,直接训练即可,本文例子的参考Solver如下:

  1. net: "lenet_fusion_train_val.prototxt"
  2. test_iter: 500
  3. test_initialization: false
  4. test_interval: 1000
  5. base_lr: 0.01
  6. momentum: 0.9
  7. weight_decay: 0.0005
  8. lr_policy: "step"
  9. gamma: 0.707
  10. stepsize: 1000
  11. display: 200
  12. max_iter: 30000
  13. snapshot: 30000
  14. snapshot_prefix: "mnist_lenet_fused"
  15. solver_mode: GPU

在Caffe中实现模型融合的更多相关文章

  1. pycaffe︱caffe中fine-tuning模型三重天(函数详解、框架简述)

    本文主要参考caffe官方文档[<Fine-tuning a Pretrained Network for Style Recognition>](http://nbviewer.jupy ...

  2. 总结一下一般游戏中3D模型各种勾边方法遇到的工程性问题

    以前做过简单的rim light勾边,几何勾边,这次又做了后处理的勾边,工程化的时候,都遇到很多问题,简单总结一下. 首先是火炬之光勾边效果,类似轮廓光的实现,简单的卡通渲染也是通过类似的算法加采样色 ...

  3. Gluon炼丹(Kaggle 120种狗分类,迁移学习加双模型融合)

    这是在kaggle上的一个练习比赛,使用的是ImageNet数据集的子集. 注意,mxnet版本要高于0.12.1b2017112. 下载数据集. train.zip test.zip labels ...

  4. Caffe学习笔记(一):Caffe架构及其模型解析

    Caffe学习笔记(一):Caffe架构及其模型解析 写在前面:关于caffe平台如何快速搭建以及如何在caffe上进行训练与预测,请参见前面的文章<caffe平台快速搭建:caffe+wind ...

  5. caffe 中如何打乱训练数据

    第一: 可以选择在将数据转换成lmdb格式时进行打乱: 设置参数--shuffle=1:(表示打乱训练数据) 默认为0,表示忽略,不打乱. 打乱的目的有两个:防止出现过分有规律的数据,导致过拟合或者不 ...

  6. caffe中的前向传播和反向传播

    caffe中的网络结构是一层连着一层的,在相邻的两层中,可以认为前一层的输出就是后一层的输入,可以等效成如下的模型 可以认为输出top中的每个元素都是输出bottom中所有元素的函数.如果两个神经元之 ...

  7. caffe中LetNet-5卷积神经网络模型文件lenet.prototxt理解

    caffe在 .\examples\mnist文件夹下有一个 lenet.prototxt文件,这个文件定义了一个广义的LetNet-5模型,对这个模型文件逐段分解一下. name: "Le ...

  8. Windows下使用python绘制caffe中.prototxt网络结构数据可视化

    准备工具: 1. 已编译好的pycaffe 2. Anaconda(python2.7) 3. graphviz 4. pydot  1. graphviz安装 graphviz是贝尔实验室开发的一个 ...

  9. caffe中batch norm源码阅读

    1. batch norm 输入batch norm层的数据为[N, C, H, W], 该层计算得到均值为C个,方差为C个,输出数据为[N, C, H, W]. <1> 形象点说,均值的 ...

随机推荐

  1. 一个不错的windows编程网址

    http://www.zklmc.com/ 含有MFC,C#,web开发资料

  2. TI(德州仪器) TMS320C674x逆向分析之一

    一.声明 作者并不懂嵌入式开发,整个逆向流程都是根据自身逆向经验,一步一步摸索出来,有什么错误请批评指正,或者有更好的方法请不吝赐教.个人写作水平有限,文中会尽量把过程写清楚,有问题或是写的不清楚的地 ...

  3. 微信小程序开发入门

    微信小程序 首先说下结构吧,看看小程序到底长什么样子 这是一个微信提供的自己的开发工具,相当于xcode吧,由此也可以看出腾讯的野心并不小啊,左边的就是编辑调试什么的,往右就是一个模拟器,你可以选择i ...

  4. css4激动人心的新特性及浏览器支持度

    CSS3的选择器提供了很多像:nth-child这样有用的选择器,并且得到浏览器支持.CSS的第四代 选择器CSS4选择器),经我们带来了更多有用的选择器. 1.否定伪类:not 否定伪类选择器其实在 ...

  5. VS2012 百度云下载 开发工具

    百度云下载地址:链接: http://pan.baidu.com/s/1qWDIDPi密码: 5nr0 ASP.NET MVC4.0+ WebAPI+EasyUI+KnockOutJS快速开发框架 通 ...

  6. Java第三天

    0.注释: 目的:方便其他人阅读理解我们的代码 三种: // 单行注释,进行解释 /**/ 多行注释 /***/ 多行注释 (/***/注释通常用于方法,函数注释,在调用写好的方法或是函数时可以通过点 ...

  7. 打造“黑客“手机--Kali Nethunter

    从三月份开始,继续更新技术文章.一个月没有更新技术文章了,这一个月有一部分时间是在休息,另一部分时间是在学习汇编和操作系统,沉淀底层和逆向方面的技术. 今年年初,为了玩一下 kali NetHunte ...

  8. 使用grunt完成requirejs的合并压缩和js文件的版本控制

    最近有一个项目使用了 requirejs 来解决前端的模块化,但是随着页面和模块的越来越多,我发现我快要hold不住这些可爱的js文件了,具体表现在每个页面都要设置一堆 requirejs 的配置( ...

  9. Angular控制器

    这里使用的是angular-1.0.1.min.js Angular的前端渲染 <div> <ul> <li ng-repeat="i in [1,2,3]&q ...

  10. DIV+CSS布局命名规范

    一.命名规则说明 1).所有的命名最好都小写2).属性的值一定要用双引号("")括起来,且一定要有值如class="divcss5",id="divc ...