SELU:自标准化的指数线性单元.

SELU全称是scaled exponential linear unit,是对ELU (exponential linear unit)的改进。

SELU激活函数的设计思路是若激活函数的输入是均值为$0$,方差为$1$的i.i.d.随机变量,则通过激活函数后仍然保持均值为$0$,方差为$1$。

激活函数处的数据流如上图所示。记$a_1,…,a_K$为网络上一层的输出,假设它们是独立同分布的随机变量(不一定服从Gaussian),求和后得到$z=\sum_{k=1}^{K}a_kw_k$;由中心极限定理$z$近似服从Gaussian。通过设计SELU激活函数使得本层输出$a=f(z)$仍然服从Gaussian,则实际上一层的输出(若也使用SELU)也是服从Gaussian的。

通过合适的激活函数参数设计,可以使得SELU激活函数的输出服从$\mathcal{N}(0,1)$。通过合适的参数初始化方法,可以使得激活函数的输入也近似服从$\mathcal{N}(0,1)$。

1. SELU的表达式

ELU的表达式为:

\[\text{ELU}(x) = \begin{cases} x, & \text{if $x≥0$} \\ α(e^x-1), & \text{if $x<0$} \end{cases}\]

SELUELU的基础上额外增加了一个缩放因子:

\[\text{SELU}(x) = \begin{cases} \lambda x, & \text{if $x≥0$} \\ \lambda α(e^x-1), & \text{if $x<0$} \end{cases}\]

其中:

\[α=1.6732632423543772848170429916717\] \[\lambda=1.0507009873554804934193349852946\]

2. 输出均值为0,方差为1

SELU激活函数通过选择合适的参数$\alpha,\lambda$,使得输出服从$\mathcal{N}(0,1)$。参数的取值可以通过求解方程得到。

给出输出应满足的一阶统计量(均值$=0$)对应的积分方程:

\[\int_{-∞}^{+∞} \frac{1}{\sqrt{2\pi}}e^{-\frac{x^2}{2}} \cdot \text{SELU}(x)dx = 0\]

及二阶统计量(方差$=1$)对应的积分方程:

\[\int_{-∞}^{+∞} \frac{1}{\sqrt{2\pi}}e^{-\frac{x^2}{2}} \cdot (\text{SELU}(x))^2dx = 1\]

整理得到方程组:

\[\int_{-∞}^{0} \frac{1}{\sqrt{2\pi}}e^{-\frac{x^2}{2}} \cdot \lambda α(e^x-1)dx + \int_{0}^{+∞} \frac{1}{\sqrt{2\pi}}e^{-\frac{x^2}{2}} \cdot \lambda xdx = 0\] \[\int_{-∞}^{0} \frac{1}{\sqrt{2\pi}}e^{-\frac{x^2}{2}} \cdot \lambda^2 α^2(e^x-1)^2dx + \int_{0}^{+∞} \frac{1}{\sqrt{2\pi}}e^{-\frac{x^2}{2}} \cdot \lambda^2 x^2dx = 1\]

使用sympy库可以快速求解上述方程组:

import sympy
from sympy import Symbol, nsolve, integrate

x = Symbol('x')
a = Symbol('a')
l = Symbol('l')
int1 = integrate(sympy.exp(-x**2/2)*(sympy.exp(x)-1), (x,-sympy.oo,0))
int2 = integrate(sympy.exp(-x**2/2)*x, (x,0,sympy.oo))
fn1 = a*l/sympy.sqrt(2*sympy.pi)*int1 + l/sympy.sqrt(2*sympy.pi)*int2 - 0
int3 = integrate(sympy.exp(-x**2/2)*(sympy.exp(x)-1)**2, (x,-sympy.oo,0))
int4 = integrate(sympy.exp(-x**2/2)*x**2, (x,0,sympy.oo))
fn2 = a**2*l**2/sympy.sqrt(2*sympy.pi)*int3 + l**2/sympy.sqrt(2*sympy.pi)*int4 - 1
z = nsolve([fn1,fn2], [a,l], [1,1])
print(z) # Matrix([[1.67326324235438], [1.05070098735548]])

求解得到:

\[α=1.67326324235438\] \[\lambda=1.05070098735548\]

3. 输入均值为0,方差为1

SELU有效的前提是输入激活函数的变量应近似服从$\mathcal{N}(0,1)$,下面讨论如何实现。

激活函数的输入表示为$z=\sum_{k=1}^{K}a_kw_k$, 其中$a_1,…,a_K$为网络上一层的输出,$w_1,…,w_K$为网络参数。

计算输入$z$的均值:

\[E[z] = E[\sum_{k=1}^{K}a_kw_k]\]

假设网络上一层的输出为独立同分布的随机变量,且均值为$E[a_k]=0$;则:

\[E[z] = E[\sum_{k=1}^{K}a_kw_k] = \sum_{k=1}^{K}w_kE[a_k] = 0\]

计算输入$z$的方差:

\[D[z] = E[(\sum_{k=1}^{K}a_kw_k)^2]-E^2[\sum_{k=1}^{K}a_kw_k]= E[(\sum_{k=1}^{K}a_kw_k)^2] \\ = E[\sum_{k=1}^{K}(a_kw_k)^2+\sum_{i=1}^{K}\sum_{j=1}^{K}a_iw_ia_jw_j]= E[\sum_{k=1}^{K}(a_kw_k)^2] = \sum_{k=1}^{K}w_k^2E[a_k^2]\]

假设网络上一层输出的方差为$D[a_k]=E[a_k^2]=1$;则:

\[D[z] = \sum_{k=1}^{K}w_k^2E[a_k^2] = \sum_{k=1}^{K}w_k^2 = \sum_{k=1}^{K}D[w] =K \cdot D[w]\]

不妨取$E[w]=0$,$D[w]=\frac{1}{K}$,则有:

\[D[z] = \sum_{k=1}^{K}w_k^2 = \sum_{k=1}^{K}(w_k-E[w])^2 = \sum_{k=1}^{K}D[w] =K \cdot D[w] = 1\]

综上所述,若希望激活函数的输入近似服从$\mathcal{N}(0,1)$,则需要上一层的输出独立同分布于$\mathcal{N}(0,1)$,且该层参数服从$\mathcal{N}(0,\frac{1}{K})$。

在实践中,若所有隐藏层都使用SELU激活函数,则隐藏层的输入自动满足近似服从$\mathcal{N}(0,1)$;对于输入层只需对数据进行归一化即可;对于每一层的参数则可以从$\mathcal{N}(0,\frac{1}{K})$中采样进行随机初始化。

4. 实验分析

作者对比了使用BatchNormSELU激活函数的效果。实验表明SELU激活函数具有类似于BatchNorm的对隐藏层自动进行归一化的效果,且提供了更稳定的训练过程和更好的模型表现。