模型优化:BatchNorm合并到卷积中

转自:https://blog.csdn.net/wfei101/article/details/78635557

1:BN合并的必要性

   BN层即batch-norm层,一般是深度学习中用于加速训练速度和一种方法,一般放置在卷积层(conv层)或者全连接层之后,将数据归一化并加速了训练拟合速度。但是BN层虽然在深度学习模型训练时起到了一定的积极作用,但是在预测时因为凭空多了一些层,影响了整体的计算速度并占用了更多内存或者显存空间。所以我们设想如果能将BN层合并到相邻的卷积层或者全连接层之后就好了,于是就有了这篇文章所提到的工作。

TIPS:BN一般是合并到前面的conv中去。

2:BN合并本身的数学原理

BN层一般在神经网络中‘所处的位置如下图所示:

如上图可以看到,BN层的位置一般在conv(or Fc)层的后面,也有一些情况BN在conv(or Fc)层的前面。

我们先来两种情况分别来考虑。

情况1:BN层在conv层之后的情形

 

 

注意点:conv,Bn,Scale,层之间的top和bottom的名字要相同

情况2:BN在前,卷积在后的合并方式

       这种情况下,FC层的合并方式和之前2.1的结果类似,但是bn在前,conv在后的情形,因为conv存在pad的情形,所以无法合并。

 

3:卷积和BN合并的代码实现

目前在网络上看到两种版本:github上自己搜一下就找到了

1:PVA

2:ENet

#!/usr/bin/env python  
import _init_paths  
import numpy as np  
import sys  
import os  
import os.path as osp  
import google.protobuf as pb  
from argparse import ArgumentParser  
import sys  
import caffe  
  
  
def load_and_fill_biases(src_model, src_weights, dst_model, dst_weights):  
    with open(src_model) as f:  
        model = caffe.proto.caffe_pb2.NetParameter()  
        pb.text_format.Merge(f.read(), model)  
  
    for i, layer in enumerate(model.layer):  
        if layer.type == 'Convolution': # or layer.type == 'Scale':  
            # Add bias layer if needed  
            if layer.convolution_param.bias_term == False:  
                layer.convolution_param.bias_term = True  
                layer.convolution_param.bias_filler.type = 'constant'  
                layer.convolution_param.bias_filler.value = 0.0  
  
    with open(dst_model, 'w') as f:  
        f.write(pb.text_format.MessageToString(model))  
  
    caffe.set_mode_cpu()  
    net_src = caffe.Net(src_model, src_weights, caffe.TEST)  
    net_dst = caffe.Net(dst_model, caffe.TEST)  
    for key in net_src.params.keys():  
        for i in range(len(net_src.params[key])):  
            net_dst.params[key][i].data[:] = net_src.params[key][i].data[:]  
  
    if dst_weights is not None:  
        # Store params  
        pass  
  
    return net_dst  
  
  
def merge_conv_and_bn(net, i_conv, i_bn, i_scale):  
    # This is based on Kyeheyon's work  
    assert(i_conv != None)  
    assert(i_bn != None)  
  
    def copy_double(data):  
        return np.array(data, copy=True, dtype=np.double)  
  
    key_conv = net._layer_names[i_conv]  
    key_bn = net._layer_names[i_bn]  
    key_scale = net._layer_names[i_scale] if i_scale else None  
  
    # Copy  
    bn_mean = copy_double(net.params[key_bn][0].data)  
    bn_variance = copy_double(net.params[key_bn][1].data)  
    num_bn_samples = copy_double(net.params[key_bn][2].data)  
  
    # and Invalidate the BN layer  
    net.params[key_bn][0].data[:] = 0  
    net.params[key_bn][1].data[:] = 1  
    net.params[key_bn][2].data[:] = 1  
    if num_bn_samples[0] == 0:  
        num_bn_samples[0] = 1  
  
    if net.params.has_key(key_scale):  
        print 'Combine {:s} + {:s} + {:s}'.format(key_conv, key_bn, key_scale)  
        scale_weight = copy_double(net.params[key_scale][0].data)  
        scale_bias = copy_double(net.params[key_scale][1].data)  
        net.params[key_scale][0].data[:] = 1  
        net.params[key_scale][1].data[:] = 0  
    else:  
        print 'Combine {:s} + {:s}'.format(key_conv, key_bn)  
        scale_weight = 1  
        scale_bias = 0  
  
    weight = copy_double(net.params[key_conv][0].data)  
    bias = copy_double(net.params[key_conv][1].data)  
    alpha = scale_weight / np.sqrt(bn_variance / num_bn_samples[0] + np.finfo(np.double).eps)  
    net.params[key_conv][1].data[:] = bias * alpha + (scale_bias - (bn_mean / num_bn_samples[0]) * alpha)  
    for i in range(len(alpha)):  
        net.params[key_conv][0].data[i] = weight[i] * alpha[i]  
  
