层和块

深入理解深度学习中层和块的概念,掌握PyTorch中自定义模块的实现方法

发布于 2025-08-04 更新于 2025-08-041,715 字 4 分钟
层和块的文章封面
展开文章目录
  1. 1. 从单层到块的演进
  2. 1.1 深度学习框架的发展历程
  3. 1.2 学习目标
  4. 2. 块是对层的功能性封装
  5. 2.1 从层到块的概念演进
  6. 2.2 块的重要特性
  7. 2.3 PyTorch中的块实现
  8. 3. 使用块类实现模型
  9. 3.1 实现 MLP
  10. 3.2 实现自定义 Sequential
  11. 3.3 实现特定控制流的前向传播
  12. 3.4 实现块的嵌套
  13. 4. 块设计的最佳实践
  14. 4.1 设计原则
  15. 4.2 参数管理
  16. 参考资料

1. 从单层到块的演进

1.1 深度学习框架的发展历程

Theano 库虽已于 2017 年停止维护,但其开创性的设计逻辑在深度学习发展的早期阶段,起到了重要的推动作用。后来兴起的新框架,如 TensorFlow、PyTorch 等,逐步演化为更高级的抽象。

1.2 学习目标

截至目前,我们已经了解了如何从零开始,或使用高级 API 构建深度学习模型。接下来,将继续探索深度学习计算中的关键组件、更多的细节与特性。


2. 块是对层的功能性封装

2.1 从层到块的概念演进

层次结构

单层 (layer) 模型特点:

  • 线性回归模型:接收输入,不断更新参数,输出单个标量
  • SoftMax回归模型:接收输入,不断更新参数,输出一组向量
  • 功能单一:每层只完成特定的变换操作

多层网络 (MLP) 特点:

  • 由多个上述类似的层叠加而来
  • 组成更复杂的网络结构
  • 能够学习更复杂的非线性映射关系

块 (block) 的概念:

  • 由多个层构成的层组 (groups of layers)
  • 通常包含卷积层、全连接层、批归一化层、激活函数等
  • 按特定顺序和结构排列,组成具有特定功能的子网络

2.2 块的重要特性

实际应用案例:ResNet

广泛应用的残差神经网络 (Residual Network, ResNet) 是计算机视觉任务的首选架构:

  • 使用残差块 (Residual Block) 的跳跃连接 (Skip 或 Shortcut Connections) 进行架构
  • 每个残差块包含 3 个卷积层
  • 通过减少中间层的通道数减少计算量

2.3 PyTorch中的块实现

在面向对象程序实现上,用继承父类的方式组织块级别的抽象,以简洁的代码表示复杂的神经网络。


3. 使用块类实现模型

3.1 实现 MLP

回顾:Sequential的简洁实现

查看MLP的Sequential实现

与之前学习的MLP简洁实现类似,以下代码使用nn.Sequential块实例,维护了一个有序列表,按顺序依次向前计算:

import torch
from torch import nn

MLP = nn.Sequential(nn.Linear(in_features=20, out_features=256), nn.ReLU(),  # 全连接隐藏层
                    nn.Linear(in_features=256, out_features=10))  # 输出层

if __name__ == '__main__':
    mlp = MLP(torch.rand(2, 20))
    print(mlp)

输出结果:

tensor([[ 0.0719, -0.1779,  0.2055, -0.1123, -0.0242,  0.1615,  0.1049,  0.0698,
          0.1137, -0.0367],
        [-0.0217, -0.1339,  0.2907, -0.1309, -0.1343,  0.1238,  0.2223,  0.0750,
          0.0533, -0.0908]], grad_fn=<AddmmBackward0>)

自定义MLP块类实现

现在我们使用块类来实现相同的MLP模型:

import torch
from torch import nn
from torch.nn.functional import relu


class MLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.hide = nn.Linear(in_features=20, out_features=256)  # 全连接隐藏层
        self.out = nn.Linear(in_features=256, out_features=10)  # 输出层

    def forward(self, data):
        return self.out(relu(self.hide(data)))


if __name__ == '__main__':
    mlp = MLP()
    print(mlp(torch.randn(2, 20)))

输出结果:

tensor([[-0.4986, -0.1931, -0.2063,  0.1460, -0.1169,  0.2062,  0.0629, -0.0160,
         -0.0103,  0.1804],
        [-0.3573,  0.0874, -0.3731,  0.1806,  0.1149, -0.2240,  0.2753, -0.2787,
         -0.3732, -0.0407]], grad_fn=<AddmmBackward0>)

3.2 实现自定义 Sequential

torch.nn.modules.container.Sequential通常简称为nn.Sequential,用于将多个子模块(层、激活函数等)按顺序组织在一起构建神经网络模型。我们可以使用块类实现类似的功能:

import torch
from torch import nn


class MySequential(nn.Module):
    def __init__(self, *modules):
        super().__init__()
        for i, module in enumerate(modules):
            self._modules[str(i)] = module  # 成员变量 _modules 继承自 Module 类,是一个有序字典 OrderedDict

    def forward(self, data):
        for module in self._modules.values():
            data = module(data)
        return data


