环境声明

  • Python版本:Python 3.10+
  • PyTorch版本:PyTorch 2.0+
  • 开发工具:PyCharm / VS Code / Jupyter Notebook
  • 操作系统:Windows / macOS / Linux(通用)
  • GPU支持:CUDA 11.8+(可选,但推荐)

学习目标

通过本章学习,你将掌握:

  1. 理解权重初始化对深度神经网络训练的重要性
  2. 掌握Xavier/Glorot初始化的原理和适用场景
  3. 掌握He初始化的原理和适用场景
  4. 了解正交初始化和谱初始化的应用场景
  5. 学会使用各种学习率调度策略优化训练过程
  6. 能够根据网络结构选择合适的初始化方法

内容摘要

权重初始化是深度学习训练的基础环节,良好的初始化可以加速收敛、防止梯度消失或爆炸。本章将深入讲解Xavier、He等经典初始化方法,以及学习率调度策略中的Step、Cosine Annealing和Warmup技术,帮助你构建更稳定、更高效的神经网络。


1. 权重初始化的重要性和影响

1.1 为什么权重初始化如此重要

想象你正在建造一座摩天大楼,如果地基打得不好,无论上面的建筑多么精美,最终都可能倒塌。权重初始化就是深度学习模型的"地基"。

权重初始化不当会导致的问题:

  1. 梯度消失(Vanishing Gradient):当权重过小,信号在深层网络中逐层衰减,导致前面层几乎无法学习
  2. 梯度爆炸(Exploding Gradient):当权重过大,信号在传播过程中指数级增长,导致参数更新失控
  3. 对称性问题:如果所有权重初始化为相同值,神经元将学习到相同的特征,失去表达能力
  4. 收敛缓慢:不合适的初始化可能导致模型需要更多轮次才能收敛

1.2 前向传播与反向传播中的信号流动

在深度神经网络中,信息通过前向传播计算输出,通过反向传播更新参数。我们来看信号是如何流动的:

设第l层的输入为 x(l)x^{(l)}x(l),权重为 W(l)W^{(l)}W(l),激活函数为 fff,则:

z(l)=W(l)x(l)+b(l)z^{(l)} = W^{(l)} x^{(l)} + b^{(l)}z(l)=W(l)x(l)+b(l)
x(l+1)=f(z(l))x^{(l+1)} = f(z^{(l)})x(l+1)=f(z(l))

在反向传播中,梯度通过链式法则传递:

∂L∂W(l)=∂L∂x(l+1)⋅∂x(l+1)∂z(l)⋅∂z(l)∂W(l)\frac{\partial L}{\partial W^{(l)}} = \frac{\partial L}{\partial x^{(l+1)}} \cdot \frac{\partial x^{(l+1)}}{\partial z^{(l)}} \cdot \frac{\partial z^{(l)}}{\partial W^{(l)}}W(l)L=x(l+1)Lz(l)x(l+1)W(l)z(l)

关键洞察:如果每一层的权重方差控制不当,多层累积后信号的方差会发生剧烈变化。

1.3 方差分析的基本思想

假设输入 xxx 和权重 www 都是独立同分布的随机变量,均值为0,方差分别为 Var(x)Var(x)Var(x)Var(w)Var(w)Var(w)。对于线性变换 y=∑i=1nwixiy = \sum_{i=1}^{n} w_i x_iy=i=1nwixi

Var(y)=∑i=1nVar(wixi)=n⋅Var(w)⋅Var(x)Var(y) = \sum_{i=1}^{n} Var(w_i x_i) = n \cdot Var(w) \cdot Var(x)Var(y)=i=1nVar(wixi)=nVar(w)Var(x)

为了保持信号强度稳定,我们希望 Var(y)≈Var(x)Var(y) \approx Var(x)Var(y)Var(x),这意味着:

n⋅Var(w)≈1⇒Var(w)≈1nn \cdot Var(w) \approx 1 \Rightarrow Var(w) \approx \frac{1}{n}nVar(w)1Var(w)n1

