神经网络的层与块

什么是层?什么是块?

在深度学习中,层(Layer)块(Block) 是构建神经网络的核心概念,尤其在 PyTorch、TensorFlow 等框架中,二者既紧密关联又有明确分工。理解它们的定义、关系和用法,是掌握神经网络设计的基础。

一、核心定义

1. 层(Layer)

层是神经网络中最基本的计算单元,实现特定的数学操作(如线性变换、卷积、激活函数等)。

  • 功能单一:通常只完成一种特定计算(如nn.Linear实现线性变换y = Wx + bnn.ReLU实现激活函数y = max(0, x))。
  • 可复用性低:单个层一般不单独使用,需与其他层组合才能完成复杂任务。
2. 块(Block)

块是由多个层(或其他块)组合而成的复杂单元,封装了一组相关的计算逻辑。

  • 功能复合:可以包含多个层(如 "卷积层 + 激活函数 + 池化层" 组成的卷积块),甚至嵌套其他块(如 ResNet 中的残差块包含多个卷积块)。
  • 可复用性高:块可以被看作 "超级层",在网络中重复使用(如 Transformer 中的 Encoder 块被重复堆叠)。

二、本质关系:层是块的 "原子",块是层的 "组合"

在 PyTorch 中,所有层和块都继承自nn.Module ,因此它们在接口上保持一致(都有__init__初始化方法和forward前向传播方法)。

  • 层是 "最小化的块":单个层(如nn.Linear)可以视为只包含一个计算步骤的特殊块。
  • 块是 "结构化的层集合":块通过组合多个层(或块),实现更复杂的功能(如特征提取、残差连接等)。

三、具体区别与联系

维度 层(Layer) 块(Block)
组成 单一计算单元(如矩阵乘法、卷积) 多个层 / 块的组合(如 "线性层 + 激活函数 + dropout")
功能 实现基础操作(如线性变换、非线性激活) 实现复杂功能(如特征提取、残差连接、注意力机制)
复用性 低(通常作为块的组成部分) 高(可作为模块重复嵌入到不同网络中)
示例 nn.Linearnn.Conv2dnn.ReLU nn.Sequential、ResNet 的残差块、Transformer 的 Encoder 块

为什么需要自定义 Sequential?

虽然 PyTorch 已有nn.Sequential,但自定义版本有以下用途:

  1. 学习原理:理解 PyTorch 如何管理模块和参数。
  2. 扩展功能:例如,添加日志记录、中间输出缓存等功能。
  3. 简化接口:在特定场景下提供更简洁的 API。

MySequentialnn.Sequential功能基本相同,有那些细微差异?

特性 MySequential nn.Sequential
模块命名 自动生成索引(如 "0", "1") 可自定义名称(如nn.Sequential(relu=nn.ReLU())
初始化方式 接收任意数量的模块 接收多个模块或有序字典
实现复杂度 约 20 行代码 更复杂(支持更多特性)

完整代码

python 复制代码
"""
文件名: 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)] = module
    def 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))

实验结果

相关推荐
Coffeeee7 小时前
帮你快速理解AI Agent之我想招个Android实习生
android·人工智能·agent
新新技术迷7 小时前
AI聊天自动跟随滚动,附回到底部按钮
人工智能
先锋部队7 小时前
用Web Worker解析AI返回的大文本不卡UI
人工智能
把你拉进白名单7 小时前
8.OpenClaw源码解析——三层洋葱重试
人工智能·llm·agent
用户632415031787 小时前
拖文档进AI对话框解析,前端要处理哪些脏活
人工智能
姗姗来迟了7 小时前
AI回答里的引用来源卡片,前端怎么做
人工智能
用户7106207733407 小时前
Codex-端口配置错误排查案例(stream disconnected before completion)
人工智能
IT_陈寒8 小时前
JavaScript的默认参数挖坑实录,我掉进去了
前端·人工智能·后端
米小虾8 小时前
多Agent系统编排详解:从架构设计到代码实现
人工智能·agent
米小虾9 小时前
多Agent系统的编排:架构、协议与企业级应用
人工智能·agent