Skip to content

实验 4:MobileNet & ShuffleNet

一、实验内容

MobileNet

MobileNet 是 Google 用于移动设备的视觉分类模型,既然要用在移动设备上,自然参数量不能太大,否则模型的效率就会很低。如何在参数量和网络效果上作权衡?它使用了深度可分离卷积这一概念来构建轻量模型。

V1

所谓深度可分离,就是说把卷积核的深度和空间维度分开,从而达到降低复杂度的目的。不分离的时候,需要几个输出通道,就需要几个卷积核,每个卷积核都要对各个深度进行卷积然后求和,参数量大约是 Cinput×Coutput×k2,这个乘积就是优化的关键。

深度卷积,就是让卷积核的作用范围从各个深度维度变成单个深度维度,这样就能将第一步的参数量降到 Cinput×k2,即 Coutput=1。然后再在第二步进行 1×1 卷积整合各通道的信息,参数量是 Cinput×Coutput​。这样就把参数量给降下去了。

V2

V2 相比较 V1 主要的改进就是加入了残差机制,和延展、投影处理。

这样应该是能对通道信息进一步整合,避免了信息提取效率低下的问题。

V3

V3 是由 NAS(神经网络架构搜索)技术设计出来的网络模型,论文的重点就不是在介绍“MobileNet 如何好”了,而是“如何得到更好的 MobileNet”,感觉反倒是在介绍 NAS 技术。

这个模型相比较 V2 区别就没有很具有逻辑性了,优化了最后几层的结构,换用了 h-swish 激活函数和 ReLU6。

ShuffleNet

所谓 shuffle,就是打散、洗牌。为了弥补跨通道信息损失,ShuffleNet 提出了通过均匀打乱通道,使得信息在不同通道间流通的做法。

SENet & CBAM

SENet

所谓 SE 就是 Squeeze and Excitation,把特征“挤压”后再“激励”从而得到更好的效果,本质上是感受野的改变带来的效果。它是属于空间注意力机制,作为一种特征选择的方法,调节不同通道的强度。

全局池化部分就是 Squeeze,获取全局感受野,FC 全局连接和非线性变换就是 Excitation 过程,构建特征通道的相关性。在 Colab 中补全训练函数并训练:

python
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)

transform_train = transforms.Compose([
    transforms.RandomCrop(32, padding=4),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))])

transform_test = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,  download=True, transform=transform_train)
testset  = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform_test)

trainloader = torch.utils.data.DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2)
testloader = torch.utils.data.DataLoader(testset, batch_size=128, shuffle=False, num_workers=2)

net = SENet().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)

for epoch in range(10):  # 重复多轮训练
    for i, (inputs, labels) in enumerate(trainloader):
        inputs = inputs.to(device)
        labels = labels.to(device)
        # 优化器梯度归零
        optimizer.zero_grad()
        # 正向传播 + 反向传播 + 优化 
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        # 输出统计信息
        if i % 100 == 0:   
            print('Epoch: %d Minibatch: %5d loss: %.3f' %(epoch + 1, i + 1, loss.item()))

print('Finished Training')

测试结果准确率可达 86.51%。

CBAM

和 SE 相比,CBAM 更使用了空间信息做注意力权重的计算,达到跨通道的信息混合。同时使用 Max pooling 和 Average pooling,以期增加空间不变性,在通道注意力混合不同空间信息,而在空间注意力混合不同通道信息,再顺序性地组合两个区域。这个思想和前面的可分离其实很像,核心都在于分解反向以得到更高的效率。

HybridSN 高光谱分类

首先先按照要求补齐模型代码。这里还是简单用了个 ReLU 作为激活函数(好像就是 ReLU?)。

