在Caffe中实现模型融合
模型融合
有的时候我们手头可能有了若干个已经训练好的模型,这些模型可能是同样的结构,也可能是不同的结构,训练模型的数据可能是同一批,也可能不同。无论是出于要通过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层,以训练奇数分类的模型为例:
name: "LeNet"
layer {
  name: "mnist"
  type: "ImageData"
  top: "data"
  top: "label"
  include {
    phase: TRAIN
  }
  transform_param {
    mean_value: 128
    scale: 0.00390625
  }
  image_data_param {
    source: "train_odd.txt"
    is_color: false
    batch_size: 25
  }
}
layer {
  name: "mnist"
  type: "ImageData"
  top: "data"
  top: "label"
  include {
    phase: TEST
  }
  transform_param {
    mean_value: 128
    scale: 0.00390625
  }
  image_data_param {
    source: "val_odd.txt"
    is_color: false
    batch_size: 20
  }
}
layer {
  name: "conv1"
  type: "Convolution"
  bottom: "data"
  top: "conv1"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  convolution_param {
    num_output: 20
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "pool1"
  type: "Pooling"
  bottom: "conv1"
  top: "pool1"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}
layer {
  name: "conv2"
  type: "Convolution"
  bottom: "pool1"
  top: "conv2"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  convolution_param {
    num_output: 50
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "pool2"
  type: "Pooling"
  bottom: "conv2"
  top: "pool2"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}
layer {
  name: "ip1"
  type: "InnerProduct"
  bottom: "pool2"
  top: "ip1"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  inner_product_param {
    num_output: 500
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "relu1"
  type: "ReLU"
  bottom: "ip1"
  top: "ip1"
}
layer {
  name: "ip2"
  type: "InnerProduct"
  bottom: "ip1"
  top: "ip2"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  inner_product_param {
    num_output: 5
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "accuracy"
  type: "Accuracy"
  bottom: "ip2"
  bottom: "label"
  top: "accuracy"
  include {
    phase: TEST
  }
}
layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "ip2"
  bottom: "label"
  top: "loss"
}
训练偶数分类的prototxt的唯一区别就是ImageData层中数据的来源不一样。
模型(及数据)准备:Start From Scratch
当然也可以自行训练这两个模型,毕竟只是个用于演示的小例子,很简单。方法如下:
第一步 下载MNIST数据
直接运行download_mnist.sh这个脚本
第二步 转换MNIST数据为图片
运行convert_mnist.py,可以从mnist.pkl.gz中提取所有图片为jpg
import os
import pickle, gzip
from matplotlib import pyplot
# Load the dataset
print('Loading data from mnist.pkl.gz ...')
with gzip.open('mnist.pkl.gz', 'rb') as f:
    train_set, valid_set, test_set = pickle.load(f)
imgs_dir = 'mnist'
os.system('mkdir -p {}'.format(imgs_dir))
datasets = {'train': train_set, 'val': valid_set, 'test': test_set}
for dataname, dataset in datasets.items():
    print('Converting {} dataset ...'.format(dataname))
    data_dir = os.sep.join([imgs_dir, dataname])
    os.system('mkdir -p {}'.format(data_dir))
    for i, (img, label) in enumerate(zip(*dataset)):
        filename = '{:0>6d}_{}.jpg'.format(i, label)
        filepath = os.sep.join([data_dir, filename])
        img = img.reshape((28, 28))
        pyplot.imsave(filepath, img, cmap='gray')
        if (i+1) % 10000 == 0:
            print('{} images converted!'.format(i+1))
第三步 生成奇数、偶数和全部数据的列表
运行gen_img_list.py,可以分别生成奇数、偶数和全部数据的训练及验证列表:
import os
import sys
mnist_path = 'mnist'
data_sets = ['train', 'val']
for data_set in data_sets:
    odd_list = '{}_odd.txt'.format(data_set)
    even_list = '{}_even.txt'.format(data_set)
    all_list = '{}_all.txt'.format(data_set)
    root = os.sep.join([mnist_path, data_set])
    filenames = os.listdir(root)
    with open(odd_list, 'w') as f_odd, open(even_list, 'w') as f_even, open(all_list, 'w') as f_all:
        for filename in filenames:
            filepath = os.sep.join([root, filename])
            label = int(filename[:filename.rfind('.')].split('_')[1])
            line = '{} {}\n'.format(filepath, label)
            f_all.write(line)
            line = '{} {}\n'.format(filepath, int(label/2))
            if label % 2:
                f_odd.write(line)
            else:
                f_even.write(line)
第四步 训练两个不同的模型
就直接训练就行了。Solver的例子如下:
net: "lenet_odd_train_val.prototxt"
test_iter: 253
test_initialization: false
test_interval: 1000
base_lr: 0.01
momentum: 0.9
weight_decay: 0.0005
lr_policy: "step"
gamma: 0.707
stepsize: 1000
display: 200
max_iter: 30000
snapshot: 30000
snapshot_prefix: "mnist_lenet_odd"
solver_mode: GPU
注意到test_iter是个奇怪的253,这是因为MNIST的验证集中奇数样本多一些,一共是5060个,训练随便取个30个epoch,应该是够了。
制作融合后模型的网络定义
前面提到了模型融合的难题之一在于层的名字可能是相同的,解决这个问题非常简单,只要把名字改成不同就可以,加个前缀就行。按照这个思路,我们给奇数分类和偶数分类的模型的每层前分别加上odd/和even/作为前缀,同时我们给每层的学习率置为0,这样融合的时候就可以只训练融合的全连接层就可以了。实现就是用Python自带的正则表达式匹配,然后进行字符串替换,代码就是第一部分第一个链接中的rename_n_freeze_layers.py:
import sys
import re
layer_name_regex = re.compile('name:\s*"(.*?)"')
lr_mult_regex = re.compile('lr_mult:\s*\d+\.*\d*')
input_filepath = sys.argv[1]
output_filepath = sys.argv[2]
prefix = sys.argv[3]
with open(input_filepath, 'r') as fr, open(output_filepath, 'w') as fw:
    prototxt = fr.read()
    layer_names = set(layer_name_regex.findall(prototxt))
    for layer_name in layer_names:
        prototxt = prototxt.replace(layer_name, '{}/{}'.format(prefix, layer_name))
    lr_mult_statements = set(lr_mult_regex.findall(prototxt))
    for lr_mult_statement in lr_mult_statements:
        prototxt = prototxt.replace(lr_mult_statement, 'lr_mult: 0')
    fw.write(prototxt)
这个方法虽然土,不过有效,另外需要注意的是如果确定不需要动最后一层以外的参数,或者原始的训练prototxt中就没有lr_mult的话,可以考虑用Caffe的propagate_down这个参数。把这个脚本分别对奇数和偶数模型执行,并记住自己设定的前缀even和odd,然后把数据层到ip1层的定义复制并粘贴到一个文件中,然后把ImageData层和融合层的定义也写入到这个文件,注意融合前需要先用Concat层把特征拼接一下:
name: "LeNet"
layer {
  name: "mnist"
  type: "ImageData"
  top: "data"
  top: "label"
  include {
    phase: TRAIN
  }
  transform_param {
    mean_value: 128
    scale: 0.00390625
  }
  image_data_param {
    source: "train_all.txt"
    is_color: false
    batch_size: 50
  }
}
layer {
  name: "mnist"
  type: "ImageData"
  top: "data"
  top: "label"
  include {
    phase: TEST
  }
  transform_param {
    mean_value: 128
    scale: 0.00390625
  }
  image_data_param {
    source: "val_all.txt"
    is_color: false
    batch_size: 20
  }
}
...
### rename_n_freeze_layers.py 生成的网络结构部分 ###
...
layer {
  name: "concat"
  bottom: "odd/ip1"
  bottom: "even/ip1"
  top: "ip1_fused"
  type: "Concat"
  concat_param {
    axis: 1
  }
}
layer {
  name: "ip2"
  type: "InnerProduct"
  bottom: "ip1_fused"
  top: "ip2"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  inner_product_param {
    num_output: 10
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "accuracy"
  type: "Accuracy"
  bottom: "ip2"
  bottom: "label"
  top: "accuracy"
  include {
    phase: TEST
  }
}
layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "ip2"
  bottom: "label"
  top: "loss"
}
分别读取每个模型的权重并生成融合模型的权重
这个思路就是用pycaffe进行读取,然后按照层名字的对应关系进行值拷贝,最后再存一下就可以,代码如下:
import sys
sys.path.append('/path/to/caffe/python')
import caffe
fusion_net = caffe.Net('lenet_fusion_train_val.prototxt', caffe.TEST)
model_list = [
    ('even', 'lenet_even_train_val.prototxt', 'mnist_lenet_even_iter_30000.caffemodel'),
    ('odd', 'lenet_odd_train_val.prototxt', 'mnist_lenet_odd_iter_30000.caffemodel')
]
for prefix, model_def, model_weight in model_list:
    net = caffe.Net(model_def, model_weight, caffe.TEST)
    for layer_name, param in net.params.iteritems():
        n_params = len(param)
        try:
            for i in range(n_params):
                net.params['{}/{}'.format(prefix, layer_name)][i].data[...] = param[i].data[...]
        except Exception as e:
            print(e)
fusion_net.save('init_fusion.caffemodel')
训练融合后的模型
这个也没什么好说的了,直接训练即可,本文例子的参考Solver如下:
net: "lenet_fusion_train_val.prototxt"
test_iter: 500
test_initialization: false
test_interval: 1000
base_lr: 0.01
momentum: 0.9
weight_decay: 0.0005
lr_policy: "step"
gamma: 0.707
stepsize: 1000
display: 200
max_iter: 30000
snapshot: 30000
snapshot_prefix: "mnist_lenet_fused"
solver_mode: GPU
												
											在Caffe中实现模型融合的更多相关文章
- pycaffe︱caffe中fine-tuning模型三重天(函数详解、框架简述)
		
本文主要参考caffe官方文档[<Fine-tuning a Pretrained Network for Style Recognition>](http://nbviewer.jupy ...
 - 总结一下一般游戏中3D模型各种勾边方法遇到的工程性问题
		
以前做过简单的rim light勾边,几何勾边,这次又做了后处理的勾边,工程化的时候,都遇到很多问题,简单总结一下. 首先是火炬之光勾边效果,类似轮廓光的实现,简单的卡通渲染也是通过类似的算法加采样色 ...
 - Gluon炼丹(Kaggle 120种狗分类,迁移学习加双模型融合)
		
这是在kaggle上的一个练习比赛,使用的是ImageNet数据集的子集. 注意,mxnet版本要高于0.12.1b2017112. 下载数据集. train.zip test.zip labels ...
 - Caffe学习笔记(一):Caffe架构及其模型解析
		
Caffe学习笔记(一):Caffe架构及其模型解析 写在前面:关于caffe平台如何快速搭建以及如何在caffe上进行训练与预测,请参见前面的文章<caffe平台快速搭建:caffe+wind ...
 - caffe 中如何打乱训练数据
		
第一: 可以选择在将数据转换成lmdb格式时进行打乱: 设置参数--shuffle=1:(表示打乱训练数据) 默认为0,表示忽略,不打乱. 打乱的目的有两个:防止出现过分有规律的数据,导致过拟合或者不 ...
 - caffe中的前向传播和反向传播
		
caffe中的网络结构是一层连着一层的,在相邻的两层中,可以认为前一层的输出就是后一层的输入,可以等效成如下的模型 可以认为输出top中的每个元素都是输出bottom中所有元素的函数.如果两个神经元之 ...
 - caffe中LetNet-5卷积神经网络模型文件lenet.prototxt理解
		
caffe在 .\examples\mnist文件夹下有一个 lenet.prototxt文件,这个文件定义了一个广义的LetNet-5模型,对这个模型文件逐段分解一下. name: "Le ...
 - Windows下使用python绘制caffe中.prototxt网络结构数据可视化
		
准备工具: 1. 已编译好的pycaffe 2. Anaconda(python2.7) 3. graphviz 4. pydot 1. graphviz安装 graphviz是贝尔实验室开发的一个 ...
 - caffe中batch norm源码阅读
		
1. batch norm 输入batch norm层的数据为[N, C, H, W], 该层计算得到均值为C个,方差为C个,输出数据为[N, C, H, W]. <1> 形象点说,均值的 ...
 
随机推荐
- 读书笔记 effective c++ Item 23 宁可使用非成员非友元函数函数也不使用成员函数
			
1. 非成员非友元好还是成员函数好? 想象一个表示web浏览器的类.这样一个类提供了清除下载缓存,清除URL访问历史,从系统中移除所有cookies等接口: class WebBrowser { pu ...
 - AlloyTouch之无限循环select插件
			
写在前面 当滚动的内容很多,比如闹钟里设置秒,一共有60项.让使用者从59ms滚回01ms是一件很痛苦的事情,所以: 在列表项太多的情况下,我们希望能够有个无限循环的滚动.00ms和01ms是无缝链接 ...
 - Java中代理对象的使用小结
			
在某些情况下,一个客户不想或不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到了中介作用,这不仅仅使用代理模式,还可以实现适配器模式.装饰模式等. 代理对象内部含有对真实对象的引用,从而 ...
 - EAS组件编辑和显示的自定义
			
KDFormattedTextField kdtEntrys_returnAmount_TextField = new KDFormattedTextField(); kdtEntrys_return ...
 - github 删除远程仓库项目中的任意文件夹
			
今天上传代码把不需要的push上去了.结果想删除那个不想要的怎么弄都不行.网上大部分都是把那个项目整个暴力删除.那可不行啊那么多都删除.下次上传不是要命啊! 试啊试终于解决了.顺便记录一下也帮助下需要 ...
 - struts2知识点复习
			
一. MVC Model 1:将所有的程序代码,都写到JSP页面中. Model 2:JSP(流程控制.数据显示) + JavaBean 改进的Model2:Servlet(流程控制) + Jsp(数 ...
 - java Pattern和Matcher详解
			
结论:Pattern与Matcher一起合作.Matcher类提供了对正则表达式的分组支持,以及对正则表达式的多次匹配支持. 单独用Pattern只能使用Pattern.matcher(String ...
 - hdoj1421(bfs)
			
bfs 练习题,简单bfs 题意:给一块地图,找出油田的块的数量,这里要考虑油田的八个方向,上下左右(左右)上(左右)下,存在则可以并在一起.@是油田,*是土地,m是行,n是列. 解题思路:用一个二维 ...
 - idea调试SpringMvc, 出现:”通配符的匹配很全面, 但无法找到元素 'mvc:annotation-driven' 的声明“错误的解决方法
			
调试json格式输出,出现以下错误: HTTP Status 500 - Servlet.init() for servlet HelloDispatcher threw exception ty ...
 - MVC学习笔记3 - JsRender
			
许多发展平台减少代码和简化维护,使用模板和 HTML5 和 JavaScript 也不例外. JsRender 是一个 JavaScript 库使您可以一次定义一个样板文件结构,并使用它来动态地生成 ...