这就是权重初始化方法设计的核心思想。


2. Xavier/Glorot初始化

2.1 方法背景

Xavier初始化(又称Glorot初始化)由Xavier Glorot和Yoshua Bengio在2010年的论文《Understanding the difficulty of training deep feedforward neural networks》中提出。这是深度学习领域最具影响力的初始化方法之一。

2.2 核心原理

Xavier初始化的目标是:保持前向传播和反向传播时信号方差的一致性

假设:

  • 权重 wijw_{ij}wij 独立同分布,均值为0
  • 输入 xjx_jxj 独立同分布,均值为0
  • 激活函数关于0对称(如tanh、sigmoid在0附近近似线性)

对于前向传播,输出方差为:
Var(y)=nin⋅Var(w)⋅Var(x)Var(y) = n_{in} \cdot Var(w) \cdot Var(x)Var(y)=ninVar(w)Var(x)

为了保持 Var(y)=Var(x)Var(y) = Var(x)Var(y)=Var(x),需要:
Var(w)=1ninVar(w) = \frac{1}{n_{in}}Var(w)=nin1

对于反向传播,考虑梯度回传:
Var(∂L∂x)=nout⋅Var(w)⋅Var(∂L∂y)Var(\frac{\partial L}{\partial x}) = n_{out} \cdot Var(w) \cdot Var(\frac{\partial L}{\partial y})Var(xL)=noutVar(w)Var(yL)

为了保持梯度方差稳定,需要:
Var(w)=1noutVar(w) = \frac{1}{n_{out}}Var(w)=nout1

2.3 Xavier初始化公式

为了同时满足前向和反向传播的要求,Xavier初始化取两者的调和平均:

Var(w)=2nin+noutVar(w) = \frac{2}{n_{in} + n_{out}}Var(w)=nin+nout2

均匀分布版本:
w∼U[−6nin+nout,6nin+nout]w \sim U\left[-\sqrt{\frac{6}{n_{in} + n_{out}}}, \sqrt{\frac{6}{n_{in} + n_{out}}}\right]wU[nin+nout6 ,nin+nout6 ]

正态分布版本:
w∼N(0,2nin+nout)w \sim N\left(0, \sqrt{\frac{2}{n_{in} + n_{out}}}\right)wN(0,nin+nout2 )

其中:

  • ninn_{in}nin:输入神经元数量(输入维度)
  • noutn_{out}nout:输出神经元数量(输出维度)

2.4 PyTorch实现

import torch
import torch.nn as nn

# 方法1:使用PyTorch内置的Xavier初始化
layer = nn.Linear(784, 256)
nn.init.xavier_uniform_(layer.weight)  # 均匀分布
# 或者
nn.init.xavier_normal_(layer.weight)   # 正态分布

# 方法2:在模型定义中应用
class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.fc1 = nn.Linear(784, 256)
        self.fc2 = nn.Linear(256, 128)
        self.fc3 = nn.Linear(128, 10)
        
        # 应用Xavier初始化
        self._initialize_weights()
    
    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Linear):
                nn.init.xavier_uniform_(m.weight)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
    
    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = self.fc3(x)
        return x

2.5 适用场景

推荐使用Xavier初始化的场景:

  1. 激活函数为tanh或sigmoid:这些函数在0附近近似线性,符合Xavier的假设
  2. 对称激活函数:输出关于0对称的激活函数
  3. 浅层到中层网络:网络深度不是特别极端时表现良好
  4. 全连接层和卷积层:都可以应用Xavier初始化

不推荐的场景:

  • 使用ReLU激活函数时(应使用He初始化)
  • 非常深的网络可能需要更精细的初始化策略

3. He初始化

3.1 方法背景

He初始化(又称Kaiming初始化)由Kaiming He等人在2015年的论文《Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification》中提出,专门解决ReLU激活函数的初始化问题。

3.2 为什么Xavier不适用于ReLU

