【项目实战】基于 MobileNetV3 实现恶意文件静态检测(上)

771 阅读6分钟

本文正在参加 人工智能创作者扶持计划

前言

在之前的文章中,也有介绍过关于恶意文件静态检测项目的相关内容,传送门如下:

鉴于恶意文件的特殊性,使用 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 模型支持用户自定义,为构建分类、目标检测和语义分割。

网络结构

image.png

官方提供了两种变体:Large 和 Small。二者是用相同的代码构建的,唯一的区别是配置(模块的数量、大小、激活函数等)不同。

MobileNetV3 的网络结构可以分为三个部分:

  • 起始部分:1 个 3x3 的卷积层,用来提取特征;
  • 中间部分:n 个卷积层,Large 和 Small 版本各不同;
  • 末尾部分:2 个 1x1 的卷积层,代替全连接,输出类别;

引入了非线性激活函数 hswishh-swish 替代 ReLUReLU,公式如下所示:

hswish[x]=xReLU6(x+3)6h-swish[x] = x\frac{ReLU6(x+3)}{6}

image.png

代码如下所示:

F.relu6(x + 3., self.inplace) / 6

关于 MobileNetV3 的网络结构不展开详细介绍,有需要的读者可以自行阅读论文,接下来介绍项目需要的部分。

参数配置

鉴于项目中使用的是 Small 版本,因此对于 Large 版本的就不进行展示了。

image.png

  • 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 模型的一些相关内容后,接下来就开始构造代码,进行项目实战。

起始部分

image.png

不论是 MobileNetV3 的 Large 还是 Small 变体,起始部分都是一样的,即 1 个 3x3 的卷积层,包括卷积层、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. 1x1 卷积,由输入通道,转换为膨胀通道;
  2. 3x3 或 5x5 卷积,膨胀通道,使用步长 stride;
  3. 1x1 卷积,由膨胀通道,转换为输出通道。

两个可选步骤:

  1. SESE 结构:Squeeze-and-Excite;
  2. 连接操作,Residual 残差,步长为1,同时输入和输出通道相同;

其中激活函数有两种:ReLUReLUhswishh-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)

末尾部分

image.png

末尾部分通过将 Avg Pooling 提前,从而减少计算量,并将 Squeeze 操作省略,直接使用 1x1 的卷积,如图所示:

image.png

伪代码构造如下所示:

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 搭建定制机器人

💖 我是 𝓼𝓲𝓭𝓲𝓸𝓽,期待你的关注;

👍 创作不易,请多多支持;

🔥 系列专栏:AI 项目实战