Skip to content

第 7 章 神经网络基础

学习目标

  • 掌握 nn.Module:神经网络组件的基类
  • 掌握 nn.Linear 全连接层与 nn.Sequential
  • 理解 nn.Parametermodel.parameters()
  • 理解 forwardmodel(x) 的调用方式
  • 会搭建第一个多层感知机(MLP)并做前向传播

7.1 从手动参数到 nn.Module

第 6 章的 w = torch.tensor(..., requires_grad=True) 是手工管理参数。神经网络有成百上千个参数,需要统一管理:能一起 parameters() 取出来、一起进优化器、一起保存。PyTorch 用 nn.Module 完成这件事。

nn.Module 是 PyTorch 所有神经网络组件的基类:一个层、一个网络,都是它的子类。核心约定:

  1. __init__ 里定义子层和参数;
  2. forward 里写数据怎么流经网络;
  3. model(x) 调用网络,不要直接调 model.forward(x)

最常用的层是 nn.Linear(全连接层),它实现了第 4 章的 输入 @ 权重 + 偏置:

python
import torch
import torch.nn as nn

lin = nn.Linear(2, 1)    # 2 个输入特征 → 1 个输出
print(lin)
print(lin.weight.shape, lin.weight.requires_grad)
print(lin.bias.shape)

输出:

Linear(in_features=2, out_features=1, bias=True)
torch.Size([1, 2]) True
torch.Size([1])

weight 的形状是 (1, 2):输出数 × 输入数;bias 形状是 (1,)。参数创建时自动 requires_grad=True,可以直接参与 autograd。

前向传播(推理)一次:

python
import torch
import torch.nn as nn

torch.manual_seed(42)
lin = nn.Linear(2, 1)
x = torch.tensor([[1., 2.],
                  [3., 4.],
                  [5., 6.]])
y = lin(x)
print(y)
print(y.shape)

输出:

tensor([[1.5488],
        [3.8038],
        [6.0588]], grad_fn=<AddmmBackward0>)
torch.Size([3, 1])

输入 (3, 2)(3 个样本、2 个特征)经过 Linear 变成 (3, 1)。输出带 grad_fn=<AddmmBackward0>,说明它参与了计算图——这就是第 6 章自动求导的入口。

7.2 自定义一个最简单的网络

一个「单权重」网络演示 nn.Module 的最小结构:

python
import torch
import torch.nn as nn

class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.weight = nn.Parameter(torch.tensor(2.0))
        self.bias = nn.Parameter(torch.tensor(1.0))

    def forward(self, x):
        return self.weight * x + self.bias

net = Net()
print(net)
print("weight requires_grad:", net.weight.requires_grad)
print("forward(3):", net(torch.tensor(3.0)))

输出:

Net()
weight requires_grad: True
forward(3): tensor(7., grad_fn=<AddBackward0>)

2×3 + 1 = 7,符合预期。要点:

  • 必须调用 super().__init__(),否则模块内部注册机制不生效;
  • nn.Parameter 是「声明为参数」的张量,创建后自动 requires_grad=True,并被登记到模块;
  • forward 就是普通 Python 方法,可以写任意张量运算;
  • net(x) 会先做一些内部处理(如登记钩子)再调用 forward

parameters()named_parameters() 可以拿到模块管理的全部参数:

python
print("参数数量:", len(list(net.parameters())))
for name, p in net.named_parameters():
    print(name, p)

输出:

参数数量: 2
weight Parameter containing:
tensor(2., requires_grad=True)
bias Parameter containing:
tensor(1., requires_grad=True)

训练时优化器直接接收 net.parameters(),一个不漏。

7.3 nn.Sequential:串起多个层

nn.Sequential(顺序容器)把多个层按顺序排好,输入依次流过:

python
import torch
import torch.nn as nn

torch.manual_seed(42)
net = nn.Sequential(
    nn.Linear(2, 4),    # 2 个特征 → 4 个隐藏单元
    nn.ReLU(),          # 激活函数
    nn.Linear(4, 1),    # 4 个隐藏单元 → 1 个输出
)
print(net)

输出:

Sequential(
  (0): Linear(in_features=2, out_features=4, bias=True)
  (1): ReLU()
  (2): Linear(in_features=4, out_features=1, bias=True)
)

前向传播:

python
x = torch.tensor([[1., 2.]])
print(net(x))

输出:

tensor([[-0.0214]], grad_fn=<AddmmBackward0>)

数据流:(1, 2) → Linear 变成 (1, 4) → ReLU(逐元素把负数变 0)→ Linear 变成 (1, 1)。这就是一个最简单的 多层感知机(MLP)

激活函数(activation) 的作用是给网络引入非线性。两个线性层叠在一起本质上还是一个线性函数,ReLU 让网络能表达更复杂的函数关系,深度学习的「深」才真正有意义。

7.4 参数初始化

nn.Linear 默认会做合理的参数初始化(权重按均匀分布,范围由输入输出大小决定),所以示例里没手动初始化。需要指定时,可以在 __init__ 里覆盖:

python
import torch
import torch.nn as nn

class FixedNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc = nn.Linear(2, 1)
        with torch.no_grad():
            self.fc.weight.fill_(0.5)
            self.fc.bias.fill_(0.0)

    def forward(self, x):
        return self.fc(x)

