为什么要初始化权重
权重初始化的目的是防止深度神经网络的正向(前向)传播过程中层激活函数的输出损失梯度出现爆炸或消失.如果发生任何一种情况,损失梯度太大或者太小,就无法有效向后反传,即便可以向后反传,网络也需要花更长的时间来达到收敛.
矩阵乘法是神经网络的基本数学运算。在多层深度神经网络中,一个正向传播仅需要在每层对该层的输入和权重矩阵执行连续的矩阵乘法。这样每层的乘积成为后续层的输入,依此类推。
训练神经网络的标准做法,是让输入值落入类似一个均值为0,标准差为1的正态分布中,以确保其被归一化。
假设有一个没有激活函数的简单的100层网络,并且每层都有一个包含这层权重的矩阵a,我们有一个包含网络输入的向量x。为了完成单个正向传播,我们必须对每层输入和权重进行矩阵乘法,总共100次连续的矩阵乘法。
x = torch.randn(512)
for i in range(100):
a = torch.randn(512,512)
x = a @ x
if torch.isnan(x.std()):
print(i)
break
28
在网络的第29个激活层输出发生梯度爆炸,很明显网络的权重初始化值过大。
为了看看当网络权重初始值太小时会发生什么 - 我们将缩小例子的权重值。
x = torch.randn(512)
for i in range(100):
a = torch.randn(512,512) * 0.01
x = a @ x
print(x.mean(), x.std())
tensor(0.), tensor(0.)
在上述正向传播过程中,激活层输出出现了完全消失的现象。
总结一下,权重初始值太大或者太小,网络都将无法很好地进行学习。
怎样才能找到初始化最佳值?
可以证明,在某给定层,根据标准正态分布初始化的输入x和权重矩阵a的乘积,通常具有非常接近输入连接数平方根的标准差,在我们的例子中是 512 \sqrt{512} 512。
如果我们从矩阵乘法定义来看这个值就再正常不过了:为了计算y,我们将输入向量x的某个元素乘以权重矩阵a的一列所得的512个乘积相加。在我们的例子中使用了标准正态分布来初始化x和a,所以这512个乘积的均值都为0,标准差都为1。
mean, var = 0.0, 0.0
import torch, math
for i in range(10000):
x = torch.randn(1)
w = torch.randn(1)
y = w @ x
mean += y.item()
var += y.pow(2).item()
print(mean/10000, math.sqrt(var/10000))
(-0.00445413,0.9906728)
然后,这512个乘积的总和的均值为0,方差为512,因此标准差为 512 \sqrt{512} 512。
mean, var = 0.0, 0.0
import torch, math
for i in range(10000):
x = torch.randn(512)
w = torch.randn(512, 512)
y = w @ x
mean += y.mean().item()
var += y.pow(2).mean().item()
print(mean/10000, math.sqrt(var/10000))
0.0007024360485374928 22.634334207951234
print(math.sqrt(512))
22.627416997969522
我们想要的是每层输出具有大约1的标准差,这样就可以使我们在尽可能多的网络层上重复矩阵乘法,而不会发生梯度爆炸或消失。
mean, var = 0.0, 0.0
import torch, math
for i in range(10000):
x = torch.randn(512)
w = torch.randn(512, 512) * math.sqrt(1./512)
y = w @ x
mean += y.mean().item()
var += y.pow(2).mean().item()
print(mean/10000, math.sqrt(var/10000))
-0.00018611006735591218 0.9996481987888542
让我们重新运行之前的100层神经网络。与之前一样,我们首先从[-1,1]内的标准正态分布中随机选择层权重值,但这次我们用 1 n \frac{1}{\sqrt n} n1来缩小权重,其中n是每层的网络输入连接数,在我们的例子是512。
x = torch.randn(512)
for i in range(100):
a = torch.randn(512,512) * math.sqrt(1./512)
x = a @ x
print(x.mean(), x.std())
tensor(0.0358), tensor(1.0825)
即使连续传播了100层之后,层输出也没有发生梯度爆炸或者消失。
Xavier初始化
直到几年前,最常用的激活函数还是基于给定点对称的,并且函数曲线在该点加/减一定数值的范围内。双曲正切线和softsign函数就是这类激活函数。
我们在假设的100层网络每一层之后添加双曲正切激活函数,然后看看当使用我们自己的权重初始化方案时会发生什么,这里层权重按
1
n
\frac{1}{\sqrt n}
n1缩小。
def tanh(x):
return torch.tanh(x)
x = torch.randn(512)
for i in range(100):
a = torch.randn(512, 512) * math.sqrt(1./512)
x = tanh(a @ x)
print(x.mean(), x.std())
tensor(0.0028) tensor(0.0744)
第100层的激活输出的标准偏差低至约0.07。这绝对是偏小的一面,但至少激活并没有完全消失!
Xavier初始化将每层权重设置为在有界的随机均匀分布中选择的值
其中
n
ᵢ
n_ᵢ
nᵢ是该层的传入网络连接数或该层的 “fan-in”,
n
ᵢ
₊
1
n_{ᵢ₊1}
nᵢ₊1是该层的传出网络连接数,也称为fan-out。
**Xavier权重初始化将保持激活函数和反向传播梯度的方差,一直向上或向下传播到神经网络的每一层。**在他们的实验中,他们观察到Xavier初始化使一个5层网络能够将每层的权重梯度维持在基本一致的方差上。
相反,若不使用Xavier初始化,直接使用“标准”初始化会导致网络较低层(较高)的权值梯度与最上层(接近于零)的权值梯度之间的差异更大。
让我们再次重新运行我们的100层tanh网络,这次使用Xavier初始化:
def xavier(fan_in, fan_out):
return torch.Tensor(fan_in, fan_out).uniform_(-1, 1)*math.sqrt(6./(fan_in + fan_out))
x = torch.randn(512)
for i in range(100):
# a = torch.randn(512, 512) * math.sqrt(1./512)
a = xavier(512, 512)
x = tanh(a @ x)
print(x.mean(), x.std())
tensor(-0.0020) tensor(0.0861)
Kaiming初始化
当使用关于零对称且在[-1,1]内有输出的激活函数(例如softsign和tanh)时,我们希望每层的激活输出的平均值为0,平均标准偏差大约为1,这是有道理的。
如果我们使用ReLU激活函数呢?以同样的方式缩放随机初始权重值是否仍然有意义?
guajiguaji
为了看看会发生什么,让我们在先前假设的网络层中使用ReLU激活来代替tanh,并观察其输出的预期标准偏差。
def relu(x):
return x.clamp_min(0.)
mean, var = 0.0, 0.0
for i in range(10000):
x = torch.randn(512)
w = torch.randn(512, 512)
y = relu(w @ x)
mean += y.mean().item()
var += y.pow(2).mean().item()
print(mean/10000, math.sqrt(var/10000))
print(math.sqrt(512/2))
9.015778208112717 15.990627241160206
16.0
事实证明,当使用ReLU激活时,单个层的平均标准偏差将非常接近输入连接数的平方根除以2的平方根,在我们的例子中也就是 512 2 \frac{\sqrt{512}}{\sqrt{2}} 2512。
通过该值缩放权重矩阵a将使每个单独的ReLU层平均具有1的标准偏差。
def relu(x):
return x.clamp_min(0.)
mean, var = 0.0, 0.0
for i in range(10000):
x = torch.randn(512)
w = torch.randn(512, 512) * math.sqrt(2./512)
y = relu(w @ x)
mean += y.mean().item()
var += y.pow(2).mean().item()
print(mean/10000, math.sqrt(var/10000))
0.5639020895034075 0.999664555805342
保持层激活的标准偏差大约为1将允许我们在深度神经网络中堆叠更多层而不会出现梯度爆炸或消失。
Kaiming初始化方案是专门用来处理这些非对称,非线性激活的深层神经网络的。
输入权重初始化策略:
- 使用适合给定图层的权重矩阵创建张量,并使用从标准正态分布中随机选择的数字填充它。
- 将每个随机选择的数字乘以 2 n \frac{\sqrt{2}}{\sqrt{n}} n2,其中n是从前一层输出到指定层的连接数(也称为“fan-in”)。
- 偏差张量初始化为零。
带ReLu激活函数的Xavier初始化结果,激活输出几乎为0
x = torch.randn(512)
for i in range(100):
# a = torch.randn(512, 512) * math.sqrt(1./512)
a = xavier(512, 512)
x = relu(a @ x)
print(x.mean(), x.std())
tensor(4.7158e-16) tensor(7.3041e-16)
带ReLu激活函数的Kaiming初始化结果,效果不错,鼓掌呱唧呱唧
x = torch.randn(512)
for i in range(100):
# a = torch.randn(512, 512) * math.sqrt(1./512)
a = kaiming(512, 512)
x = relu(a @ x)
print(x.mean(), x.std())
tensor(0.3823) tensor(0.5436)
为什么神经网络参数不能全部初始化为全0?
如果全部初始化为零,那么4节点和5节点的输出一样,如果最后没有激活层,那么节点6的输出为0.
我们需要反向传播更新权值,使得输出值和真实值越来越接近,可以知道,通过反向传播后,节点4,5的梯度改变一样,那么他们的更新参数是一样的,同理可以得出输入层和隐藏层之间的参数更新都是一样的,那么更新后的所有参数都是相同的,不论进行多少次正向传播和反向传播,每两层之间的参数都是一样的.
隐藏层与其他层多个节点,其实仅仅相当于一个节点
本来我们希望不同的节点学习到不同的参数,但是由于参数相同以及输出值都一样,不同的节点根本无法学习到不同的特征,这样就失去了网络学习特征的意义了.
w初始化全为0,很可能直接导致模型失效,无法收敛。
可以对w初始化为随机值解决(在cnn中,w的随机化,也是为了使得同一层的多个filter,初始w不同,可以学到不同的特征,如果都是0或某个值,由于计算方式相同,可能达不到学习不同特征的目的)