热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

RepVGG(ReparameterizationVGG):MakingVGGstyleConvNetsGreatAgain

参考:RepVGG网络简介_太阳花的小绿豆的博客-CSDN博客参考:Repvgg详解及其实现(pytorch)_一方热衷.

参考:RepVGG网络简介_太阳花的小绿豆的博客-CSDN博客

参考:Repvgg详解及其实现(pytorch)_一方热衷.的博客-CSDN博客

论文作者知乎:RepVGG:极简架构,SOTA性能,让VGG式模型再次伟大(CVPR-2021) - 知乎

官方开源代码:https://github.com/DingXiaoH/RepVGG

本文部分内容参考自其他作者博客,如有侵权请联系删除。



        尽管很多复杂的卷积神经网络模型比简单网络获得了更好的性能,但是这些复杂网络也有显著的缺点:


  1. 复杂的多分支网络结构设计(如ResNet的残差模块,Inception网络),导致模型能难实现,降低模型推理性能,增加显卡内存占用
  2. 一些轻量化的操作,如ShuffleNet中使用的通道shuffle,以及MobileNet中使用的深度可分离卷积操作,这些操作虽然可以降低模型的参数量,但是增加了访问内存的次数,并且这些操作不能很好的被一些设备支持(通常3x3卷积被优化和支持的最好)

MobileNetV1《MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications》_胖胖大海的博客-CSDN博客

《MobileNetV2: Inverted Residuals and Linear Bottlenecks》_胖胖大海的博客-CSDN博客_invertedresidual

        单纯基于模型的参数量,浮点计算量FLOPs来衡量模型的处理效率和推理速度是不准确的,比如MobileNet使用了深度可分离卷积,大幅降低了参数量和浮点计算量,但是增加了访问内存的次数,导致模型的推理速度提升并没有达到理想的程度。比如下图中EfficientNet的FLOPs计算量和参数量更小,但是处理速度确并不一定快。

        在学术论文中通常喜欢使用模型的参数量和浮点运算量FLOPs来衡量模型的大小以及处理速度,但是本文作者提出,参数量和FLOPs并不能真正反映出模型真实的推理速度。另外两个影响推理速度的重要因素分别是:


  1. 模型访问内存的次数Memory Access Cost(MAC),Multi-Branch模型每个分支都要访问内存,都要保存特征图,虽然有的分支参数量和计算量并不高(比如1x1卷积分支,Identity分支,分组卷积等),但是访问内存的次数以及占用的内存大小都增加了
  2. 模型的并行化程度,Multi-Branch模型不同branch的速度不同,但是需要等待其他分支,导致算力资源的浪费,并行度不高

RepVGG的优点:


  1. RepVGG的模型在推理阶段,是一个想VGG网络一样扁平化的网络,没有任何分支结构,这样的模型占用更少的显卡内存,访问内存的次数更少,计算并行度更高,所以计算效率就高
  2. RepVGG的模型在推理阶段网络中只包括3x3的卷积操作和ReLU激活操作,3x3的卷积操作执行效率很高的
  3. RepVGG网络模型没有经过特殊的设计,比如NAS搜索等

多分支Multi-branch模型为什么效果好?


  1. 从特征融合的层面理解,不同的分支学习到了不同的表征,融合之后的表征能力更强
  2. 从特征和梯度复用的层面理解,比如ResNeXt和DenseNet,多个分支之间可以进行特征和梯度的复用
  3. 从集成学习的层面理解,比如ResNet里面的short-cut连接,每遇到一个short-cut,模型就可能有两种可能,这样从头到尾模型就有2的N次方种可能,就像是将2的N次方个模型的结果进行综合集成

单分支扁平化的模型为什么快?


  1. 只有一个分支不存在特征复制,占用更少的显卡内存
  2. 由于不存在其他分支访问特征,访问内存的次数更少
  3. 由于不存在其他分支进行并行计算,所以不用等待其他分支处理完
  4. 扁平化的模型算子种类更单一,比如RepVGG里面只有3x3卷积和ReLU,执行效率更高