if __name__ == '__main__':
    mlp = MySequential(
        nn.Linear(20, 256), nn.ReLU(),
        nn.Linear(256, 10)
    )
    print(mlp)
    print(mlp(torch.randn(2, 20)))

输出结果:

MySequential(
  (0): Linear(in_features=20, out_features=256, bias=True)
  (1): ReLU()
  (2): Linear(in_features=256, out_features=10, bias=True)
)
tensor([[ 0.1527,  0.3147, -0.1843,  0.2630, -0.3651, -0.1950,  0.3764,  0.1457,
          0.1444,  0.0282],
        [-0.1666, -0.2401, -0.3169,  0.6245, -0.0906, -0.0290,  0.2561,  0.1047,
         -0.1085,  0.0437]], grad_fn=<AddmmBackward0>)

3.3 实现特定控制流的前向传播

当处理特定常数参数时,需要定义具有特定 Python 控制流的前向传播过程。

数学表达

假设某层以x\mathbf{x}为输入、w\mathbf{w}为参数、c\mathbf{c}为层的权重常量,计算过程为:

f(x,w)=cwxf(\mathbf{x},\mathbf{w})=c\cdot\mathbf{w}^\top\mathbf{x}\mathrm{}

代码实现

import torch
from torch import nn
import torch.nn.functional as F


class TestBlock(nn.Module):
    def __init__(self):
        super().__init__()
        self.cons_weight = torch.rand((20, 20), requires_grad=False)  # 常量。不参与梯度计算,不被更新
        self.linear = nn.Linear(20, 20)

    def forward(self, data):
        data = self.linear(data)  # 全连接层
        data = F.relu(data @ self.cons_weight + 1)  # 激活函数
        data = self.linear(data)  # 复用全连接层,共享参数
        while data.abs().sum() > 1:  # 控制流
            data /= 2
        return data.sum()


if __name__ == '__main__':
    net = TestBlock()
    print(net(torch.rand(2, 20)))

输出结果:

tensor(0.3900, grad_fn=<SumBackward0>)

3.4 实现块的嵌套

块的一个重要特性是可以嵌套使用,形成更复杂的层次结构:

import torch
from torch import nn


class Chimera(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(),
                                 nn.Linear(64, 32), nn.ReLU())
        self.linear = nn.Linear(32, 16)

    def forward(self, data):
        return self.linear(self.net(data))


if __name__ == '__main__':
    net = nn.Sequential(
        Chimera(),
        nn.Linear(16, 20),
        Chimera()
    )
    print(net(torch.rand(2, 20)))

输出结果:

tensor([[ 0.0206, -0.1596,  0.0709,  0.0345, -0.0782,  0.0555, -0.1312,  0.0473,
          0.0342, -0.0228,  0.1037, -0.1580, -0.0944,  0.0130,  0.0675,  0.1281],
        [ 0.0214, -0.1608,  0.0681,  0.0350, -0.0785,  0.0535, -0.1306,  0.0468,
          0.0351, -0.0210,  0.1031, -0.1583, -0.0949,  0.0136,  0.0673,  0.1277]],
       grad_fn=<AddmmBackward0>)

4. 块设计的最佳实践

4.1 设计原则

设计原则

模块化设计:

  • 每个块应该有明确的功能定义
  • 块之间的接口应该清晰简洁
  • 避免块之间的强耦合

可复用性:

  • 设计通用的块,可以在不同的网络中复用
  • 通过参数化控制块的行为
  • 避免硬编码特定的尺寸或参数

可组合性:

  • 块应该能够轻松地与其他块组合
  • 支持嵌套和递归结构
  • 保持输入输出接口的一致性

4.2 参数管理

在自定义块时,需要注意参数的正确管理:

class CustomBlock(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super().__init__()
        # 正确注册参数
        self.linear1 = nn.Linear(input_size, hidden_size)
        self.linear2 = nn.Linear(hidden_size, output_size)
        
        # 自定义参数
        self.custom_weight = nn.Parameter(torch.randn(hidden_size, hidden_size))
        
        # 常量(不参与训练)
        self.register_buffer('constant', torch.ones(hidden_size))
    
    def forward(self, x):
        x = self.linear1(x)
        x = torch.matmul(x, self.custom_weight) + self.constant
        x = self.linear2(x)
        return x

参考资料

如果这篇记录对你有帮助,可以留下一句回应。

前往留言

DISCUSSION

讨论与回应

欢迎补充细节、指出问题,或分享与这篇文章有关的经验。

昵称与邮箱为必填项,邮箱仅用于头像和回复通知,不会公开。网址可以留空。

正在准备留言区…

演示赞赏界面

谢谢你愿意支持长期写作。

第一阶段不会发起支付或收集任何信息。接入真实赞赏渠道后,这里会展示清楚的金额、渠道和完成状态。

输入关键词开始搜索 · 按 Esc 关闭
打开完整搜索页 →