net = FixedNet()
print(net(torch.tensor([[2., 4.]])))

输出:

tensor([[3.]], grad_fn=<AddmmBackward0>)

0.5×2 + 0.5×4 + 0 = 3,手工初始化生效。初始化通常包在 torch.no_grad() 里,因为初始化不是训练、不需要梯度。

动手实践

  1. 创建 nn.Linear(3, 2),打印 weight.shapebias.shapeweight.requires_grad;输入一个 (5, 3) 张量,验证输出形状。
  2. nn.Sequential 搭一个 Linear(4, 8) → ReLU → Linear(8, 2),打印结构,输入 (2, 4) 张量验证输出形状。
  3. 自定义一个 ScaleNet(一个可学习缩放因子 nn.Parameter,forward 返回 w * x),打印 named_parameters()
  4. 对第 7.2 节的 Net,手动调用 net.forward(3.0)net(3.0),观察结果;说出为什么推荐用 net(x)
  5. torch.no_grad()nn.Linear(2, 1) 的权重填成 1、偏置填成 0,验证 net(x) 等价于求和。

常见错误

错误写法现象原因
__init__ 里忘了 super().__init__()AttributeError 或参数不被登记父类初始化负责注册子模块与参数
把参数存成普通 torch.tensor 而不是 nn.Parameterparameters() 里没有它,优化器不更新要声明为 nn.Parameter
model.forward(x) 代替 model(x)大部分情况能跑,但钩子等功能失效约定用 model(x)
forward 忘记 return输出是 None,后续报错forward 必须返回张量
输入维度与 in_features 不匹配RuntimeError: mat1 and mat2 shapes cannot be multiplied检查输入最后一维是否等于 in_features
nn.ReLU 当函数调用 nn.ReLU(x)TypeError先实例化 relu = nn.ReLU()relu(x);或直接用 torch.relu(x)

章末练习

基础

  1. 创建 nn.Linear(2, 1),打印它的 weightbias 形状,并说明 weight 的形状为什么是 (1, 2)
  2. nn.SequentialLinear(3, 5) → ReLU → Linear(5, 1),输入 (4, 3) 张量,验证输出形状是 (4, 1)
  3. 自定义 AddBias(nn.Module)(只有一个可学习偏置,forward 返回 x + bias),打印它的参数列表。

提高

  1. 解释 nn.Parameter 与普通 torch.tensor(..., requires_grad=True) 的区别(提示:参数登记与 parameters())。
  2. nn.Sequential 搭一个 3 层 MLP(2 → 16 → 16 → 1,ReLU 激活),统计 len(list(net.parameters())) 并说明每个参数张量的形状。

挑战

  1. 不运行代码,预测 nn.Linear(2, 1) 输出 (3, 2) 输入时的形状;再预测 nn.Sequential(Linear(2,4), ReLU(), Linear(4,1))(5, 2) 输入的输出形状,运行验证。
  2. 手写一个不依赖 nnLinear 等价实现(用 nn.Parameterweightbias,forward@),并验证它与 nn.Linear 输出一致(设置相同权重后比较)。

章末自测

每题选择一个最佳答案。本书不附答案:完成后交由老师或 AI 老师批改讲解。

  1. PyTorch 所有网络组件的基类是?
    • A. nn.Tensor
    • B. nn.Module
    • C. nn.Network
    • D. torch.Model
  2. nn.Linear(2, 1).weight.shape 是?
    • A. torch.Size([2, 1])
    • B. torch.Size([1, 2])
    • C. torch.Size([2])
    • D. torch.Size([1])
  3. nn.Linear 的前向计算等价于?
    • A. x * w
    • B. x @ w + b
    • C. w @ x + b
    • D. x + b
  4. 自定义模块的 __init__ 里,第一行应该写?
    • A. self.init = True
    • B. super().__init__()
    • C. nn.Module.__init__(self, x)
    • D. 什么都不用写
  5. 要把一个张量声明为模块的可学习参数,应该用?
    • A. torch.tensor(1.0)
    • B. nn.Parameter(torch.tensor(1.0))
    • C. nn.Parameter(1.0)
    • D. torch.parameter(1.0)
  6. 前向传播的正确调用方式是?
    • A. model.forward(x)
    • B. model(x)
    • C. model.call(x)
    • D. model.run(x)
  7. nn.Sequential(Linear(2, 4), ReLU(), Linear(4, 1))(5, 2) 输入的输出形状是?
    • A. torch.Size([5, 1])
    • B. torch.Size([1, 5])
    • C. torch.Size([5, 4])
    • D. torch.Size([5, 2])
  8. 激活函数(如 ReLU)的作用是?
    • A. 让输出变成整数
    • B. 引入非线性,否则多层等价于单层线性变换
    • C. 加速训练的唯一手段
    • D. 防止过拟合的唯一手段
  9. nn.Linear 的参数默认?
    • A. requires_grad=False
    • B. requires_grad=True
    • C. 由 forward 决定
    • D. 创建后不能改
  10. list(net.parameters()) 返回的是?
    • A. 网络中所有子模块
    • B. 网络中所有参数张量
    • C. 网络的名字
    • D. 网络的结构字符串