YOLO-v3合并卷积层与BN层

本文介绍如何在YOLO-v3中合并卷积层与批量归一化(BN)层,通过修改代码实现BN层参数整合到卷积层,以此提高模型推理速度并减少内存占用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

批量归一化-BN层(Batch Normalization)

随机梯度下降法(SGD)对于训练深度网络简单高效,但是它有个毛病,就是需要我们人为的去选择参数,比如学习率、参数初始化、权重衰减系数、Drop out比例等。这些参数的选择对训练结果至关重要,以至于我们很多时间都浪费在这些的调参上。那么使用BN层之后,你可以不需要那么刻意的慢慢调整参数。(详见论文《Batch Normalization_ Accelerating Deep Network Training by Reducing Internal Covariate Shift》 )。

在神经网络训练网络模型时,BN层能够加速网络收敛,并且能够控制过拟合现象的发生,一般放在卷积层之后,激活层之前。BN层将数据归一化后,能够有效解决梯度消失与梯度爆炸问题。虽然BN层在训练时起到了积极作用,然而,在网络Inference时多了一些层的运算,影响了模型的性能,且占用了更多的内存或者显存空间。因此,有必要将 BN 层的参数合并到卷积层,减少计算来提升模型Inference的速度。

BN计算公式:

在yolo-v3中,BN计算过程如下:
BN计算
其中x_out为BN计算结果,x_conv为BN前面的卷积计算结果,其余的参数都保存在.weights文件中。

合并卷积层与BN层:

卷积+BN:
在这里插入图片描述
此时.weights文件中的参数只剩下权值w和偏置b,合并后的参数要写到新的.weights文件中,注意再次运行Inference代码时,记得将.cfg文件中所有的batch_normalize=1改为batch_normalize=0。

部分代码实现

保存合并后的参数,【文件parser.c中增加代码】

//保存convolutional_weights
void save_convolutional_weights_nobn(layer l, FILE *fp)
{
    if(l.binary){
        //save_convolutional_weights_binary(l, fp);
        //return;
    }
#ifdef GPU
    if(gpu_index >= 0){
        pull_convolutional_layer(l);
    }
#endif
    int num = l.nweights;
    //fwrite(l.biases, sizeof(float), l.n, fp);
    /*if (l.batch_normalize){
        fwrite(l.scales, sizeof(float), l.n, fp);
        fwrite(l.rolling_mean, sizeof(float), l.n, fp);
        fwrite(l.rolling_variance, sizeof(float), l.n, fp);
    }*/
    if (l.batch_normalize) {
		for (int j = 0; j < l.n; j++) {
			l.biases[j] = l.biases[j] - l.scales[j] * l.rolling_mean[j] / (sqrt(l.rolling_variance[j]) + 0.000001f);
			for (int k = 0; k < l.size*l.size*l.c; k++) {
				l.weights[j*l.size*l.size*l.c + k] = l.scales[j] * l.weights[j*l.size*l.size*l.c + k] / (sqrt(l.rolling_variance[j]) + 0.000001f);
			}
		}
	}
    fwrite(l.biases, sizeof(float), l.n, fp);

    fwrite(l.weights, sizeof(float), num, fp);
}

Inference时加载更改后的.weights文件:【文件parser.c中增加代码】


void load_convolutional_weights_nobn(layer l, FILE *fp)
{
    if(l.binary){
        //load_convolutional_weights_binary(l, fp);
        //return;
    }
    if(l.numload) l.n = l.numload;
    int num = l.c/l.groups*l.n*l.size*l.size;
    fread(l.biases, sizeof(float), l.n, fp);
	//fprintf(stderr, "Loading l.biases num:%d,size:%d*%d\n", l.n, l.n, sizeof(float));
    
    fread(l.weights, sizeof(float), num, fp);
	//fprintf(stderr, "Loading weights num:%d,size:%d*%d\n", num, num,sizeof(float));
    if(l.c == 3) scal_cpu(num, 1./256, l.weights, 1);
    if (l.flipped) {
        transpose_matrix(l.weights, l.c*l.size*l.size, l.n);
    }
    if (l.binary) binarize_weights(l.weights, l.n, l.c*l.size*l.size, l.weights);
}

增加配置参数代码:在文件detector.c中


//detector.c
//void run_detector(int argc, char **argv)中增加部分代码