ReLU激活函数定义为 f(x)=max(0,x)f(x) = max(0, x)f(x)=max(0,x),它将所有负值置为0。这破坏了Xavier初始化关于激活函数对称性的假设。

关键问题:当输入为负时,ReLU输出为0,导致大约50%的神经元在初始化时被"关闭"。这使得实际参与计算的神经元数量减少,信号方差发生变化。

3.3 He初始化原理

对于ReLU激活函数,考虑到大约一半的神经元被置零,前向传播的方差变为:

Var(y)=12nin⋅Var(w)⋅Var(x)Var(y) = \frac{1}{2} n_{in} \cdot Var(w) \cdot Var(x)Var(y)=21ninVar(w)Var(x)

为了保持 Var(y)=Var(x)Var(y) = Var(x)Var(y)=Var(x),需要:

Var(w)=2ninVar(w) = \frac{2}{n_{in}}Var(w)=nin2

3.4 He初始化公式

均匀分布版本:
w∼U[−6nin,6nin]w \sim U\left[-\sqrt{\frac{6}{n_{in}}}, \sqrt{\frac{6}{n_{in}}}\right]wU[nin6 ,nin6 ]

正态分布版本:
w∼N(0,2nin)w \sim N\left(0, \sqrt{\frac{2}{n_{in}}}\right)wN(0,nin2 )

对于LeakyReLU的变体:

如果LeakyReLU的负斜率为 aaa,则:
Var(w)=2(1+a2)⋅ninVar(w) = \frac{2}{(1 + a^2) \cdot n_{in}}Var(w)=(1+a2)nin2

3.5 PyTorch实现

import torch
import torch.nn as nn

# 方法1:使用PyTorch内置的He初始化
layer = nn.Linear(784, 256)
nn.init.kaiming_uniform_(layer.weight, mode='fan_in', nonlinearity='relu')
# 或者
nn.init.kaiming_normal_(layer.weight, mode='fan_in', nonlinearity='relu')

# 方法2:在CNN模型中应用He初始化
class ConvNet(nn.Module):
    def __init__(self):
        super(ConvNet, self).__init__()
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
        self.fc1 = nn.Linear(64 * 7 * 7, 128)
        self.fc2 = nn.Linear(128, 10)
        
        self._initialize_weights()
    
    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                # He初始化适用于ReLU
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif 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)
    
    def forward(self, x):
        x = torch.relu(self.conv1(x))
        x = torch.max_pool2d(x, 2)
        x = torch.relu(self.conv2(x))
        x = torch.max_pool2d(x, 2)
        x = x.view(x.size(0), -1)
        x = torch.relu(self.fc1(x))
        x = self.fc2(x)
        return x

3.6 适用场景

推荐使用He初始化的场景:

  1. ReLU激活函数:这是He初始化的主要设计目标
  2. LeakyReLU、PReLU等ReLU变体:可以使用带参数的He初始化
  3. 深层卷积神经网络:ResNet、VGG等现代CNN架构
  4. 计算机视觉任务:图像分类、目标检测等

4. 正交初始化与谱初始化

4.1 正交初始化

正交初始化通过生成正交矩阵来初始化权重,确保变换保持向量长度不变。

数学原理:

正交矩阵 QQQ 满足 QTQ=IQ^T Q = IQTQ=I,这意味着:

  • 变换保持向量长度:∥Qx∥=∥x∥\|Qx\| = \|x\|Qx=x
  • 特征值绝对值为1,避免梯度消失或爆炸

PyTorch实现:

import torch
import torch.nn as nn

# 正交初始化
layer = nn.Linear(256, 256)
nn.init.orthogonal_(layer.weight, gain=1.0)

# gain参数用于缩放,对于ReLU通常使用sqrt(2)
nn.init.orthogonal_(layer.weight, gain=torch.sqrt(torch.tensor(2.0)))

适用场景:

  • 循环神经网络(RNN、LSTM)的隐藏层到隐藏层连接
  • 需要保持信号强度的深层网络
  • 对抗训练中的生成器和判别器