这里贴上论文作者知乎解答:

        鉴于多分支模型训练性能好,推理性能差,单分支扁平化模型训练性能差,推理性能好的情况,将二者综合,试图构建一种网络模型,在模型训练阶段使用多分支训练获得更好的训练性能,在模型推理阶段将训练好的多分支模型恒等转换为单分支的扁平化模型,推理阶段的网络模型中只有3x3的卷积和ReLU激活这两种操作。这其中的核心问题就是如何把多分支的模型转换为一个单分支的模型?RepVGG里面把这个过程叫做结构重参数化技术。

 

 

1、Conv3x3 + BN --> Conv3x3:

        BatchNorm在推理阶段的计算涉及4组参数,每组参数的数量和特征的维度相同,使用这4组参数计算BatchNorm。对于2D卷积的结果,特征的维度大小就是输出的feature map的通道数。将Conv3x3 + BN融合成为一步Conv3x3,重新设置Conv3x3中的权重和偏置参数。

2、Conv1x1 + BN -> Conv3x3:


  1. 将1x1卷积核补0变成3x3卷积核,为了保证卷积之后的输出特征图大小不变,给原始特征图的四周进行padding,padding的大小为1,把Conv1x1 + BN -> Conv3x3 + BN
  2. 使用第一步Conv3x3 + BN -> Conv3x3方法进行融合计算,得到Conv1x1 + BN -> Conv3x3 + BN -> Conv3x3

3、BN -> Conv3x3:


  1. 由于只有一个BN,没有卷积操作,先构建一个恒等的卷积操作,卷积核的大小为1x1,第n个卷积核的第n个通道权重为1,其余通道权重为0
  2. 然后使用与Conv1x1 + BN -> Conv3x3相同的方法,把1x1的卷积核补0扩展成3x3的卷积核,Conv1x1 + BN -> Conv3x3 + BN
  3. 然后使用第一步Conv3x3 + BN -> Conv3x3方法进行融合计算,得到BN -> Conv1x1 + BN -> Conv3x3 + BN -> Conv3x3

4、多分支Conv3x3融合成一个Conv3x3

        现在三个分支都转换成了Conv3x3操作,并且输出的特征图形状相同,由于卷积操作具有可加性,多分支的卷积结果相加,就等于多分支的卷积权重相加,偏置相加构成一个新的卷积操作,然后对输入特征图做一次卷积,这样就把三次卷积压缩成一次卷积了。

备注:RepVGG是为GPU和专用硬件设计的高效模型,追求高速度、省内存,较少关注参数量和理论计算量。在低算力设备上,可能不如MobileNet和ShuffleNet系列适用。

 

