Dynamic ReLU激活函数

第一个版本 Dynamic ReLU激活函数

论文链接:A Dynamic Rectified Linear Activation Units

年份:2019

1.简介

如果在相邻的两个训练期激活函数不同,那么相同梯度值的概率就会小。我们提出了一种新的激活函数,其形状可以在训练中动态改变。实验结果表明,这种具有“动态”特性的激活函数能够有效地避免梯度消失,使多层感知器神经网络更加深入。基于ReLU结合Leaky ReLU的特性,将Dynamic ReLU定义为:
f ( x ) = { x , x > 0 α β x , x ≤ 0 , α ∈ ( 0 , 1 ) , β = ∣ m s e τ − 1 ∣ f(x) = \begin{cases} x,&x>0\\ \alpha \beta x , &x\le 0 \end{cases}, \alpha\in(0, 1), \beta = |mse_{\tau-1}| f(x)={x,αβx,x>0x0,α(0,1),β=mseτ1
其中 τ \tau τ为当前训练的Epoch, α = 0.02 \alpha=0.02 α=0.02,而 β \beta β为动态变量,由当前训练的前一次的误差决定。因为 β \beta β在训练中是一个变量,这导致Dynamic ReLU函数的 α β \alpha \beta αβ是一个变量而不是一个常数。它不同于Leaky ReLU 的常系数 α \alpha α。与ReLU和leaky ReLU相比,Dynamic ReLU函数在整个训练过程中几乎扫描了所有的第三象限区域,在负象限区域比LReLU更灵活。

为了避免MSE值过大而导致失败,又提出了一个变体函数称为Exp-Dynamic ReLU:
E x p _ D R e L U ( x ) = { x , x > 0 α γ x , x ≤ 0 , α ∈ ( 0 , 1 ) , β = ∣ m s e τ − 1 ∣ , γ = e − β Exp\_DReLU(x) = \begin{cases}x, &x>0\\ \alpha \gamma x, &x\le 0\\ \end{cases}, \alpha\in(0, 1), \beta=|mse_{\tau-1}|, \gamma= e^{-\beta} Exp_DReLU(x)={x,αγx,x>0x0,α(0,1),β=mseτ1,γ=eβ
Exp-Dynamic ReLU可以有效地抑制 β \beta β的分布。

2.分析

  • Dynamic ReLU激活函数不需要额外的计算
  • Dynamic ReLU是动态的、收敛的。
  • Dynamic ReLU收敛性是独特的。
  • Dynamic ReLU具有更好的稳定性、准确性和高效率。
  • 没有试验就没有发言权,但该函数是一个动态的ReLU,具体的效果应该不错,但负区域不会为0,这样稀疏性的优势就没有了,而且真正运算起来,训练时间较长。

第二个版本 Dynamic ReLU激活函数

论文链接:Dynamic ReLU

年份:2020

1.简介

修正线性单元(ReLU)是深度神经网络中常用的激活函数。到目前为止,ReLU及其变体(非参数或参数)是静态的,对所有输入样本执行相同的操作。由此提出Dynamic ReLU (DY-ReLU),一个动态整流器的参数是由超函数产生的所有输入元素。关键的是DY-ReLU将全局上下文编码为超函数,并相应地适应分段线性激活函数。与静态对等体相比,DY-ReLU的额外计算成本可以忽略不计,但表示能力显著提高,特别是对于轻量级神经网络。

Dynamic ReLU (DY-ReLU)是一个动态分段函数,其参数依赖于输入。它既不增加网络的深度,也不增加网络的宽度,但可以有效地增加模型的能力,而额外的计算成本可以忽略不计。动态激活函数被定义为具有可学习参数 θ ( x ) \theta(x) θ(x) f θ ( x ) ( x ) f_{\theta(x)}(x) fθ(x)(x)的激活函数。
在这里插入图片描述

  • 超函数 θ ( x ) \theta(x) θ(x):计算激活函数的参数。
  • 激活函数 f θ ( x ) ( x ) f_{\theta(x)}(x) fθ(x)(x):利用参数 θ ( x ) \theta(x) θ(x)产生所有通道的激活函数。
    超参数对所有输入元素 x c ∈ x x_c \in \mathbf{x} xcx的全局上下文进行编码,以确定合适的激活函数。

2. 定义

ReLU的公式为: f ( x ) = m a x ( x , 0 ) f(x) = max(x, 0) f(x)=max(x,0) x x x为输入向量。对于第 c t h c^{th} cth通道的输入为 x c x_c xc,由此可以计算出激活值为 f c ( x ) = m a x ( x c , 0 ) f_{c}(x) = max(x_c, 0) fc(x)=max(xc,0)。将ReLU被推广为一个参数分段函数 f c ( x ) = m a x k ( a c k x + b c k ) f_{c}(x) = max_k(a_c^k x + b_c^k) fc(x)=maxk(ackx+bck),根据所有输入元素 x = { x c } \mathbf{x} = \{x_c\} x={xc}调整 a c k a_c^k ack b c k b_c^k bck,将这个分段函数由静态扩展到动态。
f θ ( x ) ( x ) = max ⁡ 1 ≤ k ≤ K { a c k ( x ) x c + b c k ( x ) } f_{\theta(x)}(x) = \max_{1\le k \le K}\{a_c^k(\mathbf{x})x_c + b_c^k(\mathbf{x})\} fθ(x)(x)=1kKmax{ack(x)xc+bck(x)}
其中 ( a c k , b c k ) (a_c^k, b_c^k) (ack,bck) θ ( x ) \theta(\mathbf{x}) θ(x)的输出:
[ a 1 1 , ⋯   , a C 1 , ⋯   , a 1 K , ⋯   , a C K , b 1 1 , ⋯   , b i K , ⋯   , b 1 K , ⋯   , b C K ] T = θ ( x ) \left[a_1^1, \cdots, a_C^1, \cdots, a_1^K, \cdots, a_C^K, b_1^1, \cdots, b_i^K, \cdots, b_1^K, \cdots, b_C^K \right]^T = \theta(\mathbf{x}) [a11,,aC1,,a1K,,aCK,b11,,biK,,b1K,,bCK]T=θ(x)
K K K为函数数量, C C C为通道数量。激活参数不仅与 x c x_c xc相关,也与 x j ≠ c x_{j\neq c} xj=c相关。这些超参数具体怎么获得呢?

2.1 超函数 θ ( x ) \theta(x) θ(x)的定义:

超参数 θ ( x ) \theta(x) θ(x)就是一些列的层结构,与SENet网络的Squeeze-and-Excitation的结构相似。对于一个输入维度为 R C × H × W \mathbb{R}^{C\times H\times W} RC×H×W x \mathbf{x} x,首先对空间信息进行全局平均池化压缩输出为 R C × 1 × 1 \mathbb{R}^{C\times 1\times 1} RC×1×1;再经过FC(全连接层)层 → \to ReLU激活函数 → \to FC(全连接层)层 → \to Normalization层。其中Normalization层为 2 ⋅ s i g m o i d ( x ) − 1 2\cdot sigmoid(x) - 1 2sigmoid(x)1,最终输出按初始化和变化之和计算如下:
a c k ( x ) = α k + λ a Δ a c k ( x ) a_c^k(x) = \alpha^k + \lambda_a \Delta a_c^k(x) ack(x)=αk+λaΔack(x)
b c k ( x ) = β k + λ b Δ b c k ( x ) b_c^k(x) = \beta^k + \lambda_b \Delta b_c^k(x) bck(x)=βk+λbΔbck(x)

其中 α k \alpha^k αk β k \beta^k βk分别为 a c k a_c^k ack b c k b_c^k bck的初始值。 λ a \lambda_a λa λ b \lambda_b λb是控制残差输出系数的尺度。 α k \alpha^k αk β k \beta^k βk λ a \lambda_a λa λ b \lambda_b λb是超参数。当 K = 2 K=2 K=2时,默认值为 α 1 = 1 \alpha^1 = 1 α1=1 α 2 = β 1 = β 2 = 0 \alpha^2=\beta^1=\beta^2 = 0 α2=β1=β2=0。而 λ a \lambda_a λa λ b \lambda_b λb分别是1.0和0.5。

2.2 各种Dynamic ReLU的变体

2.2.1 DY-ReLU-A:
  • 所有空间位置和通道共享相同的分段线性激活函数。计算量较小,但表示能力较弱
    Dynamic ReLU-A的具体运算示意图如下图所示:
    在这里插入图片描述
    具体的Dynamic ReLU-A的pytorch代码如下:
import torch
import torch.nn as nn
import torchvision
import torch.nn.functional as F

class Normalization(nn.Module): #将输出系数归一化到(-1,1)之间
    def forward(self, x):
        return 2 * nn.sigmoid(x) - 1
        
class DynamicReLU_A(nn.Module):
    def __init__(self, channels, K=2,ratio=6):
        super(DynamicReLU_A, self).__init__()
        mid_channels = 2*K #2代表两个系数即斜率与截距,K为函数的个数

        self.K = K
        self.lambdas = torch.Tensor([1.]*K + [0.5]*K).float() #[1.0, 1.0, 0.5, 0.5]
        self.init_v = torch.Tensor([1.] + [0.]*(2*K - 1)).float()#[1.0, 0, 0, 0]

        self.avg_pool = nn.AdaptiveAvgPool2d(output_size=1)#平均池化
        self.dynamic = nn.Sequential(
            nn.Linear(in_features=channels,out_features=channels // ratio),#第一个FC
            nn.ReLU(inplace=True),
            nn.Linear(in_features=channels // ratio, out_features=mid_channels),#第二个FC
            Normalization()
        )

    def forward(self, x):
        b, c, _, _ = x.size()#维度[b, c, h, w]
        y = self.avg_pool(x).view(b, c)#维度[b, c]
        z = self.dynamic(y)#维度 [b, 2*K]

        relu_coefs = z.view(-1, 2 * self.K) * self.lambdas + self.init_v #首先把z维度变成[b, 2*k],然后乘系数加上偏置,输出维度[b, 2*k]
        x_perm = x.transpose(0, -1).unsqueeze(-1)#[b, c, h, w]-->[w, c, h, b]-->[w, c, h, b, 1 ]目的是为了让batch维度放在最后,这样容易与系数相乘
        output = x_perm * relu_coefs[:, :self.K] + relu_coefs[:, self.K:]# relu_coefs[:,:self.k]也就是最后一个维度的前k个为斜率,后k个为截距,输出维度为[w, c, h, b, 2]
        output = torch.max(output, dim=-1)[0].transpose(0, -1) # [w,c,h,b,2]-->[w,c,h,b,1]-->[w,c,h,b]-->[b,c,h,w]
        return output


##测试:
x = torch.rand([128, 3, 28, 28])
dyrelu_a = DynamicReLU_A(channels=3)
output = dyrelu_a(x)

2.2.2 DY-ReLU-B:

  • 空间共享而通道不共享,超函数输出 2 K C 2KC 2KC个参数,每个通道 2 K 2K 2K个参数。
    Dynamic ReLU-B的具体运算示意图如下图所示
    在这里插入图片描述
    具体的Dynamic ReLU-B的pytorch代码如下:
class Normalization(nn.Module): #将输出系数归一化到(-1,1)之间
    def forward(self, x):
        return 2 * nn.sigmoid(x) - 1
        
class DynamicReLU_B(nn.Module):
    def __init__(self, channels, K=2,ratio=6):
        super(DynamicReLU_B, self).__init__()
        mid_channels = 2*K*channels #输出的特征数为2*k*channels就,这里主要是通道不共享,为了之后变形出通道维度

        self.K = K
        self.channels = channels
        self.lambdas = torch.Tensor([1.]*K + [0.5]*K).float()#[1., 1., 0.5, 0.5]
        self.init_v = torch.Tensor([1.] + [0.]*(2*K - 1)).float()#[1., 0., 0.,0.]

        self.avg_pool = nn.AdaptiveAvgPool2d(output_size=1)# 平均池化[b, c, 1, 1]
        self.dynamic = nn.Sequential(
            nn.Linear(in_features=channels,out_features=channels // ratio),#第一个FC层[b, c/ratio]
            nn.ReLU(inplace=True),
            nn.Linear(in_features=channels // ratio, out_features=mid_channels),#第二个FC层[b, 2*k*c]
            Normalization()#归一化将输出归一到(-1, 1)之间[b, 2*k*c]
        )

    def forward(self, x):
        b, c, _, _ = x.size()#[b, c, h, w]
        y = self.avg_pool(x).view(b, c)#[b, c, 1, 1]-->[b, c]
        z = self.dynamic(y)#[b, 2*k*c]

        relu_coefs = z.view(-1, self.channels, 2 * self.K) * self.lambdas + self.init_v#[b, c, 2*k],这里的将z变化为[b, c, 2*k],其它的维度没有了,把通道这个维度提取出来,因为通道不共享。
        x_perm = x.permute(2, 3, 0, 1).unsqueeze(-1)#[b, c, h, w]--->[h, w, b, c]--->[h, w, b, c, 1]
        output = x_perm * relu_coefs[:, :, :self.K] + relu_coefs[:, :, self.K:]#[h, w, b, c, 1]--->[h, w, b, c, 2]
        output = torch.max(output, dim=-1)[0].permute(2, 3, 0, 1)
        #[h, w, b, c, 2]--->max--->[h, w, b, c, 1]--->[h, w, b, c]--->permute--->[b, c, h, w]
        return output

2.2.3 DY-ReLU-C :

  • 空间通道都不共享,虽然效果最好,但是参数太大。
    空间位置和通道均不共享,每个维度的每个元素都有对应的激活函数, m a x k { a c , h , w k x c , h , w + b c , h , w k } max_k\{a_{c,h,w}^k x_{c,h,w} + b_{c,h,w}^k\} maxk{ac,h,wkxc,h,w+bc,h,wk}。这个版本的表达能力很强,但需要输出的参数为 2 K C H W 2KCHW 2KCHW。由此需要用全连接层输出带来额外的计算,将空间位置分解到另一个Attention分支,最后将维度参数乘以空间位置 π 1 : H W \pi_{1:HW} π1:HW。具体的Attention的计算使用 1 × 1 1\times 1 1×1和归一化的方法,归一化使用带约束的Softmax函数。
    π h , w = min ⁡ { γ e z h , w τ ∑ h , w e z h , w τ , 1 } \pi_{h,w} =\min \{ \frac{\gamma e^{\frac{z_{h,w}}{\tau}}}{\sum_{h,w}e^{\frac{z_{h,w}}{\tau}}}, 1 \} πh,w=min{h,weτzh,wγeτzh,w,1}
    γ \gamma γ用于平均Attention,设为 H W 3 \frac{HW}{3} 3HW,早期训练阶段使用 τ = 10 \tau=10 τ=10来防止稀疏化。
    Dynamic ReLU-C的具体运算示意图如下图所示
    在这里插入图片描述
    具体的Dynamic ReLU-C的pytorch代码如下:
class Normalization(nn.Module): #将输出系数归一化到(-1,1)之间
    def forward(self, x):
        return 2 * F.sigmoid(x) - 1
    
def softmax(z,gamma,t):

    bs=z.shape[0]# batch_size
    results=torch.zeros_like(z)
    for i in range(bs):
        x=z[i]#当前batch的数据
        x=x/t # 防止稀疏
        x_exp=torch.exp(x) #分子
        x_sum=torch.sum(x_exp) #分母
        result=gamma*x_exp/x_sum # softmax
        result[result>1]=1 #min(result, 1),比较result与1谁更小
        results[i]=result
    return results

class Spatial_Attention(nn.Module):
    def __init__(self, t=10, divisor=3):
        super(Spatial_Attention,self).__init__()
        self.t = t
        self.divisor = divisor
        
    def forward(self, x):
        H,w = x.shape[2:]
        gamma = H*w/self.divisor
        in_channel = x.shape[1]
        x = nn.Conv2d(in_channel, 1, kernel_size=1)(x)#1x1的卷积,输出channel为1
        #[b, c, w, h]--->[b, 1, h, w]
        x = softmax(x, gamma, self.t)
        #[b, 1, w, h]所有元素均少于1
        return x
class DynamicReLU_C(nn.Module):
    def __init__(self, channels, K=2,ratio=6,t=10,d=3):
        super(DynamicReLU_C, self).__init__()
        mid_channels = 2*K*channels*1 #输出的特征数为2*k*channels

        self.K = K
        self.t = t
        self.d = d
        self.ratio = ratio
        self.channels = channels
        self.lambdas = torch.Tensor([1.]*K + [0.5]*K).float()#[1., 1., 0.5, 0.5]
        self.init_v = torch.Tensor([1.] + [0.]*(2*K - 1)).float()#[1., 0., 0.,0.]

        self.avg_pool = nn.AdaptiveAvgPool2d(output_size=1)# 平均池化[b, c, 1, 1]
        self.dynamic = nn.Sequential(
            nn.Linear(in_features=channels,out_features=channels // self.ratio),#第一个FC层[b, c/ratio]
            nn.ReLU(inplace=True),
            nn.Linear(in_features=channels // self.ratio, out_features=mid_channels),#第二个FC层[b, 2*k*c]
            Normalization()#归一化将输出归一到(-1, 1)之间[b, 2*k*c*1]
        )
        self.spatial = Spatial_Attention(t,d)

    def forward(self, x):
        b, c, _, _ = x.size()#[b, c, h, w]
        y = self.avg_pool(x).view(b, c)#[b, c, 1, 1]-->[b, c]
        z = self.dynamic(y)#[b, 2*k*c]
        s = self.spatial(x)#[b,1,h,w]
        relu_coefs = z.view(-1, self.channels, 2 * self.K) * self.lambdas + self.init_v#[b, c, 2*k]
        x_perm = x.permute(2, 3, 0, 1).unsqueeze(-1)#[b, c, h, w]--->[h, w, b, c]-->[h,w,b,c,1]
        output = x_perm * relu_coefs[:, :, :self.K] + relu_coefs[:, :, self.K:]
        s = s.permute(2,3,0,1).unsqueeze(-1)#[b, 1, h, w]--->[h,w,b,1]--->[h,w,b,1,1]
        output = s*output
        output = torch.max(output, dim=-1)[0].permute(2, 3, 0, 1)
        return output

3.分析

  • Dynamic ReLU激活函数传说是ReLU激活函数最好的改版
  • 通过试验表现,Dynamic ReLU的性能确实不错,但是导致参数量大量增加,对于一个小型网络,参数量的增加可以接受。但当用于一个大型网络,参数量的增加是十分恐怖的,特别是对于Dynamc ReLU-C版本。
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值