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增加一个隐藏层?

相关推荐
shuju_dajiwang3 分钟前
数据大集网:重构企业贷获客生态的线上获客新范式
人工智能
day>day>up14 分钟前
django uwsgi启动报错failed to get the Python codec of the filesystem encoding
后端·python·django
Sunhen_Qiletian32 分钟前
《深入浅出K-means算法:从原理到实战全解析》预告(提纲)
人工智能·机器学习·支持向量机
Shun_Tianyou40 分钟前
Python Day25 进程与网络编程
开发语言·网络·数据结构·python·算法
Giser探索家1 小时前
什么是2米分辨率卫星影像数据?
大数据·人工智能·数码相机·算法·分类·云计算
芯希望1 小时前
芯伯乐XBL6019 60V/5A DC-DC升压芯片的优质选择
大数据·人工智能·物联网·dc-dc·电子元器件·电源管理ic·xblw芯伯乐
科大饭桶2 小时前
AI大模型专题:LLM大模型(Prompt提示词工程)
人工智能·语言模型·llm·prompt·deepseek
都叫我大帅哥2 小时前
LangGraph条件判断:让AI工作流"聪明"起来
python·langchain
六毛的毛2 小时前
LangChain入门:内存、记录聊天历史 ChatMessageHistory、模型、提示 ( Prompt )、模式 ( Schema )
人工智能·langchain·prompt
饭碗、碗碗香2 小时前
【Dify学习笔记】:Dify搭建表单信息提交系统
人工智能·笔记·学习·ai