SELU:自标准化的指数线性单元.
- paper:Self-Normalizing Neural Networks
- arXiv:link
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}\]SELU在ELU的基础上额外增加了一个缩放因子:
\[\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. 实验分析
作者对比了使用BatchNorm和SELU激活函数的效果。实验表明SELU激活函数具有类似于BatchNorm的对隐藏层自动进行归一化的效果,且提供了更稳定的训练过程和更好的模型表现。