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

展开文章目录
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 控制流的前向传播过程。
数学表达
假设某层以为输入、为参数、为层的权重常量,计算过程为:
代码实现
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
讨论与回应
欢迎补充细节、指出问题,或分享与这篇文章有关的经验。
昵称与邮箱为必填项,邮箱仅用于头像和回复通知,不会公开。网址可以留空。