py
class HybridSN(nn.Module):
    def __init__(self):
        super(HybridSN, self).__init__()
        self.conv3d_1 = nn.Conv3d(1, 8, kernel_size=(7, 3, 3), stride=(1, 1, 1), padding=(0, 0, 0))
        self.conv3d_2 = nn.Conv3d(8, 16, kernel_size=(5, 3, 3), stride=(1, 1, 1), padding=(0, 0, 0))
        self.conv3d_3 = nn.Conv3d(16, 32, kernel_size=(3, 3, 3), stride=(1, 1, 1), padding=(0, 0, 0))
        self.conv2d_1 = nn.Conv2d(576, 64, kernel_size=(3, 3), stride=(1, 1), padding=(0, 0))
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(p=0.4)
        self.fc1 = nn.Linear(18496, 256)
        self.fc2 = nn.Linear(256, 128)
        self.fc3 = nn.Linear(128, class_num)

    def forward(self, x):
        x = self.relu(self.conv3d_1(x))
        x = self.relu(self.conv3d_2(x))
        x = self.relu(self.conv3d_3(x))

        # 2D Conv
        x = x.view(-1, 576, 19, 19)  
        x = self.conv2d_1(x)
        x = self.relu(x)

        # FC
        x = x.view(x.size(0), -1)
        x = self.relu(self.fc1(x))
        x = self.dropout(x)
        x = self.relu(self.fc2(x))
        x = self.dropout(x)
        x = self.fc3(x)

        return x

训练并测试,可以看到正常输出分类类别。准确率在 96.89%。

二、问题总结与体会

思考题

  • 训练 HybridSN,然后多测试几次,会发现每次分类的结果都不⼀样,请思考为什么?

白天重新训练了一波,效果变差了一些。显然几次测试用的都是同一个模型,所以和训练过程等并没有联系,使用的是训练好的权重。我觉得原因是出在 0.4 的 dropout 上,随机关闭的神经元让预测结果变得随机。

  • 如果想要进⼀步提升高光谱图像的分类性能,可以如何改进?

    使用自注意力机制以更好地学习空间和光谱信息之间的关系,Attention is All You Need! 然后就是我觉得不同领域分类任务用的网络框架其实都大同小异,反倒是数据预处理 / 数据增强这一步可以进一步探究。

  • depth-wise conv 和 分组卷积有什么区别与联系?

    深度卷积就是在深度这一个维度上作卷积,对每一个 depth-channel 作卷积;而分组卷积是将通道分组后再进行卷积。两者都用来减少参数量,但前者往往结合可分离卷积,结合 1x1 卷积整合通道信息,进而将计算量的乘法化为加法,而后者仅仅只是减少了一定倍率的计算量而且缺少通道信息的整合。极端情况下,分组卷积分成通道数量那么多组的时候,它和 depth-wise convolution 就没什么区别了。

  • SENet 的注意力是不是可以加在空间位置上?

    可以,这样就可以得到类似于 CBAM 的网络结构。

  • 在 ShuffleNet 中,通道的 shuffle 如何用代码实现?

    查了代码可以看到:

    py
    def shuffle_channels(x, groups):
        """shuffle channels of a 4-D Tensor"""
        batch_size, channels, height, width = x.size()
        assert channels % groups == 0
        channels_per_group = channels // groups
        # split into groups
        x = x.view(batch_size, groups, channels_per_group,
                   height, width)
        # transpose 1, 2 axis
        x = x.transpose(1, 2).contiguous()
        # reshape into orignal
        x = x.view(batch_size, channels, height, width)
        return x

    它先将通道分组,每个组有 channels // groups 个通道,然后将每个组的张量的第 1 和第 2 维度进行转置,最后再恢复到原来的 shape。所以实际上它并不是真的随机分组,而是一种有规律的打乱。

体会

神经网络处理视觉任务很大的一个问题就是参数量爆炸,如何在轻量化和好效果之间权衡前人作了不少研究。这次实验的深度可分离卷积就是其中之一,通过将卷积操作分解为深度卷积和逐点卷积,减少了参数量,同时保持了较好的效果。而 ShuffleNet 通过将通道分组,然后进行通道 shuffle,也是一种减少参数量的方法。感觉轻量化就是现在模型发展的一个大趋势,毕竟移动端上的计算资源有限,保证隐私和安全性也是一个重要的考虑因素。