本文记录了飞桨复现MobileNetV3分类模型的过程。因飞桨框架及相关工具无现成实现,作者基于PaddleClas套件代码修改,将脚本改写成notebook代码。复现中解决了飞桨比PyTorch多一层卷积、SE模块通道数不对齐、下采样与空洞卷积差异等问题,最终实现前向对齐,为相关学习提供参考。
☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

在我准备复现的时候,发现整个AIStudio中都没有几个MobileNetV3复现项目,大部分都是PaddleX或者PaddleClas等工具和套件的使用。
前期在复现RobustVideoMatting模型的时候,发现其骨干网络可以选ResNet50和MobileNetV3,在实现了resNet50骨干后,本想就这么算了,但是总感觉不完美,于是投入到复现MobileNetV3的工作中。
本想省事,拿现成的代码,但是整个AIStudio中都没有找到现成的代码可以抄(划掉,应该是可以参考),于是尝试了如下几种方案和思路:
对了,整个复现过程本着实用原则,是至上而下进行的,哪里没有跟Pytorch对齐就改哪里。步骤和方法不同于科班式的自下而上方式。
道路是曲折的,未来是光明的。复现过程中间有几个小坑,最终成功复现出来,心里还是有点小激动呢!
仅以此文,留档学习。
作为一个飞桨人,拿到复现任务的时候,第一反应就是:看看飞桨里有没有现成的!
非常可惜,飞桨官网框架中只有paddle.vision.mobilenet_v2,而没有版本3.
非常可惜+1,X2Paddle中也没有现成的MobilNetV3代码。
非常可惜+2,AIStudio中竟然也没有MobilNetV3的实现项目,大部分是应用项目。
非常幸运飞桨还有庞大的套件库,不出意外的在PaddleClas套件中找到了飞桨版本的MobilNetV3实现,所以我们成功的实现了第一步:
后面一步就是
为了调试方便,我们要
改写的时候需要把相关的PaddleClas库里的代码都放上来,以便可以在不导入PaddleClas库的时候可以用。
此步是个力气活,就用传说中的Ctrl+C Ctrl+V大法即可。为了项目美观,这里就不贴出长长的代码了,所有代码放在mobilenetv3.py文件中,后面演示的时候调用其中的代码。
测试一下代码是否可以正常执行
import paddlefrom mobilenetv3error import MobileNetV3_large_x1_0
model = MobileNetV3_large_x1_0()
a = paddle.ones([1,3,224,224])
out = model(a)print("out shape", out.shape)print ("test MobileNetV3_large_x1_0 ok")执行测试通过!
这就是从上到下复现的好处,整套程序代码在第一步就能顺利执行!
对比飞桨和torch的模型结构,在飞桨和torch执行如下命令,后面就是用眼睛看就行了!
model = MobileNetV3_large_x1_0() a = paddle.ones([1,3,224,224]) out = model(a)for i in model.state_dict(): print(i,model.state_dict()[i].shape)
找不同开始啦!结果一对比就看出不同,第一层一样,第二层就不一样。飞桨比torch多了一层:blocks.0.expand_conv.conv.weight [16, 16, 1, 1],后面几层大约都一样,没问题。
飞桨 blocks.0.expand_conv.conv.weight [16, 16, 1, 1]blocks.0.expand_conv.bn.weight [16]blocks.0.expand_conv.bn.bias [16]blocks.0.expand_conv.bn._mean [16]blocks.0.expand_conv.bn._variance [16]blocks.0.bottleneck_conv.conv.weight [16, 1, 3, 3]blocks.0.bottleneck_conv.bn.weight [16]blocks.0.bottleneck_conv.bn.bias [16]blocks.0.bottleneck_conv.bn._mean [16]blocks.0.bottleneck_conv.bn._variance [16]blocks.0.linear_conv.conv.weight [16, 16, 1, 1]blocks.0.linear_conv.bn.weight [16]blocks.0.linear_conv.bn.bias [16]blocks.0.linear_conv.bn._mean [16]blocks.0.linear_conv.bn._variance [16]torch features.1.block.0.0.weight torch.Size([16, 1, 3, 3]) features.1.block.0.1.weight torch.Size([16]) features.1.block.0.1.bias torch.Size([16]) features.1.block.0.1.running_mean torch.Size([16]) features.1.block.0.1.running_var torch.Size([16]) features.1.block.0.1.num_batches_tracked torch.Size([]) features.1.block.1.0.weight torch.Size([16, 16, 1, 1]) features.1.block.1.1.weight torch.Size([16]) features.1.block.1.1.bias torch.Size([16]) features.1.block.1.1.running_mean torch.Size([16]) features.1.block.1.1.running_var torch.Size([16]) features.1.block.1.1.num_batches_tracked torch.Size([])
既然有不同,就到代码中去找。发现两者的不同,在飞桨class ResidualUnit(TheseusLayer) 中, 第一层x = self.expand_conv(x) 而在torch中,有判断语句
if cnf.expanded_channels != cnf.input_channels:
layers.append(ConvBNActivation(cnf.input_channels, cnf.expanded_channels, kernel_size=1, norm_layer=norm_layer, activation_layer=activation_layer))也就是如果输入和输出通道数一样的话,torch就省略掉一层ConvBNActivation层。而飞桨没有省略。
解决的方法就是像torch一样加上判断,另外由于飞桨和torch代码实现不同,飞桨除了在forward中加上判断,还要在初始化中加上判断
if self.in_c != self.mid_c:
self.expand_conv = ConvBNLayer( in_c=in_c, out_c=mid_c, filter_size=1, stride=1, padding=0, if_act=True, act=act)如果不在初始化中加上判断,那么尽管在前向计算的时候没有该层,但是在看模型结构的时候却能看到,容易造成困扰。 因为这个事情看着很灵异,还专门发帖求助了:灵异事件,MobileNetV3模型修改去掉一层结果去不掉
整体结构对齐后,再进行细节对比
发现mid_se么有对齐。比如飞桨 Block.3.mid_se.conv1.weight 和torch blocks.4.SqueezeExcitation.Conv2d,飞桨是18, torch是24
blocks.3.mid_se.conv1.weight [18, 72, 1, 1]blocks.3.mid_se.conv1.bias [18]blocks.3.mid_se.conv2.weight [72, 18, 1, 1]blocks.3.mid_se.conv2.bias [72](2): SqueezeExcitation(
(fc1): Conv2d(72, 24, kernel_size=(1, 1), stride=(1, 1))
(relu): ReLU(inplace=True)
(fc2): Conv2d(24, 72, kernel_size=(1, 1), stride=(1, 1))原来是在SEModule这里,torch使用了_make_divisible,保证为8 的倍数。前期看torch代码的时候,也看到了,但是没有注意。 飞桨默认这里没有使用_make_divisible,导致conv shape对不齐。
将SEModule模块里的中间通道用_make_divisible处理成8的倍数,飞桨和torch的shape终于对齐了。
class SEModule(TheseusLayer):
def __init__(self, channel, reduction=4):
super().__init__()
squeeze_channels = _make_divisible(channel // reduction)由于在RobustVideoMatting模型中,mobilenetv3模型的最后几层没有使用,所以没有再花精力去对齐它们。
这样就复现完成了!
其实不是。
飞桨的是: MobileNetV3LargeEncoder ok ! 输入数据shape:[2, 2, 3, 224, 224] 输出数据长度:4[2, 2, 16, 112, 112][2, 2, 24, 56, 56][2, 2, 40, 28, 28][2, 2, 960, 7, 7]torch的是: MobileNetV3LargeEncoder ok ! 输入数据shape:torch.Size([2, 2, 3, 224, 224]) 输出数据长度:4torch.Size([2, 2, 16, 112, 112]) torch.Size([2, 2, 24, 56, 56]) torch.Size([2, 2, 40, 28, 28]) torch.Size([2, 2, 960, 14, 14])
仔细对比,发现是飞桨的x = self.blocks12 这一步,也进行了下采样 。 看来要去看mobilenetv3的代码,对比一下shape了。
飞桨的12对应toch的13
torch 13
(13): InvertedResidual(
(block): Sequential(
(0): ConvBNActivation(
(0): Conv2d(112, 672, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(672, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(2): Hardswish()
)
(1): ConvBNActivation(
(0): Conv2d(672, 672, kernel_size=(5, 5), stride=(1, 1), padding=(4, 4), dilation=(2, 2), groups=672, bias=False)
(1): BatchNorm2d(672, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(2): Hardswish()
)
(2): SqueezeExcitation(
(fc1): Conv2d(672, 168, kernel_size=(1, 1), stride=(1, 1))
(relu): ReLU(inplace=True)
(fc2): Conv2d(168, 672, kernel_size=(1, 1), stride=(1, 1))
)
(3): ConvBNActivation(
(0): Conv2d(672, 160, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(160, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(2): Identity()
)
)
)
飞桨12
(12): ResidualUnit(
(expand_conv): ConvBNLayer(
(conv): Conv2D(112, 672, kernel_size=[1, 1], data_format=NCHW)
(bn): BatchNorm()
(act): Hardswish()
)
(bottleneck_conv): ConvBNLayer(
(conv): Conv2D(672, 672, kernel_size=[5, 5], stride=[2, 2], padding=2, groups=672, data_format=NCHW)
(bn): BatchNorm()
(act): Hardswish()
)
(mid_se): SEModule(
(avg_pool): AdaptiveAvgPool2D(output_size=1)
(conv1): Conv2D(672, 168, kernel_size=[1, 1], data_format=NCHW)
(relu): ReLU()
(conv2): Conv2D(168, 672, kernel_size=[1, 1], data_format=NCHW)
(hardsigmoid): Hardsigmoid()
)
(linear_conv): ConvBNLayer(
(conv): Conv2D(672, 160, kernel_size=[1, 1], data_format=NCHW)
(bn): BatchNorm()
)
)也就是飞桨使用了步长为2的卷积,torch使用了步长为1,dilation为2的空洞卷积。
from mobilenetv3error import MobileNetV3LargeEncoderimport paddle
model = MobileNetV3LargeEncoder()
a = paddle.randn([2, 3, 224, 224])# print(a)tmp = model(a)print(f"输入数据shape:{a.shape} 输出数据长度:{len(tmp)} ")for i in tmp : print(i.shape)针对上面找到的不同卷积,仔细阅读MobileNetV3飞桨代码和torch代码,找不同。发现飞桨的代码中,模型配置为:
飞桨: "large": [ # k, exp, c, se, act, s
[3, 16, 16, False, "relu", 1],
[3, 64, 24, False, "relu", 2],
[3, 72, 24, False, "relu", 1],
[5, 72, 40, True, "relu", 2],
[5, 120, 40, True, "relu", 1],
[5, 120, 40, True, "relu", 1],
[3, 240, 80, False, "hardswish", 2],
[3, 200, 80, False, "hardswish", 1],
[3, 184, 80, False, "hardswish", 1],
[3, 184, 80, False, "hardswish", 1],
[3, 480, 112, True, "hardswish", 1],
[3, 672, 112, True, "hardswish", 1],
[5, 672, 160, True, "hardswish", 2],
[5, 960, 160, True, "hardswish", 1],
[5, 960, 160, True, "hardswish", 1],
],
torch的配置为:
inverted_residual_setting = [
bneck_conf(16, 3, 16, 16, False, "RE", 1, 1),
bneck_conf(16, 3, 64, 24, False, "RE", 2, 1), # C1
bneck_conf(24, 3, 72, 24, False, "RE", 1, 1),
bneck_conf(24, 5, 72, 40, True, "RE", 2, 1), # C2
bneck_conf(40, 5, 120, 40, True, "RE", 1, 1),
bneck_conf(40, 5, 120, 40, True, "RE", 1, 1),
bneck_conf(40, 3, 240, 80, False, "HS", 2, 1), # C3
bneck_conf(80, 3, 200, 80, False, "HS", 1, 1),
bneck_conf(80, 3, 184, 80, False, "HS", 1, 1),
bneck_conf(80, 3, 184, 80, False, "HS", 1, 1),
bneck_conf(80, 3, 480, 112, True, "HS", 1, 1),
bneck_conf(112, 3, 672, 112, True, "HS", 1, 1),
bneck_conf(112, 5, 672, 160 // reduce_divider, True, "HS", 2, dilation), # C4
bneck_conf(160 // reduce_divider, 5, 960 // reduce_divider, 160 // reduce_divider, True, "HS", 1, dilation),
bneck_conf(160 // reduce_divider, 5, 960 // reduce_divider, 160 // reduce_divider, True, "HS", 1, dilation),
]torch比飞桨的参数多,在倒数第三层,飞桨的步长step值为2,这样导致在conv层多下采样了一次。仔细思考怎样才能像torch那样处理,感觉还是需要像torch那样多一列信息,于是增加dilation空洞卷积参数
"large": [ # k, exp, c, se, act, s
[3, 16, 16, False, "relu", 1, 1],
[3, 64, 24, False, "relu", 2, 1],
[3, 72, 24, False, "relu", 1, 1],
[5, 72, 40, True, "relu", 2, 1],
[5, 120, 40, True, "relu", 1, 1],
[5, 120, 40, True, "relu", 1, 1],
[3, 240, 80, False, "hardswish", 2, 1],
[3, 200, 80, False, "hardswish", 1, 1],
[3, 184, 80, False, "hardswish", 1, 1],
[3, 184, 80, False, "hardswish", 1, 1],
[3, 480, 112, True, "hardswish", 1, 1],
[3, 672, 112, True, "hardswish", 1, 1],
[5, 672, 160, True, "hardswish", 2, 2,],
[5, 960, 160, True, "hardswish", 1, 2,],
[5, 960, 160, True, "hardswish", 1, 2,],
],并修改代码,添加dilation的识别:
原代码:
self.blocks = nn.Sequential(* [
ResidualUnit( in_c=_make_divisible(self.inplanes * self.scale if i == 0 else self.cfg[i - 1][2] * self.scale), mid_c=_make_divisible(self.scale * exp), out_c=_make_divisible(self.scale * c), filter_size=k, stride=s, use_se=se, act=act) for i, (k, exp, c, se, act, s) in enumerate(self.cfg)
])
修改为:
self.blocks = nn.Sequential(* [
ResidualUnit( in_c=_make_divisible(self.inplanes * self.scale if i == 0 else self.cfg[i - 1][2] * self.scale), mid_c=_make_divisible(self.scale * exp), out_c=_make_divisible(self.scale * c), filter_size=k, stride=s, use_se=se, act=act, dilation=d) for i, (k, exp, c, se, act, s, d) in enumerate(self.cfg)
])修改完成之后,再看看输出shape,终于跟torch对齐了。同时也解决了其它空洞卷积的对齐问题。
from mobilenetv3 import MobileNetV3LargeEncoderimport paddle
model = MobileNetV3LargeEncoder()
a = paddle.randn([2, 3, 224, 224])
tmp = model(a)print(f"输入数据shape:{a.shape} 输出数据长度:{len(tmp)} ")for i in tmp : print(i.shape)当然现在还没有严格的测试训练对齐,因为项目中只需要前向对齐,所以复现工作到了这里就告一段落了。 最后看一下模型详细信息,跟torch进行最后一次比对。
import numpy as np a = paddle.randn([1, 3, 224, 224]) paddle.summary(model, input=a)for i in model.state_dict(): print(i,model.state_dict()[i].shape)
本次复现MobileNetV3,其实算是抄了一遍,然后再根据torch的输出shape等信息,对代码进行微调对齐。
复现方法为自上而下,直接找到最类似的飞桨代码,然后修改。提前不用看论文,不过在修改调试过程中,自然而然的就会开始探索MobileNetV3的模型结构,在修改代码中学习构建模型的知识。
整个过程波澜起伏,好几次都想放弃,幸运的是没有放弃,才终于有了这个记录过程的项目。
因为这个项目是为RobustVideoMatting模型服务的,所以在测试和调试中,会关联使用一部分RobustVideoMatting的代码,比如后面调试中碰到的MobileNetV3LargeEncoder部分。
与其它复现不同的是,此复现没有修改网络各层的名字,这样导致最终模型参数存盘文件的key值,飞桨和torch是不一样的。解决的方法是按照顺序写入,而不是按照名字写入。这样对模型参数的存储顺序也是有要求的,所以才会碰到大家普通复现不会碰到的一些问题,比如模型初始化的顺序问题。
<br/>
from torchvision.models.mobilenetv3 import MobileNetV3, InvertedResidualConfig
torch的mobilenet代码:https://github.com/pytorch/vision/blob/main/torchvision/models/mobilenetv3.py 首先尝试了x2paddle转换,转换失败后用飞桨PaddleClas里面找到的mobilenetv3的代码进行对齐。
发现模型第二层,飞桨比torch多了一层1*1conv 。查找两者代码的差别。这里碰到了较多的问题。
关键torch代码里反应不出来啊,按照代码应该跟飞桨一样才对啊,那层被吃了? torch咋会少了一层1*1 conv卷积呢? 在没明白torch为啥少了一层conv 卷积的情况下,想使用del语句删除那一层,结果失败
仔细看torch的源代码,发现
# expand
if cnf.expanded_channels != cnf.input_channels:
layers.append(
ConvNormActivation( cnf.input_channels, cnf.expanded_channels,
kernel_size=1,
norm_layer=norm_layer,
activation_layer=activation_layer,
)
)如果输入通道数和中间层通道数一样,则可以省略掉一层3*3conv层。 针对这句,在飞桨语句里加入了这样一句:
if self.in_c != self.mid_c:
# print("加入一个conv")
x = self.expand_conv(x)结果发现加入这句还是没有去掉3*3conv层。而且能看到打印语句证明其确实生效了。 原来是因为初始化里有这一层的缘故,具体解决方法放到下面的debug里面了。
发现是mid_se么有对齐。比如飞桨 Block.3.mid_se.conv1.weight 和torch blocks.4.SqueezeExcitation.Conv2d
blocks.3.mid_se.conv1.weight [18, 72, 1, 1]blocks.3.mid_se.conv1.bias [18]blocks.3.mid_se.conv2.weight [72, 18, 1, 1]blocks.3.mid_se.conv2.bias [72](2): SqueezeExcitation(
(fc1): Conv2d(72, 24, kernel_size=(1, 1), stride=(1, 1))
(relu): ReLU(inplace=True)
(fc2): Conv2d(24, 72, kernel_size=(1, 1), stride=(1, 1))原来是在SEModule这里,torch使用了_make_divisible,保证为8 的倍数。前期看torch代码的时候,也看到了,但是没有注意。 飞桨默认这里没有使用_make_divisible,导致conv对不齐。
class SEModule(TheseusLayer):
def __init__(self, channel, reduction=4):
super().__init__()
squeeze_channels = _make_divisible(channel // reduction)上午调试好,省略掉一层3 *3卷积层, 下午再重新进入,首先上午的修改内容荡然无存(在缓存页面的时候,特别容易出现用老内容冲掉新内容的情况,所以一定要养成用完关闭页面的好习惯。),其次按照上午的思路,竟然没法去掉那层3 *3卷积,那句代码用print语句看已经生效了,但是就是没有去掉卷积。
自己看代码torch里面是根据输入和中间层的通道数不一样 ,判断是否写这一层卷积。 最终手写判断语句,但是那一层3*3 conv卷积就是去不掉。
太灵异了。
发了求助贴:https://aistudio.baidu.com/paddle/forum/topic/show/993808 后来是自己发现:在__init__里面内容和顺序影响最终的顺序,天啊,跟预想的不一样啊。
最终就是在__init__和forward里面都用了判断语句,终于把模型跟torch对齐了。
当然最后几层没有再去对齐,因为在需要使用的地方没有用到最后几层。
---> 12 x = normalize(x, [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 13 x = self.conv(x) 14 x = self.blocks[0](x)
/opt/conda/lib/python3.6/site-packages/paddle/vision/transforms/functional.py in normalize(img, mean, std, data_format, to_rgb) 670
671 if _is_tensor_image(img):
--> 672 return F_t.normalize(img, mean, std, data_format) 673 else: 674 if _is_pil_image(img):
/opt/conda/lib/python3.6/site-packages/paddle/vision/transforms/functional_tensor.py in normalize(img, mean, std, data_format) 101
102 """
--> 103 _assert_image_tensor(img, data_format) 104
105 mean = paddle.to_tensor(mean, place=img.place)
/opt/conda/lib/python3.6/site-packages/paddle/vision/transforms/functional_tensor.py in _assert_image_tensor(img, data_format) 33 raise RuntimeError( 34 'not support [type={}, ndim={}, data_format={}] paddle image'.
---> 35 format(type(img), img.ndim, data_format)) 36
37 RuntimeError: not support [type=<class 'paddle.Tensor'>, ndim=4, data_format=CHW] paddle image不知是否维度太大导致的,把测试数据由4D降低到3D,报错
ValueError: (InvalidArgument) The input of Op(Conv) should be a 4-D or 5-D Tensor. But received: input's dimension is 3, input's shape is [3, 244, 244]. [Hint: Expected in_dims.size() == 4 || in_dims.size() == 5 == true, but received in_dims.size() == 4 || in_dims.size() == 5:0 != true:1.] (at /paddle/paddle/fluid/operators/conv_op.cc:74) [operator < conv2d > error]
于是查找normalize的手册,先暂时修改成: 传入numpy数据,并在normalize之后,转换成tensor类型。
<ipython-input-77-9920eb18ba6c> in forward_time_series(self, x)
49 def forward_time_series(self, x): 50 B, T = x.shape[:2]---> 51 features = self.forward_single_frame(x.flatten(0, 1)) 52 # features = [f.unflatten(0, (B, T)) for f in features]
53 # # features = [f.unflatten(0, (B, T)) for f in features]TypeError: flatten() takes at most 1 argument (2 given)参数用列表,顺便把unflatten也改正。 不对,这里跟以前resnet一样啊,怎么就报错呢?
看来是normalize的问题,只好在内部,普通处理的时候用tensor,normalize的时候用numpy ,先能运行下去。
最终的解决方法,是根据定义,重新写了normalize函数
def normalize(x, mean, std): mean = paddle.to_tensor(mean).reshape((-1, 1, 1)) std = paddle.to_tensor(std).reshape((-1, 1, 1))
out = (x -mean)/std
return out在测试model = MattingNetwork('mobilenetv3') 时报错:
---> 26 self.backbone = MobileNetV3LargeEncoder(pretrained_backbone) 27 self.aspp = LRASPP(960, 128) 28 self.decoder = RecurrentDecoder([16, 24, 40, 128], [80, 40, 32, 16]) NameError: name 'MobileNetV3LargeEncoder' is not defined
很难理解,已经定义了啊!
在这里也有这个报错
Traceback (most recent call last): File "model.py", line 8, in <module> from .mobilenetv3 import MobileNetV3LargeEncoderModuleNotFoundError: No module named '__main__.mobilenetv3'; '__main__' is not a packageroot@e7cf009009d45011ec08bb20f3128d160513-task1-0:/code/paddlerobustvideomatting/model#
不明白为什么调不进去。resnet就没问题。
是不是应该用from model.mobilenetv3 import MobileNetV3LargeEncoder ?大概是
飞桨的是: MobileNetV3LargeEncoder ok ! 输入数据shape:[2, 2, 3, 224, 224] 输出数据长度:4[2, 2, 16, 112, 112][2, 2, 24, 56, 56][2, 2, 40, 28, 28][2, 2, 960, 7, 7]torch的是: MobileNetV3LargeEncoder ok ! 输入数据shape:torch.Size([2, 2, 3, 224, 224]) 输出数据长度:4torch.Size([2, 2, 16, 112, 112]) torch.Size([2, 2, 24, 56, 56]) torch.Size([2, 2, 40, 28, 28]) torch.Size([2, 2, 960, 14, 14])
仔细对比,发现是飞桨的x = self.blocks12 这一步,也进行了下采样 。 看来要去看mobilenetv3的代码,对比一下shape了。
飞桨的12对应toch的13
torch 13
(13): InvertedResidual(
(block): Sequential(
(0): ConvBNActivation(
(0): Conv2d(112, 672, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(672, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(2): Hardswish()
)
(1): ConvBNActivation(
(0): Conv2d(672, 672, kernel_size=(5, 5), stride=(1, 1), padding=(4, 4), dilation=(2, 2), groups=672, bias=False)
(1): BatchNorm2d(672, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(2): Hardswish()
)
(2): SqueezeExcitation(
(fc1): Conv2d(672, 168, kernel_size=(1, 1), stride=(1, 1))
(relu): ReLU(inplace=True)
(fc2): Conv2d(168, 672, kernel_size=(1, 1), stride=(1, 1))
)
(3): ConvBNActivation(
(0): Conv2d(672, 160, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(160, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(2): Identity()
)
)
)
飞桨12
(12): ResidualUnit(
(expand_conv): ConvBNLayer(
(conv): Conv2D(112, 672, kernel_size=[1, 1], data_format=NCHW)
(bn): BatchNorm()
(act): Hardswish()
)
(bottleneck_conv): ConvBNLayer(
(conv): Conv2D(672, 672, kernel_size=[5, 5], stride=[2, 2], padding=2, groups=672, data_format=NCHW)
(bn): BatchNorm()
(act): Hardswish()
)
(mid_se): SEModule(
(avg_pool): AdaptiveAvgPool2D(output_size=1)
(conv1): Conv2D(672, 168, kernel_size=[1, 1], data_format=NCHW)
(relu): ReLU()
(conv2): Conv2D(168, 672, kernel_size=[1, 1], data_format=NCHW)
(hardsigmoid): Hardsigmoid()
)
(linear_conv): ConvBNLayer(
(conv): Conv2D(672, 160, kernel_size=[1, 1], data_format=NCHW)
(bn): BatchNorm()
)
)以上就是飞桨源码MobileNetV3分类模型对齐Pytorch-省事版的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号