趋近智
初始化神经网络的权重似乎是个小细节,但这是显著影响训练过程的重要一步。如章前所述,为权重选择合适的起始点有助于避免梯度消失或梯度爆炸等问题,这些问题是指信号在网络层中传播时变得过弱或过强。不佳的初始化可能导致收敛缓慢,甚至完全阻止网络学习。
可以把训练想象成让一个球沿着复杂崎岖的表面(损失曲面)滚向最低点。最初放置球的位置会影响它找到底部的难易程度和速度。如果你把它放在平坦高原上或陡峭悬崖边,它可能会被卡住或剧烈冲过头。智能初始化将球放在一个更可能平稳滚下坡的区域。
早期方法通常使用简单的随机初始化,从标准正态分布(均值0,方差1)或小均匀分布中抽取权重。然而,这并未考虑神经元的输入或输出数量。在深度网络中,这种简单方法可能导致层输出的方差随层数呈指数级增长或缩小,正好导致我们想要避免的梯度爆炸或梯度消失问题。
为了避免神经网络训练中出现的梯度消失或梯度爆炸等问题,并保持激活和梯度的合理方差,开发了特定的初始化策略。两种最受欢迎且有效的方法是戈洛特(也称Glorot)初始化和何凯明初始化。
该策略由Xavier Glorot和Yoshua Bengio于2010年提出,旨在保持激活和梯度的方差在各层间大致恒定。其核心思想是根据层的输入(nin)和输出(nout)单元数量来调整初始权重。
这种方法对于那些关于零对称且在零附近导数接近1的激活函数(如双曲正切(tanh)或logistic sigmoid函数)特别有效。
主要观点是将某一层权重W的方差设置为:
Var(W)=nin+nout2为了实现这一点,你通常从根据该方差缩放的正态分布或均匀分布中采样权重。
正态分布: 从 N(0,σ2) 中采样权重,其中标准差 σ 为:
σ=nin+nout2均匀分布: 从 U[−limit,limit] 中采样权重,其中限度为:
limit=nin+nout6(因子 6 来自均匀分布方差与其限度之间的关系:Var(U[−a,a])=a2/3)。
戈洛特初始化有助于确保信号在使用tanh或sigmoid等激活函数时不会过快消失或爆炸,从而促进更稳定的训练。
尽管戈洛特初始化对对称激活函数效果良好,但修正线性单元(ReLU)激活函数的出现带来了挑战。ReLU将所有负输入设为零(f(x)=max(0,x)),这意味着它不对称于零,并且在反向传播过程中,对于负输入的神经元,它实际上“杀死”了一半的梯度。这种非线性改变了方差动态。
何凯明等人于2015年观察到这个问题,并提出了一种专门针对ReLU及其变体(如Leaky ReLU,PReLU)的修改。由于ReLU丢弃负值,大致将输入贡献的方差减半,何凯明初始化通过将方差加倍来补偿,相较于戈洛特公式之一。
何凯明初始化推荐的方差为:
Var(W)=nin2类似于戈洛特,你可以使用正态分布或均匀分布来实现这一点:
正态分布: 从 N(0,σ2) 中采样权重,其中标准差 σ 为:
σ=nin2注意:一些实现可能使用 σ=nin1,但论文的推导指向 2/nin 以确保前向传播方差的稳定性。框架默认通常使用 2/nin 版本。
均匀分布: 从 U[−limit,limit] 中采样权重,其中限度为:
limit=nin6何凯明初始化有助于在使用基于ReLU的激活函数时保持健康的信号方差,这对于训练当今常见的非常深度网络很重要。
让我们想象使用ReLU激活函数将数据通过几层。我们可以看到激活分布如何根据初始化策略而变化。理想情况下,我们希望方差保持相对稳定,而不是坍塌到零或爆炸。
使用ReLU激活函数经过多层后的激活分布的简化表示。零初始化导致激活为零。朴素随机正态初始化常常导致方差显著缩小。戈洛特初始化改善了这一点,但对ReLU并非最优。何凯明初始化旨在更好地保持ReLU下的方差,防止激活过快消失。
大多数深度学习框架都为这些初始化策略提供了内置函数。在PyTorch中,你可以在定义网络时或之后通过遍历层来使用它们。
以下是在自定义nn.Module中将何凯明初始化(特指kaiming_normal_)应用于线性层和卷积层的常见方法:
import torch
import torch.nn as nn
import math
class SimpleNet(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(SimpleNet, self).__init__()
self.layer1 = nn.Linear(input_size, hidden_size)
self.relu1 = nn.ReLU()
self.layer2 = nn.Linear(hidden_size, hidden_size)
self.relu2 = nn.ReLU()
self.layer3 = nn.Linear(hidden_size, output_size)
# 应用何凯明初始化
self._initialize_weights()
def forward(self, x):
x = self.relu1(self.layer1(x))
x = self.relu2(self.layer2(x))
x = self.layer3(x)
return x
def _initialize_weights(self):
for m in self.modules():
if isinstance(m, nn.Linear):
# 线性层的何凯明初始化
nn.init.kaiming_normal_(m.weight, mode='fan_in', nonlinearity='relu')
# 可选:将偏置初始化为零
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Conv2d):
# 卷积层的何凯明初始化
nn.init.kaiming_normal_(m.weight, mode='fan_in', nonlinearity='relu')
if m.bias is not None:
nn.init.constant_(m.bias, 0)
# 如果需要,可添加其他层类型的elif
# 使用示例:
input_dim = 784 # 示例:展平的MNIST图像
hidden_dim = 128
output_dim = 10 # 示例:MNIST的10个类别
model = SimpleNet(input_dim, hidden_dim, output_dim)
print("使用何凯明正态初始化后的layer1权重:")
print(model.layer1.weight.data)
在这个例子中:
_initialize_weights 方法遍历所有模块(self.modules())。isinstance 检查模块是否是线性层或二维卷积层。nn.init.kaiming_normal_ 应用使用正态分布的何凯明初始化。mode='fan_in' 在计算中使用 nin,这是前馈网络的标准用法。nonlinearity='relu' 被指定是因为何凯明初始化是为ReLU设计的。nn.init.constant_(m.bias, 0) 将偏置设为零,这是一种常见做法。如果你使用tanh激活函数,你会将 nn.init.kaiming_normal_ 替换为 nn.init.xavier_normal_ 或 nn.init.xavier_uniform_,并可能将 nonlinearity='relu' 改为 nonlinearity='tanh'。
选择正确的初始化策略,通常ReLU网络用何凯明,tanh/sigmoid网络用戈洛特,可以为优化算法提供一个更好的起点,带来更快的收敛和更稳定的训练。这是深度学习实践者工具箱中一个简单而有效的技术。
这部分内容有帮助吗?
© 2026 ApX Machine Learning用心打造