初始化神经网络的权重似乎是个小细节,但这是显著影响训练过程的重要一步。如章前所述,为权重选择合适的起始点有助于避免梯度消失或梯度爆炸等问题,这些问题是指信号在网络层中传播时变得过弱或过强。不佳的初始化可能导致收敛缓慢,甚至完全阻止网络学习。可以把训练想象成让一个球沿着复杂崎岖的表面(损失曲面)滚向最低点。最初放置球的位置会影响它找到底部的难易程度和速度。如果你把它放在平坦高原上或陡峭悬崖边,它可能会被卡住或剧烈冲过头。智能初始化将球放在一个更可能平稳滚下坡的区域。早期方法通常使用简单的随机初始化,从标准正态分布(均值0,方差1)或小均匀分布中抽取权重。然而,这并未考虑神经元的输入或输出数量。在深度网络中,这种简单方法可能导致层输出的方差随层数呈指数级增长或缩小,正好导致我们想要避免的梯度爆炸或梯度消失问题。为解决这个问题,开发了特定的初始化策略,以在整个网络中保持激活和梯度的合理方差。两种最受欢迎且有效的方法是戈洛特(也称Glorot)初始化和何凯明初始化。戈洛特 / Glorot 初始化该策略由Xavier Glorot和Yoshua Bengio于2010年提出,旨在保持激活和梯度的方差在各层间大致恒定。其核心思想是根据层的输入($n_{in}$)和输出($n_{out}$)单元数量来调整初始权重。这种方法对于那些关于零对称且在零附近导数接近1的激活函数(如双曲正切(tanh)或logistic sigmoid函数)特别有效。主要观点是将某一层权重$W$的方差设置为:$$ Var(W) = \frac{2}{n_{in} + n_{out}} $$为了实现这一点,你通常从根据该方差缩放的正态分布或均匀分布中采样权重。正态分布: 从 $\mathcal{N}(0, \sigma^2)$ 中采样权重,其中标准差 $\sigma$ 为: $$ \sigma = \sqrt{\frac{2}{n_{in} + n_{out}}} $$均匀分布: 从 $\mathcal{U}[-limit, limit]$ 中采样权重,其中限度为: $$ limit = \sqrt{\frac{6}{n_{in} + n_{out}}} $$ (因子 $\sqrt{6}$ 来自均匀分布方差与其限度之间的关系:$Var(\mathcal{U}[-a, a]) = a^2/3$)。戈洛特初始化有助于确保信号在使用tanh或sigmoid等激活函数时不会过快消失或爆炸,从而促进更稳定的训练。何凯明初始化尽管戈洛特初始化对对称激活函数效果良好,但修正线性单元(ReLU)激活函数的出现带来了挑战。ReLU将所有负输入设为零($f(x) = max(0, x)$),这意味着它不对称于零,并且在反向传播过程中,对于负输入的神经元,它实际上“杀死”了一半的梯度。这种非线性改变了方差动态。何凯明等人于2015年观察到这个问题,并提出了一种专门针对ReLU及其变体(如Leaky ReLU,PReLU)的修改。由于ReLU丢弃负值,大致将输入贡献的方差减半,何凯明初始化通过将方差加倍来补偿,相较于戈洛特公式之一。何凯明初始化推荐的方差为:$$ Var(W) = \frac{2}{n_{in}} $$类似于戈洛特,你可以使用正态分布或均匀分布来实现这一点:正态分布: 从 $\mathcal{N}(0, \sigma^2)$ 中采样权重,其中标准差 $\sigma$ 为: $$ \sigma = \sqrt{\frac{2}{n_{in}}} $$ 注意:一些实现可能使用 $\sigma = \frac{1}{\sqrt{n_{in}}}$,但论文的推导指向 $\sqrt{2/n_{in}}$ 以确保前向传播方差的稳定性。框架默认通常使用 $\sqrt{2/n_{in}}$ 版本。均匀分布: 从 $\mathcal{U}[-limit, limit]$ 中采样权重,其中限度为: $$ limit = \sqrt{\frac{6}{n_{in}}} $$何凯明初始化有助于在使用基于ReLU的激活函数时保持健康的信号方差,这对于训练当今常见的非常深度网络很重要。激活方差可视化让我们想象使用ReLU激活函数将数据通过几层。我们可以看到激活分布如何根据初始化策略而变化。理想情况下,我们希望方差保持相对稳定,而不是坍塌到零或爆炸。{"data":[{"x":[-2.5,-2.0,-1.5,-1.0,-0.5,0.0,0.5,1.0,1.5,2.0,2.5],"y":[0,0,0,0,0,0,1,8,15,8,1],"type":"bar","name":"零初始化(激活坍塌)","marker":{"color":"#ced4da"}},{"x":[-2.5,-2.0,-1.5,-1.0,-0.5,0.0,0.5,1.0,1.5,2.0,2.5],"y":[0,0,0,0,1,18,12,3,1,0,0],"type":"bar","name":"朴素正态初始化(方差缩小)","marker":{"color":"#ffc9c9"}},{"x":[-2.5,-2.0,-1.5,-1.0,-0.5,0.0,0.5,1.0,1.5,2.0,2.5],"y":[0,0,0,0,1,15,10,5,3,1,0],"type":"bar","name":"戈洛特初始化(较好,但ReLU下仍会缩小)","marker":{"color":"#a5d8ff"}},{"x":[-2.5,-2.0,-1.5,-1.0,-0.5,0.0,0.5,1.0,1.5,2.0,2.5],"y":[0,0,0,0,0,8,12,8,5,2,1],"type":"bar","name":"何凯明初始化(ReLU下,方差更稳定)","marker":{"color":"#b2f2bb"}}],"layout":{"title":"经过多层ReLU后的激活分布","xaxis":{"title":"激活值(分组)"},"yaxis":{"title":"频率"},"barmode":"group","legend":{"orientation":"h","yanchor":"bottom","y":-0.3,"xanchor":"center","x":0.5}}}使用ReLU激活函数经过多层后的激活分布的简化表示。零初始化导致激活为零。朴素随机正态初始化常常导致方差显著缩小。戈洛特初始化改善了这一点,但对ReLU并非最优。何凯明初始化旨在更好地保持ReLU下的方差,防止激活过快消失。PyTorch中的实际使用大多数深度学习框架都为这些初始化策略提供了内置函数。在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' 在计算中使用 $n_{in}$,这是前馈网络的标准用法。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网络用戈洛特,可以为优化算法提供一个更好的起点,带来更快的收敛和更稳定的训练。这是深度学习实践者工具箱中一个简单而有效的技术。