神经网络初始化问题

log:

2024/11/22:部分图片

1
2
3
4
import numpy
from numpy import random
import matplotlib.pyplot as plt
import math

初始化权重预实验:

【输入】常见做法:将输入值缩放到均值为0,标准差为1的正态分布中。

【权值】初始化:若采用相同的标准正态分布N(0,1)N(0,1)

1
2
3
4
5
x=random.normal(loc=0, scale=1, size=512)#均值为0,方差为1的正态分布,512个数据
for i in range(100):
a=random.normal(loc=0, scale=1, size=(512,512))#a对应每层神经网络中的W:512*512
x=a@x
x.mean(), x.std()###向量.mean()输出所有元素的均值;.std()输出所有元素标准差
(7.943177084708433e+133, 2.849247295078971e+135)

如上可观察到,在100层的神经网络中,对初值和权重取N(0,1)N(0,1)分布,最终输出极大。

1
2
3
4
5
x=random.normal(loc=0, scale=0.01, size=512)#均值为0,方差为0.01的正态分布,512个数据
for i in range(100):
a=random.normal(loc=0, scale=0.01, size=(512,512))
x=a@x
x.mean(), x.std()
(3.0804500580946894e-69, 2.214484543669681e-67)

如上可观察到,在100层的神经网络中,对初值和权重取N(0,0.01)N(0,0.01)分布,最终输出极小。(ps: 在取N(0,0.1)N(0,0.1)时输出也很大)

即:初始化权重过大过小均会影响学习效果

1

上式中y[i]表达式可描述一层神经元的映射关系

1
2
3
4
5
6
7
8
9
mean, var=0.,0.
#i的大小与神经网络层数无关,意义只在于i够大时结果更精确
for i in range(1000):
x=random.normal(loc=0, scale=1, size=(512))
a=random.normal(loc=0, scale=1, size=(512,512))
y=a@x
mean+=y.mean().item()#item()函数取值比索引取值精度更高
var+=numpy.multiply(y,y).mean().item()
mean/1000,math.sqrt(var/1000)
(-0.03692881605069143, 22.68767143694423)
1
math.sqrt(512)
22.627416997969522

此时我们注意到Y的标准差Σjmmean(yi2)m\frac{\Sigma_j^m mean(y_i^2)}{m}【y平均值为0】, 其逼近x维数(神经元个数)的平方根。

1
2
3
4
5
6
7
8
9
#它教会了我如何计算均值为0的一组数据的标准差(通过D(X)=E(X^2))。
mean, var=0.,0.
for i in range(1000):
x=random.normal(loc=0, scale=1, size=(512))
a=random.normal(loc=0, scale=1, size=(512,512))
y=a@x
mean+=y.mean().item()#item()函数取值比索引取值精度更高(不知道为什么)
var+=numpy.multiply(y,y).mean().item()
mean/1000,math.sqrt(var/1000)
(-0.02495151144918943, 22.6154885506345)

一些数学推导:

方差D(X)=E[XE(X)]2D(X)=E([X-E(X)]^2)

D(XY)=E([XYE(XY)]2)D(XY)=E([XY-E(XY)]^2)

又当X,Y相互独立时,有E(XY)=E(X)E(Y)E(XY)=E(X)E(Y)

故有D(XY)=E(X2Y2+E2(XY)2XYE(XY))D(XY)=E(X^2Y^2+E^2(XY)-2XYE(XY))

当X,Y满足正态分布N(0,1)N(0,1),有D(XY)=E(X2Y2)=E(X2)E(Y2)=D(X)D(Y)D(XY)=E(X^2Y^2)=E(X^2)E(Y^2)=D(X)D(Y)

D(X+Y)=E[X+YE(X+Y)]2=E[X+Y]2)=E(X2)+E(Y2)+2E(XY)=E(X2)+E(Y2)=D(X)+D(Y)D(X+Y)=E([X+Y-E(X+Y)]^2)=E([X+Y]^2)=E(X^2)+E(Y^2)+2E(XY)=E(X^2)+E(Y^2)=D(X)+D(Y)

有了上述结论,对N(0,1)N(0,1)分布的X,YX,Y而言,有D(XY)=D(X)D(Y),D(X+Y)=D(X)+D(Y)D(XY)=D(X)D(Y),D(X+Y)=D(X)+D(Y)

因此上述代码中,Y的每个元素方差为512。

