
Transformer太难 大核卷积太慢 到底怎么办?
Transformer 大核卷积虽好,速度不要太慢,怎么办,怎么办,到底怎么办
★★★ 本文源自AlStudio社区精品项目,【点击此处】查看更多精品内容 >>>
一、项目背景
近年来,随着深度学习技术的发展,计算机视觉领域的发展也日趋迅速。在这一领域,高精度的网络结构成为了一种热门的研究方向。例如,Transformer和大核卷积是目前比较流行的高精度网络结构,其中Transformer的代表是Swin-Transformer,大核卷积的代表是ConvNeXt。
然而,这些高精度网络结构的优点与不足也逐渐显现出来。虽然它们可以在一些基准数据集上取得很好的表现,但是在实际任务中应用时,却往往面临速度和精度的平衡问题。比如,ConvNeXt网络在Flops与ResNet相当的情况下,可以取得比ResNet更高的精度,但是它的运行速度也比ResNet慢得多。这些问题使得高精度网络结构在实际应用中并不划算,需要进一步优化。
而对于上述存在的Transformer网络设计复杂,运行速度低且需要更多的内存和计算资源问题,大 kernel depth-wise 卷积网络虽然精度提升显著,但是不能很好的利用部署设备资源的问题,以及传统的框架对于大 kernel depth-wise 卷积优化不足的问题。以ConvNeXt网络为基准,我们逐渐将网络进行一系列的探索。我们的探索以一个关键问题为导向:如何在目前现有的传统深度学习框架上使得大 kernel depth-wise 卷积既能在网络中提升精准度,同时还能保证网络的运行速度不熟较大影响。我们希望的是在关注模型精准度以及参数量和计算复杂度的同时,请不要忘记运行速度也是其评判标准的一部分。
(补充) Transformer与ConvNeXt介绍
- Transformer链接:https://arxiv.org/abs/2103.14030
ViT是2020年Google团队提出的将Transformer应用在图像分类的模型,虽然不是第一篇将transformer应用在视觉任务的论文,但是因为其模型“简单”且效果好,可扩展性强(scalable,模型越大效果越好),成为了transformer在CV领域应用的里程碑著作,也引爆了后续相关研究。Swin Transformer是一种Hierarchical Vision Transformer using Shifted Windows,它基于了ViT模型的思想,创新性的引入了滑动窗口机制和层次化特征映射。Swin Transformer将Vision Transformer中固定大小的采样块按照层次分成不同大小的块(Windows),每一个块之间的信息并不共通、独立运算从而大大提高了计算效率。同时,Swin Transformer还引入了分辨率逐渐降低的过程,分别是4倍,8倍,16倍下采样,而VIT一直保持16倍下采样,因为transformer本身是一个长度序列不变的变换。
- ConvNeXt链接:https://arxiv.org/abs/2201.03545
ConvNeXt是一种基于ResNet50/200网络的改进,它将Transformer网络的一些先进思想对现有的经典ResNet50/200网络做一些调整改进,将Transformer网络的最新的部分思想和技术引入到CNN网络现有的模块中从而结合这两种网络的优势,提高CNN网络的性能表现。ConvNeXt网络并没有在整体的网络框架和搭建思路上做重大的创新,它仅仅是依照Transformer网络的一些先进思想对现有的经典ResNet50/200网络做一些调整改进。
二、大核卷积轻量化之路
ConvNeXt为了降低大核卷积对模型大小的影响引入深度可分离卷积,此举降低了ConvNeXt的模型大小,一定程度上提升了网络的速度,但是由于一些限制,深度可分离卷积在GPU上的速度并不快,这就导致ConvNeXt的网络的模型大小降到了与ResNet相同的程度,但是ConvNeXt在速度上与ResNet较大距离,从而导致了模型大小与模型运行速度的脱钩。本节中我们将展示ConvNeXt的轻量化之路的整个过程,最终相比较ConvNeXt-tiny我们在保证较高精度的同时速度相比较ConvNeXt提升一倍。我们的起点为ConvNeXt-tiny,我们研究了一系列设计决策,总结为4个部分
- CSP结构化
- 宏观设计
- Block改变
- 注意力引入。
2.1 CSP结构化
Cross Stage Partial Network (CSPNet) 是一种用于图像分类任务的深度神经网络架构,CSPNet的核心思想是将输入的特征图分为两个部分,分别进行不同的处理,然后再将它们合并在一起。这种方法可以大大减少神经网络的参数数量,提高模型的效率。同时CSPNet网络与其他网络结合,提高其他网络在下游任务中的表现,比如YOLOv4以后,主干网络就常常是由CSPNet网络与其他网络相结合构成。
因此我们决定将ConvNeXt与CSPNet网络相结合,构建了一个新的网络结构——CSPConvNeXt。以期望进一步提高其运行速度。具体地讲我们将ConvNeXt在不同特征尺度的Blocks外层嵌套了CSP模块,此外由于CSP网络拥有不同变种,例如原版的CSPNet网络以及在YOLOv4中使用的CSPNet结构按照特征图输出通道数的一半进行划分,其中一半进行卷积运算,再将两部分进行concat操作使得通道数变为ch_out;而PPYOLO-E中的CSPResNet则是引入了ch_mid思想先将特征图的通道数由ch_mid=ch_in变成(ch_in+ch_out)//2,再将特征图按照ch_mid的一半进行划分,其中一半进行卷积运算,再将两部分进行concat操作,然后再将通道数从ch_mid变为ch_out。我们将两种CSP结构都在Imagenet上进行实验,最终方案一的 Imagenet val top1%为78.5 Flops为1.5GFlops,方案二的Imagent val top1%为77.8,Flops为1.1GFlops。最终在平衡Flops,精度后我们选择使用方案二作为CSP结构化的最终方案,相关原因我们会在下节进行叙述。
最终结果如下:
计算量 | 参数量 | 精度 | 预测速度 | |
---|---|---|---|---|
方案一 | 1.4G | 10.1M | 78.5% | |
方案二 | 0.9G | 6.1M | 77.8% |
方案一: YOLOv4类型
援引自https://blog.csdn.net/xiahan_qian/article/details/124122458
方案二: PPYOLOE类型
2.2 宏观设计
在本节中我们对CSPConvNeXt网络的宏观架构进行改变。ConvNeXt参照Swin Transformer的架构进行设计将ConvNeXt-tiny每个stage的channels数与blocks数进行改变,同时将Stem变成了Patchify,同时参照Swin Transformer的思想将空间下采样层(the spatial downsampling)层交给一个单独的卷积核2x2,步长为2的卷积层进行下采样。
改变stage结构
ConvNeXt参照Swin Transformer的架构进行设计将ConvNeXt-tiny每个stage的channels数与blocks数变为[96,192,384,768]与[3,3,9,3]。但是其他CSPConvNet网络如CSPResNet以及CSPDarkNet每个stage的channels数都为[128,256,512,1024],同时由于ConvNeXt的stem层是由一个卷积核大小4x4步长为4的卷积,与三个卷积核大小4x4步长为2的卷积组成,但是常规的ConvNet(卷积网络)常常都是由5个步长为2的卷积核作为下采样层。最终我们选择将CSPConvNeXt的下采样层为5个卷积核大小为2x2,步长为2的卷积,五个下采样层更加符合CSPNet的结构,卷积核大小为2x2,步长为2也保留了Patchify(补丁化)的设计;同时将四个Stage层的ch_in设置为[64,128,256,512],ch_out设置为[128,256,512,1024]。
我们将方案二的stage进行改变,将下采样层从4个改变为5个,同时将四个Stage层的ch_in从[96,96,193,384]设置为[64,128,256,512],ch_out从[96,192,384,768]设置为[128,256,512,1024],得到方案二-a ,方案二-a相比较方案二计算量和参数量提升不到一倍,精度提升1.3%。方案二与方案一相比计算量相差0.5GFlops精度相差0.7%,方案二-a与方案一计算量相差0.2GFlops精度相差0.6%。得到方案二-a(option two a)与方案一(option one)的Flops提升比accuracy提升 是 方案一(option one)与方案二(option two)的Flops提升比accuracy提升 的4.3倍。因此最终选择方案二,这是因为方案二比方案一的提升潜力更大,当方案二的计算量与参数量提升到与方案一相似的程度,方案二-a的精度相比方案一精度提升0.6%。
计算量 | 参数量 | 精度 | 预测速度 | |
---|---|---|---|---|
方案二 | 0.9G | 6.1M | 77.8% | |
方案二-a | 1.5G | 10.8M | 79.1% | |
方案一 | 1.4G | 10.1M | 78.5% |
改变Stem结构
方案二(option two) 使用的是一个卷积核大小4x4步长为4的卷积核作为Stem后跟一个卷积核大小3x3步长为一的卷积核将stage one的ch_in变为ch_mid;而方案二-a(Option two a)则是使用的是一个卷积核大小2x2步长为2的卷积核作为Stem后再跟一个卷积核大小2x3步长为2的卷积核将stage one的 stem 同时将ch_in变为ch_mid。方案二只改变一次特征图的空间分布改变两次特征图的通道分布,而方案二 a改变两次特征图的空间分布改变两次特征图的通道分布。当我们在方案二 a中实验了方案二的stem结构,后者相比较前者在Flops上升0.1GFlops的情况下精度仅提升0.1%。最终参考 百度版本的ResNet50_vd 其将第一个stem使用三个3x3的卷积进行下采样,我们将CSPConvNeXt的第一个stem也做出相似改变,与之不同的是将第一个卷积核的大小从3x3替换为2x2。使得模型方案二-b(option two-b)Flops从1.6GFlops上升到2.0GFlops,accuracy从79.1%提升到79.6%。这样的操作可以更好的在特征图进行空间特征改变后再提取特征时更加充分,通道数改变的放缓也使得更多的特征得以保存下来。第一个卷积核大小2x2 步长为2的卷积使得各个点的特征互不相关,而后面的两个3x3的卷积使得patchiy得以交流。
方案二只改变一次特征图的空间分布改变两次特征图的通道分布,而方案二 a改变两次特征图的空间分布改变两次特征图的通道分布。当我们在方案二 a中实验了方案二的stem结构,命名为方案方案二-a-1(option two a 1)。当我们平衡相关计算量与精度后还是决定任然选择方案二-a的stem结构。我们参考百度版本ConvNeXt-vd进行改变。CSPConvNeXt的第一个stem也做出相似改变,与之不同的是将第一个卷积核的大小从3x3替换为2x2。使得模型方案二-b(option two-b)Flops从1.6GFlops上升到2.0GFlops,accuracy从79.1%提升到79.6%。
计算量 | 参数量 | 精度 | 预测速度 | |
---|---|---|---|---|
方案二-a | 1.5G | 10.8M | 79.1% | |
方案二-a-1 | 1.6G | 11.8M | 79.2% | |
方案二-b | 1.9G | 10.9M | 79.6% |
ConvLNLayer变换为ConvBNLayer
在ConvNeXt原论文中其讲ReLU更换为GELU,将BatchNormalization更换为LayerNormalization。同时减少了激活函数与归一化层的数量,Stem层为了训练稳定采用Conv+LN的结构,而Block层也仅仅只有一个归一化层(LN)和一个激活函数(GELU)。而当我们引入CSP结构后如果仍然采用非Block层仅仅只有 Conv+LN的结构,就会出现 几个Conv +LN层的叠加,而没有激活函数的情况,这样会使得网络的非线性能力大幅降低。同时Layer Normalization在2D的情况下的运行速度相较于Layer Normalization 1D大幅下降,而在我们的实验中将Layer Normalization替换回BatchNormalization也并未对网络运行精度造成影响,反而运行速度有巨大提升,同时对于非Block层我们也恢复成了Conv + BN + GELU的紧凑型ConvBNLayer,使得模型方案二-c(option two-c)在Flops没有大变动的情况下,accuracy从79.6%提升到79.8%且运行速度得到一定提升。
计算量 | 参数量 | 精度 | 预测速度 | |
---|---|---|---|---|
方案二-b | 1.9G | 10.9M | 79.6% | |
方案二-c | 1.9G | 10.9M | 79.8% |
2.3 Block改变
ConvNeXt Block有两个版本,第一个版本的结构如下:先进行一个卷积操作,卷积核大小为7x7,通道数为d,进行Depth-wise卷积,之后进行transpose操作,接着进行Layer Normalization,再接一个通道数为d的全连接层,经过GELU激活函数,然后再接一个通道数为4d的全连接层,再加入一个gamma,最后再进行一次transpose操作。第二个版本的结构也是先进行一个卷积操作,卷积核大小为7x7,通道数为d,进行Depth-wise卷积,之后进行Layer Normalization,再接一个通道数为4d的1x1卷积,经过GELU激活函数,然后再接一个通道数为d的1x1卷积,最后再加入一个gamma。ConvNeXt Block使用第一个版本的结构,因为第二个版本的LayerNorm和Gamma会对channel frist进行操作,从而导致时间增长。然而,在模型方案二-c(option two-c),我们将LayerNorm更换为Batchnorm2D,并将gamma的shape从[dim]变为[1],这样的改变使得Block模块的运行速度提升了42%。在模型方案二-d(option two-d)中,Flops没有大变动的情况下,accuracy从79.8%提升到了79.95%,并且相较于方案二-c,运行速度也有大幅提升。
计算量 | 参数量 | 精度 | 预测速度 | |
---|---|---|---|---|
方案二-c | 1.9G | 10.9M | 79.8% | |
方案二-d | 1.9G | 10.9M | 79.95% |
Block 速度比较
运行时间(ms) | |
---|---|
ConvNeXt-Block-a | 2.24 |
ConvNeXt-Block-b | 2.37 |
CSPConvNeXt-Block | 1.57 |
2.4 注意力引入
ConvNeXt的Block使用了两个PointWise卷积,PointWise卷积的作用是由于Depthwise Convolution运算对输入层的每个通道独立进行卷积运算,没有有效的利用不同通道在相同空间位置上的特征信息,因此PointWise卷积将输入的图像在深度方向上进行加权组合,生成新的Feature map。但是一般都是一个DepthWise卷积后跟一个PointWise卷积。而ConvNeXt的Block使用两个PointWise卷积是为了与Swin Transformer对标。我们参考PPYOLO-E和RDMNet的主干网络,他们都采用了CSP结构同时在每个CSPStage层的结尾都施加一些注意力机制,例如PPYOLO-E使用ESE Block施加通道注意力,同时参照SENet论文,可以将SE模块添加到其他网络中,比如可以在ResNet中的每个BottleNeck结尾添加一个SE模块。
而ESE Block(《CenterMask : Real-Time Anchor-Free Instance Segmentation》)作者注意到SE模块有一个缺点:由于维度的减少导致的通道信息损失。为了避免这种大模型的计算负担,se的2个fc层需要减少通道维度。特别的,当第一个fc层使用r减少输入特征通道,将通道数从c变为c/r的时候,第二个fc层又需要扩张减少的通道数到原始的通道c.在这个过程中,通道维度的减少导致了通道信息的损失。因而,effective SE(eSE)仅仅使用一个通道数为c的fc层代替了两个fc层,避免了通道信息DE丢失。
因此我们将PPYOLO-E中只在CSPStage结尾使用的ESE Block替代Block中的第二个PointWise,同时因为ConvNeXt的反瓶颈机构,而ESE Block的输入和输出通道数是相同的,因此我们在ESE BLock中添加一个 1x1卷积层,使得其能够适应反瓶颈的结构设计。最终方案二-e(option two-e)在Flops没有较大变化,但是paramter增加3M的情况下使得accuracy从79.95%到80.34%
计算量 | 参数量 | 精度 | 预测速度 | |
---|---|---|---|---|
方案二-d | 1.9G | 10.9M | 79.95% | |
方案二-e | 1.9G | 14.0M | 80.35% |
最终结果
预测在1080ti进行,部署方式为fastdeploy
计算量 | 精度 | 预测速度 | |
---|---|---|---|
ConvNeXt-t | 4.4G | 82.0% | 6.23ms |
CSPConvNeXt-t | 1.9G | 80.34% | 2.98ms |
ResNet50-vd | 4.4G | 79.12% | 3.01ms |
import paddle
import paddle.fluid as fluid
import paddle.nn.functional as F
import paddle.nn as nn
from paddle import ParamAttr
#from ppcls.utils.save_load import load_dygraph_pretrain, load_dygraph_pretrain_from_url
trunc_normal_ = nn.initializer.TruncatedNormal(std=0.02)
zeros_ = nn.initializer.Constant(value=0.0)
ones_ = nn.initializer.Constant(value=1.0)
MODEL_URLS = {
"ConvNext_tiny":
"https://passl.bj.bcebos.com/models/convnext_tiny_1k_224.pdparams",
"ConvNext_small":
"https://passl.bj.bcebos.com/models/convnext_small_1k_224.pdparams",
}
__all__ = list(MODEL_URLS.keys())
class Identity(nn.Layer):
def __init__(self):
super().__init__()
def forward(self, x):
return x
class DropBlock(nn.Layer):
def __init__(self, block_size, keep_prob, name=None, data_format='NCHW'):
"""
DropBlock layer, see https://arxiv.org/abs/1810.12890
Args:
block_size (int): block size
keep_prob (int): keep probability
name (str): layer name
data_format (str): data format, NCHW or NHWC
"""
super(DropBlock, self).__init__()
self.block_size = block_size
self.keep_prob = keep_prob
self.name = name
self.data_format = data_format
def forward(self, x):
if not self.training or self.keep_prob == 1:
return x
else:
gamma = (1. - self.keep_prob) / (self.block_size**2)
if self.data_format == 'NCHW':
shape = x.shape[2:]
else:
shape = x.shape[1:3]
for s in shape:
gamma *= s / (s - self.block_size + 1)
matrix = paddle.cast(paddle.rand(x.shape) < gamma, x.dtype)
mask_inv = F.max_pool2d(
matrix,
self.block_size,
stride=1,
padding=self.block_size // 2,
data_format=self.data_format)
mask = 1. - mask_inv
y = x * mask * (mask.numel() / mask.sum())
return y
class Block(nn.Layer):
""" ConvNeXt Block. There are two equivalent implementations:
(1) DwConv -> LayerNorm (channels_first) -> 1x1 Conv -> GELU -> 1x1 Conv; all in (N, C, H, W)
(2) DwConv -> Permute to (N, H, W, C); LayerNorm (channels_last) -> Linear -> GELU -> Linear; Permute back
Args:
dim (int): Number of input channels.
drop_path (float): Stochastic depth rate. Default: 0.0
layer_scale_init_value (float): Init value for Layer Scale. Default: 1e-6.
"""
def __init__(self, dim, drop_path=0., layer_scale_init_value=1e-6):
super().__init__()
# self.ese = nn.Conv2D(dim,dim*2,kernel_size=1)
self.dwconv = nn.Conv2D(dim, dim, kernel_size=7, padding=3,
groups=dim) # depthwise conv
self.norm =nn.BatchNorm2D(dim)
self.ese = EffectiveSELayer(dim*4,dim)
self.pwconv1 = nn.Conv2D(
dim, dim*4, 1) # pointwise/1x1 convs, implemented with linear layers
self.act = nn.GELU()
self.pwconv2 =EffectiveSELayer(dim*4, dim, )
self.gamma = paddle.create_parameter(
shape=[1],
dtype='float32',
default_initializer=nn.initializer.Constant(
value=1.0)
) if layer_scale_init_value > 0 else None
self.drop= DropBlock(3, 0.9)
def forward(self, x):
input = x
x = self.dwconv(x)
# x = x.transpose([0, 2, 3, 1]) # (N, C, H, W) -> (N, H, W, C)
# x = self.norm(x)
x = self.norm(x)
x = self.pwconv1(x)
x = self.act(x)
x = self.ese(x)
# x = self.act(x)
# x = self.pwconv2(x)
# if self.gamma is not None:
# x = self.gamma * x
# x = x.transpose([0, 3, 1, 2]) # (N, H, W, C) -> (N, C, H, W)
x = input + self.gamma * x
return x
class LayerNorm(nn.Layer):
""" LayerNorm that supports two data formats: channels_last (default) or channels_first.
The ordering of the dimensions in the inputs. channels_last corresponds to inputs with
shape (batch_size, height, width, channels) while channels_first corresponds to inputs
with shape (batch_size, channels, height, width).
"""
def __init__(self,
normalized_shape,
epsilon=1e-6,
data_format="channels_last"):
super().__init__()
self.weight = paddle.create_parameter(shape=[normalized_shape],
dtype='float32',
default_initializer=ones_)
self.bias = paddle.create_parameter(shape=[normalized_shape],
dtype='float32',
default_initializer=zeros_)
self.epsilon = epsilon
self.data_format = data_format
if self.data_format not in ["channels_last", "channels_first"]:
raise NotImplementedError
self.normalized_shape = (normalized_shape, )
def forward(self, x):
if self.data_format == "channels_last":
return F.layer_norm(x, self.normalized_shape, self.weight,
self.bias, self.epsilon)
elif self.data_format == "channels_first":
u = x.mean(1, keepdim=True)
s = (x - u).pow(2).mean(1, keepdim=True)
x = (x - u) / paddle.sqrt(s + self.epsilon)
x = self.weight[:, None, None] * x + self.bias[:, None, None]
return x
class L2Decay(fluid.regularizer.L2Decay):
def __init__(self, coeff=0.0):
super(L2Decay, self).__init__(coeff)
class EffectiveSELayer(nn.Layer):
""" Effective Squeeze-Excitation
From `CenterMask : Real-Time Anchor-Free Instance Segmentation` - https://arxiv.org/abs/1911.06667
"""
def __init__(self, channels, out_channels, act='hardsigmoid'):
super(EffectiveSELayer, self).__init__()
self.fc = nn.Conv2D(channels, out_channels, kernel_size=1, padding=0)
if out_channels!=channels:
self.fc2 = nn.Conv2D(channels, out_channels, kernel_size=1, padding=0)
else:
self.fc2 = Identity()
self.act = nn.Hardsigmoid()
def forward(self, x):
x_se = x.mean((2, 3), keepdim=True)
x_se = self.fc(x_se)
return self.fc2(x) * self.act(x_se)
class ConvBNLayer(nn.Layer):
def __init__(self,
ch_in,
ch_out,
filter_size=3,
stride=1,
groups=1,
padding=0,
act=None):
super(ConvBNLayer, self).__init__()
self.conv = nn.Conv2D(
in_channels=ch_in,
out_channels=ch_out,
kernel_size=filter_size,
stride=stride,
padding=padding,
groups=groups)
self.bn = nn.BatchNorm2D(
ch_out,
weight_attr=ParamAttr(regularizer=L2Decay(0.0)),
bias_attr=ParamAttr(regularizer=L2Decay(0.0)))
self.act = nn.GELU()
def forward(self, x):
x = self.conv(x)
x = self.bn(x)
x = self.act(x)
return x
class CSPStage(nn.Layer):
def __init__(self,
block_fn,
ch_in,
ch_out,
n,
stride,
p_rates,
layer_scale_init_value=1e-6,
act=nn.GELU,
attn='eca'):
super().__init__()
ch_mid = (ch_in+ch_out)//2
if stride == 2:
self.down = nn.Sequential(ConvBNLayer(ch_in, ch_mid , 2, stride=2, act=act))
else:
self.down = nn.Sequential(
nn.Conv2D(ch_in, ch_mid, kernel_size=3, stride=1,padding=1),
LayerNorm(ch_mid, epsilon=1e-6, data_format="channels_first"),
nn.GELU()
)
self.conv1 = ConvBNLayer(ch_mid, ch_mid // 2, 1, act=act)
self.conv2 = ConvBNLayer(ch_mid, ch_mid // 2, 1, act=act)
self.blocks = nn.Sequential(*[
block_fn(
ch_mid // 2, drop_path=p_rates[i],layer_scale_init_value=layer_scale_init_value)
for i in range(n)
])
if attn:
self.attn = EffectiveSELayer(ch_mid,ch_mid, act='hardsigmoid')
else:
self.attn = None
self.conv3 = ConvBNLayer(ch_mid, ch_out, 1, act=act)
def forward(self, x):
if self.down is not None:
x = self.down(x)
y1 = self.conv1(x)
y2 = self.blocks(self.conv2(x))
y = paddle.concat([y1, y2], axis=1)
if self.attn is not None:
y = self.attn(y)
y = self.conv3(y)
return y
class CSPConvNext(nn.Layer):
def __init__(
self,
in_chans=3,
depths=[3, 3, 9, 3],
dims=[64,128,256,512,1024],
drop_path_rate=0.,
layer_scale_init_value=1e-6,
stride=[2,2,2,2],
return_idx=[1,2,3],
depth_mult = 1.0,
width_mult = 1.0,
):
super().__init__()
depths = [int(i*depth_mult) for i in depths]
dims = [int(i*width_mult) for i in dims]
act = nn.GELU()
self.Down_Conv = nn.Sequential(
('conv1', ConvBNLayer(
3, dims[0]//2 , 2, stride=2, act=act)),
('conv2', ConvBNLayer(
dims[0]//2, dims[0]//2 , 3, stride=1,padding=1, act=act)),
('conv3', ConvBNLayer(
dims[0]//2, dims[0] , 3, stride=1,padding=1, act=act)),
)
dp_rates = [
x.item() for x in paddle.linspace(0, drop_path_rate, sum(depths))
]
n = len(depths)
self.stages = nn.Sequential(*[(str(i), CSPStage(
Block, dims[i], dims[i + 1], depths[i], stride[i], dp_rates[sum(depths[:i]) : sum(depths[:i+1])],act=nn.GELU))
for i in range(n)])
self.norm = nn.BatchNorm(dims[-1])
self.apply(self._init_weights)
def _init_weights(self, m):
if isinstance(m, (nn.Conv2D, nn.Linear)):
try:
trunc_normal_(m.weight)
zeros_(m.bias)
except:
print(m)
def forward(self, inputs):
x = inputs
x = self.Down_Conv(x)
outs = []
for idx, stage in enumerate(self.stages):
x = stage(x)
# if idx in self.return_idx:
# outs.append(x)
return self.norm(x.mean([-2, -1]))
class Model(paddle.nn.Layer):
def __init__(self, class_num=1000,):
super().__init__()
self.backbone = CSPConvNext()
self.head = ClasHead(with_avg_pool=False, in_channels=1024, num_classes=class_num)
def forward(self, x):
x = self.backbone(x)
x = self.head(x)
return x
def CSPConvNext_tiny(pretrained=False, use_ssld=False, **kwargs):
model = Model( **kwargs)
_load_pretrained(
pretrained, model, MODEL_URLS["ConvNext_tiny"], use_ssld=use_ssld)
return model
if __name__=="__main__":
model = CSPConvNext()
# Total Flops: 1189500624 Total Params: 8688640
paddle.flops(model,(1,3,224,224))
<class 'paddle.nn.layer.conv.Conv2D'>'s flops has been counted
<class 'paddle.nn.layer.norm.BatchNorm2D'>'s flops has been counted
Cannot find suitable count function for <class 'paddle.nn.layer.activation.GELU'>. Treat it as zero FLOPs.
Cannot find suitable count function for <class 'paddle.nn.layer.activation.Hardsigmoid'>. Treat it as zero FLOPs.
Cannot find suitable count function for <class '__main__.DropBlock'>. Treat it as zero FLOPs.
Cannot find suitable count function for <class '__main__.Identity'>. Treat it as zero FLOPs.
<class 'paddle.fluid.dygraph.nn.BatchNorm'>'s flops has been counted
Total Flops: 2045730320 Total Params: 146615364661536
三、结尾
这个项目总体而言还有很多地方都是可以进行改善的没比如激活函数并未进行改动,并不是我没进行尝试🤣🤣🤣🤣🤣而是当我把GELU更换为RELU和SILU时效果比GELU都差而且速度也没优势。另外这个项目总体而言本质上还是在ConvNeXt更多的做一些加减法,本质上还是没有改变大核加深度可分离卷积在GPU设备上的运行速度较低的事实。当然MegEngine在底层做优化可以进一步提升大核加深度可分离卷积的速度。总而言之目前的各种架构设计如果在参数量和计算量不太提升的同时,精度却能大幅提高,一般而言都是以牺牲预测速度为前提,不知什么时候能够出一个计算精度又高,速度又快的架构,让CV迎来自己真正的Transformer呢?
此文章为搬运
原项目链接
更多推荐