【深度学习精通】第5章 | 激活函数与归一化技术 - 从Sigmoid到SwiGLU
摘要 本文系统介绍了神经网络中的激活函数与归一化技术,主要内容包括: 激活函数:从经典的Sigmoid、Tanh到ReLU家族(ReLU、LeakyReLU、PReLU等),再到现代激活函数如Swish、Mish和SwiGLU,详细分析了它们的数学特性、优缺点及适用场景。 归一化技术:涵盖批归一化(BatchNorm)、层归一化(LayerNorm)等方法的原理与实现,解释它们如何稳定训练过程。
环境声明
- Python版本:Python 3.10+
- PyTorch版本:PyTorch 2.0+
- NumPy版本:NumPy 1.24+
- Matplotlib版本:Matplotlib 3.7+
- 开发工具:PyCharm / VS Code / Jupyter Notebook
- 操作系统:Windows / macOS / Linux(通用)
- GPU支持:CUDA 11.8+(可选,但推荐)
学习目标
通过本章学习,你将掌握:
- 理解激活函数在神经网络中的核心作用和选择原则
- 深入掌握Sigmoid、Tanh等经典激活函数的原理与适用场景
- 全面理解ReLU家族(ReLU、LeakyReLU、PReLU、ELU、GELU)的特点
- 掌握Swish、Mish、SwiGLU等现代激活函数的设计思想
- 理解批归一化(BatchNorm)的原理与PyTorch实现
- 掌握层归一化、实例归一化、组归一化的区别与选择
- 能够根据任务类型选择合适的激活函数和归一化方法
内容摘要
激活函数是神经网络的核心组件,它赋予网络非线性表达能力。从早期的Sigmoid到现代的SwiGLU,激活函数经历了长足发展。本章将系统讲解各类激活函数的原理、优缺点和适用场景,同时深入剖析归一化技术(BatchNorm、LayerNorm等)如何稳定训练过程、加速收敛。通过可视化对比和实战代码,帮助你建立完整的知识体系。
1. 激活函数的作用与选择原则
1.1 为什么需要激活函数
想象一个没有激活函数的神经网络:无论网络有多少层,它本质上只是在进行线性变换的叠加。数学上,多个线性变换的组合仍然是一个线性变换,这意味着网络的表达能力极其有限,无法学习复杂的非线性模式。
激活函数的作用可以类比为:
- 神经元的开关:决定信号是否传递、传递多少
- 非线性映射:将线性输出转换为非线性空间
- 特征筛选:增强有用特征,抑制噪声
补充:没有激活函数的神经网络等价于单层感知机,这被称为"多层网络的线性退化问题"。
1.2 激活函数的选择原则
选择激活函数时需要考虑以下因素:
| 考量因素 | 说明 | 影响 |
|---|---|---|
| 非线性程度 | 函数的非线性表达能力 | 决定网络能学习的模式复杂度 |
| 梯度特性 | 导数是否平滑、是否存在梯度消失 | 影响训练稳定性和收敛速度 |
| 计算效率 | 前向和反向传播的计算开销 | 影响模型训练和推理速度 |
| 输出范围 | 输出值是否有界 | 影响网络层间的信号传递 |
| 平滑性 | 函数是否处处可导 | 影响优化过程的稳定性 |
2. 经典激活函数详解
2.1 Sigmoid函数
Sigmoid函数是最早被广泛使用的激活函数之一,它将任意实数映射到(0, 1)区间。
数学公式:
σ(x) = 1 / (1 + e^(-x))
Python实现:
import numpy as np
import torch
import torch.nn as nn
def sigmoid(x):
"""Sigmoid激活函数的实现"""
return 1 / (1 + np.exp(-x))
# PyTorch内置实现
sigmoid_torch = nn.Sigmoid()
特性分析:
- 优点:输出范围(0,1),适合概率解释;处处可导且导数平滑
- 缺点:
- 梯度消失问题:当|x|较大时,梯度趋近于0
- 输出非零中心化:导致梯度更新效率低
- 计算涉及指数运算,相对较慢
适用场景:
- 二分类问题的输出层
- 门控机制(如LSTM的遗忘门、输入门)
- 需要概率解释的场景
2.2 Tanh函数
Tanh(双曲正切)函数是Sigmoid的改进版本,输出范围扩展到(-1, 1)。
数学公式:
tanh(x) = (e^x - e^(-x)) / (e^x + e^(-x)) = 2σ(2x) - 1
Python实现:
def tanh(x):
"""Tanh激活函数的实现"""
return np.tanh(x)
# PyTorch内置实现
tanh_torch = nn.Tanh()
特性分析:
- 优点:
- 零中心化输出:有助于梯度下降收敛
- 梯度比Sigmoid更强:导数范围(0,1] vs (0,0.25]
- 缺点:
- 仍然存在梯度消失问题
- 计算开销与Sigmoid相当
适用场景:
- 循环神经网络(RNN、LSTM)的隐藏层
- 需要零中心化输出的中间层
3. ReLU家族详解
3.1 ReLU(Rectified Linear Unit)
ReLU是深度学习领域最重要的突破之一,它简单却极其有效。
数学公式:
ReLU(x) = max(0, x)
Python实现:
def relu(x):
"""ReLU激活函数的实现"""
return np.maximum(0, x)
# PyTorch内置实现
relu_torch = nn.ReLU()
特性分析:
- 优点:
- 计算简单:仅需比较操作
无梯度消失问题(正区间梯度恒为1) - 引入稀疏性:约50%神经元输出为0
- 计算简单:仅需比较操作
- 缺点:
- 死亡ReLU问题:负区间梯度为0,神经元可能永久失活
- 输出非零中心化
适用场景:
- 卷积神经网络(CNN)的隐藏层
- 全连接网络的中间层
- 现代深度学习模型的默认选择
3.2 LeakyReLU
为解决死亡ReLU问题,LeakyReLU在负区间引入小的斜率。
数学公式:
LeakyReLU(x) = max(αx, x),其中α通常取0.01
Python实现:
def leaky_relu(x, alpha=0.01):
"""LeakyReLU激活函数的实现"""
return np.where(x > 0, x, alpha * x)
# PyTorch内置实现
leaky_relu_torch = nn.LeakyReLU(negative_slope=0.01)
特性分析:
- 保留ReLU的优点
- 负区间有微小梯度,避免神经元死亡
- 超参数α需要调优
3.3 PReLU(Parametric ReLU)
PReLU将LeakyReLU的斜率参数α变为可学习参数。
Python实现:
# PyTorch内置实现
prelu_torch = nn.PReLU() # α作为可学习参数
特性分析:
- 自适应学习负区间的最佳斜率
- 增加少量参数,提升模型灵活性
- 可能增加过拟合风险
3.4 ELU(Exponential Linear Unit)
ELU在负区间使用指数函数,使输出更接近零均值。
数学公式:
ELU(x) = x, if x > 0
ELU(x) = α(e^x - 1), if x ≤ 0
Python实现:
def elu(x, alpha=1.0):
"""ELU激活函数的实现"""
return np.where(x > 0, x, alpha * (np.exp(x) - 1))
# PyTorch内置实现
elu_torch = nn.ELU(alpha=1.0)
特性分析:
- 负区间平滑,输出接近零均值
- 缓解死亡ReLU问题
- 负区间计算涉及指数,稍慢于ReLU
3.5 GELU(Gaussian Error Linear Unit)
GELU是Transformer架构的核心激活函数,在BERT、GPT等模型中广泛使用。
数学公式:
GELU(x) = x * Φ(x) = x * 0.5 * (1 + erf(x / √2))
其中Φ(x)是标准正态分布的累积分布函数。
近似实现:
GELU(x) ≈ 0.5x * (1 + tanh[√(2/π) * (x + 0.044715x³)])
Python实现:
def gelu(x):
"""GELU激活函数的近似实现"""
return 0.5 * x * (1 + np.tanh(
np.sqrt(2 / np.pi) * (x + 0.044715 * np.power(x, 3))
))
# PyTorch内置实现
gelu_torch = nn.GELU()
特性分析:
- 平滑可导,处处非零梯度
- 自门控机制:输出与输入的概率分布相关
- 在NLP任务中表现优异
4. 现代激活函数
4.1 Swish激活函数
Swish是Google Brain提出的自门控激活函数,在深层网络中表现优异。
数学公式:
Swish(x) = x * σ(x) = x / (1 + e^(-x))
Python实现:
def swish(x):
"""Swish激活函数的实现"""
return x * sigmoid(x)
# PyTorch实现
class Swish(nn.Module):
def forward(self, x):
return x * torch.sigmoid(x)
特性分析:
- 非单调函数:在x<0时先下降后上升
- 自门控机制:输出由输入自身控制
- 深层网络中性能优于ReLU
4.2 Mish激活函数
Mish是2019年提出的激活函数,结合了Swish的优点并进一步优化。
数学公式:
Mish(x) = x * tanh(softplus(x)) = x * tanh(ln(1 + e^x))
Python实现:
def mish(x):
"""Mish激活函数的实现"""
return x * np.tanh(np.log(1 + np.exp(x)))
# PyTorch实现
class Mish(nn.Module):
def forward(self, x):
return x * torch.tanh(torch.nn.functional.softplus(x))
特性分析:
- 自正则化特性:输出范围有限,有助于稳定训练
- 平滑无界:避免硬阈值带来的信息损失
- 在目标检测等任务中表现突出
4.3 SwiGLU激活函数
SwiGLU是当前大语言模型(如LLaMA、PaLM)广泛使用的激活函数,结合了Swish和GLU(门控线性单元)的优点。
数学公式:
SwiGLU(x, W, V, b, c) = Swish(xW + b) ⊗ (xV + c)
其中⊗表示逐元素乘法。
Python实现:
class SwiGLU(nn.Module):
"""SwiGLU激活函数实现"""
def __init__(self, dim_in, dim_out):
super().__init__()
self.w1 = nn.Linear(dim_in, dim_out)
self.w2 = nn.Linear(dim_in, dim_out)
self.w3 = nn.Linear(dim_out, dim_out)
def forward(self, x):
# SwiGLU: Swish(xW) * (xV)
return self.w3(nn.functional.silu(self.w1(x)) * self.w2(x))
# 简化的SwiGLU实现(常用于Transformer FFN)
class SwiGLUFFN(nn.Module):
def __init__(self, dim, hidden_dim=None, dropout=0.0):
super().__init__()
if hidden_dim is None:
# SwiGLU通常使用更大的中间维度
hidden_dim = int(dim * 8 / 3)
self.w1 = nn.Linear(dim, hidden_dim)
self.w2 = nn.Linear(dim, hidden_dim)
self.w3 = nn.Linear(hidden_dim, dim)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
# SiLU即Swish激活函数
hidden = nn.functional.silu(self.w1(x)) * self.w2(x)
return self.dropout(self.w3(hidden))
特性分析:
- 门控机制:选择性传递信息,增强表达能力
- 在现代LLM中成为标配
- 计算效率与表达能力的最优平衡
5. 激活函数可视化对比
下面通过代码可视化各类激活函数的特性和差异:
import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
def plot_activation_functions():
"""可视化各类激活函数及其导数"""
x = np.linspace(-5, 5, 1000)
# 定义激活函数
activations = {
'Sigmoid': lambda x: 1 / (1 + np.exp(-x)),
'Tanh': lambda x: np.tanh(x),
'ReLU': lambda x: np.maximum(0, x),
'LeakyReLU(0.1)': lambda x: np.where(x > 0, x, 0.1 * x),
'ELU': lambda x: np.where(x > 0, x, np.exp(x) - 1),
'Swish': lambda x: x / (1 + np.exp(-x)),
'Mish': lambda x: x * np.tanh(np.log(1 + np.exp(x))),
'GELU': lambda x: 0.5 * x * (1 + np.tanh(
np.sqrt(2 / np.pi) * (x + 0.044715 * x**3)
))
}
# 计算导数(数值微分)
def derivative(func, x, h=1e-5):
return (func(x + h) - func(x - h)) / (2 * h)
# 创建子图
fig, axes = plt.subplots(2, 4, figsize=(16, 8))
axes = axes.flatten()
for idx, (name, func) in enumerate(activations.items()):
ax = axes[idx]
y = func(x)
dy = derivative(func, x)
ax.plot(x, y, 'b-', linewidth=2, label=f'{name}')
ax.plot(x, dy, 'r--', linewidth=2, label=f"{name}'")
ax.axhline(y=0, color='k', linestyle='-', linewidth=0.5)
ax.axvline(x=0, color='k', linestyle='-', linewidth=0.5)
ax.set_title(name, fontsize=12, fontweight='bold')
ax.set_xlabel('x')
ax.set_ylabel('y / dy')
ax.legend(loc='best')
ax.grid(True, alpha=0.3)
ax.set_xlim(-5, 5)
ax.set_ylim(-2, 5)
plt.tight_layout()
plt.savefig('activation_functions_comparison.png', dpi=150, bbox_inches='tight')
plt.show()
# 运行可视化
plot_activation_functions()
激活函数综合对比表:
| 激活函数 | 公式 | 输出范围 | 梯度消失 | 计算复杂度 | 推荐场景 |
|---|---|---|---|---|---|
| Sigmoid | 1/(1+e^(-x)) | (0, 1) | 严重 | 高 | 二分类输出、门控 |
| Tanh | (ex-e(-x))/(ex+e(-x)) | (-1, 1) | 严重 | 高 | RNN隐藏层 |
| ReLU | max(0, x) | [0, +inf) | 无(正区间) | 极低 | CNN、默认选择 |
| LeakyReLU | max(αx, x) | (-inf, +inf) | 无 | 低 | 避免死亡ReLU |
| PReLU | max(αx, x) | (-inf, +inf) | 无 | 低 | 需要自适应斜率 |
| ELU | x或α(e^x-1) | (-α, +inf) | 无 | 中 | 需要零均值输出 |
| GELU | xΦ(x) | (-inf, +inf) | 无 | 高 | Transformer、NLP |
| Swish | xσ(x) | (-inf, +inf) | 无 | 高 | 深层网络 |
| Mish | x·tanh(softplus(x)) | (-inf, +inf) | 无 | 高 | 目标检测 |
| SwiGLU | Swish(xW)·(xV) | (-inf, +inf) | 无 | 高 | 大语言模型 |
6. 归一化技术详解
6.1 为什么需要归一化
深度神经网络训练过程中,随着网络层数增加,会出现以下问题:
- 内部协变量偏移(Internal Covariate Shift):每层输入分布随训练变化
- 梯度消失/爆炸:信号在网络中传播时衰减或放大
- 训练不稳定:学习率难以调节,收敛困难
归一化技术通过对层输入进行标准化,解决上述问题。
6.2 批归一化(Batch Normalization)
BatchNorm是2015年提出的里程碑技术,极大加速了深度网络训练。
原理:
对于一个小批量数据,对每个特征维度进行标准化:
μ_B = (1/m) Σ x_i # 批量均值
σ²_B = (1/m) Σ (x_i - μ_B)² # 批量方差
x̂_i = (x_i - μ_B) / √(σ²_B + ε) # 标准化
y_i = γx̂_i + β # 缩放和平移
Python实现:
import torch
import torch.nn as nn
# PyTorch内置BatchNorm
batch_norm_1d = nn.BatchNorm1d(num_features=64) # 全连接层
batch_norm_2d = nn.BatchNorm2d(num_features=64) # 卷积层
# 手动实现BatchNorm
class BatchNormManual(nn.Module):
def __init__(self, num_features, eps=1e-5, momentum=0.1):
super().__init__()
self.eps = eps
self.momentum = momentum
# 可学习参数
self.gamma = nn.Parameter(torch.ones(num_features))
self.beta = nn.Parameter(torch.zeros(num_features))
# 运行时统计量
self.register_buffer('running_mean', torch.zeros(num_features))
self.register_buffer('running_var', torch.ones(num_features))
def forward(self, x):
if self.training:
# 训练模式:使用当前批次统计量
mean = x.mean(dim=0)
var = x.var(dim=0, unbiased=False)
# 更新运行时统计量
self.running_mean = (1 - self.momentum) * self.running_mean + \
self.momentum * mean
self.running_var = (1 - self.momentum) * self.running_var + \
self.momentum * var
else:
# 推理模式:使用运行时统计量
mean = self.running_mean
var = self.running_var
# 标准化
x_norm = (x - mean) / torch.sqrt(var + self.eps)
# 缩放和平移
return self.gamma * x_norm + self.beta
特性分析:
- 优点:
- 加速训练收敛(允许更大学习率)
- 减少对初始化的敏感性
- 具有正则化效果
- 缺点:
- 依赖批量大小,小批量时效果差
- 训练和推理行为不一致
- 对序列数据不友好
适用场景:
- 卷积神经网络(CNN)
- 大批量训练的全连接网络
- 不适用于RNN和Transformer
6.3 层归一化(Layer Normalization)
LayerNorm对每个样本的所有特征进行归一化,不依赖批量维度。
原理:
μ = (1/H) Σ x_i # 样本内均值
σ² = (1/H) Σ (x_i - μ)² # 样本内方差
y = (x - μ) / √(σ² + ε) # 标准化
Python实现:
# PyTorch内置LayerNorm
layer_norm = nn.LayerNorm(normalized_shape=64)
# 手动实现LayerNorm
class LayerNormManual(nn.Module):
def __init__(self, num_features, eps=1e-5):
super().__init__()
self.eps = eps
self.gamma = nn.Parameter(torch.ones(num_features))
self.beta = nn.Parameter(torch.zeros(num_features))
def forward(self, x):
# 在最后一个维度上计算均值和方差
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
x_norm = (x - mean) / torch.sqrt(var + self.eps)
return self.gamma * x_norm + self.beta
特性分析:
- 不依赖批量大小,适合小批量和单样本
- 训练和推理行为一致
- 是Transformer架构的标准配置
适用场景:
- Transformer模型
- 循环神经网络(RNN、LSTM)
- 小批量训练场景
6.4 实例归一化(Instance Normalization)
InstanceNorm对每个样本的每个通道单独归一化,常用于风格迁移。
Python实现:
# PyTorch内置InstanceNorm
instance_norm = nn.InstanceNorm2d(num_features=64)
# 手动实现InstanceNorm(2D图像)
class InstanceNormManual(nn.Module):
def __init__(self, num_features, eps=1e-5):
super().__init__()
self.eps = eps
self.gamma = nn.Parameter(torch.ones(num_features))
self.beta = nn.Parameter(torch.zeros(num_features))
def forward(self, x):
# x: (N, C, H, W)
N, C, H, W = x.shape
# 对每个样本的每个通道计算统计量
x_reshaped = x.view(N, C, -1) # (N, C, H*W)
mean = x_reshaped.mean(dim=2, keepdim=True).view(N, C, 1, 1)
var = x_reshaped.var(dim=2, keepdim=True).view(N, C, 1, 1)
x_norm = (x - mean) / torch.sqrt(var + self.eps)
return self.gamma.view(1, C, 1, 1) * x_norm + self.beta.view(1, C, 1, 1)
适用场景:
- 风格迁移(Style Transfer)
- 图像生成任务
- 需要保持通道独立性的场景
6.5 组归一化(Group Normalization)
GroupNorm将通道分组,在组内进行归一化,是BatchNorm和InstanceNorm的折中方案。
Python实现:
# PyTorch内置GroupNorm
group_norm = nn.GroupNorm(num_groups=8, num_channels=64)
# 手动实现GroupNorm
class GroupNormManual(nn.Module):
def __init__(self, num_groups, num_channels, eps=1e-5):
super().__init__()
self.num_groups = num_groups
self.eps = eps
self.gamma = nn.Parameter(torch.ones(num_channels))
self.beta = nn.Parameter(torch.zeros(num_channels))
def forward(self, x):
# x: (N, C, H, W)
N, C, H, W = x.shape
G = self.num_groups
# 将通道分组
x = x.view(N, G, C // G, H, W)
# 在组内计算统计量
mean = x.mean(dim=[2, 3, 4], keepdim=True)
var = x.var(dim=[2, 3, 4], keepdim=True, unbiased=False)
x_norm = (x - mean) / torch.sqrt(var + self.eps)
x_norm = x_norm.view(N, C, H, W)
return self.gamma.view(1, C, 1, 1) * x_norm + self.beta.view(1, C, 1, 1)
适用场景:
- 小批量目标检测
- 语义分割
- 批量大小受限的场景
7. 归一化方法对比与选择指南
7.1 综合对比表
| 归一化方法 | 归一化维度 | 依赖批量 | 适用场景 | 典型应用 |
|---|---|---|---|---|
| BatchNorm | (N, H, W) | 是 | CNN、大批量 | ResNet、VGG |
| LayerNorm | (C, H, W) | 否 | RNN、Transformer | BERT、GPT |
| InstanceNorm | (H, W) | 否 | 风格迁移 | CycleGAN |
| GroupNorm | (C/G, H, W) | 否 | 小批量检测 | Mask R-CNN |
7.2 选择决策流程
开始
│
▼
是否是序列数据(NLP、时间序列)?
│
├── 是 ──> 使用 LayerNorm
│
└── 否
│
▼
批量大小是否足够大(>32)?
│
├── 是 ──> 使用 BatchNorm
│
└── 否
│
▼
是否是风格迁移/生成任务?
│
├── 是 ──> 使用 InstanceNorm
│
└── 否 ──> 使用 GroupNorm
7.3 归一化位置选择
在神经网络中,归一化可以放在不同位置:
Post-Normalization(后归一化):
x → Linear/Conv → Activation → Normalization → 输出
这是原始Transformer的设计,但在深层网络中可能导致梯度问题。
Pre-Normalization(前归一化):
x → Normalization → Linear/Conv → Activation → 输出
现代大模型(如GPT-3、LLaMA)采用此设计,训练更稳定。
8. 实战:构建带归一化的神经网络
import torch
import torch.nn as nn
import torch.nn.functional as F
class ResidualBlock(nn.Module):
"""带BatchNorm的残差块"""
def __init__(self, in_channels, out_channels, stride=1):
super().__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, 3, stride, 1, bias=False)
self.bn1 = nn.BatchNorm2d(out_channels)
self.conv2 = nn.Conv2d(out_channels, out_channels, 3, 1, 1, bias=False)
self.bn2 = nn.BatchNorm2d(out_channels)
self.shortcut = nn.Sequential()
if stride != 1 or in_channels != out_channels:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, out_channels, 1, stride, bias=False),
nn.BatchNorm2d(out_channels)
)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
out += self.shortcut(x)
out = F.relu(out)
return out
class TransformerBlock(nn.Module):
"""带LayerNorm的Transformer块(Pre-Norm设计)"""
def __init__(self, dim, num_heads, mlp_ratio=4, dropout=0.1):
super().__init__()
self.norm1 = nn.LayerNorm(dim)
self.attn = nn.MultiheadAttention(dim, num_heads, dropout=dropout, batch_first=True)
self.norm2 = nn.LayerNorm(dim)
mlp_hidden_dim = int(dim * mlp_ratio)
self.mlp = nn.Sequential(
nn.Linear(dim, mlp_hidden_dim),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(mlp_hidden_dim, dim),
nn.Dropout(dropout)
)
def forward(self, x):
# Pre-Normalization
x = x + self.attn(self.norm1(x), self.norm1(x), self.norm1(x))[0]
x = x + self.mlp(self.norm2(x))
return x
class ModernFFN(nn.Module):
"""使用SwiGLU激活函数的现代前馈网络"""
def __init__(self, dim, hidden_dim=None, dropout=0.0):
super().__init__()
if hidden_dim is None:
# SwiGLU通常使用2/3*dim*2的隐藏维度
hidden_dim = int(2 * dim * 2 / 3)
self.w1 = nn.Linear(dim, hidden_dim)
self.w2 = nn.Linear(dim, hidden_dim)
self.w3 = nn.Linear(hidden_dim, dim)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
# SwiGLU: SiLU(xW1) * (xW2)
hidden = F.silu(self.w1(x)) * self.w2(x)
return self.dropout(self.w3(hidden))
9. 避坑小贴士
9.1 激活函数选择避坑
-
不要在深层网络中使用Sigmoid/Tanh作为隐藏层激活函数
- 原因:梯度消失问题严重,深层信号难以传播
- 替代方案:使用ReLU或GELU
-
使用ReLU时注意学习率设置
- 原因:过大的学习率可能导致大量神经元死亡
- 建议:配合BatchNorm使用,或使用LeakyReLU
-
Transformer中优先使用GELU而非ReLU
- 原因:GELU的平滑特性更适合注意力机制
- 注意:GELU计算开销稍大,但通常值得
-
大语言模型推荐使用SwiGLU
- 原因:SwiGLU在LLaMA、PaLM等模型中验证有效
- 注意:需要调整隐藏层维度(通常是8/3倍输入维度)
9.2 归一化使用避坑
-
不要在RNN中使用BatchNorm
- 原因:时间步之间统计量不稳定
- 替代方案:使用LayerNorm
-
BatchNorm的momentum参数调优
- 小批量(<32):减小momentum(如0.01)
- 大批量(>256):增大momentum(如0.1)
-
训练和推理时归一化行为不一致
- 原因:训练时用批次统计量,推理时用运行统计量
- 解决:确保调用model.eval()切换模式
-
GroupNorm的num_groups选择
- 通道数需能被num_groups整除
- 建议:32通道用8组,64通道用8或16组
10. 本章小结
10.1 核心知识点回顾
激活函数:
- 经典函数:Sigmoid、Tanh适用于特定场景,但深层网络受限
- ReLU家族:ReLU是默认选择,变体解决死亡ReLU问题
- 现代函数:GELU是Transformer标配,SwiGLU是大模型趋势
归一化技术:
- BatchNorm:CNN标配,依赖大批量
- LayerNorm:序列模型标配,批量无关
- InstanceNorm/GroupNorm:特定场景的有效补充
10.2 一句话总结
激活函数赋予神经网络非线性表达能力,归一化技术确保深层网络训练稳定;从Sigmoid到SwiGLU的演进,体现了深度学习领域对"平滑性"和"门控机制"的持续追求。
10.3 推荐实践
- CNN任务:Conv + BatchNorm + ReLU
- Transformer任务:LayerNorm + MultiHeadAttention + GELU/SwiGLU
- 小批量/目标检测:Conv + GroupNorm + ReLU
- 风格迁移:Conv + InstanceNorm + ReLU
文章标签:深度学习、激活函数、归一化、BatchNorm、LayerNorm、PyTorch、神经网络
建议阅读时间:45分钟
配套代码:本章所有代码可在Python 3.10+和PyTorch 2.0+环境下直接运行。
更多推荐


所有评论(0)