# coding:utf-8
from collections import OrderedDict
import numpy as np
import torch
import torch.nn as nn
def Conv3x3BNToConv3x3(g=2, in_channels=4, out_channels=4, tol=1e-4):
"""
Conv3x3 + BN -> Conv3x3
1、对于2D卷积的结果,特征的维度大小就是输出的feature map的通道数
2、BatchNorm在推理阶段的计算涉及4组参数,每组参数的数量和特征的维度相同,使用这4组参数计算BatchNorm
3、将Conv3x3 + BN融合成为一步Conv3x3,重新设置Conv3x3中的权重和偏置参数
:return:
"""
torch.random.manual_seed(0)
f1 = torch.randn(1, in_channels, 3, 3)
module = nn.Sequential(OrderedDict(
# 原始卷积不使用偏置参数
conv=nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=3, stride=1, padding=1, bias=False, groups=g),
bn=nn.BatchNorm2d(num_features=out_channels)
))
# fuse conv + bn
# 获取原始卷积权重
kernel = module.conv.weight
# 获取BN的均值,nn.Buffer
running_mean = module.bn.running_mean
# 获取BN的方差
running_var = module.bn.running_var
# 获取BN学习的权重参数
gamma = module.bn.weight
# 获取BN学习的偏置参数
beta = module.bn.bias
# BN计算时为了防止除0异常使用的数值稳定参数
eps = module.bn.eps
# 计算BN的标准差
std = (running_var + eps).sqrt()
print("kernel: {}".format(kernel.shape))
print("running_mean: {}".format(running_mean.shape))
print("running_var: {}".format(running_var.shape))
print("gamma: {}".format(gamma.shape))
print("beta: {}".format(beta.shape))
print("eps: {}".format(eps))
print("std: {}".format(std.shape))
print(gamma, beta, std)
# 计算卷积和BN融合之后对原卷积权重的缩放系数
t = (gamma / std).reshape(-1, 1, 1, 1) # [ch] -> [ch, 1, 1, 1]
# 对原始卷积的权重进行缩放
kernel = kernel * t
# 计算卷积和BN融合之后卷积操作的偏置
bias = beta - running_mean * gamma / std
fused_conv = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=3, stride=1, padding=1, bias=True, groups=g)
# 将计算得到的卷积权重和偏置赋给新的卷积操作
fused_conv.load_state_dict(OrderedDict(weight=kernel, bias=bias))
module.eval()
fused_conv.eval()
with torch.no_grad():
out1 = module(f1).detach().cpu().numpy()
out2 = fused_conv(f1).detach().cpu().numpy()
print(out1)
print(out2)
print(np.allclose(out1, out2, rtol=tol, atol=tol))
def Conv1x1BNToConv3x3(g=2, in_channels=128, out_channels=128, tol=1e-4):
"""
Conv1x1 + BN -> Conv3x3 + BN -> Conv3x3
1、将1x1卷积核补0变成3x3卷积核,为了保证卷积之后的输出特征图大小不变,给原始特征图的四周进行padding,
padding的大小为1,把Conv1x1 + BN -> Conv3x3 + BN
2、使用Conv3x3BNToConv3x3方法进行融合计算,把Conv3x3 + BN -> Conv3x3
:return:
"""
torch.random.manual_seed(0)
f1 = torch.randn(1, in_channels, 3, 3)
module = nn.Sequential(OrderedDict(
# 原始卷积不使用偏置参数
conv=nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=1, padding=0, bias=False, groups=g),
bn=nn.BatchNorm2d(num_features=out_channels)
))
# fuse conv + bn
# 获取原始1x1卷积权重
kernel = module.conv.weight
# 获取BN的均值,nn.Buffer
running_mean = module.bn.running_mean
# 获取BN的方差
running_var = module.bn.running_var
# 获取BN学习的权重参数
gamma = module.bn.weight
# 获取BN学习的偏置参数
beta = module.bn.bias
# BN计算时为了防止除0异常使用的数值稳定参数
eps = module.bn.eps
# 计算BN的标准差
std = (running_var + eps).sqrt()
# 初始化全为0的3x3卷积核
# 当使用分组卷积时,每个卷积核的通道数等于输入通道数除以分组数
weight = torch.zeros(out_channels, in_channels // g, 3, 3, dtype=torch.float)
# 将1x1卷积核放在3x3卷积核的中间,相当于对原始1x1卷积补0得到3x3卷积核
weight[:, :, 1:2, 1:2] = kernel.data
print("kernel: {}".format(kernel.data))
print("weight: {}".format(weight))
# 计算卷积和BN融合之后对原卷积权重的缩放系数
t = (gamma / std).reshape(-1, 1, 1, 1) # [ch] -> [ch, 1, 1, 1]
kernel_new = torch.nn.Parameter(weight * t, requires_grad=True)
print("kernel new: {}".format(kernel_new.data))
# 计算卷积和BN融合之后卷积操作的偏置
bias_new = beta - running_mean * gamma / std
fused_conv = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=3, stride=1, padding=1, bias=True, groups=g)
# 将计算得到的卷积权重和偏置赋给新的卷积操作
fused_conv.load_state_dict(OrderedDict(weight=kernel_new, bias=bias_new))
module.eval()
fused_conv.eval()
with torch.no_grad():
out1 = module(f1).detach().cpu().numpy()
out2 = fused_conv(f1).detach().cpu().numpy()
print(out1)
print(out2)
print(np.allclose(out1, out2, rtol=tol, atol=tol))
def BNToConv3x3Group(g=2, in_channels=4, out_channels=4, tol=1e-4):
"""
BN -> Conv1x1 + BN -> Conv3x3 + BN -> Conv3x3
1、由于只有一个BN,没有卷积操作,先构建一个恒等的卷积操作,卷积核的大小为1x1,第n个卷积核的第n个通道权重为1,其余通道权重为0
2、然后使用与Conv1x1BNToConv3x3相同的方法,把1x1的卷积核补0扩展成3x3的卷积核,Conv1x1 + BN -> Conv3x3 + BN
3、然后使用Conv3x3BNToConv3x3进行融合计算,把Conv3x3 + BN -> Conv3x3
:return:
"""
torch.random.manual_seed(0)
f1 = torch.randn(1, in_channels, 3, 3)
bn = nn.BatchNorm2d(num_features=out_channels)
# 获取BN的均值,nn.Buffer
running_mean = bn.running_mean
# 获取BN的方差
running_var = bn.running_var
# 获取BN学习的权重参数
gamma = bn.weight
# 获取BN学习的偏置参数
beta = bn.bias
# BN计算时为了防止除0异常使用的数值稳定参数
eps = bn.eps
# 计算BN的标准差
std = (running_var + eps).sqrt()
# 计算BN -> Conv3x3 + BN之后卷积权重的缩放系数
t = (gamma / std).reshape(-1, 1, 1, 1)
# 计算BN -> Conv3x3 + BN之后卷积的偏置
bias = beta - running_mean * gamma / std
# 设置卷积核,如果第n个卷积核的第n个通道的最中心一个元素权重为1,其余权重均为0
# 当使用分组卷积时,每个卷积核的通道数等于输入通道数除以分组数
# 同时要在分组卷积中保持恒等映射的效果,那么就要求在每个分组中,第n个卷积核的第n个通道的最中心一个元素权重为1,其余权重均为0
weight = torch.zeros(out_channels, in_channels // g, 3, 3, dtype=torch.float)
for i in range(in_channels):
# if g == in_channels:
# j = 0
# elif g == 1:
# j = i
# else:
# j = i % (in_channels // g)
j = i % (in_channels // g)
weight[i, j, 1:2, 1:2] = 1
kernel = torch.nn.Parameter(weight * t, requires_grad=True)
print("kernel: {}".format(kernel.data))
conv = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=3, stride=1, padding=1, bias=True, groups=g)
conv.load_state_dict(OrderedDict(weight=kernel, bias=bias))
conv.eval()
bn.eval()
with torch.no_grad():
out1 = bn(f1).detach().cpu().numpy()
out2 = conv(f1).detach().cpu().numpy()
print(out1)
print(out2)
print(np.allclose(out1, out2, rtol=tol, atol=tol))
def FuseConv3x3(g=2, in_channels=4, out_channels=4, tol=1e-4):
"""
将多个Conv3x3卷积合并为一个Conv3x3卷积
将多个并行的Conv3x3卷积输出结果相加,等同于先将多个Conv3x3卷积的权重相加,偏置相加构成一个新的卷积操作,然后作用于输入特征图
:return:
"""
torch.random.manual_seed(0)
f1 = torch.randn(1, in_channels, 3, 3)
conv1 = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=3, stride=1, padding=1, bias=True, groups=g)
conv2 = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=3, stride=1, padding=1, bias=True, groups=g)
conv3 = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=3, stride=1, padding=1, bias=True, groups=g)
fuse_conv = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=3, stride=1, padding=1, bias=True, groups=g)
kernel = conv1.weight + conv2.weight + conv3.weight
bias = conv1.bias + conv2.bias + conv3.bias
fuse_conv.load_state_dict(OrderedDict(weight=kernel, bias=bias))
conv1.eval()
conv2.eval()
conv3.eval()
fuse_conv.eval()
with torch.no_grad():
out1 = conv1(f1).detach().cpu().numpy()
out2 = conv2(f1).detach().cpu().numpy()
out3 = conv3(f1).detach().cpu().numpy()
fuse_out = fuse_conv(f1).detach().cpu().numpy()
print(out1 + out2 + out3)
print(fuse_out)
print(np.allclose(out1 + out2 + out3, fuse_out, rtol=tol, atol=tol))
if __name__ == '__main__':
# Conv3x3BNToConv3x3(g=128, in_channels=128, out_channels=128, tol=1e-6)
# Conv1x1BNToConv3x3(g=1, in_channels=128, out_channels=128, tol=1e-6)
# BNToConv3x3Group(g=128, in_channels=128, out_channels=128, tol=1e-6)
FuseConv3x3(g=128, in_channels=128, out_channels=128, tol=1e-5)



