Funnel ReLU
论文地址:https://arxiv.org/pdf/2007.11824.pdf
代码地址:https://github.com/megvii-model/FunnelAct
非线性激活层地作用是对卷积层地线性输出进行非线性变换。比如:ReLU,PReLU,sigmoid等激活函数。详情可看:https://blog.csdn.net/yx868yx/article/details/106583017
FReLU是专门为视觉任务设计的,概念上很简单:ReLU对激活值为非正的部分用0来处理,表示为y = max(x,0),PReLU是中引入了参数px,y = max(x,px)的形式,FReLU的形式为y = max(x,T(x)),其中T(·)是二维空间条件(2D spatial condition),在激活函数中引入了上下文的信息,将激活函数由1D变为2D。此外,空间条件spatial condition以简单的方式实现了像素级建模能力,并通过常规卷积捕获了复杂的视觉layouts。最后,对ImageNet数据集、COCO数据集检测任务和语义分割任务进行了实验,展示了FReLU激活函数在视觉识别任务中的巨大改进和鲁棒性。
它的实现很简单,只增加了一个可以忽略不计的计算开销。该激活函数的形式是y=max(x,T(x)),其中T(x)代表简单高效的空间上下文特征提取器。由于使用了空间条件,FReLU简单地将ReLU和PReLU扩展为具有像素化建模能力的视觉参数化ReLU。
对funnel条件的定义允许网络在非线性激活中为每个像素生成空间条件。网络进行非线性变换,同时产生空间依赖关系。这与通常的做法不同,后者在卷积层中创建空间相关性,并分别进行非线性变换。在这种情况下,激活并不明确地依赖于空间条件;在funnel ReLU中对它们确实依赖于空间条件。因此,像素级条件使网络具有像素级建模能力,函数max(\cdot )为每个像素提供了一个选择,可以选择是否查看空间上下文。形式上,考虑一个具有n个FReLU层的网络{F1;F2;:::;Fn},每个FReLU层Fi都有一个k * k参数窗口。为了简洁起见,只分析FReLU层而不考虑卷积层。因为最大选择在1 *1和k *k之间,F1之后的每个像素都有一个激活字段集{1,1+r} (r = k - 1).)。在Fn层之后,集合变为{1+r;1+2r;:::;1+nr},这为每个像素提供了更多的选择,如果n足够大,可以近似任何布局。由于激活区有许多不同的尺寸,不同尺寸的正方形可以近似于斜线和弧线的形状(见图4)。众所周知,图像中物体的布局通常不是水平或垂直的,它们通常是斜线或弧线的形状,因此提取物体的空间结构可以通过空间条件提供的像素级建模能力自然解决。我们通过实验表明,在复杂的任务中,它能更好地捕捉不规则和详细的对象布局
描述了funnel条件如何实现像素化建模能力。图中不同大小的正方形代表了顶部激活层中每个像素的不同激活场。(a)正常的激活场,每像素的方块大小相等,只能描述水平和垂直布局。与此相反,max(·)允许每个像素选择在每层中寻找或不寻找,在足够多的层数后,它们有许多不同大小的方块。因此,不同大小的方块可以近似于(b)斜线的形状, ( c)弧线的形状,这些都是比较常见的自然物体布局。
代码:
import torch.nn as nn
import torch
class FReLU(nn.Module):
r""" FReLU formulation. The funnel condition has a window size of kxk. (k=3 by default)
"""
def __init__(self, in_channels):
super(FReLU,self).__init__()
self.conv_frelu = nn.Conv2d(in_channels, in_channels, 3, 1, 1, groups=in_channels)
self.bn_frelu = nn.BatchNorm2d(in_channels)
# 初始化参数
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
elif isinstance(m, nn.BatchNorm2d) and m.affine:
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
def forward(self, x):
x1 = self.conv_frelu(x)
x1 = self.bn_frelu(x1)
x= torch.max(x, x1)
return x
Dynamic ReLU
PDF: https://arxiv.org/pdf/2003.10027.pdf
PyTorch代码: https://github.com/shanglianlm0525/PyTorch-Networks
提出的动态ReLU,能够根据输入动态地调整对应的分段激活函数,与ReLU及其变种对比,仅需额外的少量计算即可带来大幅的性能提升,能无缝嵌入到当前的主流模型中。
提供了三种形态的DY-ReLU,在空间位置和维度上有不同的共享机制。
a) spatial and channel-shared DY-ReLU-A
b) spatial-shared and channel-wise DY-ReLU-B
c) spatial and channel-wise DY-ReLU-C
DY-ReLU-A
空间位置和维度均共享(spatial and channel-shared),计算如图a所示,仅需输出个参数,计算最简单,表达能力也最弱。
DY-ReLU-B
仅空间位置共享(spatial-shared and channel-wise),计算如图b所示,输出个参数。
DY-ReLU-C
空间位置和维度均不共享(spatial and channel-wise),每个维度的每个元素都有对应的激活函数。虽然表达能力很强,但需要输出的参数()太多了,像前面那要直接用全连接层输出会带来过多的额外计算。为此论文进行了改进,计算如图c所示,将空间位置分解到另一个attention分支,最后将维度参数乘以空间位置attention。attention的计算简单地使用卷积和归一化方法,归一化使用了带约束的softmax函数:
代码:
import torch
import torch.nn as nn
import torchvision
import torch.nn.functional as F
class BatchNorm(nn.Module):
def forward(self, x):
return 2 * x - 1
class DynamicReLU_A(nn.Module):
def __init__(self, channels, K=2,ratio=6):
super(DynamicReLU_A, self).__init__()
mid_channels = 2*K
self.K = K
#λ控制 残差范围
self.lambdas = torch.Tensor([1.]*K + [0.5]*K).float()
#a\b系数初始值 a1=1,a2=b1=b2=0,即ReLU
self.init_v = torch.Tensor([1.] + [0.]*(2*K - 1)).float()
self.avg_pool = nn.AdaptiveAvgPool2d(output_size=1)
#类似SE模块,降维再升维
self.dynamic = nn.Sequential(
nn.Linear(in_features=channels,out_features=channels // ratio),
nn.ReLU(inplace=True),
nn.Linear(in_features=channels // ratio, out_features=mid_channels),
nn.Sigmoid(),
BatchNorm()
)
def forward(self, x):
b, c, _, _ = x.size()
y = self.avg_pool(x).view(b, c)
z = self.dynamic(y)
# ab值= λ*残差值 + 初始值
relu_coefs = z.view(-1, 2 * self.K) * self.lambdas + self.init_v
x_perm = x.transpose(0, -1).unsqueeze(-1)
# 激活函数 y=ax+b
output = x_perm * relu_coefs[:, :self.K] + relu_coefs[:, self.K:]
output = torch.max(output, dim=-1)[0].transpose(0, -1)
return output
class DynamicReLU_B(nn.Module):
def __init__(self, channels, K=2,ratio=6):
super(DynamicReLU_B, self).__init__()
mid_channels = 2*K*channels
self.K = K
self.channels = channels
self.lambdas = torch.Tensor([1.]*K + [0.5]*K).float()
self.init_v = torch.Tensor([1.] + [0.]*(2*K - 1)).float()
self.avg_pool = nn.AdaptiveAvgPool2d(output_size=1)
self.dynamic = nn.Sequential(
nn.Linear(in_features=channels,out_features=channels // ratio),
nn.ReLU(inplace=True),
nn.Linear(in_features=channels // ratio, out_features=mid_channels),
nn.Sigmoid(),
BatchNorm()
)
def forward(self, x):
b, c, _, _ = x.size()
y = self.avg_pool(x).view(b, c)
z = self.dynamic(y)
relu_coefs = z.view(-1, self.channels, 2 * self.K) * self.lambdas + self.init_v
x_perm = x.permute(2, 3, 0, 1).unsqueeze(-1)
output = x_perm * relu_coefs[:, :, :self.K] + relu_coefs[:, :, self.K:]
output = torch.max(output, dim=-1)[0].permute(2, 3, 0, 1)
return output
if __name__=='__main__':
model = DynamicReLU_B(64)
print(model)
input = torch.randn(1, 64, 56, 56)
out = model(input)
print(out.shape)
实验对比
我用unet网络训练Kaggle’s Carvana Image Masking Challenge数据集
原网络只训练了5个epoch,10个batchsize,预训练模型很强悍
原网络分割效果:
换了原来网络中的relu激活函数之后,原来的预训练权重再加上就不合适了,我没有重新训练大数据集的分类器,只是想验证一下激活函数,就在更改网络激活函数的情况下,训练了100个epoch,把权重加载上预测
换Funnel ReLU的分割效果:
这里也是换了激活函数后,训练了100次,很耗显存,把bachsize改成4,耗费的时间还是很长的,加载了第100次的权重预测。(肯定没有原来的预训练权重好啊)
换Dynamic ReLU的分割效果:
参考:
https://blog.csdn.net/weixin_44402973/article/details/107864585
https://3g.163.com/dy/article/FIPJD06Q0511DPVD.html
https://blog.csdn.net/shanglianlm/article/details/108534728