参数初始化终极笔记(深度学习必背)

一句话核心:训练开始前给神经网络所有 “可调节旋钮” 找一个最佳起点。起点错了,再努力训练也没用;起点对了,训练又快又好,还能避免梯度消失 / 爆炸。


🎯 一、为什么参数初始化这么重要?

1.1 神经网络的本质:百万级旋钮的猜答案机器

  • 神经网络 = 几百万甚至几十亿个可调节旋钮(参数)
  • 每个旋钮拧到不同位置,模型输出的答案就不一样
  • 训练过程 = 一点点拧旋钮,让模型猜答案越来越准

参数初始化:训练开始的第一秒,你把这几百万个旋钮第一次拧到什么位置

1.2 不好的初始化会导致什么?

错误初始化 后果 大白话解释
全 0 / 全常数初始化 网络完全学不动 所有旋钮都在同一个位置,永远学不到不同特征
数值太大 梯度爆炸,loss 直接变成 nan 信号一层一层指数级增长,最后变成无穷大
数值太小 梯度消失,loss 几乎不变 信号一层一层指数级衰减,最后几乎变成 0

1.3 好的初始化的核心目标

让信号在整个网络里稳定传递

  • 每一层的输入数值范围 ≈ 每一层的输出数值范围
  • 既不会越来越大(爆炸),也不会越来越小(消失)
  • 梯度也能稳定地从最后一层传回第一层

❌ 二、三种绝对禁止的初始化方式

2.1 全 0 / 全常数初始化(最致命)

nn.init.zeros_(linear.weight)  # 绝对禁止!
nn.init.constant_(linear.weight, 5)  # 也绝对禁止!

为什么不行?(文档原文核心)

所有权重都相同,反向传播时所有参数的更新也完全相同,导致 “权重均一化”,瓦解了神经网络的对称结构。网络永远学不到任何有用的东西,和一个神经元没有区别。

2.2 太大的初始化(标准差 > 1)

nn.init.normal_(linear.weight, mean=0, std=10)  # 绝对禁止!

后果:梯度爆炸。信号从第一层传到最后一层,数值会指数级增长,最后变成无穷大,loss 直接变成nan

2.3 太小的初始化(标准差 < 0.001)

nn.init.normal_(linear.weight, mean=0, std=0.0001)  # 绝对禁止!

后果:梯度消失。信号从第一层传到最后一层,数值会指数级衰减,最后几乎变成 0,参数根本不更新,模型像个傻子一样站在原地。


📚 三、所有主流初始化方法详解

3.1 基础随机初始化

3.1.1 正态分布初始化
  • 原理:参数服从均值为 0、指定标准差的正态分布

  • 代码

    # 均值0,标准差0.01的正态分布
    nn.init.normal_(linear.weight, mean=0.0, std=0.01)
    nn.init.zeros_(linear.bias)
    
  • 适用场景:当其他初始化方法都不适用时的兜底方案

  • 缺点:需要手动调标准差,深层网络容易梯度消失 / 爆炸

3.1.2 均匀分布初始化
  • 原理:参数在指定区间内均匀分布

  • 代码

    # [-0.1, 0.1]区间内均匀分布
    nn.init.uniform_(linear.weight, a=-0.1, b=0.1)
    nn.init.zeros_(linear.bias)
    
  • 适用场景:简单浅层网络

  • 缺点:同样需要手动调区间,深层网络不稳定

3.2 特殊初始化

3.2.1 单位矩阵初始化(秩初始化)
  • 原理:权重矩阵初始化为单位矩阵

  • 代码

    nn.init.eye_(linear.weight)
    nn.init.zeros_(linear.bias)
    
  • 适用场景:残差网络的恒等映射层

  • 优点:初始时输入等于输出,信号传递最稳定

  • 缺点:只适用于输入维度等于输出维度的情况

3.3 现代深度学习两大主流初始化

3.3.1 Xavier 初始化(Glorot 初始化)

专为 Sigmoid/Tanh 设计

  • 发明人:Xavier Glorot

  • 核心思想:让输入和输出的方差保持一致

  • 数学公式(不用背)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 代码

    # 正态分布(推荐)
    nn.init.xavier_normal_(linear.weight)
    # 均匀分布
    nn.init.xavier_uniform_(linear.weight)
    nn.init.zeros_(linear.bias)
    
  • 优点:有效缓解 Sigmoid/Tanh 的梯度消失问题

  • 缺点:不适合 ReLU,深层网络容易梯度消失

3.3.2 He 初始化(Kaiming 初始化)✅ 现在最常用

