深入理解 PyTorch 的 torch.nn:神经网络构建核心详解

一、神经网络的数学本质:线性变换与非线性激活的协同

在 PyTorch 中搭建神经网络,其本质是将数据通过一系列 线性层(如 nn.Linearnn.Conv2d)和非线性激活函数(如 nn.ReLU 进行变换组合。整个网络就是在模拟如下的数学结构:

y = XWᵀ + b

其中:

  • X:是输入张量,
  • W:是模型权重(可学习参数),
  • b:是偏置项。

单纯的线性结构(即便多层堆叠)也只是一层线性映射。唯有引入激活函数,神经网络才能捕捉复杂的非线性关系。理解这点,是掌握 torch.nn 的第一步。

二、非线性激活函数:赋予网络表达力

神经网络的“智能”来自激活函数。它们在每一层之后对线性输出进行非线性变换,帮助模型捕捉模式、增强特征选择能力。

1. ReLU:线性整流单元(nn.ReLU

ReLU(Rectified Linear Unit)是最常用的激活函数之一。

  • 规则:输入 > 0 时,输出 = 输入;否则输出为 0;

  • 优势

    • 计算开销极低(仅为一次 max 操作);
    • 输出稀疏,有利于泛化;
    • 在深度网络中表现更稳定,缓解梯度消失;
  • 应用场景

    • 常用于隐藏层的激活,尤其适用于深层神经网络

2. Sigmoid:概率压缩(nn.Sigmoid

将输出值映射到 (0, 1) 区间:

  • 用途:可作为二分类任务的输出层;
  • 中性点:输入为 0 时输出为 0.5;
  • 问题:在输入过大或过小时,梯度趋近于 0,导致梯度消失

3. Tanh:对称 S 型激活(nn.Tanh

Tanh 函数将输入压缩至 (-1, 1):

  • 优势:以 0 为中心,对称性优于 Sigmoid;
  • 缺点:仍可能在极值区间出现梯度消失;
  • 用途:LSTM/GRU 等循环结构中广泛使用。

4. Softmax & Softplus

  • nn.Softmax:将向量映射为多分类概率分布,常用于多分类输出层

  • nn.Softplus:ReLU 的平滑替代品,避免梯度爆炸,公式为:

    f(x) = log(1 + exp(x))
    

三、构建神经网络结构的核心层

神经网络之所以强大,离不开多种层之间的配合。在 torch.nn 中,构建网络主要依赖以下核心层:

1. 全连接层(nn.Linear

import torch
import torch.nn as nn

# 创建一个线性层:输入特征维度为 5,输出特征维度为 2
linear = nn.Linear(in_features=5, out_features=2)

# 输入一个形状为 [batch_size=3, input_dim=5] 的张量
x = torch.randn(3, 5)

# 前向传播
output = linear(x)

print("Output shape:", output.shape)  # [3, 2]

作用:执行线性变换 y = xW^T + b。这是神经网络中最基础的构建块,常用于:

  • 分类模型的最后输出层;
  • 中间特征融合;
  • 回归问题建模。

你可以通过 .weight.bias 获取层参数。


2. 卷积层:提取局部空间特征

PyTorch 提供了多维度的卷积层:

模块 适用对象
nn.Conv1d 序列/音频数据
nn.Conv2d 图像数据
nn.Conv3d 视频/医学图像

卷积层的核心参数包括:

参数 说明
in_channels 输入通道数(如 RGB 为 3);
out_channels 卷积核数量(即输出通道数);
kernel_size 卷积核尺寸;
stride 滑动步长;
padding 边缘填充;
bias 是否添加偏置。

示例:

import torch
import torch.nn as nn

# 定义一个二维卷积层
conv = nn.Conv2d(
    in_channels=3,     # 输入通道数,例如 RGB 图像是 3
    out_channels=16,   # 卷积核数量(输出通道数)
    kernel_size=3,     # 卷积核大小为 3x3
    stride=1,          # 步幅为 1
    padding=1          # 保证输出大小和输入一样
)

# 输入一个 batch 的图像,尺寸为 (batch_size=4, channels=3, height=32, width=32)
x = torch.randn(4, 3, 32, 32)

# 前向传播
output = conv(x)

print("Output shape:", output.shape)  # [4, 16, 32, 32]

→ 输出 16 个特征图,每个 3x3 卷积核在图像上滑动采样特征。


3. 池化层:下采样与特征提取

池化层用于减小特征图尺寸、降低计算量并提取区域代表特征。

常用的有:

  • nn.MaxPool2d:最大池化(提取最显著特征);
  • nn.AvgPool2d:平均池化(特征平滑)。
nn.MaxPool2d(kernel_size=2, stride=2)

假设原图是 4×4,最大池化后会变为 2×2,区域内保留最大值。


4. 数据展平层:为全连接层准备数据

在卷积层之后,必须将输出展平才能输入到全连接层。

  • nn.Flatten(start_dim=1):自动将 start_dim 之后的维度展平,适用于 nn.Sequential
  • .view(-1, shape):手动变换张量结构,用于自定义结构中。

\color{red}{注意}.view() 操作要求输入张量在内存中是连续的。如果你之前对张量做过 .transpose().permute() 等操作,可能导致张量变为非连续,此时需要:

x = x.contiguous().view(batch_size, -1)

这样才能安全地将数据展平。

展平是连接 CNN 与全连接层的必要桥梁。


5. 转置卷积层:模型中的“放大镜”

在图像生成、超分辨率等任务中,我们需要将低分辨率特征图还原成高分辨率图像,这时就用到了转置卷积(Transposed Convolution),也称“反卷积”。

PyTorch 提供了如下模块:

模块 应用场景
nn.ConvTranspose1d 时间序列重构、上采样
nn.ConvTranspose2d 图像上采样、图像生成
nn.ConvTranspose3d 3D 数据恢复、重建

转置卷积的本质是将标准卷积中的“前向传播”反向操作,实现特征图尺寸的还原。常用于:

  • AutoEncoder 的解码器部分;
  • GAN 的生成器部分;
  • 超分辨率模型中图像恢复。

四、构建模块组合:用 torch.nn 搭积木

为了构建复杂网络结构,PyTorch 提供了结构化的组合容器:

  • nn.Sequential:顺序叠加模块:
import torch
import torch.nn as nn

# 使用 nn.Sequential 构建一个简单的全连接网络
model = nn.Sequential(
    nn.Linear(20, 64),
    nn.ReLU(),
    nn.Linear(64, 32),
    nn.ReLU(),
    nn.Linear(32, 10)
)

# 输入一个随机张量,模拟 batch size = 5,特征维度 = 20
x = torch.randn(5, 20)
output = model(x)

print("Output shape:", output.shape)

  • nn.ModuleList:以列表形式存储多个层,适合动态结构;
import torch
import torch.nn as nn
import torch.nn.functional as F

class DynamicMLP(nn.Module):
    def __init__(self, input_dim, hidden_dims, output_dim):
        super(DynamicMLP, self).__init__()
        self.layers = nn.ModuleList()
        
        # 创建多层线性层
        prev_dim = input_dim
        for hidden_dim in hidden_dims:
            self.layers.append(nn.Linear(prev_dim, hidden_dim))
            prev_dim = hidden_dim

        self.output_layer = nn.Linear(prev_dim, output_dim)

    def forward(self, x):
        for layer in self.layers:
            x = F.relu(layer(x))
        return self.output_layer(x)

# 用法示例:
model = DynamicMLP(input_dim=10, hidden_dims=[64, 128, 64], output_dim=3)
print(model)
  • nn.ModuleDict:以字典形式组织模块,可方便地调用不同分支结构。
class MultiTaskNet(nn.Module):
    def __init__(self, input_dim, shared_dim, task_dims):
        super(MultiTaskNet, self).__init__()
        self.shared_layer = nn.Linear(input_dim, shared_dim)

        # 定义不同任务的输出头
        self.task_heads = nn.ModuleDict({
            task_name: nn.Linear(shared_dim, out_dim)
            for task_name, out_dim in task_dims.items()
        })

    def forward(self, x):
        shared = F.relu(self.shared_layer(x))
        outputs = {}
        for task_name, head in self.task_heads.items():
            outputs[task_name] = head(shared)
        return outputs

# 用法示例:
model = MultiTaskNet(input_dim=100, shared_dim=64, task_dims={
    "classification": 10,
    "regression": 1
})
print(model)

五、正则化层:稳定训练与提升泛化能力

深度神经网络虽然强大,但也容易出现训练不稳定、过拟合、收敛慢等问题。正则化层正是为了解决这些问题而存在。
正则化不是“提取特征”的功能层,而是帮助模型更好训练、更强泛化的重要手段。

1. 批量归一化(nn.BatchNorm2d

适用于图像数据的 2D 特征图。

import torch
import torch.nn as nn

# 定义一个 BatchNorm 层,输入通道数是 16(对应某一层卷积的输出)
bn = nn.BatchNorm2d(num_features=16)

# 输入:batch_size=4, channels=16, height=32, width=32
x = torch.randn(4, 16, 32, 32)

# 前向传播
output = bn(x)

print("Output shape:", output.shape)  # [4, 16, 32, 32]

参数说明

参数 说明
num_features 输入通道数(如卷积输出通道数);
eps 避免除零的小常数;
momentum 用于更新均值/方差的动量;
affine 是否学习缩放(γ)和偏移(β);
track_running_stats 是否追踪运行时均值/方差。

2. 层归一化(nn.LayerNorm

适用于 Transformer、RNN 等结构。

import torch
import torch.nn as nn

# 假设输入形状为 [batch_size, features],例如 [4, 10]
x = torch.randn(4, 10)

# 对每个样本的 10 个特征做 LayerNorm
layer_norm = nn.LayerNorm(normalized_shape=10)

output = layer_norm(x)
print("Output shape:", output.shape)  # [4, 10]

参数说明

参数 说明
normalized_shape 要归一化的维度;
eps 避免除零;
elementwise_affine 是否学习缩放和偏移。

3. 实例归一化(nn.InstanceNorm2d

多用于风格迁移任务,每个样本独立归一化。

import torch
import torch.nn as nn

# 创建 InstanceNorm2d 层,假设通道数为 3(如 RGB 图像)
inst_norm = nn.InstanceNorm2d(num_features=3)

# 输入张量形状:[batch_size=2, channels=3, height=32, width=32]
x = torch.randn(2, 3, 32, 32)

# 前向传播
output = inst_norm(x)

print("Output shape:", output.shape)  # [2, 3, 32, 32]

参数说明

参数 说明
num_features 通道数;
affine 是否添加可学习参数 γ 和 β;
track_running_stats 是否追踪均值方差。

4. 组归一化(nn.GroupNorm

适用于小批量训练时的归一化操作。

import torch
import torch.nn as nn

# 将通道分为 4 组,每组归一化;一共 8 个通道
group_norm = nn.GroupNorm(num_groups=4, num_channels=8)

# 输入:[batch_size=2, channels=8, height=32, width=32]
x = torch.randn(2, 8, 32, 32)

output = group_norm(x)

print("Output shape:", output.shape)  # [2, 8, 32, 32]

参数说明

参数 说明
num_groups 分组数(如 4 组);
num_channels 通道数(需被 num_groups 整除);
eps 防止除以 0;
affine 是否包含可学习参数。

5. Dropout(nn.Dropout

训练时随机将一部分神经元输出设为 0,以此降低模型对特定路径的依赖。

import torch
import torch.nn as nn

dropout = nn.Dropout(p=0.5)  # 50% 的概率将元素置为 0

x = torch.randn(4, 10)

# 模拟训练阶段
dropout.train()
out_train = dropout(x)

# 模拟推理阶段(推理时不会随机丢弃)
dropout.eval()
out_eval = dropout(x)

print("Input:\n", x)
print("After dropout (train):\n", out_train)
print("After dropout (eval):\n", out_eval)

参数说明

参数 说明
p 丢弃的概率;
inplace 是否原地修改输入数据。

六、循环神经网络:处理序列信息的利器

对于文本、语音、时间序列等输入长度可变、具有时序关系的任务,循环神经网络(RNN)系列提供了天然的建模能力。

1. 基本结构:nn.RNNnn.LSTMnn.GRU

  • nn.RNN
    最简单,记忆能力弱。RNN 是可以在序列中“记住历史”的神经网络
    它的基本结构是:每个时刻的输出不仅取决于当前输入,还取决于上一个隐藏状态。
    理解为:记录历史 → 分析当前 → 推测未来
import torch
import torch.nn as nn

rnn = nn.RNN(input_size=10, hidden_size=20, num_layers=1, batch_first=True)

# 输入:batch_size=3,序列长度=5,特征维度=10
x = torch.randn(3, 5, 10)
output, h_n = rnn(x)

print("RNN Output shape:", output.shape)  # [3, 5, 20]
print("RNN Last hidden state shape:", h_n.shape)  # [1, 3, 20]

  • nn.LSTM
    引入门控机制,能记住长期信息。它的目标是解决 RNN “记忆太短”的问题

    • 机制:
      遗忘门:决定忘记哪些旧信息;
      输入门:决定当前有哪些信息要写入内存
      输出门:决定从内存输出哪些信息

    • 类比:
      RNN 就像是你脑子在记东西,但过一会儿你会忘;
      LSTM 是你有个小本子,把重要的写进去,写之前还会判断“这重要不?”、“以前那段可以忘了吗?”

lstm = nn.LSTM(input_size=10, hidden_size=20, num_layers=1, batch_first=True)

x = torch.randn(3, 5, 10)
output, (h_n, c_n) = lstm(x)

print("LSTM Output shape:", output.shape)  # [3, 5, 20]
print("LSTM Last hidden state:", h_n.shape)  # [1, 3, 20]
print("LSTM Last cell state:", c_n.shape)    # [1, 3, 20]
  • nn.GRU
    GRU 是一种能记住长期信息、又计算更快、更简单的循环神经网络。 LSTM 的“轻量级版本”。

    • 机制:
      更新门:决定当前隐藏状态要保留多少旧信息;
      重置门:决定当前输入要结合多少旧的记忆 |
gru = nn.GRU(input_size=10, hidden_size=20, num_layers=1, batch_first=True)

x = torch.randn(3, 5, 10)
output, h_n = gru(x)

print("GRU Output shape:", output.shape)  # [3, 5, 20]
print("GRU Last hidden state:", h_n.shape)  # [1, 3, 20]

2. 通用参数说明:

参数 说明
input_size 每个时间步输入的特征维度;
hidden_size 隐藏状态维度;
num_layers 层数;
batch_first 输入是否为 (batch, seq, dim)
bidirectional 是否双向;
dropout 层间 Dropout;

七、嵌入层

1. 嵌入层(nn.Embedding

用于将离散的 ID 映射为稠密向量,尤其在自然语言处理(NLP)中广泛使用。

import torch
import torch.nn as nn

# 创建一个词嵌入层,包含 100 个词,每个词表示为 16 维向量
embedding = nn.Embedding(num_embeddings=100, embedding_dim=16)

# 模拟一个输入:batch_size=4,每个样本有 5 个“词”的索引(int)
input_indices = torch.tensor([
    [1, 3, 5, 2, 9],
    [4, 7, 6, 8, 0],
    [1, 1, 1, 1, 1],
    [2, 4, 6, 8, 10]
])

# 获取嵌入向量
output = embedding(input_indices)

print("Output shape:", output.shape)  # [4, 5, 16]

  • 参数说明
参数名 含义
num_embeddings 嵌入表中总词数(通常是词表大小)
embedding_dim 每个词嵌入向量的维度
padding_idx 如果指定该索引,其嵌入向量会是全 0,不参与训练
max_norm 限制每个嵌入向量的最大范数(用于防止梯度爆炸)

八、多头注意力机制与自注意力(Self-Attention)

多头注意力机制是 Transformer 的核心组成,其背后正是自注意力机制(Self-Attention)的具体实现。

1. 自注意力机制原理:

  • 每个位置的表示依赖于整个序列中的所有位置(即全局信息);

  • 通过计算 Query、Key、Value 的加权组合实现:

    Attention(Q, K, V) = softmax(QKᵀ / √d_k) * V
    
  • 公式理解

对于每个输入向量 𝑥𝑖,
我们生成三个向量:
    Query(查询向量) → 要问别人;
    Key(键) → 别人暴露的信息;
    Value(值) → 别人的真实内容;
输入: ["猫", "在", "沙发", "上", "睡觉"]
                ↓
        每个词生成 Q/K/V 向量
                ↓
        Q 和所有 K 计算注意力权重
                ↓
        加权求和 V 向量作为输出
  • 通俗理解
Self-Attention 就像你在读一句话时,自动决定关注哪些词来更好理解当前词。
比如
  句子 “我今天心情非常好!”
  如果你要理解 “好” 这个词,它和“心情”关系更大,而不是 “我” 。

  权重的划分
    当前词(Query)
      好
    其他词(Key)
      我、今天、心情、非常、好
    权重
      0.1、0.1、0.4、0.3、0.1
      "心情" 的权重 0.4

2. nn.MultiheadAttention 使用示例:

它就是 多个 Self-Attention 并行运行,每个有自己的一套 Q/K/V 权重,然后把它们拼起来。
nn.MultiheadAttention = 多个 Self-Attention + 拼接 + 投影,是 Self-Attention 的 增强版 + 实用封装,在 Transformer 中标准使用。

import torch
import torch.nn as nn

# 假设每个 token 的嵌入维度是 64,注意力头数是 4
mha = nn.MultiheadAttention(embed_dim=64, num_heads=4, batch_first=True)

# 输入是一个 batch 的序列:[batch_size=2, seq_len=5, embed_dim=64]
x = torch.randn(2, 5, 64)  # 可以看作是 query = key = value

# 自注意力:Q = K = V = x
output, attn_weights = mha(x, x, x)

print("Output shape:", output.shape)         # [2, 5, 64]
print("Attention weights shape:", attn_weights.shape)  # [2, 4, 5, 5]:batch, heads, target_len, source_len

参数说明

参数 说明
embed_dim 输入/输出的特征维度(比如 512),会被分成多个 head
num_heads 注意力头的数量,要求能整除 embed_dim(每个 head = embed_dim / num_heads
dropout=0.0 注意力权重的 dropout(默认 0.0)
bias=True 是否为 Q/K/V 添加偏置(默认 True)
add_bias_kv=False 是否为 K/V 添加额外的 bias(可选机制)
add_zero_attn=False 是否在 attention 前加一个全零向量,默认 False(用于特殊处理)
kdim Key 的维度,如果与 embed_dim 不同需指定
vdim Value 的维度,如果与 embed_dim 不同需指定
batch_first=False 如果为 True,则输入/输出为 (batch, seq, dim) 而不是 (seq, batch, dim)

九、损失函数与优化器

1. 常见损失函数

损失函数(Loss Function) 是用来评估模型的预测值与真实值之间的差异。
损失越小,模型表现越好;优化器的目标就是最小化损失。

  • nn.MSELoss(y_pred, y_true) 回归任务,均方误差
    • 特点:
      适用于回归任务;对异常值敏感(因为平方放大误差);用于输出是连续值的情况。
    • 参数:
      y_pred: 预测值
      y_true:真实值
import torch
import torch.nn as nn

# 创建 MSELoss 实例
criterion = nn.MSELoss()

# 模拟模型输出和真实标签
y_pred = torch.tensor([[2.5], [0.0], [2.1]], dtype=torch.float)
y_true = torch.tensor([[3.0], [0.0], [2.0]], dtype=torch.float)

# 计算 MSE 损失
loss = criterion(y_pred, y_true)

print("MSE Loss:", loss.item())  # 输出一个标量

  • nn.CrossEntropyLoss(y_pred, y_true) 交叉熵损失
    • 特点:
      分类任务首选
      输入必须是 未归一化的 logits。logits 是未经过激活函数的输出,值域在 (−∞,+∞);
      标签格式是 整数(非 one-hot)
  • nn.NLLLoss
    是用于分类任务的 负对数似然 损失函数,适用于模型输出是对数概率(log-probabilities)的情况
    • 参数:
      weight: 每类的损失权重,用于类别不平衡(比如第0类比第1类重要
      ignore_index: 忽略指定标签的样本(不参与 loss 计算)
      reduction: mean(默认,平均)/ sum / none(逐元素输出)
常见搭配
nn.NLLLoss() ⬅️ 搭配 ➡️ nn.LogSoftmax(dim=1)
或
nn.CrossEntropyLoss()  ⬅️ 它等价于 nn.NLLLoss + nn.LogSoftmax
import torch
import torch.nn as nn

# 假设 batch_size=3,类别数=4
log_softmax = nn.LogSoftmax(dim=1)
nll_loss = nn.NLLLoss()

# 模拟模型输出 logits(还未 softmax)
logits = torch.tensor([[2.0, 0.5, 0.1, 0.2],
                       [0.1, 2.1, 0.3, 0.4],
                       [1.0, 0.2, 3.0, 0.3]])

# 类别标签(每个样本的正确类别索引)
targets = torch.tensor([0, 1, 2])  # 每个值在 0~3 之间

# 对 logits 做 log softmax
log_probs = log_softmax(logits)

# 计算 NLLLoss
loss = nll_loss(log_probs, targets)

print("NLL Loss:", loss.item())

2. 优化器(包括 SGD、Adam、Adagrad 等)

优化器是模型参数更新的关键,PyTorch 提供了丰富的优化器模块,均位于 torch.optim 中。

1)SGD(随机梯度下降)

import torch
import torch.nn as nn
import torch.optim as optim

# 一个简单的线性模型:y = wx + b
model = nn.Linear(1, 1)

# 均方误差损失
criterion = nn.MSELoss()

# SGD 优化器,学习率 0.01
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 模拟训练数据
x = torch.tensor([[1.0], [2.0], [3.0]])
y = torch.tensor([[2.0], [4.0], [6.0]])  # y = 2x

# 训练 100 步
for epoch in range(100):
    # 前向传播
    y_pred = model(x)
    # 计算损失
    loss = criterion(y_pred, y)
    # 梯度清零
    optimizer.zero_grad()
    # 反向传播
    loss.backward()
    # 更新参数
    optimizer.step()
    
    if epoch % 10 == 0:
        print(f"Epoch {epoch}, Loss: {loss.item():.4f}")

  • lr:学习率;
  • momentum:加速收敛并抑制震荡;
  • weight_decay:L2 正则化,防止过拟合。

2)Adam(自适应矩估计)

optimizer = torch.optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999), eps=1e-8)
  • betas:一阶和二阶矩估计的指数衰减率;
  • eps:防止除零。
  • Adam 结合了 Momentum 和 RMSprop 的优点,默认参数下性能稳定。

3)Adagrad、RMSprop(适用于稀疏/非平稳目标)

optimizer = torch.optim.Adagrad(model.parameters(), lr=0.01)
optimizer = torch.optim.RMSprop(model.parameters(), lr=0.001, alpha=0.99)

优化器通用用法:

optimizer.zero_grad()      # 清空上一步的梯度信息
loss.backward()            # 反向传播,计算当前梯度
optimizer.step()           # 根据梯度更新参数

3. 学习率调度器(torch.optim.lr_scheduler

学习率调度器用于动态调整学习率,提升模型收敛效率,常配合优化器一起使用。

常见调度策略:

  • StepLR: 每隔固定 epoch 衰减一次;
  • MultiStepLR: 在指定 epoch 点衰减;
  • ExponentialLR: 指数衰减;
  • ReduceLROnPlateau: 验证集无进展时自动衰减;
  • CosineAnnealingLR: 余弦衰减,适用于高性能模型。

示例:StepLR 每隔固定 epoch 让学习率乘以一个系数:

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

# 简单模型和损失函数
model = nn.Linear(10, 1)
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.1)

# 每 10 个 epoch 将 lr 乘以 0.1
scheduler = StepLR(optimizer, step_size=10, gamma=0.1)

# 假数据
x = torch.randn(32, 10)
y = torch.randn(32, 1)

# 训练 30 个 epoch
for epoch in range(30):
    # 前向传播
    y_pred = model(x)
    loss = criterion(y_pred, y)

    # 反向传播
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    # 学习率调度器更新
    scheduler.step()

    # 打印当前学习率
    current_lr = scheduler.get_last_lr()[0]
    print(f"Epoch {epoch}, Loss: {loss.item():.4f}, LR: {current_lr:.6f}")

调度器常在每个 epoch 结束后调用 .step(),动态调整当前学习率。

  • SGD 支持 Momentum(动量);
  • Adam 具备自适应学习率能力,效果稳定,使用广泛。

十、ResNet 与残差连接:深层网络的训练利器

在深度神经网络中,随着网络层数的增加,模型训练会遇到梯度消失或梯度爆炸问题,导致性能下降。ResNet(Residual Network)通过引入“残差连接”结构,有效缓解了这一问题,极大推动了深度网络的发展。

1. 残差结构的思想

传统网络层直接学习从输入到输出的映射函数 H(x),而 ResNet 改为学习残差函数 F(x) = H(x) - x,也就是说:

y = F(x) + x

这种设计思想让网络可以更容易地拟合恒等映射,尤其在一些层实际上无需学习变化时,直接“跳过”成为最优选择。

2. 残差连接的实现方式

残差连接是通过“快捷路径(skip connection)”将输入直接加到输出上,如:

out = self.block(x)  # 普通卷积层或非线性变换
out += x              # 加上原始输入,实现跳跃连接
return F.relu(out)    # 激活函数

在 PyTorch 中实现一个基本残差块:

class ResidualBlock(nn.Module):
    def __init__(self, in_channels):
        super().__init__()
        self.block = nn.Sequential(
            nn.Conv2d(in_channels, in_channels, kernel_size=3, padding=1),
            nn.BatchNorm2d(in_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels, in_channels, kernel_size=3, padding=1),
            nn.BatchNorm2d(in_channels)
        )

    def forward(self, x):
        return F.relu(self.block(x) + x)

注意:当输入和输出维度不一致时,需使用 nn.Conv2d 进行升维或降维匹配。

3. ResNet 的应用与演化

  • ResNet 最初提出了 ResNet-18/34/50/101 等系列模型,通过堆叠多个残差块构成深层网络;
  • 在图像分类、目标检测、图像分割等任务中广泛使用;
  • 也衍生出 ResNeXt、SE-ResNet 等变种模型,提升表达能力。

通过残差连接,ResNet 实现了网络加深与训练稳定性的双赢,使得“百层网络”变为现实,其思想也被广泛应用于 NLP、CV 等各类模型设计中。

y = F(x) + x
  • F(x) 是网络学习的“残差”,x 是原始输入;
  • 避免梯度消失,使深层网络更易训练;
  • 可堆叠至 100+ 层。

示例:

class BasicBlock(nn.Module):
    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU()
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1)
        self.bn2 = nn.BatchNorm2d(out_channels)

    def forward(self, x):
        # 把原始输入保存下来
        identity = x              
   
       # conv → bn → relu :学习“变化函数” 𝐹(𝑥):它告诉模型“输入应该如何调整”
        out = self.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))

        # 残差
        # 把变化量加到原始输入上,得到最终输出 𝐻(𝑥)=𝐹(𝑥)+𝑥
        out = identity + out

        out = self.relu(out)
        return out

十一、模型参数管理与微调

1. 冻结参数

冻结前面层,只训练后面层:

import torch
import torch.nn as nn

# 定义一个简单模型
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.feature_extractor = nn.Sequential(
            nn.Linear(100, 50),
            nn.ReLU()
        )
        self.classifier = nn.Linear(50, 10)

    def forward(self, x):
        x = self.feature_extractor(x)
        return self.classifier(x)

model = MyModel()

# 冻结 feature_extractor 的参数
for param in model.feature_extractor.parameters():
    param.requires_grad = False

# 只训练 classifier 的参数
optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=0.001)

# 检查哪些参数被冻结
for name, param in model.named_parameters():
    print(f"{name}: requires_grad = {param.requires_grad}")

2. 解冻

# 解冻 feature_extractor
for param in model.feature_extractor.parameters():
    param.requires_grad = True

3. 获取模型参数名与结构

for name, param in model.named_parameters():
    print(name, param.shape)

输出示例:

layer1.0.conv1.weight torch.Size([64, 3, 3, 3])
  • layer1
    层组; ResNet 的第 1 个残差层组
  • .0
    第几个block; 这个组里的第 0 个残差块(block)
  • .conv1
    第几个conv层; 该残差块的第一个卷积层
  • .weight
    哪个参数(权重); 卷积核权重张量(不是 bias)
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容