轻量的神经网络

标签: CV | 发表时间:2022-04-30 20:43 | 作者:
出处:https://muyuuuu.github.io/

很久之前我觉得移动端应用几百兆的模型不切实际,在不考虑蒸馏、量化等压缩方法下,发现了 MobileNet 设计的很神奇,大小只有几 MB,可以说是一股清流了。就整理发布了一下,然后今天发现找不到了,神奇。(于是顺手和 ShuffleNet 一并整理到轻量化的神经网络中)

MobileNet-V1

基本上可以说这个版本是后面几个版本的出发点。先来看一下创新点:提出 depthwise separable conv 和 pointwise conv 来降低网络的计算次数。还是直接画图吧:

对于传统卷积而言,输入一个三通道的图片,如果想要输出五通道,那么就需要 5 个 $3\times 3 \times 3$ 的卷积核。一般一些,假设传统卷积处理图像的大小是 $D_F\times D_F$,有 $M$ 个通道,卷积核的大小是 $D_K$,输出的通道数数 $N$,那么计算量就是 $D_K \cdot D_K \cdot M \cdot N \cdot D_F \cdot D_F$。

在得到相同大小输出的情况下,使用 DW 卷积和 PW 卷积来简化一下这个计算过程:

如果换成深度可分离卷积和逐点卷积,可以看到达到同样的输出,参数量从 $27\times 5$ 减少到了 $27+15$,而且计算量为 $D_K \cdot D_K \cdot M \cdot D_F \cdot D_F + M \cdot N \cdot D_F \cdot D_F$。两者的比值是 $1/N+1/D_K^2$。

1     
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class MobileNetV1(nn.Module):     
def __init__(self, ch_in, n_classes):
super(MobileNetV1, self).__init__()

def conv_bn(inp, oup, stride):
return nn.Sequential(
nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
nn.BatchNorm2d(oup),
nn.ReLU(inplace=True)
)

def conv_dw(inp, oup, stride):
return nn.Sequential(
# dw
# 输入通道和输出通道相等,groups 表示每个卷积核只处理一个通道
nn.Conv2d(inp, inp, 3, stride, 1, groups=inp, bias=False),
nn.BatchNorm2d(inp),
nn.ReLU(inplace=True),

# pw
# 卷积核大小为 1X1
nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
nn.ReLU(inplace=True),
)

self.model = nn.Sequential(
conv_bn(ch_in, 32, 2),
conv_dw(32, 64, 1),
conv_dw(64, 128, 2),
conv_dw(128, 128, 1),
conv_dw(128, 256, 2),
conv_dw(256, 256, 1),
conv_dw(256, 512, 2),
conv_dw(512, 512, 1),
conv_dw(512, 512, 1),
conv_dw(512, 512, 1),
conv_dw(512, 512, 1),
conv_dw(512, 512, 1),
conv_dw(512, 1024, 2),
conv_dw(1024, 1024, 1),
nn.AdaptiveAvgPool2d(1)
)
self.fc = nn.Linear(1024, n_classes)

def forward(self, x):
x = self.model(x)
x = x.view(-1, 1024)
x = self.fc(x)
return x

MobileNet-V2

V1 的思想可以概括为:首先利用 3×3 的深度可分离卷积提取特征,然后利用 1×1 的卷积来扩张通道。但是有人在实际使用的时候,发现训完之后发现 dw 卷积核有不少是空的。

作者认为这是 ReLU 激活函数导致的。于是做了一个实验,就是对一个 n 维空间中的一个东西乘以矩阵 $T$,而后做 ReLU 运算,然后利用 $T$ 的逆矩阵恢复,对比 ReLU 之后的结果与 Input 的结果相差有多大。作者发现:低维度做 ReLU 运算,很容易造成信息的丢失。而在高维度进行 ReLU 运算的话,信息的丢失则会很少。

由于卷积本身没有改变通道的能力,来的是多少通道输出就是多少通道。上面又得出低维通道不好的结论,因此使用 PW 卷积升维再降维,这也就形成了 Inverted Residuals 这种结构,因为传统的残差结构和本文相反,传统的是先降维在升维。

这样高维的仍然使用 ReLU 激活函数,低维的换成线性激活函数。因为有先升维在降维的结构,因此使用了残差连接来提升性能。

1     
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class InvertedResidual(nn.Module):     
def __init__(self, inp, oup, stride, expand_ratio):
super(InvertedResidual, self).__init__()
self.stride = stride
assert stride in [1, 2]

hidden_dim = int(inp * expand_ratio)
self.use_res_connect = self.stride == 1 and inp == oup