推荐阅读
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 统一知识图谱学习和建议:更好地理解用户偏好
    本文介绍了一种将知识图谱纳入推荐系统的方法,以提高推荐的准确性和可解释性。与现有方法不同的是,本方法考虑了知识图谱的不完整性,并在知识图谱中传输关系信息,以更好地理解用户的偏好。通过大量实验,验证了本方法在推荐任务和知识图谱完成任务上的优势。 ... [详细]
  • Learning to Paint with Model-based Deep Reinforcement Learning
    本文介绍了一种基于模型的深度强化学习方法,通过结合神经渲染器,教机器像人类画家一样进行绘画。该方法能够生成笔画的坐标点、半径、透明度、颜色值等,以生成类似于给定目标图像的绘画。文章还讨论了该方法面临的挑战,包括绘制纹理丰富的图像等。通过对比实验的结果,作者证明了基于模型的深度强化学习方法相对于基于模型的DDPG和模型无关的DDPG方法的优势。该研究对于深度强化学习在绘画领域的应用具有重要意义。 ... [详细]
  • 【论文】ICLR 2020 九篇满分论文!!!
    点击上方,选择星标或置顶,每天给你送干货!阅读大概需要11分钟跟随小博主,每天进步一丢丢来自:深度学习技术前沿 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 浏览器中的异常检测算法及其在深度学习中的应用
    本文介绍了在浏览器中进行异常检测的算法,包括统计学方法和机器学习方法,并探讨了异常检测在深度学习中的应用。异常检测在金融领域的信用卡欺诈、企业安全领域的非法入侵、IT运维中的设备维护时间点预测等方面具有广泛的应用。通过使用TensorFlow.js进行异常检测,可以实现对单变量和多变量异常的检测。统计学方法通过估计数据的分布概率来计算数据点的异常概率,而机器学习方法则通过训练数据来建立异常检测模型。 ... [详细]
  • 背景应用安全领域,各类攻击长久以来都危害着互联网上的应用,在web应用安全风险中,各类注入、跨站等攻击仍然占据着较前的位置。WAF(Web应用防火墙)正是为防御和阻断这类攻击而存在 ... [详细]
  • 本博文基于《Amalgamationofproteinsequence,structureandtextualinformationforimprovingprote ... [详细]
  • OCR:用字符识别方法将形状翻译成计算机文字的过程Matlab:商业数学软件;CUDA:CUDA™是一种由NVIDIA推 ... [详细]
  • 深度学习中的Vision Transformer (ViT)详解
    本文详细介绍了深度学习中的Vision Transformer (ViT)方法。首先介绍了相关工作和ViT的基本原理,包括图像块嵌入、可学习的嵌入、位置嵌入和Transformer编码器等。接着讨论了ViT的张量维度变化、归纳偏置与混合架构、微调及更高分辨率等方面。最后给出了实验结果和相关代码的链接。本文的研究表明,对于CV任务,直接应用纯Transformer架构于图像块序列是可行的,无需依赖于卷积网络。 ... [详细]
  • {moduleinfo:{card_count:[{count_phone:1,count:1}],search_count:[{count_phone:4 ... [详细]
  • AstridDAO 专访:波卡稳定币黑马 BAI
    加入Pol ... [详细]
author-avatar
杜庆坤66
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有