本文正在参加 人工智能创作者扶持计划
前言
在之前的文章中,也有介绍过关于恶意文件静态检测项目的相关内容,传送门如下:
鉴于恶意文件的特殊性,使用 ViT 模型训练的效果并不理想,因此,本篇文章转换思路,使用轻量级模型 MobileNetV3 进行训练。
主要参考论文:Searching for MobileNetV3
介绍
We present the next generation of MobileNets based on a combination of complementary search techniques as well as a novel architecture design. MobileNetV3 is tuned to mobile phone CPUs through a combination of hardware-aware network architecture search (NAS) complemented by the NetAdapt algorithm and then subsequently improved through novel architecture advances.
MobileNetV3 基于互补搜索技术和新颖架构设计组合,也就是硬件感知网络架构搜索(NAS) 与 NetAdapt 算法相结合,再通过新架构进一步的改进。
MobileNetV3 模型支持用户自定义,为构建分类、目标检测和语义分割。
网络结构
官方提供了两种变体:Large 和 Small。二者是用相同的代码构建的,唯一的区别是配置(模块的数量、大小、激活函数等)不同。
MobileNetV3 的网络结构可以分为三个部分:
- 起始部分:1 个 3×3 的卷积层,用来提取特征;
- 中间部分:n 个卷积层,Large 和 Small 版本各不同;
- 末尾部分:2 个 1×1 的卷积层,代替全连接,输出类别;
引入了非线性激活函数 h−swishh-swish 替代 ReLUReLU,公式如下所示:
h−swish[x]=xReLU6(x+3)6h-swish[x] = xfrac{ReLU6(x+3)}{6}
代码如下所示:
F.relu6(x + 3., self.inplace) / 6
关于 MobileNetV3 的网络结构不展开详细介绍,有需要的读者可以自行阅读论文,接下来介绍项目需要的部分。
参数配置
鉴于项目中使用的是 Small 版本,因此对于 Large 版本的就不进行展示了。
- SE:Squeeze-and-Excite 结构,压缩和激发;
- NL:Non-Linearity,非线性;HS:h-swish 激活函数,RE:ReLU 激活函数;
- bneck:bottleneck layers,瓶颈层;
- exp size:expansion factor,膨胀参数;
代码如下所示:
if mode == 'small':
mobile_setting = [
# k, exp, c, se, nl, s,
[3, 16, 16, True, 'RE', 2],
[3, 72, 24, False, 'RE', 2],
[3, 88, 24, False, 'RE', 1],
[5, 96, 40, True, 'HS', 2],
[5, 240, 40, True, 'HS', 1],
[5, 240, 40, True, 'HS', 1],
[5, 120, 48, True, 'HS', 1],
[5, 144, 48, True, 'HS', 1],
[5, 288, 96, True, 'HS', 2],
[5, 576, 96, True, 'HS', 1],
[5, 576, 96, True, 'HS', 1],
]
用户可以自定义 InvertedResidual 设置,并直接传递给 MobileNetV3 类,一些关键的配置参数如下:
width_mult
参数是一个乘数,决定模型管道的数量,默认值是 1,通过调节默认值可以改变卷积过滤器的数量,包括第一层和最后一层,实现时要确保过滤器的数量是 8 的倍数。这是一个硬件优化技巧,可以加快操作的向量化进程。reduced_tail
参数主要用于运行速度优化,它使得网络最后一个模块的管道数量减半。该版本常被用于目标检测和语义分割模型。根据 MobileNetV3 相关论文描述,使用 reduced_tail 参数可以在不影响准确性的前提下,减少 15% 的延迟。dilated
参数主要影响模型最后 3 个 InvertedResidual 模块,可以将这些模块的 Depthwise 卷积转换成 Atrous 卷积,用于控制模块的输出步长,并提高语义分割模型的准确性。
了解了 MobileNetV3 模型的一些相关内容后,接下来就开始构造代码,进行项目实战。
起始部分
不论是 MobileNetV3 的 Large 还是 Small 变体,起始部分都是一样的,即 1 个 3×3 的卷积层,包括卷积层、BN 层、h-switch 激活层三个部分。
根据 Small 的规范构建如下伪代码:
self.features = [conv_bn(3, input_channel, 2, nlin_layer=Hswish)]
def conv_bn(inp, oup, stride, conv_layer=nn.Conv2d, norm_layer=nn.BatchNorm2d, nlin_layer=nn.ReLU):
return nn.Sequential(
conv_layer(inp, oup, 3, stride, 1, bias=False),
norm_layer(oup),
nlin_layer(inplace=True)
)
中间部分
中间部分是多个含有卷积层的块(MobileBlock)的网络结构,Large 和 Small 的块数不尽相同,具体参考相关规范,这里以 Small 进行讲解。
中间部分的伪代码如下:
for k, exp, c, se, nl, s in mobile_setting:
output_channel = make_divisible(c * width_mult)
exp_channel = make_divisible(exp * width_mult)
self.features.append(MobileBottleneck(input_channel, output_channel, k, s, exp_channel, se, nl))
input_channel = output_channel
其中,make_divisible
函数保证输出的数可以整除 divisor
,这里是保证被 8 整除,代码如下所示:
def make_divisible(x, divisor=8):
import numpy as np
return int(np.ceil(x * 1. / divisor) * divisor)
至于为什么要保证被 8 整除,可以参考下面取自一篇论文中的描述:
In practice,with d = 8 the US-Nets already provide enough adjustable widths. Also in much hardware, matrix multiplication with size divisible by d = 8,16,…, may be as fast as a smaller size due to alignment of processing unit (e.g., warp size in GPU is 32).
如果仅从数学角度来考虑,是得不到答案的,这个问题要从计算机处理器单元的架构上考虑,在大多数硬件中,size 可以被 d = 8, 16 等整除的矩阵乘法比较快,因为这些 size 符合处理器单元的对齐位宽。
MobileBottleneck
是自定义的类,主要是依据 MobileBlock 的三个必要步骤和两个可选步骤进行设计;
三个必要步骤:
- 1×1 卷积,由输入通道,转换为膨胀通道;
- 3×3 或 5×5 卷积,膨胀通道,使用步长 stride;
- 1×1 卷积,由膨胀通道,转换为输出通道。
两个可选步骤:
- SESE 结构:Squeeze-and-Excite;
- 连接操作,Residual 残差,步长为1,同时输入和输出通道相同;
其中激活函数有两种:ReLUReLU 和 h−swishh-swish。
最终构造代码如下:
class MobileBottleneck(nn.Module):
def __init__(self, inp, oup, kernel, stride, exp, se=False, nl='RE'):
super(MobileBottleneck, self).__init__()
assert stride in [1, 2]
assert kernel in [3, 5]
padding = (kernel - 1) // 2
self.use_res_connect = stride == 1 and inp == oup
conv_layer = nn.Conv2d
norm_layer = nn.BatchNorm2d
if nl == 'RE':
nlin_layer = nn.ReLU # or ReLU6
elif nl == 'HS':
nlin_layer = Hswish
else:
raise NotImplementedError
if se:
SELayer = SEModule
else:
SELayer = Identity
self.conv = nn.Sequential(
# pw
conv_layer(inp, exp, 1, 1, 0, bias=False),
norm_layer(exp),
nlin_layer(inplace=True),
# dw
conv_layer(exp, exp, kernel, stride, padding, groups=exp, bias=False),
norm_layer(exp),
SELayer(exp),
nlin_layer(inplace=True),
# pw-linear
conv_layer(exp, oup, 1, 1, 0, bias=False),
norm_layer(oup),
)
def forward(self, x):
if self.use_res_connect:
return x + self.conv(x)
else:
return self.conv(x)
末尾部分
末尾部分通过将 Avg Pooling 提前,从而减少计算量,并将 Squeeze 操作省略,直接使用 1×1 的卷积,如图所示:
伪代码构造如下所示:
if mode == 'small':
last_conv = make_divisible(576 * width_mult)
self.features.append(conv_1x1_bn(input_channel, last_conv, nlin_layer=Hswish))
self.features.append(nn.AdaptiveAvgPool2d(1))
self.features.append(nn.Conv2d(last_conv, last_channel, 1, 1, 0))
self.features.append(Hswish(inplace=True))
后记
本文到此就结束了,文章细致的讲解了 MobileNetV3 模型的三个部分,结合代码加深理解,模型的完整代码点击此处。
在下一篇博文中,将讲解如何通过 MobileNetV3 对恶意文件静态检测模型进行训练以及验证评估,敬请期待!
以上就是 【项目实战】基于 MobileNetV3 实现恶意文件静态检测(上) 的全部内容了,希望本篇博文对大家有所帮助!
📝 上篇精讲:【项目实战】基于 go-cqhttp 与 Flask 搭建定制机器人
💖 我是 𝓼𝓲𝓭𝓲𝓸𝓽,期待你的关注;
👍 创作不易,请多多支持;