激活函数是神经网络中的非线性函数,为了增强网络的表示能力和学习能力,激活函数有以下几点性质:

  • 连续且可导(允许少数点上不可导)的非线性函数。可导的激活函数可以直接利用数值优化的方法来学习网络参数。
  • 激活函数及其导函数要尽可能的简单,有利于提高网络计算效率。
  • 激活函数的导函数的值域要在一个合适的区间内(不能太大也不能太小),否则会影响训练的效率和稳定性。

Sigmoid

Sigmoid函数(也被称为Logistic函数)的表达式如下:
σ ( x ) = exp ⁡ ( x ) exp ⁡ ( x ) + exp ⁡ ( 0 ) = 1 1 + e x p ( − x ) \sigma(x)=\frac{\exp (x)}{\exp (x)+\exp (0)} = \frac {1}{1+exp(-x)} σ(x)=exp(x)+exp(0)exp(x)=1+exp(x)1

其导数为
d d x σ ( x ) = σ ( x ) ( 1 − σ ( x ) ) \frac{d}{d x} \sigma(x)=\sigma(x)(1-\sigma(x)) dxdσ(x)=σ(x)(1σ(x))

其图像如下图,是一个S型曲线,所以Sigmoid函数可以看做一个“挤压”函数,把一个实数域的输入“挤压”到(0,1)。当输入值在0附近时,Sigmoid函数近似为线性函数;当输入值靠近两端时,对输入进行抑制;输入越小,越接近于0;输入越大,越接近于1。

在这里插入图片描述

from matplotlib import pyplot as plt
import numpy as np
import torch
from torch import nn

x = np.linspace(-6, 6, 600)
m0 = nn.Sigmoid()
output0 = m0(torch.Tensor(x))
plt.plot(x, output0, label='Sigmod')
plt.title("Sigmoid Activation Function")
plt.xlabel("x")
plt.ylabel("Activation")
plt.grid()
plt.legend()
plt.show()

Sigmoid激活函数的缺点:

  • 倾向于梯度消失
  • 函数输出不是以0为中心,会使其后一层的神经元的输入发生偏置偏移(Bias Shift),进而使得梯度下降的收敛速度变慢,也就是会降低权重更新的效率
  • 公式中包括指数运算,计算机运行较慢

Tanh

Tanh 函数也是一种S型函数,其定义为
t a n h ( x ) = exp ⁡ ( x ) − exp ⁡ ( − x ) exp ⁡ ( x ) + exp ⁡ ( − x ) tanh(x)=\frac{\exp (x) - \exp (-x)}{\exp (x)+\exp (-x)} tanh(x)=exp(x)+exp(x)exp(x)exp(x)

Tanh函数可以看做放大并平移的Sigmoid函数,其值域为(-1,1),并且Tanh与Sigmoid函数关系如下式:
t a n h ( x ) = 2 σ ( 2 x ) − 1 tanh(x) = 2 \sigma(2x) -1 tanh(x)=2σ(2x)1
Tanh函数如下图所示,它的输入是零中心化的了。
在这里插入图片描述

x = np.linspace(-6, 6, 600)
m0 = nn.Sigmoid()
output0 = m0(torch.Tensor(x))
plt.plot(x, output0, label='Sigmod')
m0_1 = nn.Tanh()
output0_1 = m0_1(torch.Tensor(x))
plt.plot(x, output0_1, label='Tanh')

plt.title("Sigmoid and Tanh Activation Functions")
plt.xlabel("x")
plt.ylabel("Activation")
plt.grid()
plt.legend()
plt.show()

ReLU

