【论文笔记】CondConv: Conditionally Parameterized Convolutions for Efficient Inference

本文介绍了条件参数化卷积(CondConv),一种用于深度学习的新型卷积层,旨在打破传统卷积的静态特性。CondConv允许每个样本拥有定制的卷积核,通过学习的权重函数动态计算,从而提高模型的容量,同时保持高效的推理计算。实验证明CondConv在ImageNet分类和COCO目标检测任务上提升了模型性能。该方法通过引入多个专家卷积核并基于输入动态分配权重,实现了计算效率与模型性能的平衡。
摘要由CSDN通过智能技术生成

论文

论文题目:CondConv: Conditionally Parameterized Convolutions for Efficient Inference

论文地址:https://arxiv.org/abs/1904.04971

发表于:NeurIPS 2019

代码地址(TensorFlow版本):tpu/models/official/efficientnet/condconv at master · tensorflow/tpu · GitHub

代码地址(Pytorch版本):GitHub - xmu-xiaoma666/External-Attention-pytorch: 🍀 Pytorch implementation of various Attention Mechanisms, MLP, Re-parameter, Convolution, which is helpful to further understand papers.⭐⭐⭐

参考博客:【经典重温】所有数据无需共享同一个卷积核!谷歌提出条件参数化卷积CondConv(附Pytorch复现代码) (qq.com)

 动态卷积|CondConv - 知乎

修改当前的常用卷积(卷积参数共享),提出条件卷积,(更似一种动态卷积),打破了静态卷积的设定:卷积核对所有输入“一视同仁”。提升卷积核生成的计算量要远比添加更多卷积或更多通道数高效CondConv同样突出一个重要的研究问题:如何更好的覆盖、表达与利用样本的相关性以提升模型性能

 有关动态卷积可参考:【论文笔记】Dynamic Convolution: Attention over Convolution Kernels_m0_61899108的博客-CSDN博客

前言

卷积层是深度神经网络的基本组成部分之一,目前的卷积网络的一个基本假设是卷积核应该为数据集中的所有样本所共享 。在本文中,作者打破了这个假设,提出了条件参数化卷积(CondConv) ,它为每个样本学习专门的卷积核。用CondConv替换普通卷积能够增加网络的大小和容量,同时保持有效的推理。

通过实验,作者证明了CondConv网络在分类和检测任务上,改善了现有几种卷积神经网络结构的性能和推理成本的权衡。在ImageNet分类任务上,将EfficientNet-B0中的卷积替换为CondConv,可以在 413M multiply-adds的计算量下,实现78.3%的Top-1准确率。

动机

卷积神经网络(CNN)在计算机视觉的许多任务上都取得了SOTA的性能,而这些性能的提高主要来自于模型大小和容量的增加。然而,目前增加模型容量的方法计算成本很高,大模型的推理往往需要占据巨大的计算资源,这限制了其在移动设备上的部署和计算。

卷积层设计中的一个基本假设是,相同的卷积核应用于数据集中的每个样本 。为了增加模型的容量,通常会添加更多的卷积层或增加现有卷积层的大小(卷积核高度/宽度、输入/输出通道的数量)。在这两种情况下,额外的计算成本与卷积输入的大小成比例地增加,这使得卷积网络的计算量可能非常大。

目前有许多计算机视觉应用,比如自动驾驶,要求模型是低延迟、能够实时计算的,因此在推理时减少计算量成为了一个非常重要的工作。在本文中,作者提出了条件卷积的思想来设计模型,这使得深度学习能够更好地服务于这些应用。

在本文中,作者提出的条件参数化卷积(CondConv) ,通过基于输入来动态计算卷积核,从而避免了传统静态的卷积中所有样本共享一个卷积核的缺点。具体地,作者将CondConv层中的卷积核参数化为n个专家(卷积核)的线性组合,即,其中是通过梯度下降学习的权重函数。

这比增加卷积核本身的大小更具计算效率,因为所有专家在每个输入中仅组合一次,并且这些专家(卷积核)组合之后只需要进行一次卷积,因此,多出来的额外计算只是计算权重 和聚合卷积核 的计算量,而这些计算量相比于卷积的计算量是非常小的。