上面代码块中var中右式实际上为EY2)E(Y^2),当i迭代次数够大求和平均等价于D(Y)D(Y)

1
2
3
4
5
6
7
8
mean, var=0.,0.
for i in range(1000):
x=random.normal(loc=0, scale=1, size=(512))
a=random.normal(loc=0, scale=1, size=(512,512))*math.sqrt(1./512)#D(cX)=c^2 D(X)
y=a@x
mean+=y.mean().item()
var+=numpy.multiply(y,y).mean().item()
mean/1000,math.sqrt(var/1000)
(-0.0009292457617292619, 0.9998508585319182)

由上可见,把N(0,1)N(0,1)的初始化权值缩放至1/n1/\sqrt{n}后输出层不再出现爆炸。

1
2
3
4
5
x=random.normal(loc=0, scale=1, size=512)#均值为0,方差为1的正态分布,512个数据
for i in range(100):
a=random.normal(loc=0, scale=1, size=(512,512))*math.sqrt(1./512)
x=a@x
x.mean(), x.std()
(-0.013509481923589781, 0.669791021961652)

由上可见,把N(0,1)N(0,1)的初始化权值缩放至1/n1/\sqrt{n}后输出层(均值和方差)不再出现爆炸。即便在100层后亦是如此。

当我们考虑激活函数

饱和激活函数F:limx>F>0lim_{x->\infty}F'->0

非饱和激活函数F:limx>F不趋于0lim_{x->\infty}F'不趋于0

典型的饱和函数有Sigmoid,Tanh函数。

不满足饱和函数条件的函数则称为非饱和激活函数

ReLU及其变体则是“非饱和激活函数”。

使用“非饱和激活函数”的优势在于两点:

  1. "非饱和激活函数”能解决所谓的“梯度消失”问题。

  2. 它能加快收敛速度。

Xavier初始化(适用于饱和激活函数)

image

1
2
3
4
5
x=random.normal(loc=0, scale=1, size=512)#均值为0,方差为1的正态分布,512个数据
for i in range(100):
a=random.normal(loc=0, scale=1, size=(512,512))*math.sqrt(1./512)
x=numpy.array([math.tanh(c)for c in a@x])
x.mean(), x.std()
(0.0008083661917762789, 0.05757114952184833)

可以观察到,在一百层神经网络后,x的均值与标准差值仍处于有效范围,激活未完全消失。

1
2
3
4
5
x=numpy.random.uniform(low=-1,high=1,size=512)#均值为0,方差为1/3的均匀分布(从-1到1取值),512个数据
for i in range(100):
a=random.normal(loc=0, scale=1, size=(512,512))*math.sqrt(1./512)
x=numpy.array([math.tanh(c)for c in a@x])
x.mean(), x.std()
(0.0013361727805947863, 0.0572257326796936)
1
2
3
4
5
6
x=random.normal(loc=0, scale=1, size=512)#均值为0,方差为1的正态分布,512个数据
for i in range(100):
#权值取均值为0,方差为1/3的均匀分布(从-1到1取值),512*512个数据*1/n
a=numpy.random.uniform(low=-1,high=1,size=(512,512))*math.sqrt(1./512)
x=numpy.array([math.tanh(c)for c in a@x])
x.mean(), x.std()
(6.753492971342052e-26, 1.2087455023485755e-24)

注意到两种算法区别在于,上述x的1/3方差只出现一次,大量迭代后不会对后续结果造成影响,后者令a的权值初始化为1/3,a会出现100次,导致了最终激活梯度无穷小。

1
2
3
4
5
6
x=random.normal(loc=0, scale=1, size=512)#均值为0,方差为1的正态分布,512个数据
for i in range(100):
#权值取均值为0,方差为1的均匀分布(从-sqrt3到sqrt3取值),512*512个数据*1/n
a=numpy.random.uniform(low=-1.73205081,high=1.73205081,size=(512,512))*math.sqrt(1./512)
x=numpy.array([math.tanh(c)for c in a@x])
x.mean(), x.std()
(0.0018487857398481779, 0.05713394734193917)

如上所示,当a取均匀分布但方差为1时,100次迭代后x的均值与标准差值仍处于有效范围,激活未完全消失。

因此笔者猜想,或者核心在于使a@x(或者说 WTXW^TX )的方差为1

对于Xavier初始化方法,其将每层的权重设置为:

±6ni+ni+1\pm\frac{\sqrt{6}}{\sqrt{n_i+n_{i+1}}}

上式中nin_i为传入该层的连接的数量(扇入),ni+1n_{i+1}为传出该层的连接的数量,也被称为(扇出)。

1
2
3
4
def xavier(m,h):
return numpy.random.uniform(low=-1,high=1,size=(m,h))*math.sqrt(6./(m+h))
def xbvier(m,h):
return numpy.random.normal(loc=0, scale=1, size=(m,h))*math.sqrt(6./(m+h))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#xavier初始化
x=random.normal(loc=0, scale=1, size=512)#均值为0,方差为1的正态分布,512个数据
for i in range(100):
a=xavier(512,512)
x=numpy.array([math.tanh(c)for c in a@x])
print(x.mean(), x.std())
#xavier初始化,但是权重并非U(-1,1)而是N(0,1)分布
x1=random.normal(loc=0, scale=1, size=512)#均值为0,方差为1的正态分布,512个数据
for i in range(100):
a=xbvier(512,512)
x1=numpy.array([math.tanh(c)for c in a@x1])
print(x1.mean(), x1.std())
#凑方差为1初始化,权重U(-1,1)分布
x2=random.normal(loc=0, scale=1, size=512)#均值为0,方差为1的正态分布,512个数据
for i in range(100):
a=numpy.random.uniform(low=-1,high=1,size=(512,512))*math.sqrt(3./512)
x2=numpy.array([math.tanh(c)for c in a@x2])
print(x2.mean(), x2.std())

#以下为无激活函数的情况
#xavier初始化
x=random.normal(loc=0, scale=1, size=512)#均值为0,方差为1的正态分布,512个数据
for i in range(100):
a=xavier(512,512)
x=a@x
print(x.mean(), x.std())
#xavier初始化,但是权重并非U(-1,1)而是N(0,1)分布
x1=random.normal(loc=0, scale=1, size=512)#均值为0,方差为1的正态分布,512个数据
for i in range(100):
a=xbvier(512,512)
x1=a@x
print(x1.mean(), x1.std())
#凑方差为1初始化,权重U(-1,1)分布
x2=random.normal(loc=0, scale=1, size=512)#均值为0,方差为1的正态分布,512个数据
for i in range(100):
a=numpy.random.uniform(low=-1,high=1,size=(512,512))*math.sqrt(3./512)
x2=numpy.array([math.tanh(c)for c in a@x2])
print(x2.mean(), x2.std())
-0.0011354134828090012 0.06322324251617281
-0.04347840333790784 0.6898032256032508
-0.0025500434343385574 0.04863383928260507
-0.016140350889364705 0.7301846105693588
-0.08559922112658865 1.341156944469779
0.000651830089541752 0.09598924410829489

此时对于权值初始化U(1,1)U(-1,1)的情况,Xavier初始化表现良好。
数学上很简单,此时±6ni+ni+1=±325122\pm\frac{\sqrt{6}}{\sqrt{n_i+n_{i+1}}}=\pm\frac{\sqrt{3*2}}{\sqrt{512*2}},即3512\frac{\sqrt{3}}{\sqrt{512}},等价于令方差为1/3,均值为0,从(-1,1)均匀取值,512个数据的X对应的a@x(或者说 𝑊𝑇𝑋 )的方差为1。

以上内容对有无激活函数情况下:xavier初始化对权重U(-1,1)分布、N(0,1)分布、凑方差1初始化进行了比较。

小结

  1. 有激活函数时方差传递效果比无激活函数时较弱,推测原因在于tanhx这一激活函数本身特性限制了方差传递
  2. xavier初始化对权重N(0,1)分布相较于U(-1,1)分布,理论上每层方差变化分别为3、1,结果上确实可以观测到最终权重N(0,1)分布引出的x1的方差较大,但并未在100层传递下出现爆炸(10000层也不会)。
    方差凑1法仍需继续讨论

Xavier方法的适用条件:

  1. 权重U(-1,1)分布。
    2.激活函数为饱和激活函数。
  2. 网络层为前馈全连接神经网络层。(应该吧)

image

为了说明这一点,Glorot和Bengio证明,使用Xavier初始化的网络在CIFAR-10图像分类任务上实现了更快的收敛速度和更高的准确性。

Kaiming初始化(he初始化)

