参数的初始化
在神经网络中,参数初始化(Parameter Initialization)指的是在训练开始之前设置网络权重(weights)和偏置(biases)的初始值。合适的初始化策略对于神经网络的训练过程至关重要。好的初始化方法能够加速收敛,提高模型的性能,避免一些常见的问题,如梯度消失或梯度爆炸。
参数初始化的作用
- 一个好的初始化策略能帮助模型更快地收敛。合适的初始化会避免在训练初期陷入困境,使得梯度在反向传播时不会因为初始值太小或太大而导致学习困难。
- 如果初始化过小或过大,可能导致梯度在反向传播时变得非常小(梯度消失)或非常大(梯度爆炸),从而影响训练的稳定性。
- 例如,深度网络中的梯度消失问题会导致模型训练速度极慢,甚至无法更新参数;而梯度爆炸则可能导致训练过程中权重更新过快,导致权重的值极大,进而造成不稳定的训练。
- 如果所有神经元的初始权重相同,那么每个神经元将执行相同的操作,导致它们在训练过程中学习到相同的特征,这会大大降低模型的表现。
- 通过合理的初始化方法,确保每个神经元从不同的初始值开始学习,使得它们能学习到不同的特征,提高模型的多样性和表现。
- 如果所有权重被初始化为相同的值,所有神经元的输出也将相同,导致网络在训练过程中无法破除对称性,使得每个神经元的更新是一样的,无法发挥出网络的深度特性。合理初始化权重能够避免这一问题。
初始化方式
1. 常数初始化
将参数初始化为固定的常数或简单的矩阵结构的方式。
- 将神经网络中的所有权重参数初始化为 0/1
- 将神经网络中的所有权重参数初始化为某个固定值
- 把d维单位矩阵填充进二维张量中,其中d是这个二维张量维度的最小值即d = argmin(tensor.shape)
- 把一个 3/4/5 维卷积核张量初始化成 Dirac delta 函数(狄拉克δ函数),使得卷积层在初始时尽可能保持输入恒等(identity)。
2. 简单随机初始化
从基本的概率分布中采样。
2.1 均匀分布初始化
- 从均匀分布
U
(
a
,
b
)
U(a, b)
U(a,b) 中采样,默认U
(
0
,
1
)
U(0,1)
U(0,1)。
2.2 正态分布初始化
- 从正态分布
N
(
mean
,
std
)
\\mathcal{N}(\\text{mean}, \\text{std})
N(mean,std) 中采样
2.3 截断正态分布初始化
-
从截断的
N
(
mean
,
std
)
\\mathcal{N}(\\text{mean}, \\text{std})
N(mean,std) 中采样,截断区间为
(
a
,
b
)
(a,b)
(a,b),可以有效避免极端值
-
截断正态分布把采样范围限制在
[
a
,
b
]
[a, b]
[a,b] 内,并重新归一化概率密度,使其积分仍为 1:
f
(
x
∣
μ
,
σ
,
a
,
b
)
=
ϕ
(
x
−
μ
σ
)
σ
[
Φ
(
b
−
μ
σ
)
−
Φ
(
a
−
μ
σ
)
]
,
a
≤
x
≤
b
f(x \\mid \\mu, \\sigma, a, b) = \\frac{\\phi\\!\\left(\\frac{x-\\mu}{\\sigma}\\right)}{\\sigma\\left[\\Phi\\!\\left(\\frac{b-\\mu}{\\sigma}\\right) – \\Phi\\!\\left(\\frac{a-\\mu}{\\sigma}\\right)\\right]}, \\quad a \\le x \\le b
f(x∣μ,σ,a,b)=σ[Φ(σb−μ)−Φ(σa−μ)]ϕ(σx−μ),a≤x≤b
-
作用
- 避免极端值: 正态分布理论上无界。在深层网络中,偶尔出现的
4
σ
4\\sigma
4σ、5
σ
5\\sigma
5σ 权重可能导致:- 激活值爆炸;
- 梯度消失/爆炸;
- 训练早期 loss 出现 NaN。
- 比均匀分布更"合理"的先验: 很多场景下,我们相信权重应该集中在 0 附近(小权重优先),而不是均匀地散在整个区间。均匀分布给
[
−
r
,
r
]
[-r, r]
[−r,r] 内每个值同等概率,这在先验上未必合理。截断正态以 0 为中心、钟形衰减,更符合"权重应小且集中"的直觉。
3. 自适应初始化(基于网络结构所提出的特定初始化方式)
3.1 kaiming 初始化
He 初始化是专为 ReLU 类激活函数设计的权重初始化方法,由何恺明等人于 2015 年在论文《Delving Deep into Rectifiers》中提出。 核心思想是:根据每层的 fan 数自动计算初始化尺度,让信号在多层前向/反向传播中保持方差稳定。
计算方式:
std
=
gain
fan_mode
\\text{std} = \\frac{\\text{gain}}{\\sqrt{\\text{fan\\_mode}}}
std=fan_modegain 其中:
- gain:激活函数增益,如激活函数为ReLU 时
gain
=
2
\\text{gain} = \\sqrt{2}
gain=2 - fan_mode:fan_in(输入维度,默认)或 fan_out(输出维度)
- 正态分布的He初始化: 从
N
(
0
,
std
2
)
\\mathcal{N}(0, \\text{std}^2)
N(0,std2)采样 - 均匀分布的He初始化: 从
U
(
−
3
std)
,
3
std)
)
\\mathcal{U}(-\\sqrt{3}\\text{std)},\\sqrt{3}\\text{std)})
U(−3std),3std))采样
本质上,
N
(
0
,
std
2
)
\\mathcal{N}(0, \\text{std}^2)
N(0,std2)和
U
(
−
3
std)
,
3
std)
)
\\mathcal{U}(-\\sqrt{3}\\text{std)},\\sqrt{3}\\text{std)})
U(−3
std),3
std))的期望和方差一致。
两者的区别在于:
- normal 版可能出现超出表示范围的值,需要额外裁剪;
- uniform 版范围已知,更容易做 scale 和量化。
不过貌似大多数论文都用正态的He初始化??可能是为了更方便进行理论分析(CLT之类)??
3.2 xavier 初始化
Xavier 初始化是专为 Sigmoid / Tanh 等"近似线性"激活函数设计的权重初始化方法,由 Glorot 和 Bengio 于 2010 年在论文《Understanding the difficulty of training deep feedforward neural networks》中提出。 核心目标:让信号在多层前向和反向传播中保持方差稳定,避免梯度消失或爆炸。 xavier 初始化同样有两个版本:
w
∼
U
(
−
a
,
a
)
,
a
=
gain
×
6
fan_in
+
fan_out
w \\sim \\mathcal{U}(-a, a), \\quad a = \\text{gain} \\times \\sqrt{\\frac{6}{\\text{fan\\_in} + \\text{fan\\_out}}}
w∼U(−a,a),a=gain×fan_in+fan_out6
w
∼
N
(
0
,
std
2
)
,
std
=
gain
×
2
fan_in
+
fan_out
w \\sim \\mathcal{N}(0, \\text{std}^2), \\qquad \\text{std} = \\text{gain} \\times \\sqrt{\\frac{2}{\\text{fan\\_in} + \\text{fan\\_out}}}
w∼N(0,std2),std=gain×fan_in+fan_out2 其中,fan_in 是输入神经元的个数, fan_out 是输出的神经元个数,gain 是一个可选缩放因子,默认 1.0,用于适配不同激活函数
原理
Xavier初始化试图使得每一层的输出的方差接近于其输入的方差。简单来说,在不考虑激活函数和偏置项的情况下: 假设
x
=
{
x
j
}
,
(
j
=
1
,
2
,
⋯
,
fan_in
)
x = \\{x_j \\}, (j = 1,2,\\cdots,\\text{fan\\_in})
x={xj},(j=1,2,⋯,fan_in),
y
=
{
y
i
}
=
{
∑
j
fan_in
x
j
∗
w
i
j
}
,
(
i
=
1
,
2
,
⋯
,
fan_out
)
\\displaystyle y=\\{y_i\\} = \\{ \\sum_{j}^{\\text{fan\\_in}} x_j * w_{ij} \\} ,(i = 1,2,\\cdots,\\text{fan\\_out})
y={yi}={j∑fan_inxj∗wij},(i=1,2,⋯,fan_out) 且计算过程中的随机变量都服从零均值的独立同分布假设。于是:
D
(
y
i
)
=
D
(
∑
j
=
1
fan_in
w
i
j
x
j
)
=
E
[
(
∑
j
=
1
fan_in
w
i
j
x
j
)
2
]
=
∑
j
=
1
fan_in
E
[
w
i
j
2
]
E
[
x
j
2
]
=
fan_in
∗
D
(
w
)
∗
D
(
x
)
D
(
∂
L
∂
x
j
)
=
D
(
∑
i
=
1
fan_out
∂
L
∂
y
i
⋅
∂
y
i
∂
x
j
)
=
D
(
∑
i
=
1
fan_out
∂
L
∂
y
i
⋅
w
i
j
)
=
E
(
[
∑
i
=
1
fan_out
∂
L
∂
y
i
⋅
w
i
j
]
2
)
=
∑
i
=
1
fan_out
E
[
(
∂
L
∂
y
i
)
2
]
⋅
E
[
w
i
j
2
]
=
fan_out
∗
D
(
w
)
∗
D
(
∂
L
∂
y
i
)
\\begin{align} &D(y_i) = D(\\sum_{j=1}^{\\text{fan\\_in}}{w_{ij}x_j}) = E[(\\sum_{j=1}^{\\text{fan\\_in}}{w_{ij}x_j})^2] = \\sum_{j=1}^{\\text{fan\\_in}} E[w_{ij}^2]E[x_j^2] = \\text{fan\\_in} * D(w) * D(x) \\\\ &D(\\frac{\\partial{L}}{\\partial{x_j}}) = D(\\sum_{i=1}^{\\text{fan\\_out}} \\frac{\\partial{L}}{\\partial{y_i}} \\cdot \\frac{\\partial{y_i}}{\\partial{x_j}}) \\\\ &= D(\\sum_{i=1}^{\\text{fan\\_out}} \\frac{\\partial{L}}{\\partial{y_i}} \\cdot w_{ij}) \\\\ &= E([\\sum_{i=1}^{\\text{fan\\_out}} \\frac{\\partial{L}}{\\partial{y_i}} \\cdot w_{ij}]^2) \\\\ &= \\sum_{i=1}^{\\text{fan\\_out}} E[(\\frac{\\partial{L}}{\\partial{y_i}})^2] \\cdot E[w_{ij}^2] = \\text{fan\\_out } * D(w) * D(\\frac{\\partial{L}}{\\partial{y_i}}) \\end{align}
D(yi)=D(j=1∑fan_inwijxj)=E[(j=1∑fan_inwijxj)2]=j=1∑fan_inE[wij2]E[xj2]=fan_in∗D(w)∗D(x)D(∂xj∂L)=D(i=1∑fan_out∂yi∂L⋅∂xj∂yi)=D(i=1∑fan_out∂yi∂L⋅wij)=E([i=1∑fan_out∂yi∂L⋅wij]2)=i=1∑fan_outE[(∂yi∂L)2]⋅E[wij2]=fan_out ∗D(w)∗D(∂yi∂L) 为了同时兼顾前向传播和反向传播时两边的方差,于是取其调和平均数
D
(
w
)
=
2
fan_in + fan_out
D(w) = \\frac{2}{\\text{fan\\_in + fan\\_out}}
D(w)=fan_in + fan_out2
a
=
6
fan_in + fan_out
a = \\sqrt{\\frac{6}{\\text{fan\\_in + fan\\_out}}}
a=fan_in + fan_out6
时,
D
(
X
)
=
(
2
∗
l
i
m
i
t
)
2
12
=
2
fan_in + fan_out
D(X) = \\frac{(2 * limit)^2}{12} = \\frac{2}{\\text{fan\\_in + fan\\_out}}
D(X)=12(2∗limit)2=fan_in + fan_out2
3.3 正交初始化
- 将矩阵初始化为(半)正交矩阵,常用于 RNN 中缓解梯度消失/爆炸。
3.4 稀疏初始化
- 将二维张量初始化为稀疏矩阵,非零元素来自正态分布。
4. 增益率
如果知道了具体的激活函数,你可以通过计算它的增益来调整方差
D
(
w
)
D(w)
D(w)。或者你可以在pytorch中查找对应函数的增益率。计算方式如下
g
a
i
n
=
D
(
x
)
D
[
f
(
x
)
]
f
(
x
)
为对应的激活函数
gain = \\sqrt{\\frac{D(x)}{D[f(x)]}} \\quad f(x)\\text{为对应的激活函数}
gain=D[f(x)]D(x)
f(x)为对应的激活函数 不过计算积分比较复杂,所以推荐查表或使用蒙特卡洛法计算:
import torch
import math
def estimate_gain(activation_func, num_samples=1000000):
x = torch.randn(num_samples) # 标准正态分布
y = activation_func(x)
var_x = x.var().item()
var_y = y.var().item()
return math.sqrt(var_x / var_y)
# 估计SiLU的增益
gain_silu = estimate_gain(lambda x: x * torch.sigmoid(x))
print(gain_silu)
## tanh的增益率
print(torch.nn.init.calculate_gain('tanh'))
使用:
- torch.nn.init.normal_(weight, mean=0.0, std=math.sqrt(2.0/(fan_in+0.0))*gain)
- torch.nn.init.xavier_normal_(weight,gain=gain)
- torch.nn.init.kaiming_normal_(weight,nonlinearity='relu')
================= ====================================================
nonlinearity gain
================= ====================================================
Linear / Identity :math:`1`
Conv{1,2,3}D :math:`1`
Sigmoid :math:`1`
Tanh :math:`\\frac{5}{3}`
ReLU :math:`\\sqrt{2}`
Leaky Relu :math:`\\sqrt{\\frac{2}{1 + \\text{negative\\_slope}^2}}`
SELU :math:`\\frac{3}{4}`
================= ====================================================
参考
- pytorch官方文档
- 动手深度学习