4.2 谱初始化

谱初始化(Spectral Normalization)主要用于生成对抗网络(GAN)中的判别器,通过限制权重矩阵的谱范数来控制Lipschitz常数。

核心思想:

谱范数 σ(W)\sigma(W)σ(W) 是矩阵 WWW 的最大奇异值。谱初始化确保:
σ(W)=1\sigma(W) = 1σ(W)=1

这限制了梯度的大小,使训练更加稳定。

PyTorch实现:

import torch
import torch.nn as nn

# 使用spectral_norm包装层
layer = nn.utils.spectral_norm(nn.Linear(256, 256))

# 在GAN判别器中的应用示例
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.fc1 = nn.utils.spectral_norm(nn.Linear(784, 512))
        self.fc2 = nn.utils.spectral_norm(nn.Linear(512, 256))
        self.fc3 = nn.utils.spectral_norm(nn.Linear(256, 1))
    
    def forward(self, x):
        x = torch.leaky_relu(self.fc1(x), 0.2)
        x = torch.leaky_relu(self.fc2(x), 0.2)
        x = self.fc3(x)
        return x

5. 初始化方法对比

初始化方法 适用激活函数 方差公式 主要应用场景 优点 缺点
Xavier/Glorot tanh、sigmoid、softsign 2nin+nout\frac{2}{n_{in} + n_{out}}nin+nout2 全连接层、浅层网络 平衡前向/反向传播 不适用于ReLU
He/Kaiming ReLU、LeakyReLU 2nin\frac{2}{n_{in}}nin2 CNN、深层网络 专为ReLU设计 对tanh效果一般
正交初始化 任意 保持范数 RNN、深层网络 防止梯度消失/爆炸 计算成本较高
谱初始化 任意 谱范数=1 GAN判别器 训练稳定 限制表达能力
随机初始化 任意 自定义 简单实验 实现简单 需要仔细调参

选择建议:

  1. 使用ReLU族激活函数 -> 选择He初始化
  2. 使用tanh/sigmoid激活函数 -> 选择Xavier初始化
  3. 训练RNN/LSTM -> 考虑正交初始化
  4. 训练GAN -> 判别器使用谱初始化

6. 学习率调度策略

6.1 为什么需要学习率调度

学习率是深度学习训练中最重要的超参数之一。固定的学习率往往不能适应训练的不同阶段:

  • 训练初期:需要较大学习率快速接近最优解
  • 训练中期:需要适中学习率稳定收敛
  • 训练后期:需要较小学习率精细调整

6.2 StepLR:阶梯式衰减

StepLR在固定的训练轮次后按因子衰减学习率。

原理:
lrepoch=lrinitial×γ⌊epoch/step_size⌋lr_{epoch} = lr_{initial} \times \gamma^{\lfloor epoch / step\_size \rfloor}lrepoch=lrinitial×γepoch/step_size

PyTorch实现:

import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import StepLR

# 定义模型、损失函数和优化器
model = NeuralNetwork()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9)

# 每30个epoch将学习率乘以0.1
scheduler = StepLR(optimizer, step_size=30, gamma=0.1)

# 训练循环
for epoch in range(100):
    # 训练代码...
    train(model, optimizer, criterion)
    
    # 更新学习率
    scheduler.step()
    
    print(f"Epoch {epoch}, LR: {scheduler.get_last_lr()[0]}")

适用场景:

  • 训练过程相对稳定的任务
  • 需要明确控制学习率下降时机的场景
  • 传统图像分类任务

6.3 ExponentialLR:指数衰减

指数衰减使学习率按指数函数连续下降。

原理:
lrepoch=lrinitial×γepochlr_{epoch} = lr_{initial} \times \gamma^{epoch}lrepoch=lrinitial×γepoch

PyTorch实现:

from torch.optim.lr_scheduler import ExponentialLR

