PyTorch 2.x 深度学习专题【左扬精讲】—— 残差连接:让信息直接"跳过"层传播
PyTorch 2.x 深度学习专题【左扬精讲】—— 残差连接:让信息直接"跳过"层传播
在之前的文章中,我们讲了 Self-Attention 和多头注意力。它们是 Transformer 的核心组件,能让序列中任意两个位置直接对话。但还有一个问题:如果把很多层 Attention 堆在一起,会发生什么?
答案是:梯度会消失,模型会退化,训练会变得非常困难。
这个问题不是 Transformer 独有的,所有深层神经网络都会遇到。ResNet 通过残差连接解决了这个问题,而 Transformer 借鉴了这个思想。残差连接(Residual Connection)加上层归一化(Layer Normalization),是 Transformer 能堆到几百层的关键。
torch.nn.LayerNorm ← 层归一化
torch.nn.modules.module.add_module ← PyTorch 添加模块方法
torch.autograd.grad ← 梯度计算
x + F(x) ← 残差连接核心公式
Residual Connection Layer Normalization 梯度消失 Transformer 深层网络
学习重点提示
- 必须掌握:残差连接的核心思想、层归一化的作用
- 需要理解:为什么残差能解决梯度消失、如何在 PyTorch 中实现
- 建议了解:残差和跳跃连接的区别、What-If 实验
一、从一个问题开始:为什么深层网络难训练?
What — 梯度消失是什么?
在解释残差连接之前,我们需要先理解为什么深层网络难训练。
神经网络训练靠的是反向传播:计算梯度,然后用梯度更新参数。梯度从最后一层传回第一层,就像一条长长的链条。
问题来了:如果这条链条上某一步的梯度很小,那么传回去的梯度就会越来越小,最后接近于零。
这就像:
- 第一层的参数几乎收不到梯度
- 第一层几乎学不到东西
- 整个网络变成了一个浅层网络
Why — 为什么 Attention 层特别容易梯度消失?
原因一:多层堆叠
Transformer 通常有 6-12 层编码器,每层都有:
- 多头注意力层
- 前馈神经网络层
- 层归一化
每一层都会"稀释"一点梯度,6-12 层累积下来,梯度就很小了。
原因二:残差连接前没有它
早期的 Transformer 变体(如原始论文的实现)在残差连接上处理不当,导致梯度问题更严重。
后果:深层 Transformer 难以训练
- 收敛速度极慢
- 训练 loss 居高不下
- 甚至完全无法收敛
让我们用简单的数学理解梯度消失:
import torch
import torch.nn as nn
# 假设一个简单的网络:3层全连接
# 每层输出 = relu(W * 输入 + b)
class ShallowNetwork(nn.Module):
def __init__(self):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 512),
)
def forward(self, x):
return self.layers(x)
# 问题:梯度从第3层传回第1层
# 第1层的梯度 = 第3层梯度 * 第2层梯度 * 第1层梯度
# 如果每层的梯度都 < 1,累积效果就是梯度接近0
# 演示:模拟梯度衰减
print("=== 梯度衰减演示 ===")
depths = [1, 3, 6, 12, 24] # 网络层数
per_layer_gradient = 0.5 # 假设每层传递50%的梯度
for depth in depths:
total_gradient = per_layer_gradient ** depth
print(f"层数={depth:2d}, 末端梯度={total_gradient:.6f} ({total_gradient*100:.4f}%)")
print("\n结论:层数越深,梯度衰减越严重!")
print("24层后,梯度只剩下原来的0.001%,基本学不到东西了")
本节要点
- 梯度消失:梯度在反向传播中逐层衰减,前层几乎收不到梯度
- Transformer 问题:多层堆叠导致梯度衰减严重
- 后果:深层网络变成"伪装的浅层网络",无法发挥深度优势
二、残差连接的灵感和核心思想
What — 残差连接是什么?
残差连接(Residual Connection)的想法来自 ResNet(残差网络),由何恺明等人于 2015 年提出。
核心思想:与其让网络学习 x -> F(x) 的映射,不如让它学习 x -> F(x) + x 的映射。
翻译成人话:
- 之前:第1层输出 = f(第0层输出)
- 现在:第1层输出 = f(第0层输出) + 第0层输出
这个"+"号,就是残差连接的灵魂。
Why — 残差连接为什么有效?
设计灵感:学习"残差"比学习"完整映射"容易
假设我们想让网络学习一个恒等映射(输入=输出)。
如果不用残差:
- 网络需要让 f(x) = x
- 这对神经网络来说并不简单
如果用残差:
- 网络只需要让 f(x) = 0
- 让 f(x) = 0 比让 f(x) = x 容易得多
梯度传播路径
加入残差连接后,反向传播的梯度会经过两条路径:
多层残差网络的前向传播:
Layer_1: h1 = x + F1(x)
Layer_2: h2 = h1 + F2(h1)
...
Layer_n: output = hn-1 + Fn(hn-1)
反向传播时,第l层的梯度:
∂L/∂hl-1 = ∂L/∂hl * ∂hl/∂hl-1
= ∂L/∂hl * (1 + ∂Fn/∂hl-1)
↑ ↑
恒等映射的梯度 F的梯度
关键洞察:即使 ∂Fn/∂hl-1 ≈ 0(梯度消失),
还有 +1 来自恒等映射,保证梯度至少是 1!
这就是残差连接的精妙之处:恒等映射的梯度永远是1,不会消失。
让我们用图解理解残差连接:
===== 没有残差连接 =====
Layer 1: x ──→ [W1] ──→ relu ──→ h1
Layer 2: h1 ──→ [W2] ──→ relu ──→ h2
Layer 3: h2 ──→ [W3] ──→ relu ──→ output
梯度传播: ∂L/∂W1 = ∂L/∂output * ∂output/∂h2 * ∂h2/∂h1 * ∂h1/∂W1
如果任何一步很小,梯度就消失了 ❌
===== 有残差连接 =====
Layer 1: x ──→ [W1] ──→ relu ──→ h1 ──⊕──→ h1+x
↑
Layer 2: x ──→ [W2] ──→ relu ──→ h2 ──⊕──→ h2+h1+x
↑
Layer 3: x ──→ [W3] ──→ relu ──→ h3 ──⊕──→ h3+h2+h1+x
梯度传播: ∂输出/∂x = ∂F(x)/∂x + 1 ✅
即使 ∂F(x)/∂x ≈ 0,梯度仍然 ≈ 1!
====== PyTorch 实现 ======
import torch
import torch.nn as nn
class ResidualBlock(nn.Module):
"""残差块:输出 = F(x) + x"""
def __init__(self, d_model, d_ff, dropout=0.1):
super().__init__()
self.attention = nn.MultiheadAttention(embed_dim=d_model, num_heads=8)
self.ffn = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(d_ff, d_model)
)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
# x: (seq_len, batch, d_model) 或 (batch, seq_len, d_model)
# ===== 子层1:多头注意力 + 残差连接 =====
# 残差连接:Attention的输出 + 输入
attn_output, _ = self.attention(x, x, x)
x = self.norm1(x + self.dropout(attn_output))
# ===== 子层2:前馈网络 + 残差连接 =====
# 残差连接:FFN的输出 + 输入
ffn_output = self.ffn(x)
x = self.norm2(x + self.dropout(ffn_output))
return x # 返回处理后的结果
# 验证:残差连接让梯度流通顺畅
def verify_gradient_flow():
model = ResidualBlock(d_model=512, d_ff=2048)
x = torch.randn(10, 2, 512) # seq_len=10, batch=2, d_model=512
# 前向传播
output = model(x)
print(f"输入形状: {x.shape}")
print(f"输出形状: {output.shape}")
# 反向传播测试
loss = output.sum()
loss.backward()
# 检查第1层参数的梯度
# 如果有残差连接,第1层参数的梯度不会消失
print(f"第1个线性层的权重梯度范数: {model.ffn[0].weight.grad.norm().item():.6f}")
verify_gradient_flow()
本节要点
- 核心公式:输出 = F(x) + x,而不是输出 = F(x)
- 残差学习:让网络学习 F(x) = 输出 - x,而不是直接学习输出
- 梯度保护:梯度中的 +1 项保证梯度不会完全消失
- 恒等映射:如果 F(x)=0,输出就等于输入,网络自动变成恒等函数
三、层归一化:稳定训练的另一个关键
What — 层归一化是什么?
层归一化(Layer Normalization)和残差连接是 Transformer 的"黄金搭档"。
层归一化的核心:对每一层的输出做归一化,让数值更稳定。
具体来说:
- 对每个样本的某一层所有神经元做归一化
- 减去均值,除以标准差
- 再加上可学习的缩放和偏移参数
Why — 为什么需要层归一化?
问题一:数值不稳定
多层网络堆叠后,数值可能变得很大或很小(Internal Covariate Shift)。这让学习率的选择变得困难:太大容易爆炸,太小容易消失。
问题二:残差连接需要归一化配合
残差连接让 x + F(x) 直接相加,但如果 F(x) 的数值尺度不对,加出来的结果会失控。层归一化把 F(x) 拉回到稳定范围,再和 x 相加。
设计意图
- 让每层的输入都是均值为0、方差为1的分布
- 稳定梯度流,加速收敛
- 允许使用更大的学习率
import torch
import torch.nn as nn
class LayerNormDemo(nn.Module):
"""层归一化的手动实现"""
def __init__(self, d_model, eps=1e-6):
super().__init__()
# 可学习的缩放和偏移参数
self.gamma = nn.Parameter(torch.ones(d_model)) # 缩放 γ
self.beta = nn.Parameter(torch.zeros(d_model)) # 偏移 β
self.eps = eps # 防止除零
def forward(self, x):
# x: (batch, seq_len, d_model) 或 (seq_len, batch, d_model)
# 计算均值和标准差(沿着特征维度)
mean = x.mean(dim=-1, keepdim=True) # (batch, seq_len, 1)
std = x.std(dim=-1, keepdim=True) # (batch, seq_len, 1)
# 归一化
x_norm = (x - mean) / (std + self.eps) # (batch, seq_len, d_model)
# 缩放和偏移
return self.gamma * x_norm + self.beta
def compare_batchnorm_layernorm():
"""对比 BatchNorm 和 LayerNorm"""
batch_size = 4
seq_len = 10
d_model = 512
x = torch.randn(batch_size, seq_len, d_model)
# LayerNorm:对每个样本的每层所有特征归一化
# 归一化的轴是 d_model(最后一维)
ln = nn.LayerNorm(d_model)
x_ln = ln(x)
# BatchNorm:对每个特征的所有样本归一化
# 归一化的轴是 batch(第一维)
bn = nn.BatchNorm1d(d_model)
x_bn = bn(x.transpose(1, 2)).transpose(1, 2) # 需要调整维度
print(f"原始输入形状: {x.shape}")
print(f"LayerNorm后均值: {x_ln.mean(dim=-1).mean():.4f}, 标准差: {x_ln.std(dim=-1).mean():.4f}")
print(f"BatchNorm后均值: {x_bn.mean(dim=0).mean():.4f}, 标准差: {x_bn.std(dim=0).mean():.4f}")
compare_batchnorm_layernorm()
# ===== Transformer 中的标准用法 =====
class TransformerLayerWithNorm(nn.Module):
def __init__(self, d_model=512, n_heads=8):
super().__init__()
self.attention = nn.MultiheadAttention(embed_dim=d_model, num_heads=n_heads, batch_first=True)
self.ffn = nn.Sequential(
nn.Linear(d_model, d_model * 4),
nn.GELU(),
nn.Linear(d_model * 4, d_model)
)
# 层归一化在残差连接之前(Pre-LN)或者之后(Post-LN)
self.norm1 = nn.LayerNorm(d_model) # 注意力后的归一化
self.norm2 = nn.LayerNorm(d_model) # FFN后的归一化
def forward_post_ln(self, x):
"""Post-LN(原始Transformer使用)"""
# 1. 多头注意力 + 残差连接
attn_out, _ = self.attention(x, x, x)
x = x + attn_out # 残差连接
x = self.norm1(x) # 然后归一化
# 2. 前馈网络 + 残差连接
ffn_out = self.ffn(x)
x = x + ffn_out # 残差连接
x = self.norm2(x) # 然后归一化
return x
def forward_pre_ln(self, x):
"""Pre-LN(更稳定,现代Transformer常用)"""
# 1. 先归一化,再注意力,再残差
x_norm = self.norm1(x)
attn_out, _ = self.attention(x_norm, x_norm, x_norm)
x = x + attn_out # 残差连接(不经过归一化)
# 2. 先归一化,再FFN,再残差
x_norm = self.norm2(x)
ffn_out = self.ffn(x_norm)
x = x + ffn_out # 残差连接(不经过归一化)
return x
print("\n两种归一化位置的对比:")
print("Post-LN: 先算 Attention,再残差,再归一化")
print("Pre-LN: 先归一化,再算 Attention,再残差")
print("Pre-LN 训练更稳定,是现代Transformer的主流选择")
本节要点
- 层归一化:对每个样本的每层所有特征做均值0方差1的归一化
- 配合残差:把 F(x) 归一化到稳定范围,再和 x 相加
- Pre-LN vs Post-LN:Pre-LN 更稳定,是现代 Transformer 的主流选择
- 可学习参数:γ 和 β 让模型学会最佳的归一化尺度
四、How — PyTorch 实现详解
What — 完整的 Transformer 编码器层
现在我们把残差连接和层归一化放在一起,看看完整的 Transformer 编码器层长什么样:
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
class TransformerEncoderLayer(nn.Module):
"""完整的 Transformer 编码器层"""
def __init__(self, d_model=512, n_heads=8, d_ff=2048, dropout=0.1):
super().__init__()
# 多头自注意力
self.self_attn = nn.MultiheadAttention(
embed_dim=d_model,
num_heads=n_heads,
dropout=dropout,
batch_first=True # 使用 (batch, seq, dim) 格式
)
# 前馈网络
self.ffn = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.GELU(), # 现代 Transformer 常用 GELU 替代 ReLU
nn.Dropout(dropout),
nn.Linear(d_ff, d_model),
nn.Dropout(dropout)
)
# 两个层归一化
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
# Dropout
self.dropout1 = nn.Dropout(dropout)
self.dropout2 = nn.Dropout(dropout)
def forward(self, x, src_key_padding_mask=None):
# x: (batch, seq_len, d_model)
# ========== 子层1:多头注意力 + 残差 + 归一化 ==========
# 1. 计算自注意力
attn_output, attn_weights = self.self_attn(
x, x, x,
key_padding_mask=src_key_padding_mask
)
# 2. 残差连接 + Dropout
x = x + self.dropout1(attn_output)
# 3. 层归一化
x = self.norm1(x)
# ========== 子层2:前馈网络 + 残差 + 归一化 ==========
# 1. 前馈网络
ffn_output = self.ffn(x)
# 2. 残差连接 + Dropout
x = x + self.dropout2(ffn_output)
# 3. 层归一化
x = self.norm2(x)
return x
# ===== 使用 torch.nn.TransformerEncoderLayer(官方实现)=====
def use_official_encoder():
d_model = 512
n_heads = 8
num_layers = 6
# 单层编码器
encoder_layer = nn.TransformerEncoderLayer(
d_model=d_model,
nhead=n_heads,
dim_feedforward=d_model * 4,
dropout=0.1,
activation='gelu',
batch_first=True,
norm_first=True # True = Pre-LN, False = Post-LN
)
# 多层堆叠
encoder = nn.TransformerEncoder(
encoder_layer=encoder_layer,
num_layers=num_layers,
norm=nn.LayerNorm(d_model) # 最后的归一化层
)
# 测试
batch_size = 2
seq_len = 100
x = torch.randn(batch_size, seq_len, d_model)
output = encoder(x)
print(f"输入形状: {x.shape}")
print(f"输出形状: {output.shape}")
return encoder
encoder = use_official_encoder()
print("官方实现测试通过!")
# ===== 残差连接的可视化验证 ======
def visualize_residual_gradient():
"""验证残差连接确实能传递梯度"""
torch.manual_seed(42)
d_model = 64
x = torch.randn(1, 10, d_model, requires_grad=True)
# 模拟一个深层网络(无残差)
class NoResidualNet(nn.Module):
def __init__(self, layers=12):
super().__init__()
self.layers = nn.ModuleList([
nn.Linear(d_model, d_model) for _ in range(layers)
])
def forward(self, x):
for layer in self.layers:
x = torch.relu(layer(x))
return x
# 模拟一个深层网络(有残差)
class ResidualNet(nn.Module):
def __init__(self, layers=12):
super().__init__()
self.layers = nn.ModuleList([
nn.Linear(d_model, d_model) for _ in range(layers)
])
def forward(self, x):
for layer in self.layers:
x = x + torch.relu(layer(x)) # 残差连接
return x
net_no_res = NoResidualNet(layers=12)
net_res = ResidualNet(layers=12)
# 前向传播
out_no_res = net_no_res(x)
out_res = net_res(x)
# 反向传播
loss_no_res = out_no_res.sum()
loss_res = out_res.sum()
loss_no_res.backward()
loss_res.backward()
# 比较第1层的梯度
grad_no_res = net_no_res.layers[0].weight.grad.abs().mean().item()
grad_res = net_res.layers[0].weight.grad.abs().mean().item()
print(f"\n=== 梯度对比(12层后)===")
print(f"无残差网络第1层梯度均值: {grad_no_res:.8f}")
print(f"有残差网络第1层梯度均值: {grad_res:.8f}")
print(f"残差网络梯度放大了 {grad_res/grad_no_res:.1f} 倍!")
visualize_residual_gradient()
五、What-If — 如果没有残差会怎样?
What-If 实验一:去掉所有残差连接
如果 Transformer 去掉所有残差连接:
- 层数超过 6 层后,训练几乎无法收敛
- 即使收敛,效果也比有残差的差很多
- 梯度消失问题严重,前几层几乎学不到东西
What-If 实验二:残差连接但没有归一化
如果只用残差但去掉层归一化:
- 梯度可以流通了
- 但数值尺度会失控:深层后输出可能变得极大或极小
- 需要用非常小的学习率,训练速度极慢
What-If 实验三:残差 + 归一化但归一化在残差之前(Post-LN)
原始 Transformer 使用 Post-LN(归一化在残差之后):
- 训练初期可能不稳定
- 需要学习率 warm-up
- 深层网络训练困难
What-If 实验四:残差 + 归一化且归一化在残差之前(Pre-LN)
现代 Transformer 常用 Pre-LN:
- 训练更稳定,不需要 warm-up
- 可以训练更深的网络(100+层)
- 成为 Transformer 的主流选择
本节要点
- 无残差:深层网络无法训练,梯度消失
- 无归一化:数值尺度失控,训练极慢
- Post-LN:需要 warm-up,深层训练困难
- Pre-LN:更稳定,可训练更深层,是现代主流
FAQ(20问)
以下是关于残差连接和层归一化的常见问题:
Q1. 残差连接和跳跃连接是一样的吗?
一句话结论:残差连接是跳跃连接的一种,但有特殊设计。展开:跳跃连接(Skip Connection)直接把前面层的输出传到后面层。残差连接在此基础上做了优化:不是直接传递,而是传递 F(x) + x,让网络学习残差 F(x)。
Q2. 残差连接会增加多少计算量?
一句话结论:几乎不增加计算量,只是多了一个加法。展开:残差连接只是 x + F(x) 的加法操作,计算量可以忽略不计。但它带来的训练稳定性和深层网络能力提升是巨大的。
Q3. 残差连接和 DenseNet 的稠密连接有什么区别?
一句话结论:残差是加法(x+F),稠密是拼接([x,F])。展开:ResNet 每个层只和上一层有残差连接。DenseNet 每个层和前面所有层都有连接,特征会拼接在一起。残差更节省内存。
Q4. LayerNorm 和 BatchNorm 有什么区别?
一句话结论:LayerNorm 归一化样本内特征,BatchNorm 归一化批次内样本。展开:LayerNorm 对每个样本的所有特征做归一化,不依赖 batch 大小。BatchNorm 对每个特征在 batch 维度做归一化,需要较大的 batch。Transformer 场景 LayerNorm 更合适。
Q5. Pre-LN 和 Post-LN 哪个更好?
一句话结论:Pre-LN 更稳定,是现代 Transformer 的主流。展开:Pre-LN 在残差连接前做归一化,训练更稳定,可以不用 warm-up。Post-LN 在残差连接后做归一化,原始 Transformer 使用,但训练初期可能不稳定。
Q6. 残差连接可以用于循环神经网络吗?
一句话结论:可以,但实现更复杂。展开:RNN 可以用残差连接让隐藏状态直接传递,但要处理序列的时序性。现代 RNN 变体(如 RNN-T)也使用残差连接。
Q7. 残差连接会和学习恒等映射冲突吗?
一句话结论:不会,残差让恒等映射更容易学习。展开:残差连接让网络只需要让 F(x)=0 就能实现恒等映射,这比让 F(x)=x 容易得多。所以残差不会让网络"变懒",反而让有用的变换更容易学习。
Q8. Transformer 有多少层残差连接?
一句话结论:每个子层都有两个残差连接。展开:Transformer 编码器每层有两个子层(注意力、FFN),每个子层都有一个残差连接。所以 N 层编码器有 2N 个残差连接。
Q9. 残差连接和 Dropout 一起用会有问题吗?
一句话结论:通常没问题,但顺序很重要。展开:残差连接内部通常是这样:x + dropout(F(x))。Dropout 在 F(x) 上应用,不影响残差路径的直通性。
Q10. 为什么残差连接不会让网络退化成常数函数?
一句话结论:因为还有归一化和学习信号。展开:残差连接保证梯度流通,但网络还有损失函数提供的学习信号。只要损失函数对输出有要求,网络就会学习有用的变换,而不是退化成常数。
Q11. 残差连接在图像处理中有什么应用?
一句话结论:ResNet 就是残差连接的典型应用。展开:ResNet 通过残差连接可以训练 1000+ 层的网络,在 ImageNet 上取得了突破性成果。这也是 Transformer 借鉴的核心思想。
Q12. 可以把残差连接用在注意力计算内部吗?
一句话结论:可以,这就是 Self-Attention 的设计。展开:实际上,注意力机制的 QKV 投影、多头合并后的输出投影都包含在残差框架内。真正的"残差"是整个子层的输入到输出的直通。
Q13. 残差连接的加法会引入数值不稳定吗?
一句话结论:不会,因为有归一化控制尺度。展开:残差连接后通常跟着层归一化,把输出拉回到稳定范围。所以即使 F(x) 很大,归一化也会把它调整好。
Q14. 残差连接和门控机制有什么关系?
一句话结论:残差是一种特殊的门控(始终开放)。展开:LSTM 的遗忘门决定多少信息保留,残差连接始终保留输入(+1 直通),然后叠加变换。 Highway Network 使用学习的门控,但残差更简单有效。
Q15. 残差连接可以让网络只学习残差而不学习主路径吗?
一句话结论:不会,两者同时学习。展开:虽然恒等映射很容易实现,但网络通常会发现同时学习一些变换是有帮助的。残差只是保证主路径的梯度流通,而不是强制网络只用主路径。
Q16. 在 SRE 场景中残差连接有什么用?
一句话结论:让深层日志分析模型能够训练。展开:分析复杂的运维日志可能需要很深的模型来捕获多种模式。残差连接让这些深层模型能够正常训练,发挥深度学习的全部能力。
Q17. 残差连接的初始化有什么讲究?
一句话结论:残差路径的权重要小,便于恒等映射。展开:常用的初始化是把残差分支的权重初始化为接近0的值,这样训练初期网络接近恒等映射,更容易优化。
Q18. 可以堆叠多少层残差连接?
一句话结论:理论上可以无限,实际受限于硬件和任务。展开:ResNet 可以训练 1000+ 层,Transformer 可以训练 100+ 层。但在实际应用中,太深的模型可能难以优化或过拟合。
Q19. 残差连接和注意力机制有什么关系?
一句话结论:残差是框架,注意力是组件。展开:残差连接提供了让深层网络可训练的结构框架,注意力机制是这个框架内的核心计算单元。两者结合,构成了现代 Transformer。
Q20. 残差连接有哪些变体?
一句话结论:预激活残差、多尺度残差、稀疏残差等。展开:Pre-activation ResNet 把归一化放在卷积前面。多尺度残差在不同尺度做残差连接。Sparse ResNet 只在部分层使用残差。
核心结论
- 残差核心:输出 = F(x) + x,梯度中的 +1 保证流通
- 层归一化:稳定数值尺度,配合残差使用
- Pre-LN:现代 Transformer 主流,更稳定
- 组合效果:让深层网络可训练,发挥深度优势
Roadmap预告
下期预告:《位置编码:为什么 Transformer 需要知道词的顺序?》
预告内容:
- 绝对位置编码:最初的 sin/cos 方案
- 相对位置编码:让模型关注相对距离
- 旋转位置编码 RoPE:当代大模型的最爱
- What-If:如果没有位置编码会怎样?
敬请期待!

浙公网安备 33010602011771号