最近有了一篇最新的论坛, MobileNetV3, 想必对于MobileNetV2大家有所耳闻, 但是这个mobilenetv3是什么鬼. 简单来说, 就是一个自动搜索出来的升级版本MobileNet, 速度更快, 精度更高, 模型体积更小. 这无疑是非常吸引人的, 看来神经网络架构搜索是一个十分有前景又是十分有用的东西, 这就好比, 以前只有大佬才能通过网络稍微修改, 参数稍微修改来达到更高的精度 ( 当然我觉得也是瞎JB一顿乱改) 而现在, 你可以通过一个优化算法, 自动搜索出最优化的架构, 并且很有可能是最优解!
既然NAS这么牛逼, 那么这个玩意怎么做呢? 这让我们想起了当初最早做极限学习机的时候, 极限学习机通常是跟权重无关,而与结构有关, 因此还写了一篇论文来研究如果通过优化极限学习机的网络结构来达到更高的准确率. 现在想起来, 这和NAS简直如出一辙. 但是, 既然涉及到优化问题, 你就需要你的损失函数.
其他的问题损失函数是那么的显而易见, 比如分类问题, 那就是你的分类损失. 可是NAS的损失函数啥呢? 当然是整个网络的准确率.
那么问题来了, 我们通常评估一个网络的准确率, 是通过训练模型, 经过几千次上万次迭代, 才能判断这个模型的准确率的, 而如果我们要这羊在海量的搜索空间去搜索一个结构 ( 试想一下, 我一个stride步长不同都可以组成很多结构, 不同的连接方式组成的网络更多 ) 这么大的搜索空间, 你如何去优化? 顺便说一句, 这和我之前做的极限学习机优化有着本质的不同, 极限学习机每次计算速度非常快, 而且不同迭代, 因此它具备搜索的可行性.
那么我们如何来进行神经网络的架构搜索呢?
最早的一篇应该是Googlebrain推出的Neural Architecture Search with Reinforcement Learning(ICLR 2017),参见:
https://blog.csdn.net/Lucifer_zzq/article/details/83188462
事实上, 在比较早期的NAS工作, 正式我们上面所思考的那样, 以至于一个想法可能需要在几百个G的TPU上运行上百天才能得出结果, 这个几乎是除了谷歌这样的公司以外都不切实际的做法. 但至少给我们探索出了NAS可能的三个重要步骤:
既然我们有了一个大概的套路, 那么我们如何开始一个NAS的实验呢? 我们想看看, 如何进行一个NAS的实际操作. 先来总结一下前辈们套索的路径.
说了这么多, 似然并没有实际的告诉我们应该怎么进行NAS. 不要着急, 上面应该提到了一个很著名的例子, MnasNet, 顾名思义, 就是寻找在移动端最优的神经网络架构搜索.
我们不如以这个作为例子, 来搜索一个MnasNet. 先放出结论, 你搜索出来的MnasNet 将会比 MobileNetV2快 1.5x. 一刻赛听!
MnasNet的论文连接来自于: https://arxiv.org/abs/1807.11626. MnasN et实际上设计的初衷就是, 设计一个自动优化步骤, 将网络的latency(也就是计算时间) 与精确度之间进行一个tradeoff, 使得他们达到一个平衡 (二者不可能兼得, 但是一定存在一个最优点). 这篇文章首选确定在同一个平台进行比对(不同平台算力不同无法比较), 最重要的是提出了一种 分层分解搜索空间的方法 来进行网络结构的搜索.
我们来分析一下MnasNet的结构涂:
计算时延和网络精度没啥可说的, 但是 这个搜索就得说一下了. 几乎是本方法核心内容. 这是一种基于梯度的增强学习寻优办法, 下图展示的便是 层级搜索空间方法:
具体搜索方式, 在以前大家搜索都是以op为单位, 就是每一层卷积当做是一个cell, 搜索cell的尺寸和形式, 比如你是3x3, 还是 5x5, 还是 7x7, 是带pooling的卷积还是不带的, 是带maxpooling的还是带averagepooling的. 这些都是需要去搜索的. 这个MnasNet的最大不同之处在于, 它还允许cell的形式, 比如同样是执行3x3的计算, 可以先用乘在用1x1的卷积, 也可以直接用3x3的卷积这个操作虽然结果一样, 但是 计算时间是不同的, 这个本质上也是mobilenetv2所改进的地方.
OK, 下面看看搜索出来的MnasNet 跟 Mobilenetv2 有啥差别.
mobilenetv2与mobilenetv1的不同之处在于在SeperableConv前面再增加了一个pw层来进行通道的扩充从而可以获取更多的特征. 这一点其实在MnasNet得到了保留, 似乎神经网络也认为这种操作是正确的.
Mobilenetv2和Resnet的insight的不同, mobilenetv2是扩张扩张提取特征再还原, 而resnet是压缩压缩提取特征再还原.
Mnasnet里面似乎与MobileNetV2没啥区别, 同样也是inverted-residual, 先扩充通道提取特征, 再还原. 最后我们看一下MnasNet的pytorch实现:
from torch.autograd import Variable
import torch.nn as nn
import torch
import math
def Conv_3x3(inp, oup, stride):
return nn.Sequential(
nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
nn.BatchNorm2d(oup),
nn.ReLU6(inplace=True)
)
def Conv_1x1(inp, oup):
return nn.Sequential(
nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
nn.ReLU6(inplace=True)
)
def SepConv_3x3(inp, oup): #input=32, output=16
return nn.Sequential(
# dw
nn.Conv2d(inp, inp , 3, 1, 1, groups=inp, bias=False),
nn.BatchNorm2d(inp),
nn.ReLU6(inplace=True),
# pw-linear
nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
class InvertedResidual(nn.Module):
def init(self, inp, oup, stride, expand_ratio, kernel):
super(InvertedResidual, self).init()
self.stride = stride
assert stride in [1, 2]
self.use_res_connect = self.stride == 1 and inp == oup
self.conv = nn.Sequential(
# pw
nn.Conv2d(inp, inp * expand_ratio, 1, 1, 0, bias=False),
nn.BatchNorm2d(inp * expand_ratio),
nn.ReLU6(inplace=True),
# dw
nn.Conv2d(inp * expand_ratio, inp * expand_ratio, kernel, stride, kernel // 2, groups=inp * expand_ratio, bias=False),
nn.BatchNorm2d(inp * expand_ratio),
nn.ReLU6(inplace=True),
# pw-linear
nn.Conv2d(inp * expand_ratio, 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)
class MnasNet(nn.Module):
def init(self, n_class=1000, input_size=224, width_mult=1.):
super(MnasNet, self).init()
# setting of inverted residual blocks
self.interverted_residual_setting = [
# t, c, n, s, k
[3, 24, 3, 2, 3], # -> 56x56
[3, 40, 3, 2, 5], # -> 28x28
[6, 80, 3, 2, 5], # -> 14x14
[6, 96, 2, 1, 3], # -> 14x14
[6, 192, 4, 2, 5], # -> 7x7
[6, 320, 1, 1, 3], # -> 7x7
]
assert input_size % 32 == 0
input_channel = int(32 * width_mult)
self.last_channel = int(1280 * width_mult) if width_mult > 1.0 else 1280
# building first two layer
self.features = [Conv_3x3(3, input_channel, 2), SepConv_3x3(input_channel, 16)]
input_channel = 16
# building inverted residual blocks (MBConv)
for t, c, n, s, k in self.interverted_residual_setting:
output_channel = int(c * width_mult)
for i in range(n):
if i == 0:
self.features.append(InvertedResidual(input_channel, output_channel, s, t, k))
else:
self.features.append(InvertedResidual(input_channel, output_channel, 1, t, k))
input_channel = output_channel
# building last several layers
self.features.append(Conv_1x1(input_channel, self.last_channel))
self.features.append(nn.AdaptiveAvgPool2d(1))
# make it nn.Sequential
self.features = nn.Sequential(*self.features)
# building classifier
self.classifier = nn.Sequential(
nn.Dropout(),
nn.Linear(self.last_channel, n_class),
)
self._initialize_weights()
def forward(self, x):
x = self.features(x)
x = x.view(-1, self.last_channel)
x = self.classifier(x)
return x
def _initialize_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
if m.bias is not None:
m.bias.data.zero_()
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
elif isinstance(m, nn.Linear):
n = m.weight.size(1)
m.weight.data.normal_(0, 0.01)
m.bias.data.zero_()
if name == ‘main’:
net = MnasNet()
x_image = Variable(torch.randn(1, 3, 224, 224))
y = net(x_image)
# print(y)
总的来说Mnasnet比较多得采用了 5x5 的卷积. 这是与mobilenet的不太一样的地方…