optimizer = optim.SGD(model.parameters(), lr=0.1)

# 每个epoch学习率乘以0.95
scheduler = ExponentialLR(optimizer, gamma=0.95)

for epoch in range(100):
    train(model, optimizer, criterion)
    scheduler.step()

6.4 Cosine Annealing:余弦退火

余弦退火使用余弦函数平滑地降低学习率,在训练结束时接近0。

原理:
lrt=lrmin+12(lrmax−lrmin)(1+cos(tTπ))lr_{t} = lr_{min} + \frac{1}{2}(lr_{max} - lr_{min})(1 + cos(\frac{t}{T}\pi))lrt=lrmin+21(lrmaxlrmin)(1+cos(Ttπ))

其中 ttt 是当前步数,TTT 是总步数。

PyTorch实现:

from torch.optim.lr_scheduler import CosineAnnealingLR

optimizer = optim.SGD(model.parameters(), lr=0.1)

# T_max是余弦周期的半周期长度
scheduler = CosineAnnealingLR(optimizer, T_max=100, eta_min=0)

for epoch in range(100):
    train(model, optimizer, criterion)
    scheduler.step()

Cosine Annealing with Warm Restarts(带热重启的余弦退火):

from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts

optimizer = optim.SGD(model.parameters(), lr=0.1)

# T_0是第一次重启的周期,T_mult是重启后周期倍数
scheduler = CosineAnnealingWarmRestarts(optimizer, T_0=10, T_mult=2)

for epoch in range(100):
    train(model, optimizer, criterion)
    scheduler.step()

7. 预热(Warmup)与余弦退火详解

7.1 Warmup的原理

Warmup是训练初期逐渐增加学习率的策略,主要用于:

  1. 稳定训练初期:避免大学习率导致的不稳定
  2. 适应大批量训练:大批量训练需要更大的学习率,但需要渐进式增加
  3. 防止早期过拟合:小学习率有助于模型先学习简单特征

线性Warmup公式:

lrt=lrbase×twarmup_steps,t<warmup_stepslr_{t} = lr_{base} \times \frac{t}{warmup\_steps}, \quad t < warmup\_stepslrt=lrbase×warmup_stepst,t<warmup_steps

7.2 Warmup + Cosine Annealing组合

这是现代深度学习训练的标准配置,特别是在Transformer和大型模型训练中。

完整学习率曲线:

学习率
  ^
  |     /-------------------\
  |    /                     \
  |   /                       \
  |  /                         \
  | /                           \
  |/                             \
  +-----------------------------------> 训练步数
  0    warmup              end

PyTorch实现(自定义):

import torch
from torch.optim.lr_scheduler import _LRScheduler

class WarmupCosineScheduler(_LRScheduler):
    """
    Warmup + Cosine Annealing 学习率调度器
    """
    def __init__(self, optimizer, warmup_steps, total_steps, base_lr, last_epoch=-1):
        self.warmup_steps = warmup_steps
        self.total_steps = total_steps
        self.base_lr = base_lr
        super(WarmupCosineScheduler, self).__init__(optimizer, last_epoch)
    
    def get_lr(self):
        if self.last_epoch < self.warmup_steps:
            # Warmup阶段:线性增加
            warmup_factor = self.last_epoch / self.warmup_steps
            return [self.base_lr * warmup_factor for _ in self.base_lrs]
        else:
            # Cosine Annealing阶段
            progress = (self.last_epoch - self.warmup_steps) / (self.total_steps - self.warmup_steps)
            cosine_decay = 0.5 * (1 + torch.cos(torch.tensor(progress * 3.14159265359)))
            return [self.base_lr * cosine_decay.item() for _ in self.base_lrs]

# 使用示例
optimizer = optim.AdamW(model.parameters(), lr=1e-4)
scheduler = WarmupCosineScheduler(
    optimizer, 
    warmup_steps=2000, 
    total_steps=100000, 
    base_lr=1e-4
)

for step in range(100000):
    train_step(model, optimizer)
    scheduler.step()