def merge_batchnorms_in_net(net):  
    # for each BN  
    for i, layer in enumerate(net.layers):  
        if layer.type != 'BatchNorm':  
            continue  
  
        l_name = net._layer_names[i]  
  
        l_bottom = net.bottom_names[l_name]  
        assert(len(l_bottom) == 1)  
        l_bottom = l_bottom[0]  
        l_top = net.top_names[l_name]  
        assert(len(l_top) == 1)  
        l_top = l_top[0]  
  
        can_be_absorbed = True  
  
        # Search all (bottom) layers  
        for j in xrange(i - 1, -1, -1):  
            tops_of_j = net.top_names[net._layer_names[j]]  
            if l_bottom in tops_of_j:  
                if net.layers[j].type not in ['Convolution', 'InnerProduct']:  
                    can_be_absorbed = False  
                else:  
                    # There must be only one layer  
                    conv_ind = j  
                    break  
  
        if not can_be_absorbed:  
            continue  
  
        # find the following Scale  
        scale_ind = None  
        for j in xrange(i + 1, len(net.layers)):  
            bottoms_of_j = net.bottom_names[net._layer_names[j]]  
            if l_top in bottoms_of_j:  
                if scale_ind:  
                    # Followed by two or more layers  
                    scale_ind = None  
                    break  
  
                if net.layers[j].type in ['Scale']:  
                    scale_ind = j  
  
                    top_of_j = net.top_names[net._layer_names[j]][0]  
                    if top_of_j == bottoms_of_j[0]:  
                        # On-the-fly => Can be merged  
                        break  
  
                else:  
                    # Followed by a layer which is not 'Scale'  
                    scale_ind = None  
                    break  
  
  
        merge_conv_and_bn(net, conv_ind, i, scale_ind)  
  
    return net  
  
  
def process_model(net, src_model, dst_model, func_loop, func_finally):  
    with open(src_model) as f:  
        model = caffe.proto.caffe_pb2.NetParameter()  
        pb.text_format.Merge(f.read(), model)  
  
  
    for i, layer in enumerate(model.layer):  
        map(lambda x: x(layer, net, model, i), func_loop)  
  
    map(lambda x: x(net, model), func_finally)  
  
    with open(dst_model, 'w') as f:  
        f.write(pb.text_format.MessageToString(model))  
  
  
# Functions to remove (redundant) BN and Scale layers  
to_delete_empty = []  
def pick_empty_layers(layer, net, model, i):  
    if layer.type not in ['BatchNorm', 'Scale']:  
        return  
  
    bottom = layer.bottom[0]  
    top = layer.top[0]  
  
    if (bottom != top):  
        # Not supperted yet  
        return  
  
    if layer.type == 'BatchNorm':  
        zero_mean = np.all(net.params[layer.name][0].data == 0)  
        one_var = np.all(net.params[layer.name][1].data == 1)  
        #length_is_1 = (net.params['conv1_1/bn'][2].data == 1) or (net.params[layer.name][2].data == 0)  
        length_is_1 =  (net.params[layer.name][2].data == 1)  
  
        if zero_mean and one_var and length_is_1:  
            print 'Delete layer: {}'.format(layer.name)  
            to_delete_empty.append(layer)  
  
    if layer.type == 'Scale':  
        no_scaling = np.all(net.params[layer.name][0].data == 1)  
        zero_bias = np.all(net.params[layer.name][1].data == 0)  
  
        if no_scaling and zero_bias:  
            print 'Delete layer: {}'.format(layer.name)  
            to_delete_empty.append(layer)  
  
