DyReLU=SE+Maxout?
DyReLU=SE+Maxout?
paper: https://arxiv.org/abs/2003.10027
❝ 该文是MSRA的研究员在DynamicConv之后,又一次在激活函数方面的改进。相比静态激活函数,动态激活函数可以将MobileNetV2的性能由72.0%提升到76.2%代价仅是增加额外的5%FLOPS。从这个角度来看,这个代价还是非常值得的。
❞
Abstract
ReLU激活函数是深度神经网络中最常用的激活函数,但是ReLU及其改进版(如LeakyReLU,PReLU)均为静态激活函数,对所有输入“一视同仁”。
该文作者提出一种动态ReLU,它通过辅助函数编码输入的全局上下文信息,自适应根据该信息构建分片线性激活函数(piece linear activation)。相比静态激活函数,DyReLU带来的额外计算量几乎可以忽略,但可以极大的提升模型的特征表达能力,尤其适合于轻量型网络。通过简单嵌入MobileNetV2,模型在ImageNet上的性能由72.0%提升到了76.2%,而代价则是额外的5%FLOPS。各位,有没有这个“交易”非常划算???
Introduction
作为在深度学习发展史上留下浓重一笔的ReLU函数,它已被广泛应用到各大网络架构中,比如ResNet、VGG、MobileNet、ShuffleNet等等。然而,ReLU及其改进版均对所有输入“一视同仁”,这就很自然的引起这样的一个疑惑:「激活函数应该是静态的还是应该为动态的」?
针对上述疑惑,作者提出了本文的动态ReLU激活函数(Dynamic ReLU),一种参数化分片线性函数,它参数通过辅助函数
计算得到。下图给出了该动态激活函数示意图,其核心观点在于:通过辅助函数编码输入的全局上下文信息并用于指导后续的分片线性激活函数。

激活函数的这种数据依赖特性使其更具特征表达性能,代价仅为近乎可以忽略的计算量。在此基础上,作者提出了三种类型的DyReLU,区别仅在于是否存在跨空间与通道的共享。
- DyReLUA: 跨空间与通道共享
- DyReLUB;跨空间共享,通道不共享
- DyReLUC:空间与通道均不共享。
作者通过实验得出以下几点发现:
- 通道不共享模式(DyReLUB与DyReLUC)更适合于图像分类任务;
- 通道不共享模式(DyReLUB与DyReLUC)更适合于关键点检测的骨干网络,而空间不共享模式(DyReLUC)更适合于关键点检测的head网络。
- 在图像分类方面,DyReLU在MobileNetV2的嵌入应用可以得到4.2% 的性能提升;
- 在关键点检测方面,DyReLU的应用可以得到3.5AP的性能提升。
Method
DyReLU是一种动态分片线性函数,它的参数具有输入依赖型。尽管DyReLU不会提升网络的深度与宽度,但它可以额外可忽略的计算消耗下提升模型的性能。
Dynamic Activation
给定输入张量,动态激活函数定义为
,其具有可学习参数
,该可学习参数具有数据依赖性,会随输入的变化而变化。

如上图所示,它包含两个函数:
- 辅助函数
:它用于计算激活函数的参数;
- 激活函数
:它用于计算输入的激活输出,它的参数通过上述辅助函数生成。
需要注意的是:辅助函数对输入进行上下文信息编码并用于辅助后续激活函数。这种数据依赖性使得所提DyReLU可以极大的提升模型的表达能能力,尤其适用于轻量型网络。
DyReLU
首先定义传统静态ReLU为,第c个通道的输出可以描述为
。相对的,DyReLU定义为多个线性函数的最大值:
其中线性系数为辅助函数
的输出:
需要注意:「每个通道的激活参数是与所有输入通道相关的」。
前面已经介绍了DyReLU的构成,接下来,将介绍一下如何进行建模。类似DyConv,作者提出了采用类似于squeeze-and-excitation的网络,整个网络有GAP+Linear+ReLU+Linear构成,最后接一个归一化层(注:这里采用的是作用是将输出归一化到
,且输出通道数为2KC,分别对应前述激活参数
)。
上述辅助函数的输出通过如下方式进行更新:
其中为初始值,而
表示用于控制范围的标量,他们均为超参数。当K=2时,上述超参数默认为:
。注:上述参数配置是为了对标静态ReLU。
下表给出了所提方法与其他已有方法的区别,「DyReLU可以视作为SE+Maxout组合」。

Variations of DyReLU
前面从广义形式上介绍了DyReLU,这里将从更精细的角度进行DyReLU的介绍,介绍其三种形态。
- DyReLUA:spatial and channel-shared;
- DyReLUB: spatial-shared and channel-wise;
- DyReLUC: spatial and channel-wise;
注:前述介绍均已DyReLUB为蓝本进行介绍。下表给出了所提三种形态DyReLU的计算复杂度对比。可以看到其计算复杂度极低。

下图给出了上述三种形态DyReLU的网络架构形态,其中DyReLUC最为复杂。

Experiments
在实验方面,作者首先进行了消融实验,对比两三种形态DyReLU在ImageNet与COCO关键点检测方面的性能提升对比。
在图像分类任务中,以MobileNetV2作为对标,相应结果见下图,可以看到:
- (1) DyReLU可以提升模型性能;
- (2) DyReLUB与DyReLUC具有更佳的性能,更适合于图像分类这种任务。

在关键点检测任务中,相关实验结果见下图,可以看到:
- 在骨干网络方面,DyReLUB与DyReLUC明显优于DyReLUA;
- 在head网络方面,DyReLUC明显优于其他两个激活函数。