7.3 Hugging Face Transformers中的实现

from transformers import get_cosine_schedule_with_warmup

optimizer = optim.AdamW(model.parameters(), lr=5e-5)

# 创建调度器
scheduler = get_cosine_schedule_with_warmup(
    optimizer,
    num_warmup_steps=500,      # 预热步数
    num_training_steps=10000    # 总训练步数
)

# 训练循环
for step in range(10000):
    loss = train_step(model, batch)
    loss.backward()
    optimizer.step()
    scheduler.step()
    optimizer.zero_grad()

8. 初始化方法对比实验

下面是一个完整的实验代码,对比不同初始化方法在相同网络结构上的表现:

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import numpy as np

# 设置随机种子确保可复现
torch.manual_seed(42)
np.random.seed(42)

# 数据准备
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

train_dataset = torchvision.datasets.MNIST(
    root='./data', train=True, download=True, transform=transform
)
train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True)

test_dataset = torchvision.datasets.MNIST(
    root='./data', train=False, download=True, transform=transform
)
test_loader = DataLoader(test_dataset, batch_size=128, shuffle=False)

# 定义测试模型
class TestModel(nn.Module):
    def __init__(self):
        super(TestModel, self).__init__()
        self.flatten = nn.Flatten()
        self.fc1 = nn.Linear(28*28, 512)
        self.fc2 = nn.Linear(512, 256)
        self.fc3 = nn.Linear(256, 128)
        self.fc4 = nn.Linear(128, 10)
        self.relu = nn.ReLU()
    
    def forward(self, x):
        x = self.flatten(x)
        x = self.relu(self.fc1(x))
        x = self.relu(self.fc2(x))
        x = self.relu(self.fc3(x))
        x = self.fc4(x)
        return x

# 初始化函数定义
def initialize_model(model, init_type):
    """根据类型初始化模型权重"""
    for m in model.modules():
        if isinstance(m, nn.Linear):
            if init_type == 'xavier_uniform':
                nn.init.xavier_uniform_(m.weight)
            elif init_type == 'xavier_normal':
                nn.init.xavier_normal_(m.weight)
            elif init_type == 'he_uniform':
                nn.init.kaiming_uniform_(m.weight, mode='fan_in', nonlinearity='relu')
            elif init_type == 'he_normal':
                nn.init.kaiming_normal_(m.weight, mode='fan_in', nonlinearity='relu')
            elif init_type == 'orthogonal':
                nn.init.orthogonal_(m.weight, gain=np.sqrt(2))
            elif init_type == 'random_normal':
                nn.init.normal_(m.weight, mean=0, std=0.01)
            elif init_type == 'random_uniform':
                nn.init.uniform_(m.weight, -0.1, 0.1)
            
            if m.bias is not None:
                nn.init.constant_(m.bias, 0)
    return model

def train_epoch(model, loader, criterion, optimizer, device):
    """训练一个epoch"""
    model.train()
    total_loss = 0
    correct = 0
    total = 0
    
    for batch_idx, (data, target) in enumerate(loader):
        data, target = data.to(device), target.to(device)
        
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()
        
        total_loss += loss.item()
        _, predicted = output.max(1)
        total += target.size(0)
        correct += predicted.eq(target).sum().item()
    
    return total_loss / len(loader), 100. * correct / total

def evaluate(model, loader, criterion, device):
    """评估模型"""
    model.eval()
    total_loss = 0
    correct = 0
    total = 0
    
    with torch.no_grad():
        for data, target in loader:
            data, target = data.to(device), target.to(device)
            output = model(data)
            loss = criterion(output, target)
            
            total_loss += loss.item()
            _, predicted = output.max(1)
            total += target.size(0)
            correct += predicted.eq(target).sum().item()
    
    return total_loss / len(loader), 100. * correct / total