CondConv可以作为CNN结构中卷积层的替代品。作者证明了用CondConv替换卷积层可以提高CNN结构在ImageNet分类和COCO目标检测方面的模型容量和性能,同时保持高效的推理。在分析中,作者发现,CondConv层能够通过输入样本学习语义上有意义的关系来计算条件卷积核。

方法

 在标准的卷积层中,所有输入样本都使用相同的卷积核。在CondConv层中,卷积核是根据输入样本的函数进行动态计算(如上图所示)。具体而言,作者通过以下方式参数化CondConv中的卷积核:

 其中,每个\alpha {_{i}} = r_{i}(x)是使用具有可学习参数的路由函数计算的依赖于输入样本的权重,n是专家(卷积核)的数量,σ是激活函数。当用CondConv替换标准卷积层时,每个卷积核wi与原始卷积中的卷积核具有相同的维度。

在以前的工作中,通常通过增加卷积核的高度/宽度或输入/输出通道的数量来增加常规卷积层的容量。但是,通过扩大卷积核的方式带来的额外计算量与输入特征图的大小呈正相关,而输入特征图中的像素数可能很大。

在CondConv层中,在应用卷积之前,首先将每个样本的卷积核计算为一组卷积核的线性组合。每个卷积核只需要计算一次,但应用于输入图像中的许多不同位置,所以这种方法可以在控制计算量的情况下提升模型的学习能力。通过增加卷积核的数量,就可以增加网络容量,而推理成本的增加相对来说比较少(相对于进行多次卷积来说)。

 CondConv层在数学上等同于计算更昂贵的专家线性混合(mixture of experts)的公式,其中每个专家对应于静态卷积(如上图所示):

因此,CondConv与n个专家的线性混合具有相同的容量,但计算效率更高,因为它只需要计算一次的卷积操作。此外,每个样本的路由函数对CondConv性能至关重要:极端情况下,如果学习到的路由函数对于所有样本都是常量,则CondConv层的容量与静态卷积层是相同的。

作者希望设计一个计算效率高、能够有意义地区分输入样本并易于解释的路由函数。因此,作者通过三个步骤来计算依赖于输入样本的路由权重\alpha {_{i}} = r_{i}(x),这三个步骤分别为全局平均池化、全连接层、Sigmoid激活函数:

 其中R是一个可学习的路由权重矩阵,将汇集的输入映射到n个专家权重。然后,基于这个动态聚合卷积核进行正常的卷积运算,因此本文的路由函数允许使用整个输入的全局上下文对局部运算进行自适应。

实验

ImageNet Classification

上表展示了在ImageNet分类任务上,不同baseline模型在添加CondConv前后计算量和性能的对比。可以看出,加入CondConv之后,模型能够在很少的计算量提升下,达到显著的性能提升。

 上图展示了MobileNetV1上,加入CondConv后的实验结果,可以看出增加CondConv专家的数量可以导致更好的模型性能。

Ablation studies

Routing function

 上表展示了不同路由函数的生成的结果。

CondConv Layer Depth

 此外,作者还在 CondConv-MobileNetV1 (0.25x)模型中分析了不同深度的CondConv层的影响。可以看出,CondConv层应用于网络中的每一层都能提高性能。

Analysis

 作者可视化了网络中三个不同深度(第12层、第26层和最终的全连接层)的四个不同类别(cliff, pug, goldfish, plane)的平均路由权重。在网络的早期层,路由权重在类之间的分布非常相似,并且在以后的层中变得越来越特定于类。

 然后,作者分析了最终的全连接层的路由权重分布。可以看出,路由权重遵循双峰分布。 

为了更好地理解每个专家的作用,作者对ImageNet验证集上四个专家的平均路由权重最高的前10个样本进行了可视化,可以看出CondConv层是能够根据图片的视觉和语义信息分配不同的权重的

总结

在本文中,作者提出了条件参数化卷积(CondConv) ,CondConv挑战了卷积核应该在所有输入样本中共享的假设。这为在保持有效推理的同时增加模型容量引入了一个新的方向:增加卷积核生成函数的大小和复杂性 。