总体而言,从消融实验方面可以得到两个结论:
- DyReLUB与DyReLUC对于图像分类任务性能提升更大;
- DyReLUB与DyReLUC更适合于关键点检测网络中骨干网络部分,在head网络部分,DyReLUC则更适合。
基于前述消融实验所得结论,在后续的实验中,图像分类任务选择DyReLUB,关键点检测任务选择DyReLUC。
首先在不同网络(MobileNetV2/V3, ResNet)中对比了DyReLU与ReLU的性能差异,可以看到DyReLU取得了一致性的性能提升,在MobileNetV2方面的性能提升最明显,最高达6.1,这进一步说明所提方法比较适合于轻量型网络。

然后,作者对比了不同激活函数的性能,其中DyReLU取得最佳性能。

最后,作者提供了COCO数据集上关键点检测任务的性能对。更多的实验结果建议查看原文。

Conclusion
作者提出一种动态激活函数,该动态激活函数的参数与输入信息存在依赖惯性。相比静态激活函数ReLU,DyReLU可以极大提升模型的表达能力而代价仅为可忽略的额外计算量(5%),DyReLU尤其适合于轻量型网络,比如MobileNetV2。所提方法不仅适合于图像分类任务,同样适合于关键点检测任务,并针对性的了分析不同形态DyReLU在不同部件网络中的适用性。
Reference code
class DyReLUA(nn.Module):
def __init__(self,
channels,
reduction=4,
k=2):
super().__init__()
self.channels = channels
self.reduction = reduction
self.k = k
self.coef = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(channels, channels // reduction, 1),
nn.ReLU(),
nn.Conv2d(channels // reduction, 2 * k, 1),
nn.Sigmoid()
)
# default parameter setting
# lambdaA = 1.0, lambdaB = 0.5;
# alphaA1 = 1, alphaA2=alphaB1=alphaB2=0
self.register_buffer('lambdas', torch.Tensor([1.] * k + [0.5] * k).float())
self.register_buffer('bias', torch.Tensor([1.] + [0.] * (2 * k - 1)).float())
def forward(self, x):
coef = self.coef(x)
coef = 2 * coef - 1
coef = coef.view(-1, 2 * self.k) * self.lambdas + self.bias
# activations
# NCHW --> NCHW1
x_perm = x.permute(1, 2, 3, 0).unsqueeze(-1)
# HWNC1 * NK --> HWCNK
output = x_perm * coef[:, :self.k] + coef[:, self.k:]
result = torch.max(output, dim=-1)[0].permute(3, 0, 1, 2)
return result
class DyReLUB(nn.Module):
def __init__(self,
channels,
reduction=4,
k=2):
super().__init__()
self.channels = channels
self.reduction = reduction
self.k = k
self.coef = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(channels, channels//reduction, 1),
nn.ReLU(),
nn.Conv2d(channels//reduction, 2 * k * channels, 1),
nn.Sigmoid()
)
# default parameter setting
# lambdaA = 1.0, lambdaB = 0.5;
# alphaA1 = 1, alphaA2=alphaB1=alphaB2=0
self.register_buffer('lambdas', torch.Tensor([1.]*k + [0.5]*k).float())
self.register_buffer('bias', torch.Tensor([1.] + [0.]*(2*k - 1)).float())
def forward(self, x):
coef = self.coef(x)
coef = 2 * coef - 1
# coefficient update
coef = coef.view(-1, self.channels, 2 * self.k) * self.lambdas + self.bias
# activations
# NCHW --> HWNC1
x_perm = x.permute(2, 3, 0, 1).unsqueeze(-1)
# HWNC1 * NCK --> HWNCK
output = x_perm * coef[:, :, :self.k] + coef[:, :, self.k:]
# maxout and HWNC --> NCHW
result = torch.max(output, dim=-1)[0].permute(2, 3, 0, 1)
return result
class DyReLUC(nn.Module):
def __init__(self,
channels,
reduction=4,
k=2,
tau=10,
gamma=1/3):
super().__init__()
self.channels = channels
self.reduction = reduction
self.k = k
self.tau = tau
self.gamma = gamma
self.coef = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(channels, channels // reduction, 1),
nn.ReLU(),
nn.Conv2d(channels // reduction, 2 * k * channels, 1),
nn.Sigmoid()
)
self.sptial = nn.Conv2d(channels, 1, 1)
# default parameter setting
# lambdaA = 1.0, lambdaB = 0.5;
# alphaA1 = 1, alphaA2=alphaB1=alphaB2=0
self.register_buffer('lambdas', torch.Tensor([1.] * k + [0.5] * k).float())
self.register_buffer('bias', torch.Tensor([1.] + [0.] * (2 * k - 1)).float())
def forward(self, x):
N, C, H, W = x.size()
coef = self.coef(x)
coef = 2 * coef - 1
# coefficient update
coef = coef.view(-1, self.channels, 2 * self.k) * self.lambdas + self.bias
# spatial
gamma = self.gamma * H * W
spatial = self.sptial(x)
spatial = spatial.view(N, self.channels, -1) / self.tau
spatial = torch.softmax(spatial, dim=-1) * gamma
spatial = torch.clamp(spatial, 0, 1).view(N, 1, H, W)
# activations
# NCHW --> HWNC1
x_perm = x.permute(2, 3, 0, 1).unsqueeze(-1)
# HWNC1 * NCK --> HWNCK
output = x_perm * coef[:, :, :self.k] + coef[:, :, self.k:]
# permute spatial from NCHW to HWNC1
spatial = spatial.permute(2, 3, 0, 1).unsqueeze(-1)
output = spatial * output
# maxout and HWNC --> NCHW
result = torch.max(output, dim=-1)[0].permute(2, 3, 0, 1)
return result
欢迎关注AIWalker公众号,在这里您将得到独家深度学习经验分享与个人思考。想支持Happy继续写下去就点个赞关注一下吧!

浙公网安备 33010602011771号