def run_experiment(init_type, epochs=10):
    """运行单个初始化方法的实验"""
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    
    model = TestModel().to(device)
    model = initialize_model(model, init_type)
    
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    
    train_losses = []
    train_accs = []
    test_losses = []
    test_accs = []
    
    for epoch in range(epochs):
        train_loss, train_acc = train_epoch(model, train_loader, criterion, optimizer, device)
        test_loss, test_acc = evaluate(model, test_loader, criterion, device)
        
        train_losses.append(train_loss)
        train_accs.append(train_acc)
        test_losses.append(test_loss)
        test_accs.append(test_acc)
        
        print(f"[{init_type}] Epoch {epoch+1}/{epochs}: "
              f"Train Loss={train_loss:.4f}, Train Acc={train_acc:.2f}%, "
              f"Test Loss={test_loss:.4f}, Test Acc={test_acc:.2f}%")
    
    return {
        'train_losses': train_losses,
        'train_accs': train_accs,
        'test_losses': test_losses,
        'test_accs': test_accs
    }

# 运行对比实验
if __name__ == "__main__":
    init_methods = [
        'xavier_uniform',
        'xavier_normal', 
        'he_uniform',
        'he_normal',
        'orthogonal',
        'random_normal'
    ]
    
    results = {}
    for init_method in init_methods:
        print(f"\n{'='*50}")
        print(f"Testing: {init_method}")
        print('='*50)
        results[init_method] = run_experiment(init_method, epochs=10)
    
    # 可视化结果
    fig, axes = plt.subplots(2, 2, figsize=(15, 10))
    
    # 训练损失
    for init_method in init_methods:
        axes[0, 0].plot(results[init_method]['train_losses'], label=init_method)
    axes[0, 0].set_title('Training Loss')
    axes[0, 0].set_xlabel('Epoch')
    axes[0, 0].set_ylabel('Loss')
    axes[0, 0].legend()
    axes[0, 0].grid(True)
    
    # 训练准确率
    for init_method in init_methods:
        axes[0, 1].plot(results[init_method]['train_accs'], label=init_method)
    axes[0, 1].set_title('Training Accuracy')
    axes[0, 1].set_xlabel('Epoch')
    axes[0, 1].set_ylabel('Accuracy (%)')
    axes[0, 1].legend()
    axes[0, 1].grid(True)
    
    # 测试损失
    for init_method in init_methods:
        axes[1, 0].plot(results[init_method]['test_losses'], label=init_method)
    axes[1, 0].set_title('Test Loss')
    axes[1, 0].set_xlabel('Epoch')
    axes[1, 0].set_ylabel('Loss')
    axes[1, 0].legend()
    axes[1, 0].grid(True)
    
    # 测试准确率
    for init_method in init_methods:
        axes[1, 1].plot(results[init_method]['test_accs'], label=init_method)
    axes[1, 1].set_title('Test Accuracy')
    axes[1, 1].set_xlabel('Epoch')
    axes[1, 1].set_ylabel('Accuracy (%)')
    axes[1, 1].legend()
    axes[1, 1].grid(True)
    
    plt.tight_layout()
    plt.savefig('initialization_comparison.png', dpi=150)
    plt.show()
    
    # 打印最终对比结果
    print("\n" + "="*60)
    print("最终测试结果对比")
    print("="*60)
    print(f"{'初始化方法':<20} {'最终训练准确率':>15} {'最终测试准确率':>15}")
    print("-"*60)
    for init_method in init_methods:
        final_train_acc = results[init_method]['train_accs'][-1]
        final_test_acc = results[init_method]['test_accs'][-1]
        print(f"{init_method:<20} {final_train_acc:>14.2f}% {final_test_acc:>14.2f}%")

9. 避坑小贴士

9.1 常见错误1:所有层使用相同的初始化

错误做法:

# 错误:所有层使用相同的初始化,不考虑激活函数类型
for m in model.modules()():
    if isinstance(m, nn.Linear):
        nn.init.xavier_uniform_(m.weight)  # 对ReLU层也使用Xavier

正确做法:

# 正确:根据激活函数选择合适的初始化
for m in model.modules():
    if isinstance(m, nn.Linear):
        if isinstance(m, nn.ReLU):
            nn.init.kaiming_normal_(m.weight, nonlinearity='relu')
        else:
            nn.init.xavier_normal_(m.weight)

9.2 常见错误2:忘记初始化偏置项

错误做法:

nn.init.xavier_normal_(layer.weight)
# 忘记初始化bias,可能导致bias初始值过大或过小

正确做法:

nn.init.xavier_normal_(layer.weight)
if layer.bias is not None:
    nn.init.constant_(layer.bias, 0)  # 偏置通常初始化为0

9.3 常见错误3:学习率调度器step调用位置错误

错误做法:

for epoch in range(epochs):
    scheduler.step()  # 错误:在epoch开始时调用
    for batch in dataloader:
        # 训练代码...

正确做法:

for epoch in range(epochs):
    for batch in dataloader:
        # 训练代码...
        optimizer.step()
    scheduler.step()  # 正确:在每个epoch结束时调用

9.4 常见错误4:Warmup步数设置不当

错误做法:

# Warmup步数过长,浪费训练时间
scheduler = get_cosine_schedule_with_warmup(
    optimizer,
    num_warmup_steps=5000,  # 对于小数据集来说太长
    num_training_steps=10000
)

正确做法:

# Warmup步数通常占总步数的5%-10%
warmup_steps = int(0.1 * total_steps)
scheduler = get_cosine_schedule_with_warmup(
    optimizer,
    num_warmup_steps=warmup_steps,
    num_training_steps=total_steps
)

9.5 常见错误5:Batch Normalization与初始化顺序

错误做法:

# 先应用初始化,再添加BN层,可能导致BN统计不准确
model = initialize_model(model, 'he')
model = add_batch_norm(model)

正确做法:

# 先构建完整模型(包括BN层),再应用初始化
model = build_model_with_bn()
model = initialize_model(model, 'he')

10. 本章小结和知识点回顾

核心知识点

  1. 权重初始化的重要性

    • 防止梯度消失和梯度爆炸
    • 加速模型收敛
    • 打破对称性,确保神经元学习不同特征
  2. Xavier/Glorot初始化

    • 适用于tanh、sigmoid等对称激活函数
    • 方差公式:Var(w)=2nin+noutVar(w) = \frac{2}{n_{in} + n_{out}}Var(w)=nin+nout2
    • 平衡前向和反向传播的方差
  3. He/Kaiming初始化

    • 专为ReLU激活函数设计
    • 方差公式:Var(w)=2ninVar(w) = \frac{2}{n_{in}}Var(w)=nin2
    • 现代CNN的标准初始化方法
  4. 正交初始化与谱初始化

    • 正交初始化:保持向量范数,适用于RNN
    • 谱初始化:限制Lipschitz常数,适用于GAN
  5. 学习率调度策略

    • StepLR:阶梯式衰减,简单可控
    • ExponentialLR:指数衰减,平滑连续
    • Cosine Annealing:余弦退火,现代训练标配
    • Warmup:训练初期渐进增加学习率,提高稳定性

一句话总结

合适的初始化让网络有一个好的起点,合理的学习率调度让网络稳步走向最优解。

实践建议

  1. 使用ReLU激活函数时,优先选择He初始化
  2. 训练大型模型时,使用Warmup + Cosine Annealing组合
  3. 实验不同初始化方法,选择最适合你任务的策略
  4. 监控训练过程中的梯度大小,及时调整初始化或学习率

补充:权重初始化领域的研究仍在不断发展。2024年以来,一些研究开始探索基于数据驱动的初始化方法,以及针对Transformer架构的自适应初始化策略。建议读者关注NeurIPS、ICML等顶级会议的最新论文。


本文是《深度学习精通》系列教程的第4章,专注于权重初始化与优化策略。如有疑问或建议,欢迎在评论区留言讨论。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