ReLU(Rectified Linear unit)是最常见的激活函数,其公式为:
R e L U ( x ) = { x    x ≥ 0 0    x < 0 = m a x ( 0 , x ) \begin {aligned} ReLU(x) &= \begin{cases} x \ \ \qquad x \ge 0 \\ 0 \ \ \qquad x<0 \end{cases} \\ &= max(0, x) \end {aligned} ReLU(x)={x  x00  x<0=max(0,x)
ReLU函数示意及后面会介绍的几种变种如下图所示:

在这里插入图片描述

x = np.linspace(-6, 6, 600)
m0 = nn.ReLU()
output0 = m0(torch.Tensor(x))
plt.plot(x, output0, label='RELU')
m1 = nn.LeakyReLU()
output1 = m1(torch.Tensor(x))
plt.plot(x, output1, label='LeakyRELU', color='red', linestyle='--')
m2 = nn.ELU()
output2 = m2(torch.Tensor(x))
plt.plot(x, output2, label='ELU', linestyle='dotted')
m3 = nn.Softplus()
output3 = m3(torch.Tensor(x))
plt.plot(x, output3, label='Softplus', linestyle='-.')

plt.title("ReLu and It's Varies Activation Functions")
plt.xlabel("x")
plt.ylabel("Activation")
plt.grid()
plt.legend()
plt.show()

ReLU函数的优点是:1. 采用ReLU的神经元只需要进行加、乘和比较的操作,计算上更加高效。2. ReLU函数被认为具有生物学合理性,比如单侧抑制、宽兴奋边界。在生物神经网络中,同时处于兴奋状态的神经元非常稀疏,比如人脑中在同一时刻大概只有 1% ∼ 4% 的神经元处于活跃状态。Sigmoid 型激活函数会导致一个非稀疏的神经网络,而 ReLU 却具有很好的稀疏性,大约 50% 的神经元会处于激活状态.3. 相对于sigmoid函数的两端饱和,ReLU函数为左饱和函数,且在x>0时的导数为1,所以相比之下一定程度上缓解了梯度消失问题,加速梯度下降的收敛速度。

ReLU函数的缺点是:1. 函数输出是非零中心化的,会使其后一层的神经元的输入发生偏置偏移(Bias Shift),进而使得梯度下降的收敛速度变慢。2. ReLU神经元在训练时比较容易”dead",如果参数在一次不恰当的更新后,第一个隐藏层中的某个ReLU神经元在所有的训练数据上都不能被激活,那么这个神经元自身参数的梯度永远都会是0,在以后的训练过程中永远不能被激活,这种现象被称为死亡ReLU问题(Dying ReLU Problem)。(其他隐藏层也是有可能发生的)

为了避免ReLU的缺点,有以下几种广泛使用的ReLU变种

Leaky ReLU

Leaky ReLU的公式如下,也就是使输入x<0时,保持一个很小的梯度 γ \gamma γ,使得神经元非激活时也有一个非零的梯度可以更新参数,避免永远不能被激活:
L e a k y R e L U ( x ) = { x    x > 0 γ x    x ≤ 0 = m a x ( 0 , x ) + γ m i n ( 0 , x ) \begin {aligned} LeakyReLU(x) &= \begin{cases} x \ \ \qquad x > 0 \\ \gamma x \ \ \qquad x \le 0 \end{cases} \\ &= max(0, x) + \gamma min(0,x) \end {aligned} LeakyReLU(x)={x  x>0γx  x0=max(0,x)+γmin(0,x)
γ \gamma γ是一个很小的常数,如0.01。 当 γ < 1 \gamma <1 γ<1时,Leaky ReLU 也可以写为
L e a k y R e L U ( x ) = m a x ( x , γ x ) LeakyReLU(x) = max(x, \gamma x) LeakyReLU(x)=max(x,γx)

PReLU

PRuLU(Parametric ReLU)引入了一个可学习的参数,不同神经元可以有不同的参数。对第i个神经元的PReLU定义为:
P R e L U i ( x ) = { x    x > 0 γ i x    x ≤ 0 = m a x ( 0 , x ) + γ i m i n ( 0 , x ) \begin {aligned} PReLU_i(x) &= \begin{cases} x \ \ \qquad x > 0 \\ \gamma_i x \ \ \qquad x \le 0 \end{cases} \\ &= max(0, x) + \gamma_i min(0,x) \end {aligned} PReLUi(x)={x  x>0γix  x0=max(0,x)+γimin(0,x)
其中 γ i \gamma_i γi x ≤ 0 x \le 0 x0时函数的斜率,所以PReLU也是非饱和函数。

如果 γ i = 0 \gamma_i=0 γi=0,PReLU就退化为ReLU。

如果 γ i \gamma_i γi是一个很小的常数,则PReLU就可以看作LeakyReLU。

PReLU可以允许不同神经元具有不同的参数,也可以一组神经元共享一个参数。

ELU

ELU(Exponential Linear Unit)的定义如下:
E R e L U ( x ) = { x    x > 0 γ ( e x p ( x ) − 1 )    x ≤ 0 = m a x ( 0 , x ) + m i n ( 0 , γ ( e x p ( x ) − 1 ) ) \begin {aligned} EReLU(x) &= \begin{cases} x \ \ \qquad x > 0 \\ \gamma (exp(x) - 1) \ \ \qquad x \le 0 \end{cases} \\ &= max(0, x) + min(0,\gamma (exp(x) - 1)) \end {aligned} EReLU(x)={x  x>0γ(exp(x)1)  x0=max(0,x)+min(0,γ(exp(x)1))
定义中的 γ ≥ 0 \gamma \ge 0 γ0是一个超参数,决定 x ≤ 0 x \le 0 x0时的饱和曲线,并调整输出均值在0附近,所以ELU是一个近似的零中心化的非线性函数。

SoftPlus

SoftPlus可以看作ReLU函数的平滑版本,其定义为:
S o f t p l u s ( x ) = l o g ( 1 + e x p ( x ) ) Softplus(x) = log(1 + exp(x)) Softplus(x)=log(1+exp(x))
SoftPlus的导数是Sigmoid函数

SoftPlus函数也有与ReLU函数一样的单侧抑制、宽兴奋边界的特性,但没有稀疏激活性。

Maxout

Maxout的输入是上一层神经元的全部原始输出,是一个向量 x = [ x 1 ; x 2 ; ⋯   , ; x D ] \mathbf{x} = [x_1;x_2;\cdots,;x_D] x=[x1;x2;,;xD]

每个Maxout单元有K个权重向量 w k ∈ R D \mathbf{w}_k \in \mathbb{R}^D wkRD ( w k = [ w k , 1 , ⋯   , w k , D ] T \mathbf{w}_k = [w_{k, 1}, \cdots, w_{k,D}]^T wk=[wk,1,,wk,D]T 为第k个权重向量) 和偏置 b k ( 1 ≤ k ≤ K ) b_k(1 \le k \le K) bk(1kK), 对于输入 x \mathbf{x} x,可以得到K个净输入 z k z_k zk 1 ≤ k ≤ K 1 \le k \le K 1kK:
z k = w k T x + b k z_k = \mathbf{w}_k^T x + b_k zk=wkTx+bk
Maxout单元的非线性函数定义为
m a x o u t ( x ) = max ⁡ k ∈ [ 1 , K ] ( z k ) maxout(\mathbf{x}) = \max_{k\in[1,K]} (z_k) maxout(x)=k[1,K]max(zk)
Maxout激活函数可以看做任意凸函数的分段线性近似,并且在有限的点上是不可微的。

Mish

Mish的表达如下式
M i s h ( x ) = x ∗ t a n h ( S o f t p l u s ( x ) ) = x ∗ t a n h ( l n ( 1 + e x ) ) \begin{aligned} Mish(x) &=x∗tanh(Softplus(x)) \\ &= x*tanh(ln(1+e^x)) \end {aligned} Mish(x)=xtanh(Softplus(x))=xtanh(ln(1+ex))
Mish的函数图像如下图

在这里插入图片描述

m1 = nn.Mish()
output1 = m1(torch.Tensor(x))
plt.plot(x, output1, label='Mish')
plt.title("Mish Activation Function")
plt.xlabel("x")
plt.ylabel("Activation")
plt.grid()
plt.legend()
plt.show()

Swish

Swish的定义如下:
s w i s h ( x ) = x σ ( β x ) = x 1 1 + e x p ( − β x ) \begin {aligned} swish(x) &= x \sigma(\beta x) \\ &= x \frac{1}{1+exp(-\beta x)} \end {aligned} swish(x)=xσ(βx)=x1+exp(βx)1
σ \sigma σ是sigmoid函数, β \beta β是可学习的参数或者一个固定超参数。 σ ( . ) ∈ ( 0 , 1 ) \sigma(.) \in (0,1) σ(.)(0,1) 可以看作一种软性的门控机制,当 σ ( β x ) \sigma(\beta x) σ(βx) 接近于1时,门的状态为“开”状态,激活函数的输出近似于x本身;当 σ ( β x ) \sigma(\beta x) σ(βx) 接近于0时,门的状态为“关”,激活函数的输出近似于0.

Swish函数的示意图如下图

在这里插入图片描述

x = np.linspace(-6, 6, 600)
m1 = nn.SiLU()
output1 = m1(torch.Tensor(x))
plt.plot(x, output1, label='Swish')
plt.title("Swish Activation Function")
plt.xlabel("x")
plt.ylabel("Activation")
plt.grid()
plt.legend()
plt.show()
  • β = 0 \beta=0 β=0时, Swish函数变成线性函数x/2
  • β = 1 \beta=1 β=1时, Swish函数在x>0时近似线性,在x<0时近似饱和,同时有一定的单调性
  • β → + ∞ \beta \rightarrow + \infty β+时, Swish函数近似为ReLU函数

所以Swish函数可以看做线性函数和ReLU函数之间的非线性插值函数,其程度由 β \beta β控制

GELU

GELU (Gaussian Error Linear Unit) 也是通过门控机制来调整其输出值的激活函数,其表达式为:
G E L U ( x ) = x P ( X ≤ x ) GELU(x) = xP(X \le x) GELU(x)=xP(Xx)
其中的 P ( X ≤ x ) P(X \le x) P(Xx)是高斯分布 N ( μ , σ 2 ) \mathcal{N}(\mu, \sigma^2) N(μ,σ2)的累积分布函数, μ \mu μ σ \sigma σ也是超参数,一般取标准分布,即 μ = 0 , σ = 1 \mu=0, \sigma=1 μ=0,σ=1

由于高斯分布的累积分布函数为S型函数,所以它可以用Tanh和Sigmoid函数来近似:
G E L U ( x ) ≈ 0.5 x ( 1 + t a n h ( 2 π ( x + 0.044715 x 3 ) ) ) G E L U ( x ) ≈ x σ ( 1.702 x ) GELU(x) \approx 0.5x \left( 1 + tanh (\sqrt{\frac{2}{\pi}} (x+0.044715x^3) ) \right) \\ GELU(x) \approx x \sigma(1.702x) GELU(x)0.5x(1+tanh(π2 (x+0.044715x3)))GELU(x)xσ(1.702x)
当用sigmoid函数来近似时,GELU相当于一种特殊的Swish函数。

GELU的示意图如下:

在这里插入图片描述

x = np.linspace(-6, 6, 600)
m1 = nn.GELU()
output1 = m1(torch.Tensor(x))
plt.plot(x, output1, label='GELU')
plt.title("GELU Activation Function")
plt.xlabel("x")
plt.ylabel("Activation")
plt.grid()
plt.legend()
plt.show()

大模型gpt3使用GELU激活函数

GLU

Gated Linear Units (GLU) 是在论文《Language Modeling with Gated Convolutional Networks》 中被提出来的,计算表达式为:
G L U ( a , b ) = a   ⊗   σ ( b ) GLU(a, b) = a \ \otimes \ \sigma(b) GLU(a,b)=a  σ(b)
上式中的 σ \sigma σ是sigmoid函数, ⊗ \otimes 是矩阵间的按元素乘。

从直觉上来说,对于语言模型门控(gate)机制允许选择对于预测下一个单词更重要的单词或特征。

在论文《GLU Variants Improve Transformer》 中下面几种GLU变种被提出。

ReGLU

ReGLU是采用ReLU函数作为激活函数的GLU变体
R e G L U ( x , W , V , b , c ) = m a x ( 0 , x W + b ) ⊗ ( x V + c ) ReGLU(x, W, V, b, c) = max(0, xW + b) \otimes (xV +c) ReGLU(x,W,V,b,c)=max(0,xW+b)(xV+c)
JINA EMBEDDINGS 2 对于large版本使用的是ReGLU,其作者说对于大模型使用GEGLU相对没有那么稳定。

SwiGLU

SwiGLU可以看做采用Swish作为激活函数的GLU变体
S w i G L U ( x , W , V , b , c ) = S w i s h 1 ( x W + b ) ⊗ ( x V + c ) SwiGLU(x, W, V, b, c) = Swish_1(xW + b) \otimes (xV +c) SwiGLU(x,W,V,b,c)=Swish1(xW+b)(xV+c)
Meta开源的LLaMALLaMA2 以及 Baichuan大模型使用的激活函数是SwiGLU。

GEGLU

GEGLU则可以看做采用GELU作为激活函数的GLU变体
G E G L U ( x , W , V , b , c ) = G E L U ( x W + b ) ⊗ ( x V + c ) GEGLU(x, W, V, b, c) = GELU(xW + b) \otimes (xV +c) GEGLU(x,W,V,b,c)=GELU(xW+b)(xV+c)

GLM-130B 大模型使用的是GEGLU。

资源

  1. https://www.jiqizhixin.com/articles/2021-02-24-7
  2. 邱锡鹏《神经网络与深度学习》
  3. A Survey of Large Language Models
  4. https://zhuanlan.zhihu.com/p/650237644
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