PyTorch 核心三件套:Tensor、Module、Autograd

欢迎来到啾啾的博客🐱。

记录学习点滴。分享工作思考和实用技巧,偶尔也分享一些杂谈💬。

有很多很多不足的地方,欢迎评论交流,感谢您的阅读和评论😄。

目录

  • 引言
  • [1 Tensor](#1 Tensor)
    • [1.1 🛠️Tensor 的核心用途(5大场景)](#1.1 🛠️Tensor 的核心用途(5大场景))
    • [1.2 表示现实世界数据|输入数据](#1.2 表示现实世界数据|输入数据)
    • [1.3 表示标签(Labels / Targets)](#1.3 表示标签(Labels / Targets))
    • [1.4 存储模型参数(Weights & Biases)](#1.4 存储模型参数(Weights & Biases))
    • [1.5 中间计算结果(Forward Pass)](#1.5 中间计算结果(Forward Pass))
    • [1.6 梯度计算(Backward Pass)](#1.6 梯度计算(Backward Pass))
    • [1.7 📚 常用 API 速查表](#1.7 📚 常用 API 速查表)
    • [1.8 💡 Tensor 演示](#1.8 💡 Tensor 演示)
  • [2 Module](#2 Module)
    • [2.1 简介](#2.1 简介)
    • [2.2 创建Module](#2.2 创建Module)
    • [2.3 Module 的核心操作](#2.3 Module 的核心操作)
  • [3 Autograd](#3 Autograd)
    • [3.1 简介](#3.1 简介)
    • [3.2 梯度追踪](#3.2 梯度追踪)
    • [3.3 反向传播](#3.3 反向传播)
    • [3.4 禁用梯度计算](#3.4 禁用梯度计算)
  • [4 📌 三件套关系图解](#4 📌 三件套关系图解)
  • [5 🌟 三件套协同工作流程(完整训练示例)](#5 🌟 三件套协同工作流程(完整训练示例))
    • [5.1 Demo:三件套简单串联](#5.1 Demo:三件套简单串联)
    • [5.2 构建一个简单线性回归模型 y=wx+b](#5.2 构建一个简单线性回归模型 y=wx+b)
    • [5.3 Demo:搭建一个简单的多层感知机](#5.3 Demo:搭建一个简单的多层感知机)
  • 刻意练习

引言

本篇将讲述PyTorch的核心"三件套":Tensor, Module, Autograd。

尝试以后端岗位工程应用角度来理解、串联起这个三个概念。

AI使用声明:本文内容由作者基于对深度学习和PyTorch框架的学习与理解撰写。在内容整理、结构优化和语言表达的过程中,我使用了人工智能(AI)工具作为辅助。

资料:
《深入浅出PyTorch 第二章节》
《动手学深度学习第四章》

1 Tensor

让我们思考一个问题,我们要怎么把现实世界的数据变成计算机能处理的数字形式呢?

通过Tensor,张量,它是现代机器学习的基础。

n维数组,也称为_张量_(tensor)。

几何代数中定义的张量是基于向量和矩阵的推广,比如我们可以将标量视为零阶张量,矢量可以视为一阶张量,矩阵就是二阶张量。

张量维度 代表含义 示例
0D 标量(Scalar) torch.tensor(3.14)
1D 向量(Vector) [1, 2, 3]
2D 矩阵(Matrix) 图像展平后的特征
3D 序列/单图 (C, H, W) 彩色图像
4D 批量图像 (B, C, H, W)

在PyTorch中, torch.Tensor 是存储和变换数据的主要工具,比起NumPy更适合深度学习。

1.1 🛠️Tensor 的核心用途(5大场景)

1.2 表示现实世界数据|输入数据

现实世界常见数据的Tensor示例如下:

数据类型 Tensor形状示例 说明
图像 (3, 224, 224) RGB 三通道图像
批量图像 (64, 3, 224, 224) 一次处理64张图
文本 (1, 512) 512个词的编码序列
音频 (1, 16000) 1秒音频(16kHz采样)
python 复制代码
# 图像转 Tensor(使用 torchvision)
from torchvision import transforms
transform = transforms.ToTensor()
image_tensor = transform(pil_image)  # PIL图像 → Tensor

1.3 表示标签(Labels / Targets)

可以告诉模型"正确答案"。

python 复制代码
# 分类任务:类别标签
labels = torch.tensor([3, 1, 7, 0])  # 4个样本的真实类别

# 回归任务:连续值
targets = torch.tensor([1.5, 2.3, 0.8])

# 语义分割:每个像素的类别
mask = torch.randint(0, 20, (1, 256, 256))  # (C,H,W)

1.4 存储模型参数(Weights & Biases)

神经网络的"记忆"就存在 Tensor 里。

python 复制代码
linear = torch.nn.Linear(10, 5)
print(linear.weight.shape)  # torch.Size([5, 10]) ← 这是个 Tensor!
print(linear.bias.shape)    # torch.Size([5])     ← 这也是 Tensor!

这些参数 Tensor 会:

  • 在训练中不断更新(梯度下降)
  • 决定模型的预测能力
  • 可以保存和加载(.pth 文件)

1.5 中间计算结果(Forward Pass)

网络每一层的输出都是 Tensor 。

python 复制代码
x = torch.randn(1, 784)          # 输入
w = torch.randn(784, 256)        # 权重
b = torch.zeros(256)             # 偏置

# 每一步都是 Tensor 运算
z = x @ w + b                    # 线性变换
a = torch.relu(z)                # 激活函数
# a 仍然是 Tensor,传给下一层

1.6 梯度计算(Backward Pass)

Autograd 用 Tensor 记录梯度 。

python 复制代码
x = torch.tensor(2.0, requires_grad=True)
y = x ** 2
y.backward()  # 计算 dy/dx
print(x.grad)  # tensor(4.) ← 梯度也存在 Tensor 中!

1.7 📚 常用 API 速查表

类别 API 说明 示例
创建 torch.tensor() 通用创建 x = torch.tensor([1,2,3])
torch.randn() 随机正态分布 x = torch.randn(2,3)
torch.zeros() 全零张量 x = torch.zeros(4,4)
转换 .numpy() → NumPy 数组 arr = x.numpy()
torch.from_numpy() ← NumPy 数组 x = torch.from_numpy(arr)
.to('cuda') → GPU x_gpu = x.to('cuda')
运算 +,-,*,/ 基础运算 z = x + y
torch.matmul() 矩阵乘法 z = torch.matmul(x,y)
x.view() 形状变换 x_2d = x.view(2,3)
自动求导 .requires_grad_() 开启梯度追踪 x.requires_grad_()
.detach() 剥离梯度计算 x_no_grad = x.detach()

1.8 💡 Tensor 演示

python 复制代码
import torch  
  
# 1. 创建张量(开启梯度追踪)  
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)  
print(f"初始张量: {x} | 是否追踪梯度: {x.requires_grad}")  
  
# 2. 张量运算(自动构建计算图)  
y = x * 2 + 3  
z = y.mean()  
print(f"中间结果 y: {y} | 最终结果 z: {z}")  
  
# 3. 自动求导(反向传播)  
z.backward()  # 计算 dz/dxprint(f"梯度 dz/dx: {x.grad}")  # 输出: [0.6667, 0.6667, 0.6667]  
  
# 4. GPU 加速演示  
if torch.cuda.is_available():  
    x_gpu = x.to('cuda')  # 一键迁移到GPU  
    print(f"GPU张量: {x_gpu} | 位于设备: {x_gpu.device}")

2 Module

让我们思考一个问题,我们要怎么定义一个神经网络的"结构"和"行为"呢?

通过 nn.Module,它是PyTorch中所有神经网络模块的基类。你可以把它想象成一个可以"思考"的容器,它不仅存储网络的参数(如权重和偏置),还定义了数据应该如何流动(前向传播)。

本质 :所有神经网络层的基类,管理参数 + 定义计算流程
核心forward() 方法定义数据流动逻辑

2.1 简介

在PyTorch中,torch.nn.Module 是构建神经网络的核心抽象。无论是简单的线性层 nn.Linear,还是复杂的ResNet模型,它们都是 nn.Module 的子类。通过继承 nn.Module,我们可以轻松地定义自己的神经网络。

经过本节的学习,你将收获:

  • nn.Module 的简介
  • 如何使用 nn.Sequential 快速构建模型
  • 如何自定义 nn.Module 子类来创建复杂模型
  • nn.Module 的核心操作(参数管理、设备迁移)

2.2 创建Module

在接下来的内容中,我们将介绍几种常见的创建 Module 的方法。

  • 使用 nn.Sequential 构建模型
    nn.Sequential 是一个有序的容器,它将传入的模块按顺序组合起来。对于不需要复杂逻辑的"直筒型"网络,这是最简单的方法。

    python 复制代码
    import torch.nn as nn
    
    # 定义一个简单的多层感知机 (MLP)
    model = nn.Sequential(
        nn.Flatten(),                    # 将图像展平成一维向量
        nn.Linear(28*28, 128),           # 全连接层 (输入784 -> 输出128)
        nn.ReLU(),                       # 激活函数
        nn.Linear(128, 10),              # 输出层 (输入128 -> 输出10类)
        nn.Softmax(dim=1)                # 输出概率分布
    )
    print(model)
    复制代码
    Sequential(
      (0): Flatten(start_dim=1, end_dim=-1)
      (1): Linear(in_features=784, out_features=128, bias=True)
      (2): ReLU()
      (3): Linear(in_features=128, out_features=10, bias=True)
      (4): Softmax(dim=1)
    )
  • 自定义 nn.Module 子类

    对于更复杂的网络结构(如包含分支、残差连接等),我们需要创建自己的类,继承 nn.Module

    python 复制代码
    import torch.nn as nn
    
    class SimpleMLP(nn.Module):
        def __init__(self):
            super().__init__()
            # 在 __init__ 中定义网络层
            self.flatten = nn.Flatten()
            self.fc1 = nn.Linear(28*28, 128)
            self.relu = nn.ReLU()
            self.fc2 = nn.Linear(128, 10)
        
        def forward(self, x):
            # 在 forward 中定义数据流动的逻辑
            x = self.flatten(x)
            x = self.fc1(x)
            x = self.relu(x)
            x = self.fc2(x)
            return x
    
    # 实例化模型
    model = SimpleMLP()
    print(model)
    复制代码
    SimpleMLP(
      (flatten): Flatten(start_dim=1, end_dim=-1)
      (fc1): Linear(in_features=784, out_features=128, bias=True)
      (relu): ReLU()
      (fc2): Linear(in_features=128, out_features=10, bias=True)
    )

一个标准的 nn.Module 子类有两个必须理解的方法。

  1. __init__ 方法:
    初始化网络层,并将它们作为类的属性,在这里定义参数。
  2. forward 方法:
    接收输入 x(一个Tensor),然后执行一系列计算并返回输出(也是一个Tensor)。
    不需要手动调用 forward,执行model实例是,PyTorch 会自动调用它的forward方法。

2.3 Module 的核心操作

在接下来的内容中,我们将介绍 Module 的几个关键操作。

  • 参数管理
    nn.Module 会自动将 nn.Parameter 或任何 nn.Module 子类的实例注册为模型的参数。我们可以使用以下方法访问它们。

    python 复制代码
    # 获取模型的所有参数
    for param in model.parameters():
        print(param.shape)
    
    # 获取模型的命名参数
    for name, param in model.named_parameters():
        print(f"{name}: {param.shape}")
    复制代码
    flatten._parameters: {} # Flatten层无参数
    fc1.weight: torch.Size([128, 784])
    fc1.bias: torch.Size([128])
    fc2.weight: torch.Size([10, 128])
    fc2.bias: torch.Size([10])
  • 设备迁移

    为了利用GPU加速,我们可以使用 .to(device) 方法将整个模型(包括其所有参数和子模块)迁移到指定设备。

    python 复制代码
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    model = model.to(device)  # 一键迁移到GPU
    print(f"模型设备: {next(model.parameters()).device}")
  • 状态保存与加载

    我们可以使用 state_dict() 来获取模型参数的字典,这非常适合保存和加载训练好的模型。

    python 复制代码
    # 保存模型
    torch.save(model.state_dict(), 'simple_mlp.pth')
    
    # 加载模型 (需要先创建相同结构的模型)
    new_model = SimpleMLP()
    new_model.load_state_dict(torch.load('simple_mlp.pth'))
    new_model.eval() # 切换到评估模式
  • 训练/评估模式切换

    一些层(如 Dropout, BatchNorm)在训练和评估时的行为不同。我们可以使用 model.train()model.eval() 来切换模式。

    python 复制代码
    model.train()  # 启用Dropout等训练专用操作
    # ... 训练代码 ...
    
    model.eval()   # 禁用Dropout,使用BatchNorm的统计值
    # ... 推理代码 ...
    with torch.no_grad():  # 通常与 no_grad() 一起使用
        output = model(input_tensor)

3 Autograd

让我们思考一个问题,我们要怎么让模型"学会"调整自己的参数呢?

通过 autograd,它是PyTorch的自动微分引擎。它会默默地记录我们对张量进行的所有操作,构建一个"计算图",然后在需要时自动计算梯度,使得反向传播变得极其简单。

3.1 简介

在深度学习中,我们通过反向传播算法来更新模型参数。手动计算梯度不仅繁琐而且容易出错。PyTorch的 autograd 包可以自动完成这一过程。

autograd 的核心是 torch.Tensortorch.Function

Tensorrequires_grad 属性为 True 时,autograd 会追踪所有作用于该张量的操作

。一旦计算完成,调用 .backward() 方法,autograd 就会自动计算所有梯度,并将它们累积到 .grad 属性中。

经过本节的学习,你将收获:

  • autograd 的工作原理
  • 如何使用 requires_grad 控制梯度追踪
  • 如何执行反向传播并查看梯度
  • 如何使用 torch.no_grad() 上下文管理器

3.2 梯度追踪

  • 开启梯度追踪

    通过设置 requires_grad=True,我们可以告诉 autograd 需要追踪对这个张量的所有操作。

    python 复制代码
    import torch
    
    # 方法1: 创建时指定
    x = torch.tensor([1., 2., 3.], requires_grad=True)
    print(f"x.requires_grad: {x.requires_grad}")
    
    # 方法2: 对已有张量启用
    y = torch.tensor([4., 5., 6.])
    y.requires_grad_(True) # 注意是方法,带下划线
    print(f"y.requires_grad: {y.requires_grad}")
  • 构建计算图

    一旦开启了梯度追踪,后续的运算都会被记录下来。

    python 复制代码
    z = x * y + 2
    print(f"z.grad_fn: {z.grad_fn}")  # <AddBackward0 object>
    print(f"z's creator: {z.grad_fn}") 

3.3 反向传播

  • 执行反向传播

    调用 .backward() 方法来触发反向传播。如果 loss 是一个标量(0维张量),可以直接调用 loss.backward()

    python 复制代码
    loss = z.sum() # loss 是一个标量
    print(f"loss: {loss}") # tensor(27., grad_fn=<SumBackward0>)
    
    loss.backward() # 计算梯度
    print(f"dx/dloss: {x.grad}") # tensor([4., 5., 6.])
    print(f"dy/dloss: {y.grad}") # tensor([1., 2., 3.])

    注意.backward()累积 梯度。在下一次迭代前,务必使用 optimizer.zero_grad() 或手动清零 x.grad.zero_()

  • 非标量输出

    如果要对非标量张量调用 .backward(),需要传入一个 gradient 参数(形状与该张量相同),作为外部梯度。

    python 复制代码
    v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
    z.backward(gradient=v) # 这里会累加梯度
    # x.grad 会增加 v * y 的值

3.4 禁用梯度计算

在模型推理(inference)或某些不需要梯度的计算中,我们可以使用 torch.no_grad() 上下文管理器来临时禁用梯度计算,这可以节省内存并加快计算速度。

python 复制代码
print(f"Before no_grad, x.requires_grad: {x.requires_grad}")

with torch.no_grad():
    a = x * 2
    print(f"Inside no_grad, a.requires_grad: {a.requires_grad}") # False

print(f"After no_grad, x.requires_grad: {x.requires_grad}") # 仍然是 True

# 另一种方式:使用 .detach()
b = x.detach() * 3
print(f"b.requires_grad: {b.requires_grad}") # False

4 📌 三件套关系图解

复制代码
  ┌─────────────┐      ┌───────────┐      ┌─────────────┐
  │             │      │           │      │             │
  │   Tensor    │─────▶│  Module   │─────▶│   Autograd  │
  │ (数据载体)  │◀─────│ (计算逻辑)│◀─────│ (梯度引擎)  │
  └─────────────┘      └───────────┘      └─────────────┘
        ▲                      │                   │
        │                      ▼                   ▼
  ┌─────┴──────┐        ┌─────────────┐     ┌─────────────┐
  │ 数据加载   │        │ 模型定义    │     │ 反向传播    │
  │ DataLoader │        │ forward()   │     │ loss.backward() 
  └────────────┘        └─────────────┘     └─────────────┘

关键口诀
"Tensor 存数据,Module 定流程,Autograd 算梯度,三件套合训练成"

5 🌟 三件套协同工作流程(完整训练示例)

资料:https://zh-v2.d2l.ai/chapter_multilayer-perceptrons/mlp-scratch.html

5.1 Demo:三件套简单串联

python 复制代码
import torch
import torch.nn as nn
import torch.optim as optim

# 1. Tensor:准备数据
x = torch.randn(64, 1, 28, 28)  # 64张28x28灰度图
y_true = torch.randint(0, 10, (64,))  # 10分类标签

# 2. Module:创建模型
model = nn.Sequential(
    nn.Flatten(),
    nn.Linear(28*28, 128),
    nn.ReLU(),
    nn.Linear(128, 10)
)

# 3. Autograd:训练循环
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 前向传播 → 计算损失 → 反向传播 → 更新参数
optimizer.zero_grad()         # 清零梯度(Autograd)
logits = model(x)             # 前向传播(Module)
loss = criterion(logits, y_true)  # 计算损失(Tensor)
loss.backward()               # 反向传播(Autograd)
optimizer.step()              # 更新参数(Tensor)

print(f"训练完成!损失值: {loss.item():.4f}")

5.2 构建一个简单线性回归模型 y=wx+b

我们将构建一个简单的线性回归模型 y = wx + b,并模拟一个真实的训练过程。

  1. 创建一个简单的线性模型(nn.Module)。
  2. 使用真实数据和模型预测来手动计算损失(均方误差)。
  3. 调用 loss.backward() 触发自动微分。
  4. 观察 模型参数(权重 w 和偏置 b)的 .grad 属性,看到梯度的产生。
python 复制代码
import torch
import torch.nn as nn

# ----------------------------------------
# 第一步:准备虚拟数据 (y = 3x + 2)
# ----------------------------------------
# 我们假设真实的世界关系是 y = 3x + 2
# 我们生成一些带点"噪声"的数据来模拟真实情况

# 创建输入 x (形状: [5, 1])
x = torch.tensor([[1.0], [2.0], [3.0], [4.0], [5.0]])  # 5个样本
print("输入 x:")
print(x)

# 创建真实标签 y_true (形状: [5, 1])
true_w = 3.0
true_b = 2.0
y_true = true_w * x + true_b + torch.randn_like(x) * 0.1  # 加点小噪声
print("\n真实标签 y_true (y ≈ 3x + 2):")
print(y_true)

# ----------------------------------------
# 第二步:定义模型 (nn.Module)
# ----------------------------------------
# 我们创建一个非常简单的模型,它就是一个线性层

class SimpleLinear(nn.Module):
    def __init__(self):
        super().__init__()
        # nn.Linear(in_features, out_features) -> y = Wx + b
        # 因为我们只有一个输入和一个输出,所以是 Linear(1, 1)
        self.linear = nn.Linear(1, 1)
    
    def forward(self, x):
        return self.linear(x)

# 实例化模型
model = SimpleLinear()
print("\n模型结构:")
print(model)

# 打印初始参数 (模型刚开始是"瞎猜"的)
print("\n训练前的模型参数:")
print(f"权重 w: {model.linear.weight.item():.3f}")  # .item() 取出单个数值
print(f"偏置 b: {model.linear.bias.item():.3f}")
# 你会发现初始的 w 和 b 是随机的,和 3.0, 2.0 差很远

# ----------------------------------------
# 第三步:前向传播 (Forward Pass)
# ----------------------------------------
# 让模型对输入 x 进行预测

model.train()  # 确保模型在训练模式
y_pred = model(x)  # 调用 forward 方法

print("\n模型预测 y_pred (训练前):")
print(y_pred)

# ----------------------------------------
# 第四步:手动计算损失 (Loss)
# ----------------------------------------
# 我们使用最简单的均方误差 (MSE) 损失
# loss = (1/N) * Σ (y_true - y_pred)²

# 手动计算损失
loss = torch.mean((y_true - y_pred) ** 2)

print(f"\n训练前的损失 (MSE): {loss.item():.4f}")

# ----------------------------------------
# 第五步:反向传播 (Backward Pass) 和 观察梯度
# ----------------------------------------
# 这是最关键的一步!
# 在反向传播之前,必须先将梯度清零,否则梯度会累积
model.zero_grad()  # 等价于 optimizer.zero_grad()

# 执行反向传播
loss.backward()

# 🔥 现在,我们来"亲眼"看看梯度产生了!
print("\n" + "="*50)
print("调用 loss.backward() 后,模型参数的梯度:")
print("="*50)

# 查看权重 w 的梯度
w_grad = model.linear.weight.grad
print(f"权重 w 的梯度 (.grad): {w_grad.item():.4f}")
# 这个梯度告诉我们:为了减小损失,w 应该增加还是减少?

# 查看偏置 b 的梯度
b_grad = model.linear.bias.grad
print(f"偏置 b 的梯度 (.grad): {b_grad.item():.4f}")
# 同理,这个梯度指导 b 如何更新

# ----------------------------------------
# 第六步:(可选)更新参数
# ----------------------------------------
# 虽然练习目标不包括更新,但我们可以手动模拟一下
# 这就是优化器(如SGD)做的事情:参数 = 参数 - 学习率 * 梯度

learning_rate = 0.01

# 手动更新权重
with torch.no_grad():  # 更新参数时不需要记录梯度
    model.linear.weight -= learning_rate * w_grad
    model.linear.bias -= learning_rate * b_grad

print("\n" + "="*50)
print("一次梯度下降更新后的模型参数:")
print("="*50)
print(f"更新后的权重 w: {model.linear.weight.item():.3f}")
print(f"更新后的偏置 b: {model.linear.bias.item():.3f}")
print("现在参数更接近真实的 3.0 和 2.0 了吗?")

在这个Demo中,三大件协作如下:

txt 复制代码
          (数据)
            ↓
        ┌─────────────┐
        │    Tensor   │ ← 存放 x, y, w, b 这些数字
        └─────────────┘
               ↓ (输入)
        ┌─────────────┐
        │   Module    │ ← 定义 y = w*x + b 这个计算公式
        └─────────────┘
               ↓ (输出预测 y_pred)
        ┌─────────────┐
        │  计算损失    │ ← 比较 y_pred 和 真实 y 的差距
        └─────────────┘
               ↓ (标量 loss)
        ┌─────────────┐
        │  Autograd   │ ← 调用 loss.backward(),自动算出 w 和 b 的梯度
        └─────────────┘
               ↓ (梯度 dw, db)
        ┌─────────────┐
        │  优化器     │ ← 用梯度更新 w 和 b,让它们更接近真实值
        └─────────────┘

5.3 Demo:搭建一个简单的多层感知机

Transformer模型中的"前馈网络(Feed-Forward Network)"部分,其本质就是一个多层感知机(MLP)。

让我们仿照教程,搭建搭建一个简单的多层感知机:手写数字分类MLP。

python 复制代码
import torch  
import torch.nn.functional as F  
from torchvision import datasets, transforms  
from torch.utils.data import DataLoader  
import numpy as np  
  
  
# =============================================================================  
# 1. Tensor:数据的容器  
# =============================================================================  
"""  
Tensor 是 PyTorch 中存储和操作数据的核心结构。  
它不仅是多维数组,还能追踪梯度,是深度学习的"通用货币"。  
"""  
  
# 演示:Tensor 的基本用途  
print("=== 1. Tensor 演示 ===")  
  
# 创建一个需要梯度追踪的张量  
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)  
print(f"初始张量: {x} | 追踪梯度: {x.requires_grad}")  
  
# 执行运算(构建计算图)  
y = x * 2 + 3  
z = y.mean()  
print(f"中间结果 y: {y} | 最终标量 z: {z}")  
  
# 反向传播  
z.backward()  
print(f"梯度 dz/dx: {x.grad}")  # 应为 [2/3, 2/3, 2/3]  
# GPU 加速(如果可用)  
if torch.cuda.is_available():  
    x_gpu = x.to('cuda')  
    print(f"GPU张量: {x_gpu} | 设备: {x_gpu.device}")  
else:  
    print("GPU不可用,使用CPU")  
  
  
# =============================================================================  
# 2. Module:计算逻辑的蓝图(手动实现)  
# =============================================================================  
"""  
我们不使用 nn.Module 和 nn.Linear,  
而是手动创建权重和偏置,并定义前向传播函数。  
"""  
  
print("\n=== 2. 手动实现 MLP(无 nn.Module / nn.Linear)===")  
  
# 超参数  
input_size = 784   # 28x28 图像展平  
hidden_size = 128  # 隐藏层大小
output_size = 10   # MNIST 10 类  
learning_rate = 0.01  # 学习率
num_epochs = 3  # 训练轮数
  
# 手动初始化参数(替代 nn.Linear)  
torch.manual_seed(42)  
W1 = torch.randn(input_size, hidden_size) * 0.01  
b1 = torch.zeros(hidden_size)  
W2 = torch.randn(hidden_size, output_size) * 0.01  
b2 = torch.zeros(output_size)  
  
# 开启梯度追踪  
W1.requires_grad_(True)  
b1.requires_grad_(True)  
W2.requires_grad_(True)  
b2.requires_grad_(True)  
  
print(f"W1: {W1.shape}, b1: {b1.shape}")  
print(f"W2: {W2.shape}, b2: {b2.shape}")  
  
  
# 定义前向传播函数(替代 forward)  
def forward(x):  
    """  
    手动实现前向传播  
    :param x: 输入张量 (B, 784)    :return: logits (B, 10)    """    z1 = x @ W1 + b1      # 第一层线性变换  
    a1 = F.relu(z1)       # 激活函数  
    z2 = a1 @ W2 + b2     # 第二层线性变换  
    return z2  
  
  
# =============================================================================  
# 3. Autograd:自动微分引擎  
# =============================================================================  
"""  
Autograd 会自动追踪所有操作,调用 loss.backward() 即可计算梯度。  
我们手动实现训练循环,替代 optimizer.step() 和 optimizer.zero_grad()"""  
  
print("\n=== 3. 数据加载与训练 ===")  
  
# 数据预处理  
transform = transforms.ToTensor()  
  
# 加载 MNIST 数据集  
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)  
test_dataset = datasets.MNIST(root='./data', train=False, transform=transform)  
  
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)  
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)  
  
print(f"训练集大小: {len(train_dataset)}")  
print(f"测试集大小: {len(test_dataset)}")  
  
# 训练循环  
train_losses = []  
  
for epoch in range(num_epochs):  
    epoch_loss = 0.0  
    count = 0  
  
    for x_batch, y_batch in train_loader:  
        # 展平图像: (B, 1, 28, 28) -> (B, 784)  
        x_batch = x_batch.view(x_batch.size(0), -1)  
  
        # 前向传播  
        logits = forward(x_batch)  
        loss = F.cross_entropy(logits, y_batch)  # 使用函数式 API  
        # 反向传播  
        loss.backward()  
  
        # 手动更新参数(替代 optimizer.step())  
        with torch.no_grad():  
            W1 -= learning_rate * W1.grad  
            b1 -= learning_rate * b1.grad  
            W2 -= learning_rate * W2.grad  
            b2 -= learning_rate * b2.grad  
  
        # 清零梯度(替代 optimizer.zero_grad())  
        W1.grad.zero_()  
        b1.grad.zero_()  
        W2.grad.zero_()  
        b2.grad.zero_()  
  
        epoch_loss += loss.item()  
        count += 1  
  
    avg_loss = epoch_loss / count  
    train_losses.append(avg_loss)  
    print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}")  
  
  
# =============================================================================  
# 4. 模型评估  
# =============================================================================  
print("\n=== 4. 模型评估 ===")  
  
correct = 0  
total = 0  
with torch.no_grad():  # 推理时关闭梯度  
    for x_batch, y_batch in test_loader:  
        x_batch = x_batch.view(x_batch.size(0), -1)  
        logits = forward(x_batch)  
        _, predicted = torch.max(logits, 1)  
        total += y_batch.size(0)  
        correct += (predicted == y_batch).sum().item()  
  
accuracy = 100 * correct / total  
print(f"测试准确率: {accuracy:.2f}%")  
  
  
# =============================================================================  
# 5. Demo:线性回归(观察梯度生成过程)  
# =============================================================================  
print("\n=== 5. 线性回归 Demo:观察梯度如何生成 ===")  
  
# 生成虚拟数据:y = 3x + 2 + noise  
x_reg = torch.tensor([[1.0], [2.0], [3.0], [4.0], [5.0]])  
true_w, true_b = 3.0, 2.0  
y_true = true_w * x_reg + true_b + torch.randn_like(x_reg) * 0.1  
  
# 初始化参数  
w = torch.randn(1, 1, requires_grad=True)  
b = torch.zeros(1, requires_grad=True)  
  
print(f"真实参数: w={true_w}, b={true_b}")  
print(f"初始参数: w={w.item():.3f}, b={b.item():.3f}")  
  
# 前向传播  
y_pred = x_reg @ w + b  
loss = F.mse_loss(y_pred, y_true)  
  
# 反向传播  
loss.backward()  
  
print(f"\n调用 loss.backward() 后:")  
print(f"权重 w 的梯度: {w.grad.item():.4f}")  
print(f"偏置 b 的梯度: {b.grad.item():.4f}")  
  
print("\n梯度方向正确:w 的梯度为正,说明当前 w 偏小,应增大")  
  
  
# =============================================================================  
# 6. 总结口诀  
# =============================================================================  
print("\n" + "="*60)  
print("🎯 PyTorch 三件套核心口诀")  
print("="*60)  
print("Tensor 存数据,Module 定流程,Autograd 算梯度")  
print("三件套合训练成,手动实现才真懂!")  
print("="*60)

概念补充:

  • 超参数:一般指一些模型的配置参数,想当于模型的学习规则。
超参数 说明 为什么重要
学习率 (Learning Rate, lr) 每次更新参数时的"步长" 太大:学得快但可能错过最优解 太小:学得慢,可能卡住
批次大小 (Batch Size) 一次训练使用的样本数量 太大:内存压力大,泛化可能差 太小:训练不稳定
训练轮数 (Epochs) 整个数据集被完整遍历的次数 太少:欠拟合 太多:过拟合(死记硬背)
隐藏层大小 (Hidden Size) 神经网络中隐藏层的神经元数量 决定了模型的"容量" 太大:容易过拟合 太小:无法学习复杂模式
网络层数 (Number of Layers) 有多少个隐藏层 层数多 → "深度网络",能学更复杂特征,但也更难训练
优化器类型 使用 SGD、Adam 还是 RMSprop 不同优化器收敛速度和稳定性不同
正则化参数 如 Dropout 概率、L2 权重衰减 用于防止过拟合

刻意练习

Q:如何给MLP增加一个隐藏层?

相关推荐
程序猿2023几秒前
Python每日一练---第十二天:验证回文串
开发语言·python
文心快码 Baidu Comate1 分钟前
双十一将至,用Rules玩转电商场景提效
人工智能·ai编程·文心快码·智能编程助手·comate ai ide
沿着路走到底5 分钟前
python 判断与循环
java·前端·python
瞻邈8 分钟前
LION运行笔记
人工智能·深度学习
Serverless 社区31 分钟前
助力企业构建 AI 原生应用,函数计算FunctionAI 重塑模型服务与 Agent 全栈生态
大数据·人工智能
大千AI助手32 分钟前
参考先验(Reference Priors)详解:理论与Python实践
人工智能·机器学习·贝叶斯·大千ai助手·参考先验·贝叶斯推断·先验
Baihai_IDP35 分钟前
面向 LLM 的 GPU 系统工程方法论
人工智能·面试·gpu
北京耐用通信1 小时前
冶金车间“迷雾”重重?耐达讯自动化Profibus转光纤为HMI点亮“透视眼”!
人工智能·物联网·网络协议·网络安全·自动化
xqlily1 小时前
Prover9/Mace4 的形式化语言简介
人工智能·算法
IT_陈寒1 小时前
Redis 高并发实战:我从 5000QPS 优化到 5W+ 的7个核心策略
前端·人工智能·后端