def remove_empty_layers(net, model):  
    map(model.layer.remove, to_delete_empty)  
  
  
# A function to add 'engine: CAFFE' param into 1x1 convolutions  
def set_engine_caffe(layer, net, model, i):  
    if layer.type == 'Convolution':  
        if layer.convolution_param.kernel_size == 1\  
            or (layer.convolution_param.kernel_h == layer.convolution_param.kernel_w == 1):  
            layer.convolution_param.engine = dict(layer.convolution_param.Engine.items())['CAFFE']  
  
  
def main(args):  
    # Set default output file names  
    if args.output_model is None:  
        file_name = osp.splitext(args.model)[0]  
        args.output_model = file_name + '_inference.prototxt'  
    if args.output_weights is None:  
        file_name = osp.splitext(args.weights)[0]  
        args.output_weights = file_name + '_inference.caffemodel'  
  
    net = load_and_fill_biases(args.model, args.weights, args.model + '.temp.pt', None)  
  
    net = merge_batchnorms_in_net(net)  
  
    process_model(net, args.model + '.temp.pt', args.output_model,  
                  [pick_empty_layers, set_engine_caffe],  
                  [remove_empty_layers])  
  
    # Store params  
    net.save(args.output_weights)  
  
  
if __name__ == '__main__':  
    parser = ArgumentParser(  
            description="Generate Batch Normalized model for inference")  
    parser.add_argument('model', help="The net definition prototxt")  
    parser.add_argument('weights', help="The weights caffemodel")  
    parser.add_argument('--output_model')  
    parser.add_argument('--output_weights')  
    args = parser.parse_args()  
    main(args)  

其他参考:

caffe层解读系列——BatchNorm:

https://blog.csdn.net/shuzfan/article/details/52729424

当卷积层后跟batch normalization层时为什么不要偏置b

https://blog.csdn.net/u010698086/article/details/78046671

caffe中的batchNorm层

