第一个版本 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>0x≤0,α∈(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>0x≤0,α∈(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} xc∈x的全局上下文进行编码,以确定合适的激活函数。
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)=1≤k≤Kmax{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
2⋅sigmoid(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版本。