3. 权重初始化策略:零初始化、随机初始化、Xavier初始化、He初始化
权重初始化这事儿,看着简单,其实坑特别多。我刚开始做深度学习那会儿,觉得不就是给参数赋个初值嘛,随便搞搞就行。结果呢?模型要么不收敛,要么loss直接炸掉。后来才明白——初始化策略选不对,后面再怎么调优都是白费力气。
说白了,权重初始化决定了你的网络在训练起点处的状态。起点选得好,梯度传播顺畅,收敛快;起点选得烂,梯度消失或爆炸,模型根本学不动。
核心观点:权重初始化的本质是控制前向传播和反向传播过程中,信号的方差保持稳定。不让它越传越小,也不让它越传越大。
3.1 零初始化——最直观但最坑的方法
把所有权重设成0,听起来很合理对吧?对称初始化嘛。但实际跑起来,你会发现一个严重问题:所有神经元在每一层学到的东西完全一样。
为什么会这样?因为如果权重都是0,那么所有神经元的输入相同、梯度相同、更新量也相同。无论网络多深,每一层内的神经元都在做完全相同的计算。这就是所谓的对称性问题。
我曾经踩过的坑:刚入行时用零初始化训练一个3层全连接网络做分类,跑了100个epoch,loss纹丝不动。我还以为是学习率太小,调大之后直接炸了。后来才发现——所有神经元都在学同样的特征,模型根本没有表达能力。
零初始化唯一的适用场景是偏置项(bias)。我个人习惯把偏置初始化为0,权重则用其他策略。
3.2 随机初始化——简单但需要技巧
既然全0不行,那就随机给值呗。最朴素的做法是从一个小的正态分布或均匀分布中采样:
# 从正态分布采样
W = np.random.randn(n_in, n_out) * 0.01
# 从均匀分布采样
W = np.random.uniform(-0.01, 0.01, (n_in, n_out))
这里乘0.01是为了让初始值足够小。但问题来了——这个0.01怎么来的?为什么不是0.1或0.001?
我告诉你,这个系数其实很敏感。系数太大,深层网络的激活值会爆炸;系数太小,激活值会趋近于0。你想想看,一个100层的网络,每层乘一个0.01的系数,到最后一层信号早就消失得无影无踪了。
我的经验:随机初始化只适合浅层网络(3层以内)。一旦网络深度超过5层,建议直接上Xavier或He初始化,别在随机系数上浪费时间调参。
3.3 Xavier初始化——让信号方差稳定传播
Xavier初始化(也叫Glorot初始化)是2010年提出的。它的核心思想很简单:让每一层的输出方差等于输入方差。
数学上,它要求权重的方差满足:
Var(W) = 2 / (n_in + n_out)
# 均匀分布版本
W = np.random.uniform(-np.sqrt(6/(n_in+n_out)), np.sqrt(6/(n_in+n_out)), (n_in, n_out))
# 正态分布版本
W = np.random.randn(n_in, n_out) * np.sqrt(2/(n_in+n_out))
这个公式考虑了输入神经元数n_in和输出神经元数n_out。为什么?因为前向传播时信号从n_in个输入来,反向传播时梯度从n_out个输出回去,两边都要照顾到。
| 特性 | Xavier初始化 |
|---|---|
| 适用激活函数 | tanh、sigmoid、softmax |
| 方差公式 | 2 / (n_in + n_out) |
| 前向方差 | 保持稳定 |
| 反向方差 | 保持稳定 |
| 对ReLU效果 | 较差(信号衰减) |
我记得第一次用Xavier初始化训练一个10层的tanh网络,收敛速度比随机初始化快了将近3倍。当时就觉得——嗯,数学推导出来的东西确实靠谱。
3.4 He初始化——专为ReLU设计
Xavier初始化在tanh上表现很好,但到了ReLU时代就不太行了。原因很简单:ReLU会把一半的神经元置0,实际传递的信号方差只有理论值的一半。
He初始化(也叫Kaiming初始化)就是针对这个问题提出的。它把方差调整为:
Var(W) = 2 / n_in
# 正态分布版本(最常用)
W = np.random.randn(n_in, n_out) * np.sqrt(2/n_in)
# 均匀分布版本
W = np.random.uniform(-np.sqrt(6/n_in), np.sqrt(6/n_in), (n_in, n_out))
注意看,这里只除以n_in,没有n_out。而且系数是2,不是Xavier的1。为什么?因为ReLU会丢弃一半的信号,所以需要把初始方差放大一倍来补偿。
实战建议:如果你用ReLU或它的变体(Leaky ReLU、PReLU等),直接用He初始化。这是目前最主流的做法,PyTorch和TensorFlow的默认初始化策略就是He。
3.5 四种策略对比总结
| 策略 | 方差公式 | 适用激活函数 | 深度适用性 | 我的推荐度 |
|---|---|---|---|---|
| 零初始化 | 0 | 无(仅偏置) | ❌ 不适用 | ⭐ |
| 随机初始化 | 手动指定 | 任意(需调参) | 浅层(≤3层) | ⭐⭐ |
| Xavier初始化 | 2/(n_in+n_out) | tanh、sigmoid | 中等深度 | ⭐⭐⭐⭐ |
| He初始化 | 2/n_in | ReLU及其变体 | 深层(50+层) | ⭐⭐⭐⭐⭐ |
3.6 实际项目中的选择策略
说了这么多理论,到底怎么选?我直接给结论:
- 用ReLU系列激活函数 → He初始化(无脑选这个)
- 用tanh或sigmoid → Xavier初始化
- 偏置项 → 零初始化
- Batch Normalization后面 → 可以适当放宽初始化要求,但我个人习惯还是用He
一个小技巧:如果你不确定用哪个,先跑一个mini-batch看看各层激活值的分布。如果深层激活值越来越小,说明初始化方差不够大;如果越来越大甚至出现NaN,说明方差太大了。He初始化在大多数情况下都能直接work。
嗯,权重初始化就这些内容。记住一句话:初始化策略要和激活函数匹配。选对了,训练事半功倍;选错了,后面再怎么调学习率、加正则化都救不回来。