专为 ReLU/LeakyReLU/PReLU 设计

  • 发明人:何恺明

  • 核心思想:ReLU 会吃掉一半的信号,所以初始化时把方差放大一倍,刚好补偿损失

  • 数学公式(不用背)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 关键参数

    • mode='fan_in'(默认):保证前向传播方差稳定,99% 场景用这个
    • mode='fan_out':保证反向传播梯度稳定,转置卷积、生成模型偶尔用
    • nonlinearity='relu':指定激活函数类型
  • 代码

    # ReLU用这个(推荐首选)
    nn.init.kaiming_normal_(
        linear.weight,
        mode='fan_in',
        nonlinearity='relu'
    )
    # LeakyReLU用这个
    # nn.init.kaiming_normal_(linear.weight, mode='fan_in', nonlinearity='leaky_relu')
    nn.init.zeros_(linear.bias)
    
  • 优点:非常稳定,100 层的深层网络也能正常训练

  • 缺点:不适合 Sigmoid/Tanh


🆚 四、两大主流初始化终极对比表

凯明初始化 (He) Xavier 初始化 (Glorot)
专为谁设计 ReLU、LeakyReLU、PReLU Sigmoid、Tanh
核心思想 放大一倍信号,补偿 ReLU 的损失 保持输入输出方差一致
深层网络表现 ✅ 非常稳定,100 层也能训 ❌ 超过 10 层就容易梯度消失
现在使用频率 ✅ 90% 的现代网络都用它 ❌ 主要在旧网络里用
推荐指数 ⭐⭐⭐⭐⭐ ⭐⭐

⚠️ 五、PyTorch 的 “天坑”:默认初始化其实很差!

90% 的小白都不知道的秘密

PyTorch 的线性层和卷积层,默认的初始化并不是凯明初始化!

PyTorch 默认用的是均匀分布初始化,对于 ReLU 来说,信号强度不够,深层网络很容易出现梯度消失。

真实对比实验

  • 网络:50 层全连接网络,ReLU 激活
  • 数据集:MNIST 手写数字识别
  • 实验 1:用 PyTorch 默认初始化 → 训练 100 轮,准确率只有 10%(和瞎猜一样)
  • 实验 2:用凯明初始化 → 训练 10 轮,准确率就达到 97%

结论:只要你用 ReLU,一定要手动改成凯明初始化,否则你的深层网络根本训不动!


💻 六、整个模型一键初始化(最实用代码)

import torch.nn as nn

def init_model_weights(model):
    """
    给整个模型自动做凯明初始化(最常用)
    支持:线性层、1D/2D/3D卷积层、BatchNorm层
    适用:所有用ReLU/LeakyReLU的网络
    """
    for module in model.modules():
        # 1. 线性层和卷积层:凯明正态初始化
        if isinstance(module, (nn.Linear, nn.Conv1d, nn.Conv2d, nn.Conv3d)):
            nn.init.kaiming_normal_(
                module.weight, 
                mode='fan_in', 
                nonlinearity='relu'
            )
            # 偏置初始化为0
            if module.bias is not None:
                nn.init.zeros_(module.bias)
        
        # 2. BatchNorm层:官方推荐初始化
        elif isinstance(module, (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d)):
            nn.init.ones_(module.weight)   # weight初始化为1
            nn.init.zeros_(module.bias)    # bias初始化为0
        
        # 3. LayerNorm层:官方推荐初始化
        elif isinstance(module, nn.LayerNorm):
            nn.init.ones_(module.weight)
            nn.init.zeros_(module.bias)

# 使用方法:定义完模型后,直接调用
model = nn.Sequential(
    nn.Linear(784, 50),
    nn.BatchNorm1d(50),
    nn.ReLU(),
    nn.Linear(50, 100),
    nn.BatchNorm1d(100),
    nn.ReLU(),
    nn.Linear(100, 10)
)

init_model_weights(model)  # 一键初始化所有参数

📝 七、小白终极使用指南(做笔记就抄这部分)

7.1 激活函数 ↔ 初始化速查表

你用的激活函数 你应该用的初始化方法
ReLU 凯明正态初始化
LeakyReLU 凯明正态初始化(nonlinearity=‘leaky_relu’)
PReLU 凯明正态初始化(nonlinearity=‘leaky_relu’)
Sigmoid Xavier 正态初始化
Tanh Xavier 正态初始化
其他 正态分布初始化(std=0.01)

7.2 5 条铁律(永远遵守)

  1. ❌ 绝对不要全 0 / 全常数初始化
  2. ✅ 只要用 ReLU 家族,一律用凯明正态初始化
  3. ✅ 偏置一律初始化为 0,不用改
  4. ✅ BatchNorm 层 weight=1,bias=0
  5. ✅ 定义完模型后,一定要手动调用初始化函数,不要用 PyTorch 的默认初始化

7.3 常见问题排查

问题 90% 的原因 解决方案
loss 一直是 nan 初始化数值太大 减小标准差,改用凯明初始化
loss 一直不变,模型不动 初始化数值太小,或用了默认初始化 改用凯明初始化,检查是否加了 BN
深层网络训练不动 没加 BN 层 + 没用到凯明初始化 每个线性层 / 卷积层后面加 BN,用凯明初始化
训练到一半 loss 突然变成 nan 学习率太大 减小学习率,和初始化无关

🎯 八、一句话背诵版

ReLU 配凯明,Tanh 配 Xavier,偏置全归零,BN 一零一,训练稳如狗。

更多推荐