前言
论文地址: https://arxiv.org/pdf/1505.00853.pdf.
论文贡献:
这篇论文并没有提出什么新的激活函数,而是对现有的非常火的几个非饱和激活函数作了一个系统性的介绍以及对他们的性能进行了对比。最后发现,在较小的数据集中(大数据集未必),Leaky ReLU及其变体(PReLU、RReLU)的性能都要优于ReLU激活函数;而RReLU由于具有良好的训练随机性,可以很好的防止过拟合。
一、背景
我们在设计神经网络时候,在选择激活函数方面,大家都有一个常识:使用非饱和激活函数取代替饱和激活函数。主要原因有两个:1)非饱和激活函数可以解决梯度爆炸问题;2)非饱和激活函数可以加快模型收敛速度。
而在非饱和激活函数中,最成功的使用范围最广的当属ReLU激活函数,在上一篇博客: ReLU Activation(2011).中我们就系统性的讨论了ReLU激活函数的由来,以及它的优缺点。我们可以知道,ReLU激活函数成功的最大秘籍在于它具有稀疏性(Sparsity)特征。
但是在最新的Leaky ReLU家族中,却打破了这个秘籍,在 x<0 部分主动的使用一些非零梯度来代替原先全为0(稀疏性)。原因也可以从前一篇博客中知道:因为 x<0,太绝对了,很容易在训练的时候产生神经元坏死现象:某些神经元可能永远不会被激活,导致相应参数永远不会被更新(在负数部分,梯度为0)。
人们先后又提出了Leaky ReLU及其变体函数来解决这个问题。4个函数图像如下图:
一、ReLU
R e L U = m a x ( 0 , x ) = { 0 , if x<0 x , if x ≥ 0 ReLU = max(0, x) = \begin{cases} 0, & \text {if x<0} \\ x, & \text{if x$\geq$0} \end{cases} ReLU=max(0,x)={0,x,if x<0if x≥0
函数图像和导函数图像如下:
二、Leaky ReLU
函数公式:
f
(
x
)
=
m
a
x
(
a
x
,
x
)
=
{
a
x
,
if x<0
x
,
if x
≥
0
f(x)=max(ax,x) = \begin{cases} ax, & \text {if x<0} \\ x, & \text{if x$\geq$0} \end{cases}
f(x)=max(ax,x)={ax,x,if x<0if x≥0
原论文中建议a最好小于0.01,但我们在设计的时候a通常会设为0.01。
函数图像:
理论上Leaky ReLU可以解决上述的dead ReLU现象。
三、PReLU(parametric ReLU)
函数公式:
注意:
- α i \alpha_i αi是可通过反向传播学习到的参数
函数图像:
理论上也可以避免dead ReLU现象;
四、RReLU(Randomized ReLU)
函数公式:
f
(
x
)
=
m
a
x
(
a
x
,
x
)
=
{
a
x
,
if x<0
x
,
if x
≥
0
f(x)=max(ax,x) = \begin{cases} ax, & \text {if x<0} \\ x, & \text{if x$\geq$0} \end{cases}
f(x)=max(ax,x)={ax,x,if x<0if x≥0
注意:
- 训练时,a服从均匀分布 U ( l , u ) , l < u U(l, u), l<u U(l,u),l<u and l , u ∈ [ 0 , 1 ) l, u \in[0, 1) l,u∈[0,1)
- 测试时,将训练的所有的a取平均值(有点像BN)
函数图像:
理论上也可以避免dead ReLU现象;
五、实验结果
CIFAR-10:
CIFAR-100:
NDSB:
可以看到在三个数据上Leaky ReLU、PReLU、RReLU的表现都要优于当前使用最多的激活函数ReLU。但这仅仅是在小数据集上的表现,更大的数据集更复杂的任务的情况下,还需要更多的实验。
六、PyTorch实现
自己代码实现
class ActivateFunc():
def __init__(self, x, b=None, lamb=None, alpha=None, a=None):
super(ActivateFunc, self).__init__()
self.x = x
self.b = b
self.lamb = lamb
self.alpha = alpha
self.a = a
def ReLU(self):
y = np.where(self.x < 0, 0, self.x)
y_grad = np.where(self.x < 0, 0, 1)
return [y, y_grad]
def LeakyReLU(self): # a大于1,指定a
y = np.where(self.x < 0, self.x / self.a, self.x)
y_grad = np.where(self.x < 0, 1 / self.a, 1)
return [y, y_grad]
def PReLU(self): # a大于1,指定a
y = np.where(self.x < 0, self.x / self.a, self.x)
y_grad = np.where(self.x < 0, 1 / self.a, 1)
return [y, y_grad]
class RReLU(Module):
__constants__ = ['lower', 'upper', 'inplace']
lower: float
upper: float
inplace: bool
def __init__(
self,
lower: float = 1. / 8,
upper: float = 1. / 3,
inplace: bool = False
):
super(RReLU, self).__init__()
self.lower = lower
self.upper = upper
self.inplace = inplace
def forward(self, input: Tensor) -> Tensor:
return F.rrelu(input, self.lower, self.upper, self.training, self.inplace)
def extra_repr(self):
inplace_str = ', inplace=True' if self.inplace else ''
return 'lower={}, upper={}{}'.format(self.lower, self.upper, inplace_str)
调包实现:
import torch.nn as nn
activation_cfg = {
# layer_abbreviation: module
'ReLU': nn.ReLU,
'LeakyReLU': nn.LeakyReLU,
'PReLU': nn.PReLU,
'RReLU': nn.RReLU,
# 'ReLU6': nn.ReLU6,
# 'SELU': nn.SELU,
# 'CELU': nn.CELU
}
def build_activation_layer(cfg):
""" Build activation layer
Args:
cfg (dict): cfg should contain:
type (str): Identify activation layer type.
layer args: args needed to instantiate a activation layer.
Returns:
layer (nn.Module): Created activation layer
"""
assert isinstance(cfg, dict) and 'type' in cfg
cfg_ = cfg.copy()
layer_type = cfg_.pop('type')
if layer_type not in activation_cfg:
raise KeyError('Unrecognized activation type {}'.format(layer_type))
else:
activation = activation_cfg[layer_type]
if activation is None:
raise NotImplementedError
layer = activation(**cfg_)
return layer