从概念上讲,当使用关于0对称且在[-1,1]内部有输出(如softsign和tanh)的激活函数时,我们希望每个层的激活输出的平均值为0,平均标准偏差为1,这是有意义的,这也正是Xavier所支持的。

但是如果我们使用ReLU激活函数呢?以同样的方式缩放随机初始权重值是否仍然有意义?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def relu(x):
for i in range(len(x)):
temp=numpy.array([x[i],0])
x[i]=temp.max()
return x

mean, var=0.,0.
for i in range(1000):
x=random.normal(loc=0, scale=1, size=(512))
a=random.normal(loc=0, scale=1, size=(512,512))
y=relu(a@x)
mean+=y.mean().item()
var+=numpy.multiply(y,y).mean().item()
mean/1000,math.sqrt(var/1000)
(9.048598009365435, 16.032879062442056)
1
math.sqrt(512/2)
16.0

我们注意到,RELU激活时y的标准差可直接计算,D(Y)=5122D(Y)=\frac{\sqrt{512}}{\sqrt{2}},即激活函数RELU将使标准差缩小12\frac{1}{\sqrt{2}}

1
2
3
4
5
6
7
8
mean, var=0.,0.
for i in range(1000):
x=random.normal(loc=0, scale=1, size=(512))
a=random.normal(loc=0, scale=1, size=(512,512))*math.sqrt(2/512)
y=relu(a@x)
mean+=y.mean().item()
var+=numpy.multiply(y,y).mean().item()
mean/1000,math.sqrt(var/1000)

使每个初始化权重乘2512\frac{\sqrt{2}}{\sqrt{512}},保持激活层标准差在1左右。

image

个人理解:n\sqrt{n}代表正态分布权值矩阵维度的影响,2\sqrt{2}代表Relu激活函数的影响

1
2
3
4
5
6
7
### 表示扇入数
def kaiming(m,h):
return numpy.random.normal(loc=0,scale=1,size=(m,h))*math.sqrt(2./h)
def kaiming1(m,h):
return numpy.random.normal(loc=0,scale=2,size=(m,h))*math.sqrt(2./h)
def kaiming2(m,h):
return numpy.random.uniform(low=-1,high=1,size=(m,h))*math.sqrt(2./h)
1
2
3
4
5
x=random.normal(loc=0, scale=1, size=512)#均值为0,方差为1的正态分布,512个数据
for i in range(100):
a=kaiming(512,512)
x=relu(a@x)
print(x.mean(), x.std())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
x=random.normal(loc=0, scale=1, size=512)#均值为0,方差为1的正态分布,512个数据
for i in range(100):
a=xavier(512,512)
x=relu(a@x)
print(x.mean(), x.std())

x=random.normal(loc=0, scale=1, size=512)#均值为0,方差为1的正态分布,512个数据
for i in range(100):
a=kaiming1(512,512)
x=relu(a@x)
print(x.mean(), x.std())


x=random.normal(loc=0, scale=1, size=512)#均值为0,方差为1的正态分布,512个数据
for i in range(100):
a=kaiming2(512,512)
x=relu(a@x)
print(x.mean(), x.std())

可以观察到,对于ReLU激活函数,使用Xavier初始化将导致激活输出在100层几乎消失。

对于不能使初始权重方差为1的情况,如U(1,1)N(0,2)U(-1,1)、N(0,2),如此方法表现并不佳,即方差凑1依旧十分重要

image

Kaiming方法的适用条件:

  1. 权重N(0,1)分布(方差为1)。
    2.激活函数为非饱和激活函数ReLU。
  2. 网络层为前馈全连接神经网络层。(应该吧)

总结

  1. 核心:方差凑1

  2. 激活函数(复杂的激活函数无法计算):

  2.1 tanh()&Sigmoid() => Xavier初始化方法:权重U(-1,1)分布6ni+ni+1*\frac{\sqrt{6}}{\sqrt{n_i+n_{i+1}}}

  2.2 ReLU() => kaiming初始化方法:权重N(-1,1)分布2n*\frac{\sqrt{2}}{\sqrt{n}}

此外上述方法理论上在前馈全连接层较为适用

实用语法:

  1. x.item():取出较x直接索引、精度更高的值
  2. numpy.random.normal(loc=0,scale=1,size=(m,h)): m*h矩阵, 元素为N(0,1)正态分布。
  3. numpy.random.uniform(low=-1,high=1,size=(m,h)): m*h矩阵, 元素为U(-1,1)均匀分布。