神经网络的层与块
什么是层?什么是块?
在深度学习中,层(Layer) 和块(Block) 是构建神经网络的核心概念,尤其在 PyTorch、TensorFlow 等框架中,二者既紧密关联又有明确分工。理解它们的定义、关系和用法,是掌握神经网络设计的基础。
一、核心定义
1. 层(Layer)
层是神经网络中最基本的计算单元,实现特定的数学操作(如线性变换、卷积、激活函数等)。
- 功能单一:通常只完成一种特定计算(如
nn.Linear
实现线性变换y = Wx + b
,nn.ReLU
实现激活函数y = max(0, x)
)。- 可复用性低:单个层一般不单独使用,需与其他层组合才能完成复杂任务。
2. 块(Block)
块是由多个层(或其他块)组合而成的复杂单元,封装了一组相关的计算逻辑。
- 功能复合:可以包含多个层(如 “卷积层 + 激活函数 + 池化层” 组成的卷积块),甚至嵌套其他块(如 ResNet 中的残差块包含多个卷积块)。
- 可复用性高:块可以被看作 “超级层”,在网络中重复使用(如 Transformer 中的 Encoder 块被重复堆叠)。
二、本质关系:层是块的 “原子”,块是层的 “组合”
在 PyTorch 中,所有层和块都继承自
nn.Module
类,因此它们在接口上保持一致(都有__init__
初始化方法和forward
前向传播方法)。
- 层是 “最小化的块”:单个层(如
nn.Linear
)可以视为只包含一个计算步骤的特殊块。- 块是 “结构化的层集合”:块通过组合多个层(或块),实现更复杂的功能(如特征提取、残差连接等)。
三、具体区别与联系
维度 层(Layer) 块(Block) 组成 单一计算单元(如矩阵乘法、卷积) 多个层 / 块的组合(如 “线性层 + 激活函数 + dropout”) 功能 实现基础操作(如线性变换、非线性激活) 实现复杂功能(如特征提取、残差连接、注意力机制) 复用性 低(通常作为块的组成部分) 高(可作为模块重复嵌入到不同网络中) 示例 nn.Linear
、nn.Conv2d
、nn.ReLU
nn.Sequential
、ResNet 的残差块、Transformer 的 Encoder 块
为什么需要自定义 Sequential?
虽然 PyTorch 已有
nn.Sequential
,但自定义版本有以下用途:
- 学习原理:理解 PyTorch 如何管理模块和参数。
- 扩展功能:例如,添加日志记录、中间输出缓存等功能。
- 简化接口:在特定场景下提供更简洁的 API。
MySequential
和nn.Sequential
功能基本相同,有那些细微差异?
特性 MySequential
nn.Sequential
模块命名 自动生成索引(如 "0", "1") 可自定义名称(如 nn.Sequential(relu=nn.ReLU())
)初始化方式 接收任意数量的模块 接收多个模块或有序字典 实现复杂度 约 20 行代码 更复杂(支持更多特性)
完整代码
"""
文件名: 5.1
作者: 墨尘
日期: 2025/7/13
项目名: dl_env
备注: 输出结果不一样,是因为Linear权值是随机初始化的
"""
import torch
from torch import nn
from torch.nn import functional as F"""多层感知机,使用自定义块实现"""
class MLP(nn.Module):# 用模型参数声明层。这里,我们声明两个全连接的层def __init__(self):# 调用MLP的父类Module的构造函数来执行必要的初始化。# 这样,在类实例化时也可以指定其他函数参数,例如模型参数params(稍后将介绍)super().__init__()self.hidden = nn.Linear(20, 256) # 隐藏层self.out = nn.Linear(256, 10) # 输出层# 定义模型的前向传播,即如何根据输入X返回所需的模型输出def forward(self, X):# 注意,这里我们使用ReLU的函数版本,其在nn.functional模块中定义。return self.out(F.relu(self.hidden(X)))"""自定义顺序块,按传入顺序连接多个模块"""# MySequential的核心目标是:将多个层按传入的顺序连接起来,前一层的输出作为后一层的输入
class MySequential(nn.Module):def __init__(self, *args):"""初始化顺序块,接收任意数量的PyTorch模块参数:*args: 任意数量的nn.Module子类实例(如nn.Linear, nn.ReLU等)"""# 调用父类nn.Module的构造函数,完成必要的初始化super().__init__()# 遍历所有传入的模块for idx, module in enumerate(args):# 将模块添加到PyTorch内置的有序字典_modules中# 键: 模块的索引(字符串形式)# 值: 具体的模块实例# _modules是nn.Module的特殊属性,PyTorch会自动管理其中的所有模块# 包括参数初始化、设备同步、序列化等self._modules[str(idx)] = moduledef forward(self, X):"""定义前向传播逻辑,按顺序依次调用所有模块参数:X: 输入张量返回:经过所有模块处理后的输出张量"""# 按_modules中保存的顺序遍历所有模块# OrderedDict保证了遍历时模块的顺序与添加时一致for block in self._modules.values():# 将输入数据依次通过每个模块# 前一个模块的输出直接作为下一个模块的输入X = block(X)# 返回最终输出return X"""在前向传播函数中执行代码"""
class FixedHiddenMLP(nn.Module):def __init__(self):"""自定义神经网络模块,展示PyTorch中的特殊用法:1. 使用固定权重(训练期间不更新)2. 层参数共享3. 前向传播中的控制流"""super().__init__()# 创建固定权重矩阵(随机初始化,但不参与训练)# requires_grad=False:禁用梯度计算,训练时权重不会更新self.rand_weight = torch.rand((20, 20), requires_grad=False)# 定义可训练的线性层self.linear = nn.Linear(20, 20)def forward(self, X):"""定义前向传播逻辑,包含非常规操作:1. 使用固定权重矩阵进行矩阵乘法2. 复用同一个线性层(参数共享)3. 使用while循环控制输出规模"""# 第一层:可训练的线性变换X = self.linear(X)# 第二层:使用固定随机权重进行矩阵乘法,添加偏置1,再通过ReLU激活# torch.mm:矩阵乘法# self.rand_weight在训练过程中保持不变X = F.relu(torch.mm(X, self.rand_weight) + 1)# 第三层:复用第一个线性层(参数共享)# 相当于两个不同层共享同一组参数X = self.linear(X)# 控制流:如果张量X的绝对值之和大于1,则不断将X除以2# 这是一个自定义的输出规范化策略while X.abs().sum() > 1:X /= 2# 返回标量值:所有元素的和return X.sum()if __name__ == '__main__':"""多层感知机构建一个包含输入层→隐藏层→输出层的全连接神经网络,对随机生成的输入数据进行计算并输出结果。"""# 线性变换负责特征的线性映射,激活函数负责注入非线性,两者交替使用才能让网络有能力学习复杂数据。# 传播过程拆解:# 输入X(形状(2,20))→ 第 1 层线性变换 → 输出X1(形状(2,256))# X1→ 第 2 层 ReLU 激活 → 输出X2(形状(2,256),所有元素非负)# X2→ 第 3 层线性变换 → 输出Y(形状(2,10))# 1. 定义神经网络# 这个前向传播函数非常简单: 它将列表中的每个块连接在一起,将每个块的输出作为下一个块的输入。net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))# 2. 生成输入数据X = torch.rand(2, 20)# 3. 前向传播并打印输出print(net(X))"""自定义块"""net = MLP()print(net(X))"""顺序块"""net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))print(net(X))"""在前向传播函数中执行代码"""net = FixedHiddenMLP()print(net(X))