由于卷积核只计算一次,然后在输入特征上进行卷积,因此增加卷积核生成函数的复杂性比添加额外的卷积或扩展现有卷积要有效得多。CondConv还强调了大型数据集趋势中的一个重要研究问题,即如何最好地发现、表示和利用样本之间的关系来提高模型性能。

在这篇文章之后,也有很多文章基于这篇文章的思想继续完善,比如Dynamic Convolution,DYNet。其实,MoE的思想大家应该都能想到,就是用多分支的结构,然后基于输入的数据去决定不同分支的权重,从而来产生依赖于输入数据的动态权重,相当于就变成了每个输入数据有自己的专属网络结构。

但是这样的操作其实引入的计算量很大,因为要进行多次卷积,这篇文章的妙就妙在,他不是对卷积之后的结果进行加权,而是对卷积核进行加权,所以就不需要进行多次卷积了 。这样可以大大减少计算量了,而计算权重和聚合卷积核的计算量相比于卷积的计算量是非常小的,所以这样的方式是非常高效的。

关键代码

# https://github.com/xmu-xiaoma666/External-Attention-pytorch/blob/master/model/conv/CondConv.py

import torch
from torch import nn
from torch.nn import functional as F

class Attention(nn.Module):
    def __init__(self,in_planes,K,init_weight=True):
        super().__init__()
        self.avgpool=nn.AdaptiveAvgPool2d(1)
        self.net=nn.Conv2d(in_planes,K,kernel_size=1,bias=False)
        self.sigmoid=nn.Sigmoid()

        if(init_weight):
            self._initialize_weights()


    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            if isinstance(m ,nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

    def forward(self,x):
        att=self.avgpool(x) #bs,dim,1,1
        att=self.net(att).view(x.shape[0],-1) #bs,K
        return self.sigmoid(att)

class CondConv(nn.Module):
    def __init__(self,in_planes,out_planes,kernel_size,stride,padding=0,dilation=1,grounps=1,bias=True,K=4,init_weight=True):
        super().__init__()
        self.in_planes=in_planes
        self.out_planes=out_planes
        self.kernel_size=kernel_size
        self.stride=stride
        self.padding=padding
        self.dilation=dilation
        self.groups=grounps
        self.bias=bias
        self.K=K
        self.init_weight=init_weight
        self.attention=Attention(in_planes=in_planes,K=K,init_weight=init_weight)

        self.weight=nn.Parameter(torch.randn(K,out_planes,in_planes//grounps,kernel_size,kernel_size),requires_grad=True)
        if(bias):
            self.bias=nn.Parameter(torch.randn(K,out_planes),requires_grad=True)
        else:
            self.bias=None
        
        if(self.init_weight):
            self._initialize_weights()

        #TODO 初始化
    def _initialize_weights(self):
        for i in range(self.K):
            nn.init.kaiming_uniform_(self.weight[i])

    def forward(self,x):
        bs,in_planels,h,w=x.shape
        softmax_att=self.attention(x) #bs,K
        x=x.view(1,-1,h,w)
        weight=self.weight.view(self.K,-1) #K,-1
        aggregate_weight=torch.mm(softmax_att,weight).view(bs*self.out_planes,self.in_planes//self.groups,self.kernel_size,self.kernel_size) #bs*out_p,in_p,k,k

        if(self.bias is not None):
            bias=self.bias.view(self.K,-1) #K,out_p
            aggregate_bias=torch.mm(softmax_att,bias).view(-1) #bs,out_p
            output=F.conv2d(x,weight=aggregate_weight,bias=aggregate_bias,stride=self.stride,padding=self.padding,groups=self.groups*bs,dilation=self.dilation)
        else:
            output=F.conv2d(x,weight=aggregate_weight,bias=None,stride=self.stride,padding=self.padding,groups=self.groups*bs,dilation=self.dilation)
        
        output=output.view(bs,self.out_planes,h,w)
        return output

if __name__ == '__main__':
    input=torch.randn(2,32,64,64)
    m=CondConv(in_planes=32,out_planes=64,kernel_size=3,stride=1,padding=1,bias=False)
    out=m(input)
    print(out.shape)

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值