void run_detector(int argc, char **argv)
{
	......
    if(0==strcmp(argv[2], "test")) test_detector(datacfg, cfg, weights, filename, thresh, hier_thresh, outfile, fullscreen);
    else if(0==strcmp(argv[2], "train")) train_detector(datacfg, cfg, weights, gpus, ngpus, clear);
    else if(0==strcmp(argv[2], "valid")) validate_detector(datacfg, cfg, weights, outfile);
    else if(0==strcmp(argv[2], "valid2")) validate_detector_flip(datacfg, cfg, weights, outfile);
    else if(0==strcmp(argv[2], "recall")) validate_detector_recall(cfg, weights);
    else if(0==strcmp(argv[2], "demo")) {
        list *options = read_data_cfg(datacfg);
        int classes = option_find_int(options, "classes", 20);
        char *name_list = option_find_str(options, "names", "data/names.list");
        char **names = get_labels(name_list);
        demo(cfg, weights, thresh, cam_index, filename, names, classes, frame_skip, prefix, avg, hier_thresh, width, height, fps, fullscreen);
    }
    //add here
    else if(0==strcmp(argv[2], "combineBN")) test_detector_comBN(datacfg, cfg, weights, filename, weightname,thresh, hier_thresh, outfile, fullscreen);

}
//增加test_detector_comBN函数
void test_detector_comBN(char *datacfg, char *cfgfile, char *weightfile, char *filename,char *weightname ,float thresh, float hier_thresh, char *outfile, int fullscreen)
{
    list *options = read_data_cfg(datacfg);
    char *name_list = option_find_str(options, "names", "data/names.list");
    char **names = get_labels(name_list);

    image **alphabet = load_alphabet();
    network *net = load_network(cfgfile, weightfile, 0);
    // 定点化保存参数
    save_weights_nobn(net, weightname);
}

实验结果

自己训练了一个模型,模型结构如下:
在这里插入图片描述
执行combineBN 命令:

./darknet detector combineBN cfg/2024.data cfg/2024-test.cfg 2024_140000.weights data/1.jpg save.weights

其中.data、.cfg、.weights为自己的参数文件,合并后的权值存储为save.weights。
使用合并后的权值进行Inference:

./darknet detector test cfg/2024.data cfg/2024-test-nobn.cfg save.weights data/1.jpg

结果:
在这里插入图片描述
在这里插入图片描述
实验是在权值和输入参数量化为8bit后进行测试的,平台为笔记本(i7-6700HQ CPU),提升约10.7%。
实验结果:

合并前/ms合并后/ms
1001894

具体代码实现

代码在https://github.com/XiaokangLei/darknet-nobn已经把测试用的yolov3-tiny.weights和yolov3-tiny-nobn.cfg放到代码里面了。
【注意!!】记得将.cfg文件中所有的batch_normalize=1改为batch_normalize=0

git clone https://github.com/XiaokangLei/darknet-nobn.git

在这里插入图片描述

cd darknet-nobn/
make all

在这里插入图片描述

./darknet detector combineBN cfg/coco.data cfg/yolov3-tiny.cfg yolov3-tiny.weights data/dog.jpg save.weights

在这里插入图片描述

./darknet detector test cfg/coco.data cfg/yolov3-tiny-nobn.cfg save.weights data/dog.jpg

在这里插入图片描述

### YOLO 模型中的卷(Conv) #### 卷的作用 在YOLO系列模型中,卷扮演着至关重要的角色。卷操作能够自动提取图像特征,对于目标检测任务来说尤为重要。通过滑动窗口的方式对输入数据进行局部感知域的操作,使得每一都能捕捉到不同尺度下的空间次信息[^1]。 批量归一化(Batch Normalization, BN)被广泛应用于现代CNN架构之中,包括但不限于YOLO家族成员。它不仅加速训练过程而且提高了泛化能力。具体而言,在前向传播阶段,BN会对每一批次的数据做标准化处理;而在反向传播过程中,则会调整相应的权重来最小化损失函数值[^2]。 #### 卷的具体实现 以下是YOLO-v3中关于合并卷BN的部分Python代码示例: ```python import torch.nn as nn class Conv_Bn_Activation(nn.Module): def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0, groups=1, bias=False, momentum=0.1, eps=1e-5, activation=True, leaky_slope=0.1): super(Conv_Bn_Activation, self).__init__() # 定义卷 self.conv = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, stride=stride, padding=padding, groups=groups, bias=bias) # 定义批规范化 self.bn = nn.BatchNorm2d(out_channels, momentum=momentum, eps=eps) if activation: self.act_fn = nn.LeakyReLU(negative_slope=leaky_slope, inplace=True) else: self.act_fn = None def forward(self, x): x = self.conv(x) x = self.bn(x) if self.act_fn is not None: x = self.act_fn(x) return x ``` 这段代码定义了一个`Conv_Bn_Activation`类,该类继承自PyTorch框架内的`nn.Module`基类。其中包含了三个主要组件:标准二维卷(`nn.Conv2d`)、批次正则化(`nn.BatchNorm2d`)以及激活函数(这里选用的是Leaky ReLU)。 当构建更复杂的YOLO版本时,比如YOLOv5,可以通过加载预训练好的`.onnx`文件并利用专门设计用于解析此类格式文档的应用程序来进行可视化分析。这样做的好处是可以直观地观察到各个卷之间的连接关系及其参数配置情况[^3]。 为了进一步扩展或修改现有网络结构,可以在遍历整个骨干网加头部模块的过程中动态添加新的子模块实例。例如下面给出了一段伪代码片段用来说明这一机制的工作原理[^4]: ```python for i, (from_, number_of_repeats, module_type, args) in enumerate(backbone_definition + head_definition): ... layer = create_module(module_type)(*args) model.add_module(f&#39;layer_{i}&#39;, layer) ... ``` 上述循环迭代了由列表组成的元组序列,每个元素代表一或多连续相同的算子描述符。根据给定的信息创建相应类型的对象,并将其注册至父级容器内作为其组成部分之一。
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值