if expand_ratio == 1:
self.conv = nn.Sequential(
# dw
nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),
nn.BatchNorm2d(hidden_dim),
nn.ReLU6(inplace=True),
# pw-linear
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
else:
self.conv = nn.Sequential(
# pw 升维
nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False),
nn.BatchNorm2d(hidden_dim),
nn.ReLU6(inplace=True),
# dw 深度可分离卷积
nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),
nn.BatchNorm2d(hidden_dim),
nn.ReLU6(inplace=True),
# pw-linear 激活
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)

def forward(self, x):
if self.use_res_connect:
return x + self.conv(x)
else:
return self.conv(x)

MobileNet-V3

主要做了两点创新,一个是在 MobileNet V2 残差分支加入了 SE(Squeeze-and-Excitation) 注意力机制的模块,一个是更新了激活函数。SE 注意力就是通过池化得到每个通道的值,并输入到全连接层学习到每个通道的权重,对每个通道的数值进行更新。

1     
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class SELayer(nn.Module):     
def __init__(self, channel, reduction=4):
super(SELayer, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
# 输入维度和输出维度相同
self.fc = nn.Sequential(
nn.Linear(channel, _make_divisible(channel // reduction, 8)),
nn.ReLU(inplace=True),
nn.Linear(_make_divisible(channel // reduction, 8), channel),
h_sigmoid()
)

def forward(self, x):
b, c, _, _ = x.size()
y = self.avg_pool(x).view(b, c)
y = self.fc(y).view(b, c, 1, 1)
# y 是每个通道的权重
return x * y

在重新设计激活函数方面,使用 h-swish 激活函数代替了 swish 激活函数,因为更容易计算。对于 swish 激活函数:

\begin{equation}
\begin{aligned}
\text{swish} x &= x \cdot \sigma(x) \\
\sigma(x) &= \frac{1}{1+e^{-x}}
\end{aligned}
\end{equation}

这个反向传播和激活的计算过程略显复杂,对量化不够友好。于是使用较为接近的 h-swish 激活函数代替:

\begin{equation}
\begin{aligned}
\text{h-sigmoid} &= \frac{\text{ReLU6}(x+3)}{6} \\
\text{h-swish} &= x \cdot \text{h-sigmoid}
\end{aligned}
\end{equation}

1     
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class h_sigmoid(nn.Module):     
def __init__(self, inplace=True):
super(h_sigmoid, self).__init__()
self.relu = nn.ReLU6(inplace=inplace)

def forward(self, x):
return self.relu(x + 3) / 6


class h_swish(nn.Module):
def __init__(self, inplace=True):
super(h_swish, self).__init__()
self.sigmoid = h_sigmoid(inplace=inplace)

def forward(self, x):
return x * self.sigmoid(x)
1     
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class InvertedResidual(nn.Module):     
def __init__(self, inp, hidden_dim, oup, kernel_size, stride, use_se, use_hs):
super(InvertedResidual, self).__init__()
assert stride in [1, 2]

self.identity = stride == 1 and inp == oup

if inp == hidden_dim:
self.conv = nn.Sequential(
# dw
nn.Conv2d(hidden_dim, hidden_dim, kernel_size, stride, (kernel_size - 1) // 2, groups=hidden_dim, bias=False),
nn.BatchNorm2d(hidden_dim),
h_swish() if use_hs else nn.ReLU(inplace=True),
# Squeeze-and-Excite
SELayer(hidden_dim) if use_se else nn.Identity(),
# pw-linear
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
else:
self.conv = nn.Sequential(
# pw 升维
nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False),
nn.BatchNorm2d(hidden_dim),
h_swish() if use_hs else nn.ReLU(inplace=True),
# dw 提取特征
nn.Conv2d(hidden_dim, hidden_dim, kernel_size, stride, (kernel_size - 1) // 2, groups=hidden_dim, bias=False),
nn.BatchNorm2d(hidden_dim),
# Squeeze-and-Excite
SELayer(hidden_dim) if use_se else nn.Identity(),
h_swish() if use_hs else nn.ReLU(inplace=True),
# pw-linear 先行激活,降维
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)

def forward(self, x):
if self.identity:
return x + self.conv(x)
else:
return self.conv(x)

ShuffleNet-V1

ShuffleNet-V2

相关 [神经网络] 推荐:

轻量的神经网络

- - Just for Life.
很久之前我觉得移动端应用几百兆的模型不切实际,在不考虑蒸馏、量化等压缩方法下,发现了 MobileNet 设计的很神奇,大小只有几 MB,可以说是一股清流了. 就整理发布了一下,然后今天发现找不到了,神奇. (于是顺手和 ShuffleNet 一并整理到轻量化的神经网络中). 基本上可以说这个版本是后面几个版本的出发点.

BP神经网络的Java实现

- - ITeye博客
课程作业要求实现一个BPNN. 此前只用Matlab实现过,这次尝试使用Java实现了一个. 关于BPNN的原理,就不赘述了.  为了验证正确性,我写了一个测试用例,目的是对于任意的整数(int型),BPNN在经过训练之后,能够准确地判断出它是奇数还是偶数,正数还是负数. System.out.println("训练完毕,下面请输入一个任意数字,神经网络将自动判断它是正数还是复数,奇数还是偶数.

神经网络与用户行为

- - 博客园_新闻
英文原文: Neurology & User Behavior: What We Know. 流量和转化率是我们衡量一个网站是否成功的两个重要指标. 网站转化率就是指用户进行了相应目标行动的访问次数(成交人数)与总访问次数的比率. 这里所指的相应的行动可以是用户登录、用户注册、用户订阅、用户下载、用户购买等一些列用户行为,因此网站转化率是一个广义的概念.

[译] 调试神经网络的清单

- - IT瘾-dev
训练深度学习模型是非常耗时的工作,没有什么比花费了好几天训练而最终结果效果不佳更让人沮丧的了. 因此翻译了这篇文章: Checklist for debugging neural networks,希望能给你一点启发,尽早发现模型中的问题. 原文地址:https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21 ,略有删减.

研究人员首次用DNA构造人工神经网络

- Robi - Solidot
加州理工研究人员首次用DNA构造出人工神经网络,类似一个简化的大脑. 此前他们曾研制过一个能计算平方根的DNA逻辑门. 研究人员112种不同的DNA链组成四个相互联系的人工神经元,它会玩一个猜心术的游戏. 研究人员和这个试管中的神经网络玩了27次游戏,每次提供的线索都不相同,而它每次都猜对了. 研究人员表示,具有人工智能的生化系统,可以在医药,化学以及生物领域带来不可估量的应用.

自组织增量学习神经网络-SOINN

- 金文 - 丕子
自组织增量学习神经网络(Self-organizing incremental neural network, SOINN)实现包括学习、记忆、联想、推理、常识等方面的研究,最终目的是实现能够模拟人类大脑的供智能机械使用的通用型智能信息处理系统——人工脑. a. 基于SOINN的监督学习、非监督学习、半监督学习算法研究.

动态神经网络工具包DyNet:比Theano和TensorFlow更快

- - IT瘾-tuicool
近日,来自卡内基梅隆大学、日本奈良先端科学技术大学、Google DeepMind、以色列巴伊兰大学、艾伦人工智能研究所、IBM T.J. Watson 研究中心、澳大利亚墨尔本大学、约翰·霍普金斯大学、谷歌、华盛顿大学、微软和英国爱丁堡大学的研究者共同发表了一篇重磅论文《DyNet: The Dynamic Neural Network Toolkit》,正式介绍了动态神经网络工具包 DyNet;该工具包也已在 GitHub 上开源:http://github.com/clab/dynet.

用神经网络训练一个文本分类器

- - 文章 – 伯乐在线
理解聊天机器人的工作原理是非常重要的. 聊天机器人内部一个非常重要的组件就是文本分类器. 我们看一下文本分类器的神经网络(ANN)的内部工作原理. 我们将会使用2层网络(1个隐层)和一个“词包”的方法来组织我们的训练数据. 文本分类有3个特点:模式匹配、算法、神经网络. 虽然使用多项朴素贝叶斯算法的方法非常有效,但是它有3个致命的缺陷:.

用paddle实现一个CNN神经网络

- - 非技术 - ITeye博客
问题:实现在MNIST数据集上,对数字的识别. (1)输入:MNIST手写的数字图片,它是一个28*28的二维图片,为了计算,我们将其转为784维的一个向量. (2)输出:分类器的输出是0-9的数字的概率. (3)分类器的设计:5层的卷积神经网络. 即:conv->pool->conv->pool->FC->softmax.

神经网络的菜鸟入门秘籍

- - 机器之心
在过去几年中,如果你打开过浏览器,那么你肯定看到过几百回“神经网络”这个词. 在这篇短文中,我将为你初步介绍这一领域和神经网络本身的背景信息. 接下来的5分钟可能不会让你迅速成为这个领域的世界级专家,但让你经历一个意义非凡的入门阶段却是很简单的. 另外,你还将学习到一些流行术语(尤其是按文后的清单做进一步阅读),这可以成为你茶余饭后的谈资.