• MobileNetV3


    相对重量级网络而言,轻量级网络的特点是参数少、计算量小、推理时间短。更适用于存储空间和功耗受限的场景,例如移动端嵌入式设备等边缘计算设备。因此轻量级网络受到了广泛的关注,其中MobileNet可谓是其中的佼佼者。MobileNetV3经过了V1和V2前两代的积累,性能和速度都表现优异,MobileNetV3 参数是由NAS(network architecture search)搜索获取的,又继承的V1和V2的一些实用成果,并引人SE通道注意力机制,可谓集大成者。本文以应用为主,结合代码剖析MobileNetV3的网络结构。

    主要特点

    1. 论文推出两个版本:Large 和 Small,分别适用于不同的场景;
    2. 使用NetAdapt算法获得卷积核和通道的最佳数量;
    3. 继承V1的深度可分离卷积;
    4. 继承V2的具有线性瓶颈的残差结构;
    5. 引入SE通道注意力结构;
    6. 使用了一种新的激活函数h-swish(x)代替Relu6,h的意思表示hard;
    7. 使用了Relu6(x + 3)/6来近似SE模块中的sigmoid;
    8. 修改了MobileNetV2后端输出head

    整体结构

     上图为MobileNetV3的网络结构图,large和small的整体结构一致,区别就是基本单元bneck的个数以及内部参数上,主要是通道数目。(左图为small,右图为large)

    上表为具体的参数设置,其中bneck是网络的基本结构。SE代表是否使用通道注意力机制。NL代表激活函数的类型,包括HS(h-swish),RE(ReLU)。NBN 代表没有BN操作。 s 是stride的意思,网络使用卷积stride操作进行降采样,没有使用pooling操作。

     

    pytorch官方代码MobileNetV3

    给出修改后的 代码

    1. from typing import Callable, List, Optional
    2. import torch
    3. from torch import nn, Tensor
    4. from torch.nn import functional as F
    5. from functools import partial
    6. def _make_divisible(ch, divisor=8, min_ch=None):
    7. """
    8. This function is taken from the original tf repo.
    9. It ensures that all layers have a channel number that is divisible by 8
    10. It can be seen here:
    11. https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
    12. """
    13. if min_ch is None:
    14. min_ch = divisor
    15. new_ch = max(min_ch, int(ch + divisor / 2) // divisor * divisor)
    16. # Make sure that round down does not go down by more than 10%.
    17. if new_ch < 0.9 * ch:
    18. new_ch += divisor
    19. return new_ch
    20. class ConvBNActivation(nn.Sequential):
    21. def __init__(self,
    22. in_planes: int,
    23. out_planes: int,
    24. kernel_size: int = 3,
    25. stride: int = 1,
    26. groups: int = 1,
    27. norm_layer: Optional[Callable[..., nn.Module]] = None,
    28. activation_layer: Optional[Callable[..., nn.Module]] = None):
    29. padding = (kernel_size - 1) // 2
    30. if norm_layer is None:
    31. norm_layer = nn.BatchNorm2d
    32. if activation_layer is None:
    33. activation_layer = nn.ReLU6
    34. super(ConvBNActivation, self).__init__(nn.Conv2d(in_channels=in_planes,
    35. out_channels=out_planes,
    36. kernel_size=kernel_size,
    37. stride=stride,
    38. padding=padding,
    39. groups=groups,
    40. bias=False),
    41. norm_layer(out_planes),
    42. activation_layer(inplace=True))
    43. class SqueezeExcitation(nn.Module):
    44. def __init__(self, input_c: int, squeeze_factor: int = 4):
    45. super(SqueezeExcitation, self).__init__()
    46. squeeze_c = _make_divisible(input_c // squeeze_factor, 8)
    47. self.fc1 = nn.Conv2d(input_c, squeeze_c, 1)
    48. self.fc2 = nn.Conv2d(squeeze_c, input_c, 1)
    49. def forward(self, x: Tensor) -> Tensor:
    50. scale = F.adaptive_avg_pool2d(x, output_size=(1, 1))
    51. scale = self.fc1(scale)
    52. scale = F.relu(scale, inplace=True)
    53. scale = self.fc2(scale)
    54. scale = F.hardsigmoid(scale, inplace=True)
    55. return scale * x
    56. class InvertedResidualConfig:
    57. def __init__(self,
    58. input_c: int,
    59. kernel: int,
    60. expanded_c: int,
    61. out_c: int,
    62. use_se: bool,
    63. activation: str,
    64. stride: int,
    65. width_multi: float):
    66. self.input_c = self.adjust_channels(input_c, width_multi)
    67. self.kernel = kernel
    68. self.expanded_c = self.adjust_channels(expanded_c, width_multi)
    69. self.out_c = self.adjust_channels(out_c, width_multi)
    70. self.use_se = use_se
    71. self.use_hs = activation == "HS" # whether using h-swish activation
    72. self.stride = stride
    73. @staticmethod
    74. def adjust_channels(channels: int, width_multi: float):
    75. return _make_divisible(channels * width_multi, 8)
    76. class InvertedResidual(nn.Module):
    77. def __init__(self,
    78. cnf: InvertedResidualConfig,
    79. norm_layer: Callable[..., nn.Module]):
    80. super(InvertedResidual, self).__init__()
    81. if cnf.stride not in [1, 2]:
    82. raise ValueError("illegal stride value.")
    83. self.use_res_connect = (cnf.stride == 1 and cnf.input_c == cnf.out_c)
    84. layers: List[nn.Module] = []
    85. activation_layer = nn.Hardswish if cnf.use_hs else nn.ReLU
    86. # expand
    87. if cnf.expanded_c != cnf.input_c:
    88. layers.append(ConvBNActivation(cnf.input_c,
    89. cnf.expanded_c,
    90. kernel_size=1,
    91. norm_layer=norm_layer,
    92. activation_layer=activation_layer))
    93. # depthwise
    94. layers.append(ConvBNActivation(cnf.expanded_c,
    95. cnf.expanded_c,
    96. kernel_size=cnf.kernel,
    97. stride=cnf.stride,
    98. groups=cnf.expanded_c,
    99. norm_layer=norm_layer,
    100. activation_layer=activation_layer))
    101. if cnf.use_se:
    102. layers.append(SqueezeExcitation(cnf.expanded_c))
    103. # project
    104. layers.append(ConvBNActivation(cnf.expanded_c,
    105. cnf.out_c,
    106. kernel_size=1,
    107. norm_layer=norm_layer,
    108. activation_layer=nn.Identity))
    109. self.block = nn.Sequential(*layers)
    110. self.out_channels = cnf.out_c
    111. self.is_strided = cnf.stride > 1
    112. def forward(self, x: Tensor) -> Tensor:
    113. result = self.block(x)
    114. if self.use_res_connect:
    115. result += x
    116. return result
    117. class MobileNetV3(nn.Module):
    118. def __init__(self,
    119. inverted_residual_setting: List[InvertedResidualConfig],
    120. last_channel: int,
    121. num_classes: int = 1000,
    122. block: Optional[Callable[..., nn.Module]] = None,
    123. norm_layer: Optional[Callable[..., nn.Module]] = None):
    124. super(MobileNetV3, self).__init__()
    125. if not inverted_residual_setting:
    126. raise ValueError("The inverted_residual_setting should not be empty.")
    127. elif not (isinstance(inverted_residual_setting, List) and
    128. all([isinstance(s, InvertedResidualConfig) for s in inverted_residual_setting])):
    129. raise TypeError("The inverted_residual_setting should be List[InvertedResidualConfig]")
    130. if block is None:
    131. block = InvertedResidual
    132. if norm_layer is None:
    133. norm_layer = partial(nn.BatchNorm2d, eps=0.001, momentum=0.01)
    134. layers: List[nn.Module] = []
    135. # building first layer
    136. firstconv_output_c = inverted_residual_setting[0].input_c
    137. layers.append(ConvBNActivation(3,
    138. firstconv_output_c,
    139. kernel_size=3,
    140. stride=2,
    141. norm_layer=norm_layer,
    142. activation_layer=nn.Hardswish))
    143. # building inverted residual blocks
    144. for cnf in inverted_residual_setting:
    145. layers.append(block(cnf, norm_layer))
    146. # building last several layers
    147. lastconv_input_c = inverted_residual_setting[-1].out_c
    148. lastconv_output_c = 6 * lastconv_input_c
    149. layers.append(ConvBNActivation(lastconv_input_c,
    150. lastconv_output_c,
    151. kernel_size=1,
    152. norm_layer=norm_layer,
    153. activation_layer=nn.Hardswish))
    154. self.features = nn.Sequential(*layers)
    155. self.avgpool = nn.AdaptiveAvgPool2d(1)
    156. self.classifier = nn.Sequential(nn.Linear(lastconv_output_c, last_channel),
    157. nn.Hardswish(inplace=True),
    158. nn.Dropout(p=0.2, inplace=True),
    159. nn.Linear(last_channel, num_classes))
    160. # initial weights
    161. for m in self.modules():
    162. if isinstance(m, nn.Conv2d):
    163. nn.init.kaiming_normal_(m.weight, mode="fan_out")
    164. if m.bias is not None:
    165. nn.init.zeros_(m.bias)
    166. elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
    167. nn.init.ones_(m.weight)
    168. nn.init.zeros_(m.bias)
    169. elif isinstance(m, nn.Linear):
    170. nn.init.normal_(m.weight, 0, 0.01)
    171. nn.init.zeros_(m.bias)
    172. def _forward_impl(self, x: Tensor) -> Tensor:
    173. x = self.features(x)
    174. x = self.avgpool(x)
    175. x = torch.flatten(x, 1)
    176. x = self.classifier(x)
    177. return x
    178. def forward(self, x: Tensor) -> Tensor:
    179. return self._forward_impl(x)
    180. def mobilenet_v3_large(num_classes: int = 1000,
    181. reduced_tail: bool = False) -> MobileNetV3:
    182. """
    183. Constructs a large MobileNetV3 architecture from
    184. "Searching for MobileNetV3" .
    185. weights_link:
    186. https://download.pytorch.org/models/mobilenet_v3_large-8738ca79.pth
    187. Args:
    188. num_classes (int): number of classes
    189. reduced_tail (bool): If True, reduces the channel counts of all feature layers
    190. between C4 and C5 by 2. It is used to reduce the channel redundancy in the
    191. backbone for Detection and Segmentation.
    192. """
    193. width_multi = 1.0
    194. bneck_conf = partial(InvertedResidualConfig, width_multi=width_multi)
    195. adjust_channels = partial(InvertedResidualConfig.adjust_channels, width_multi=width_multi)
    196. reduce_divider = 2 if reduced_tail else 1
    197. inverted_residual_setting = [
    198. # input_c, kernel, expanded_c, out_c, use_se, activation, stride
    199. bneck_conf(16, 3, 16, 16, False, "RE", 1),
    200. bneck_conf(16, 3, 64, 24, False, "RE", 2), # C1
    201. bneck_conf(24, 3, 72, 24, False, "RE", 1),
    202. bneck_conf(24, 5, 72, 40, True, "RE", 2), # C2
    203. bneck_conf(40, 5, 120, 40, True, "RE", 1),
    204. bneck_conf(40, 5, 120, 40, True, "RE", 1),
    205. bneck_conf(40, 3, 240, 80, False, "HS", 2), # C3
    206. bneck_conf(80, 3, 200, 80, False, "HS", 1),
    207. bneck_conf(80, 3, 184, 80, False, "HS", 1),
    208. bneck_conf(80, 3, 184, 80, False, "HS", 1),
    209. bneck_conf(80, 3, 480, 112, True, "HS", 1),
    210. bneck_conf(112, 3, 672, 112, True, "HS", 1),
    211. bneck_conf(112, 5, 672, 160 // reduce_divider, True, "HS", 2), # C4
    212. bneck_conf(160 // reduce_divider, 5, 960 // reduce_divider, 160 // reduce_divider, True, "HS", 1),
    213. bneck_conf(160 // reduce_divider, 5, 960 // reduce_divider, 160 // reduce_divider, True, "HS", 1),
    214. ]
    215. last_channel = adjust_channels(1280 // reduce_divider) # C5
    216. return MobileNetV3(inverted_residual_setting=inverted_residual_setting,
    217. last_channel=last_channel,
    218. num_classes=num_classes)
    219. def mobilenet_v3_small(num_classes: int = 1000,
    220. reduced_tail: bool = False) -> MobileNetV3:
    221. """
    222. Constructs a large MobileNetV3 architecture from
    223. "Searching for MobileNetV3" .
    224. weights_link:
    225. https://download.pytorch.org/models/mobilenet_v3_small-047dcff4.pth
    226. Args:
    227. num_classes (int): number of classes
    228. reduced_tail (bool): If True, reduces the channel counts of all feature layers
    229. between C4 and C5 by 2. It is used to reduce the channel redundancy in the
    230. backbone for Detection and Segmentation.
    231. """
    232. width_multi = 1.0
    233. bneck_conf = partial(InvertedResidualConfig, width_multi=width_multi)
    234. adjust_channels = partial(InvertedResidualConfig.adjust_channels, width_multi=width_multi)
    235. reduce_divider = 2 if reduced_tail else 1
    236. inverted_residual_setting = [
    237. # input_c, kernel, expanded_c, out_c, use_se, activation, stride
    238. bneck_conf(16, 3, 16, 16, True, "RE", 2), # C1
    239. bneck_conf(16, 3, 72, 24, False, "RE", 2), # C2
    240. bneck_conf(24, 3, 88, 24, False, "RE", 1),
    241. bneck_conf(24, 5, 96, 40, True, "HS", 2), # C3
    242. bneck_conf(40, 5, 240, 40, True, "HS", 1),
    243. bneck_conf(40, 5, 240, 40, True, "HS", 1),
    244. bneck_conf(40, 5, 120, 48, True, "HS", 1),
    245. bneck_conf(48, 5, 144, 48, True, "HS", 1),
    246. bneck_conf(48, 5, 288, 96 // reduce_divider, True, "HS", 2), # C4
    247. bneck_conf(96 // reduce_divider, 5, 576 // reduce_divider, 96 // reduce_divider, True, "HS", 1),
    248. bneck_conf(96 // reduce_divider, 5, 576 // reduce_divider, 96 // reduce_divider, True, "HS", 1)
    249. ]
    250. last_channel = adjust_channels(1024 // reduce_divider) # C5
    251. return MobileNetV3(inverted_residual_setting=inverted_residual_setting,
    252. last_channel=last_channel,
    253. num_classes=num_classes)

  • 相关阅读:
    【纯虚函数,final/override关键字】
    数据生产流程——采集、清洗、分析
    【Docker】从零开始:4.为什么Docker会比VM虚拟机快
    UE5 GAS 学习笔记 10.3 LyraStarter案例解析(中)
    全渠道商城授权管控经销商,渠道商管理系统助力医药企业快速扩大渠道规模
    实现一个深克隆
    厨神之蛋糕制作
    G1垃圾收集器
    离线数仓搭建_04_zookeeper-flume-kafka框架配置
    如何用python搭建神经网络,python实现人工神经网络
  • 原文地址:https://blog.csdn.net/qq_46454669/article/details/134027933