https://blog.csdn.net/wfei101/article/details/78449680/

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 深度学习是当今计算机科学领域最为热门的研究方向之一,其在图像分类、语言识别、自然语言处理等诸多领域都有广泛的应用。而卷积神经网络(CNN)是深度学习最为核心的模型之一,它能够有效地提取图像的特征并进行分类。本文将介绍如何使用matlab搭建卷积神经网络。 首先,我们需要准备好所需的数据集,这是构建卷积神经网络的必要条件。可以在网上找到一些现成的数据集,如MNIST手写数字数据集,CIFAR-10图像分类数据集等等。 接下来,我们需要创建一个卷积神经网络。在matlab,可以使用Deep Learning Toolbox来创建卷积神经网络模型。我们需要指定网络的层数、每层的神经元个数、激活函数等参数,以及网络的损失函数、优化算法等参数。 然后,我们需要将数据集分成训练集和测试集,并将其加载到matlab的工作空间。可以使用matlab自带的数据集加载函数或者自己编写数据集加载函数来完成这一步骤。 接下来,我们需要对数据进行预处理,以便将其交给卷积神经网络进行训练。预处理包括图像的标准化、图像增强、数据扩充等操作。 最后,我们需要使用trainNetwork函数来训练我们的卷积神经网络,并使用testNetwork函数来测试网络的性能。网络训练完毕后,可以保存模型以备将来使用。 总的来说,用matlab进行卷积神经网络的搭建十分方便,并且matlab还提供了许多实用的工具箱和函数来辅助网络的训练和测试。若想深入研究深度学习,matlab是一个不错的选择。 ### 回答2: 深度学习是现代计算机科学的热门领域,它通过神经网络的层次结构来学习复杂的模式,特别是在图像、语音和自然语言处理上表现突出。Matlab是一种广泛用于数学建模、数据分析和科学计算的软件工具,也被用于深度学习领域。本文将介绍如何使用Matlab从零开始搭建卷积神经网络CNN。 首先,我们需要导入一些深度学习常用的库。例如 MatConvNet 和 VLFeat,这两个库都是由Matlab编写的,方便用户在Matlab实现卷积神经网络。接下来,我们需要构建神经网络的模型,包括卷积层、池化层、全连接层等。这里,我们将使用卷积层、池化层交替的组合来搭建CNN模型,并设置相关的参数,例如数目、步长和大小等。 然后,我们需要准备好训练数据和测试数据,这些数据可以是图像、语音或文本等。我们需要将数据集进行预处理,例如归一化、预处理等,以确保数据数据量相等和格式标准化。接下来,我们需要定义模型的训练方法,例如使用反向传播算法以最小化误差和损失函数,而优化器可以选择常用的SGD、Adam或Adagrad等。 最后,我们需要对我们训练好的模型进行测试,测试过程会给出一些输入样例,然后模型会输出对应的预测结果,我们可以根据这些结果来评估模型的性能和准确度。如果模型的性能不好,我们可以使用更多的层数、更多的节点或更多的数据来改善模型。 总之,使用Matlab搭建卷积神经网络的过程比较复杂,需要对深度学习的知识有一定的了解,并具备一定的程序能力。通过本文的介绍,相信读者能够初步了解到如何搭建卷积神经网络,并在实践逐渐提高自己的能力。 ### 回答3: 在进行深度学习研究时,卷积神经网络(CNN)是一种非常常见的网络结构。使用Matlab可以高效地实现并训练CNN。下面将以一个简单的手写数字识别任务为例,从0开始介绍如何在Matlab搭建和训练卷积神经网络。 首先需要导入手写数字数据集。在Matlab已经内置了一个手写数字数据集,可以使用以下命令导入: ``` digitDatasetPath = fullfile(matlabroot,'toolbox','nnet','nndemos','nndatasets','DigitDataset'); imds = imageDatastore(digitDatasetPath, 'IncludeSubfolders', true, 'LabelSource', 'foldernames'); ``` 接下来,需要设置网络结构和超参数。这里选择一个包含两个卷积层和两个全连接层的CNN,同时设置学习率、迭代轮数等超参数。 ``` layers = [ imageInputLayer([28 28 1]) convolution2dLayer(3,16,'Padding','same') batchNormalizationLayer reluLayer maxPooling2dLayer(2,'Stride',2) convolution2dLayer(3,32,'Padding','same') batchNormalizationLayer reluLayer maxPooling2dLayer(2,'Stride',2) fullyConnectedLayer(256) batchNormalizationLayer reluLayer fullyConnectedLayer(10) softmaxLayer classificationLayer]; options = trainingOptions('adam', ... 'MaxEpochs',20,... 'MiniBatchSize',128, ... 'InitialLearnRate',0.001, ... 'Shuffle','every-epoch', ... 'Verbose',false, ... 'Plots','training-progress'); ``` 然后可以开始训练模型。使用以下命令可以开始训练: ``` net = trainNetwork(imds,layers,options); ``` 最后,可以使用测试集对训练好的模型进行评估,并计算出模型的准确率: ``` YPred = classify(net,augimdsTest); YTest = imdsTest.Labels; accuracy = sum(YPred == YTest)/numel(YTest); ``` 通过这个例子,可以看出使用Matlab搭建和训练卷积神经网络是非常简单的。同时,Matlab提供了许多预训练模型和工具箱,可以方便地进行深度学习研究和应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值