PyTorch快速入门

 前言

        参考:快速入门 — PyTorch Tutorials 2.6.0+cu124 文档 - PyTorch 深度学习库

一、张量

        参考:张量 — PyTorch 教程 2.6.0+cu124 文档 - PyTorch 深度学习库

        加速器

        默认情况下,张量在 CPU 上创建。我们需要使用 .to 方法显式地将张量移动到加速器(在检查加速器可用性之后)(加速器如 CUDA、MPS、MTIA 或 XPU)。请记住,跨设备复制大型张量在时间和内存方面可能非常昂贵!

        注意: MTIA(Meta 自研加速器)和 XPU(英特尔 GPU 加速器)目前不是 PyTorch 官方的标准支持目标,需要依赖厂商提供的扩展或自定义构建。

import torch

# 判断设备:CUDA > MPS > CPU
if torch.cuda.is_available():
    device = torch.device("cuda")  # 使用 NVIDIA GPU
elif torch.backends.mps.is_available():
    device = torch.device("mps")   # 使用 Apple M1/M2 的 GPU
else:
    device = torch.device("cpu")   # 否则使用 CPU

# 将张量移动到该设备上
tensor = tensor.to(device)
        算术运算
import torch

tensor = torch.rand(3, 3)  # 示例张量

# -------------------------------
# ✅ 矩阵乘法(matrix multiplication)
# -------------------------------
# 说明:矩阵乘法是线性代数意义下的“行乘列”操作,要求维度满足矩阵乘法法则。
y1 = tensor @ tensor.T                  # 方法1:使用 @ 运算符,等同于矩阵乘法 tensor × tensor.T
y2 = tensor.matmul(tensor.T)            # 方法2:使用 .matmul() 函数
y3 = torch.rand_like(y1)         
torch.matmul(tensor, tensor.T, out=y3)  # 方法3:将结果保存到预定义的张量中,节省内存开销



# -------------------------------
# ✅ 逐元素乘法(element-wise product)
# -------------------------------
# 说明:逐元素乘法要求两个张量形状相同或可广播,对每个位置的元素分别相乘。
z1 = tensor * tensor               # 方法1:使用 * 运算符,对应位置相乘(逐元素)
z2 = tensor.mul(tensor)            # 方法2:使用 .mul() 方法
z3 = torch.rand_like(tensor)
torch.mul(tensor, tensor, out=z3)  # 方法3:将结果保存到已有张量中,避免新分配内存


二、数据集 & DataLoaders

        参考:Datasets & DataLoaders — PyTorch Tutorials 2.6.0+cu124 文档 - PyTorch 深度学习库

        数据集 — Torchvision 0.21 文档 - PyTorch 深度学习库

        PyTorch 提供了两个数据处理基础组件:torch.utils.data.Datasettorch.utils.data.DataLoader,它们可以用于加载内置数据集自定义数据

  • Dataset(数据集类):用于存储样本及其对应的标签。你可以使用已有的数据集(如MNIST),也可以自定义这个类来处理你自己的数据 (自定义 Dataset 类必须实现三个函数:__init__、__len__ 和 __getitem__)。

  • DataLoader(数据加载器):为 Dataset 提供一个可迭代对象(iterable),方便你批量(batch)地加载数据,还支持打乱(shuffle)、多线程加载(num_workers)等功能,使训练过程更加高效。

#----- 1.加载内置的 Fashion-MNIST 数据集 -----#
from torchvision import datasets
from torchvision.transforms import ToTensor

"""
FashionMNIST(): 初始化 Fashion-MNIST 数据集。

参数:
    root (str 或 pathlib.Path): 
        数据集的根目录,数据会被下载并保存在该目录中。

    train (bool, optional): 
        如果为 True,则加载训练集(train-images-idx3-ubyte);
        如果为 False,则加载测试集(t10k-images-idx3-ubyte)。
        默认为 True。

    download (bool, optional): 
        如果为 True,则从互联网上下载数据集并将其保存到 root 指定的目录中;
        如果数据已经存在,则不会重新下载。
        默认为 False。

    transform (callable, optional): 
        用于对输入的 PIL 图像进行转换的函数或转换对象(如 transforms.ToTensor、transforms.Normalize)。
        默认为 None。

    target_transform (callable, optional): 
        用于对目标标签进行转换的函数或转换对象。
        默认为 None。

返回:
    dataset (torch.utils.data.Dataset): 
        返回一个包含 FashionMNIST 数据集的 Dataset 对象,包含训练集或测试集的图像和标签。
        该对象支持索引(获取某个样本),以及通过 DataLoader 进行批量加载。
"""

training_data = datasets.FashionMNIST(
    root="data",
    train=True,
    download=True,
    transform=ToTensor()
)

test_data = datasets.FashionMNIST(
    root="data",
    train=False,
    download=True,
    transform=ToTensor()
)




#----- 2.从数据集中随机选取样本并可视化 -----#
import torch
import matplotlib.pyplot as plt

# 标签映射字典,将数字标签转换为对应的服饰类别名称
labels_map = {
    0: "T-Shirt",
    1: "Trouser",
    2: "Pullover",
    3: "Dress",
    4: "Coat",
    5: "Sandal",
    6: "Shirt",
    7: "Sneaker",
    8: "Bag",
    9: "Ankle Boot",
}

# 创建一个画布,设置图像尺寸(宽8英寸,高8英寸)
figure = plt.figure(figsize=(8, 8))
# 设置要显示的行数和列数(3x3,共9张图片)
cols, rows = 3, 3

# 循环 9 次,每次显示一张图像
for i in range(1, cols * rows + 1):
    # 从 training_data 中随机选取一个样本索引
    sample_idx = torch.randint(len(training_data), size=(1,)).item()
    
    # 根据索引获取图像和标签
    img, label = training_data[sample_idx]
    
    # 添加一个子图到当前画布上
    figure.add_subplot(rows, cols, i)
    
    # 设置图像标题为对应的类别名
    plt.title(labels_map[label])
    
    # 不显示坐标轴
    plt.axis("off")
    
    # 显示图像(去除通道维度,使用灰度显示)
    plt.imshow(img.squeeze(), cmap="gray")

# 展示整个图像网格
plt.show()




#----- 3.使用 DataLoaders 准备小批量数据以进行训练 -----#
from torch.utils.data import DataLoader
"""
DataLoader(): 创建一个用于批量加载数据的 PyTorch DataLoader。
参数:
    dataset (torch.utils.data.Dataset): 要加载的数据集,例如 torchvision.datasets.FashionMNIST。
    batch_size (int): 每个小批量的样本数量。默认值为 64。
    shuffle (bool): 是否在每个 epoch 开始时打乱数据顺序。默认为 True。
返回:
    DataLoader: 用于迭代访问数据集的小批量数据加载器。
"""
# 创建训练数据加载器
# - training_data:传入训练集(一个 Dataset 对象,如 FashionMNIST)
# - batch_size=64:每个小批量包含 64 个样本
# - shuffle=True:每个 epoch 开始时都会打乱数据,提高模型泛化能力
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)

# 创建测试数据加载器
# - test_data:传入测试集
# - 同样设置 batch_size=64 和 shuffle=True(可以根据需要设置为 False)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)

# 获取训练数据加载器的第一个批次
# 每次 iter(train_dataloader) 创建的都是一个新的迭代器,而且由于 shuffle=True,数据顺序是随机的,所以每次创建新的迭代器时都会重新打乱数据
train_features, train_labels = next(iter(train_dataloader))

# 输出批次的特征和标签的形状
print(f"Feature batch shape: {train_features.size()}")  # 特征的形状,例如 (64, 1, 28, 28),表示 64 张 28x28 的灰度图像
print(f"Labels batch shape: {train_labels.size()}")     # 标签的形状,例如 (64),表示 64 个标签

# 选择第一个图像,使用 squeeze() 去掉多余的维度,转为二维
img = train_features[0].squeeze()
# 选择对应标签
label = train_labels[0]
# print(f"张量的数据类型: {label.dtype}")                             # torch.int64
# print(f"转换后的数据类型: {type(label.item())}    Label: {label}")  # <class 'int'>
# print(f"转换后的数据类型: {type(int(label))}    Label: {label}")    # <class 'int'>
plt.title(labels_map[label.item()])

# 显示图像,cmap="gray" 使图像以灰度显示
plt.imshow(img, cmap="gray")
plt.show()




#----- 4.迭代 DataLoader,直到全部迭代完成 -----#

# 遍历整个训练集
# for train_features, train_labels in iter(train_dataloader):                       # 返回两个元素:(features, labels)
for batch_idx, (train_features, train_labels) in enumerate(iter(train_dataloader)): # 返回三个元素(batch_idx, (features, labels))
    """
    enumerate(): 用于将可迭代对象(如列表、DataLoader 等)转化为一个索引和值的迭代器。
    
    参数:
        iterable (iterable): 可迭代对象,例如列表、元组、DataLoader 等。
        start (int, optional): 索引的起始值,默认从 0 开始。
        
    返回:
        iterator: 生成索引和对应值的元组 (index, value)。
        
    用法:
        在循环中,`batch_idx` 会依次为当前批次的索引,`train_features` 和 `train_labels` 为对应的数据和标签。
    """
    # 输出每个小批量的特征和标签的形状
    print("------------------------------------------------------")
    print(f"Batch {batch_idx + 1}")
    print(f"Feature batch shape: {train_features.size()}")  # 特征的形状,例如 (64, 1, 28, 28)
    print(f"Labels batch shape: {train_labels.size()}")     # 标签的形状,例如 (64)
    
    # 选择当前批次中的第一个图像,使用 squeeze() 去掉多余的维度
    img = train_features[0].squeeze()
    # 选择对应标签
    label = train_labels[0]
    plt.title(labels_map[label.item()])
    
    # 显示图像,cmap="gray" 使图像以灰度显示
    plt.imshow(img, cmap="gray")
    plt.show()
    
    # 如果只想显示一定数量的批次(例如 3),可以在这里设置停止条件
    if batch_idx == 2:  # 停止条件:只显示前三个批次
        break

三、Transforms

        参考:Transforms — PyTorch 教程 2.6.0+cu124 文档 - PyTorch 深度学习库

        所有 TorchVision 数据集都有两个参数:

  • transform 用于修改特征
  • target_transform 用于修改标签

        它们接受包含转换逻辑的可调用对象。torchvision.transforms 模块提供了几个常用的开箱即用的 transforms。

        FashionMNIST 特征采用 PIL 图像格式,标签是整数。对于训练,我们需要将特征作为归一化张量,标签作为 one-hot 编码张量。为了进行这些转换,我们使用 ToTensor 和 Lambda

import torch
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda

ds = datasets.FashionMNIST(
    root="data",
    train=True,
    download=True,
    transform=ToTensor(), # 把图像转为张量(shape: [1, 28, 28])
    target_transform=Lambda(lambda y: torch.zeros(10, dtype=torch.float).scatter_(0, torch.tensor(y), value=1)) # 把标签转为 one-hot 编码(shape: [10])
)
ToTensor()

        ToTensor 将 PIL 图像或 NumPy ndarray 转换为 FloatTensor,并将图像的像素强度值缩放到 [0., 1.] 范围内

Lambda Transforms

        Lambda transforms 应用任何用户定义的 lambda 函数。在这里,我们定义一个函数将整数转换为 one-hot 编码张量。它首先创建一个大小为 10 的零张量(我们数据集中的标签数量),并调用 scatter_,它在由标签 y 给出的索引上分配 value=1

Lambda(lambda y: 
    torch.zeros(10, dtype=torch.float)           # 创建长度为10的零向量
    .scatter_(0, torch.tensor(y), value=1)       # 在第y个位置上设置为1
)

        例如标签 3 变成 One-Hot Encoding 如下:

tensor([0., 0., 0., 1., 0., 0., 0., 0., 0., 0.])

四、构建神经网络(Neural Network)

        参考:构建神经网络 — PyTorch Tutorials 2.6.0+cu124 文档 - PyTorch 深度学习库

        神经网络由执行数据操作的层(或模块)组成。torch.nn 命名空间提供了构建神经网络所需的所有基础组件。PyTorch 中的每个模块都是 nn.Module 的子类。神经网络本身也是一个模块,由其他模块(即各个层)构成。这种嵌套结构使得构建和管理复杂的网络架构变得更加容易。

        我们通过继承 nn.Module 来定义自己的神经网络,并在 __init__ 方法中初始化网络的各个层。每个 nn.Module 的子类都需要实现一个 forward 方法,用于定义输入数据在网络中的前向传播过程。

#----- 1.获取训练设备 -----#
import torch

# 判断设备:CUDA > MPS > CPU
if torch.cuda.is_available():
    device = torch.device("cuda")  # 使用 NVIDIA GPU
elif torch.backends.mps.is_available():
    device = torch.device("mps")   # 使用 Apple M1/M2 的 GPU
else:
    device = torch.device("cpu")   # 否则使用 CPU

print("device: ", device, "\n") # cuda




#----- 2.定义模型类 -----#
from torch import nn
"""
    nn.Linear(in_features, out_features, bias=True)

    作用:
        创建一个线性(全连接)层, 对输入应用线性变换: y = x @ w^T + b。

    参数:
        in_features (int):  输入张量的最后一维大小,即每个输入样本的特征数。
        out_features (int): 输出张量的最后一维大小,即每个输出样本的特征数。
        bias (bool, optional): 是否包含可学习的偏置项 b。默认值为 True。
        device (torch.device, optional): 创建参数张量的设备类型,如 'cpu' 或 'cuda'。默认使用当前默认设备。
        dtype (torch.dtype, optional): 创建参数张量的数据类型。默认使用 torch 的默认数据类型。

    输入形状(x):
        (*, in_features): 其中 * 表示任意批次维度,最后一维为输入特征数。即行为样本数,列为特征数。

    输出形状(y):
        (*, out_features): 保留输入的批次维度,将最后一维变为输出特征数。

    权重参数:
        weight (Tensor): 权重矩阵,形状为 (out_features, in_features)。初始化为均匀分布 U(-k, k), k = 1 / sqrt(in_features)。

    偏置参数:
        bias (Tensor): 偏置向量(一维行向量),形状为 (out_features),若 bias=True 则存在。初始化为均匀分布 U(-k, k), k = 1 / sqrt(in_features)。

    返回:
        torch.nn.Linear: 一个线性层模块,可用于神经网络中的前向传播。

    示例:
        >>> linear = nn.Linear(784, 256)
        >>> x = torch.randn(64, 784)
        >>> output = linear(x)  # 输出形状为 (64, 256)
"""

class NeuralNetwork(nn.Module):
    """
    NeuralNetwork 类定义了一个简单的前馈神经网络(FNN),用于处理 Fashion-MNIST 等 28x28 灰度图像分类任务。

    网络结构如下:
    - 输入层:将 28x28 图像展平为 784 维向量
    - 隐藏层1: Linear(784 -> 512) + ReLU 激活函数
    - 隐藏层2: Linear(512 -> 512) + ReLU 激活函数
    - 输出层: Linear(512 -> 10),输出 10 类的 logits(未规范化的logits, 不表示概率分布)

    该模型适用于分类任务,例如图像分类。
    """

    def __init__(self):
        """
        初始化神经网络的各层组件。

        参数:
            无需外部传入参数,结构在此定义固定。

        属性:
            flatten (nn.Flatten): 
                将二维图像 (1, 28, 28) 展平成一维向量 (784)。

            linear_relu_stack (nn.Sequential): 
                多层全连接网络,包含线性层和 ReLU 激活函数的组合。
                - Linear(784, 512)
                - ReLU()
                - Linear(512, 512)
                - ReLU()
                - Linear(512, 10)
        """
        super().__init__()
        self.flatten = nn.Flatten()              # nn.Flatten(start_dim=1, end_dim=-1):自动把形如 (B, C, H, W) 的张量展平为 (B, C*H*W),用于连接全连接层。
        self.linear_relu_stack = nn.Sequential(  # nn.Sequential():将多个层串联在一起。 当给定输入数据时,Sequential实例将数据传入到第一层, 然后将第一层的输出作为第二层的输入,以此类推。
            nn.Linear(28 * 28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        """
        定义前向传播过程。

        参数:
            x (Tensor): 输入张量,形状应为 (batch_size, Channel, Height, Width),表示 batch_size 张 28x28 的单通道图像。

        返回:
            logits (Tensor): 输出张量,形状为 (batch_size, 10),表示每张图像在 10 个类别上的预测得分。
        """
        x = self.flatten(x)  # 将输入展平为 (batch_size, 784)
        logits = self.linear_relu_stack(x)  # 通过多层感知机得到输出
        return logits




#----- 3.创建模型实例并使用模型进行推理 -----#
# 创建一个 NeuralNetwork 的实例,将其移动到 device上,并打印模型结构,显示每一层的详细信息
model = NeuralNetwork().to(device)
print("model: ", model, "\n")

# 输出模型的权重 w 和 偏置 b
# for name, param in model.named_parameters():
#     if "weight" in name:
#         print("weight shape: ", param.shape)     # torch.Size([out_features, in_features])
#     if "bias" in name:
#         print("bias shape: ", param.shape, "\n") # torch.Size([out_features]). 偏置b为一维向量,在广播机制中被当做行向量处理

# for name, param in model.named_parameters():
#     # param[:2] ①对于一维向量,取前两个元素。 ②对于二维矩阵,取前两行元素
#     print(f"Layer: {name}")
#     print(f"Size: {param.size()}")
#     # print(f"Values : {param[:2]} \n")

# 构造一个随机的输入张量,模拟一张 28x28 的灰度图像,输入形状为(*, in_features),即(1, 1*28*28)
X = torch.rand(1, 1, 28, 28, device=device) # (b, c, h, w)
# print(X)

# 将输入张量送入模型,推理得到未规范化的预测(logits,即线性输出,未经过 softmax 运算处理)
logits = model(X) # 推荐方式,内部会调用 forward 方法,并执行一些必要的后台操作。不要直接调用 model.forward()!
print("logits shape: ", logits.shape, "\n") # torch.Size([1, 10]). 输出形状为 (*, out_features)

# 使用 Softmax 将 logits 转换为概率分布
# dim=1 表示在类别维度(10 个类别)上进行 softmax。行为样本数,列为特征数。
pred_probab = nn.Softmax(dim=1)(logits)

# 取最大概率对应的索引作为预测类别
y_pred = pred_probab.argmax(dim=1)

# 输出预测的类别编号(范围为 0~9)
print(f"Predicted class: {y_pred}\n")

# # 保留1行
# m = nn.Softmax(dim=0)
# input = torch.randn(2, 3)
# output = m(input)
# print(output)

# # 保留1列,正确!
# m = nn.Softmax(dim=1)
# input = torch.randn(2, 3)
# output = m(input)
# print(output)
# ----- 4.模型层 -----#
import torch
from torch import nn

class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits


device = torch.device("cuda")
model = NeuralNetwork().to(device)



# 模型输入:3 张 28x28 的灰度图像,输入形状为(*, in_features),即(3, 1*28*28)
input_image = torch.rand(3,1,28,28)
print("----------模型输入----------")
print(input_image.size())

# nn.Flatten
flatten = nn.Flatten()
flat_image = flatten(input_image)
print("----------nn.Flatten----------")
print(flat_image.size())

# nn.Linear
layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print("----------nn.Linear----------")
print(hidden1.size())

# nn.ReLU
print("----------nn.ReLU----------")
print(f"Before ReLU: {hidden1}\n\n")
hidden1 = nn.ReLU()(hidden1)
print(f"After ReLU: {hidden1}")

# nn.Sequential
print("----------nn.Sequential----------")
seq_modules = nn.Sequential(
    flatten,
    layer1,
    nn.ReLU(),
    nn.Linear(20, 10)
)
input_image = torch.rand(3,1,28,28)
logits = seq_modules(input_image)
print(logits.shape)
print("logits: \n", logits)

# nn.Softmax
print("----------nn.Softmax----------")
softmax = nn.Softmax(dim=1)
pred_probab = softmax(logits)
print(pred_probab.shape)
print("pred_probab: \n", pred_probab)

# 模型参数
print("----------模型参数----------")
print(f"Model structure: {model}\n\n")

for name, param in model.named_parameters():
    # param[:2] ①对于一维向量,取前两个元素。 ②对于二维矩阵,取前两行元素
    print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \n")


五、使用 torch.autograd 进行自动微分

        参考:使用 torch.autograd 进行自动微分 — PyTorch Tutorials 2.6.0+cu124 文档 - PyTorch 深度学习库

        关于 pytorch中backward()函数 及其参数  gradient 可以参考相应链接。

计算图

        y=(a+b)(b+c) 可以构建如下计算图(参数a、b、c均被指定为叶子节点时):

# ----- 1.张量、函数和计算图 -----#
import torch

x = torch.ones(5)   # 输入张量
y = torch.zeros(3)  # 期望输出
w = torch.randn(5, 3, requires_grad=True) # requires_grad=True,需要计算梯度(用于自动求导)
b = torch.randn(3, requires_grad=True)    # requires_grad=True,需要计算梯度(用于自动求导)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y) # 二元交叉熵损失函数 loss = −y⋅log(σ(z))−(1−y)⋅log(1−σ(z))

        上述 pytorch 代码中的 计算图 如下图所示,计算图中的叶子节点(leaf nodes),即 Parameters 通过 requires_grad=True 指定!

注意:

  • 在 PyTorch 中,计算图是动态构建的(即“动态图”)。为了节省内存资源,每次完成一轮反向传播(即调用一次 backward())后,计算图会被立即释放
  • 因此,如果你需要对同一个计算图执行多次反向传播,就必须在第一次调用 backward() 时传入 retain_graph=True,以保留计算图结构,避免被释放。
  • 另外,出于性能优化的考虑,默认情况下一个计算图只能被反向传播一次,这是 PyTorch 的设计机制。
禁用梯度跟踪

        默认情况下,所有 requires_grad=True 的张量都会记录它们的计算历史,并支持梯度计算。然而在某些情况下,我们并不需要进行梯度追踪,例如:

  • 将神经网络中的某些参数标记为“冻结参数”(即这些参数在训练过程中不参与梯度更新);

  • 在只进行前向传播时加快计算速度,因为不追踪梯度的张量在计算时会更加高效。

        这时,我们可以通过将计算过程放在 torch.no_grad() 代码块中,来停止对计算的追踪,从而节省内存并加快推理速度。

# ----- 3.禁用梯度跟踪 -----#
z = torch.matmul(x, w)+b
print(z.requires_grad)

with torch.no_grad():
    z = torch.matmul(x, w)+b
print(z.requires_grad)


# 实现相同结果的另一种方式
z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)
计算图的更多内容

        从概念上讲,autograd 会记录数据(张量)以及所有执行过的操作(包括产生的新张量),并将这些信息组织成一个有向无环图(DAG, Directed Acyclic Graph),图中的节点是 Function 对象。

在这个计算图中:

  • 叶子节点(leaves)是输入张量;

  • 根节点(roots)是输出张量。

        通过从根节点向叶子节点反向追踪这张图,autograd 可以利用 链式法则(chain rule)自动计算出每个张量的梯度。


🔁 在前向传播时,autograd 同时做了两件事:

  1. 执行你请求的操作来计算输出张量

  2. 在计算图中记录该操作的梯度函数.grad_fn),以备后续反向传播使用。


🔁 当你在计算图的根节点上调用 .backward(),就启动了反向传播过程:

  • autograd 从每个 .grad_fn 开始计算梯度

  • 将计算结果累加到对应张量的 .grad 属性中

  • 通过链式法则,梯度会逐层传播到所有叶子张量。


⚠️ 注意:

PyTorch 中的计算图是动态的(Dynamic Graph)
这意味着:每次调用 .backward() 之后,原来的计算图就被销毁autograd 会在下一次前向传播时重新构建一张新的图。

正是由于这个“动态图”机制,你可以在模型中使用 if、for 等控制语句,在每一轮迭代中自由地改变张量的形状、大小或操作方式

        总的代码如下:

# ----- 1.张量、函数和计算图 -----#
import torch

x = torch.ones(5)   # 输入张量
y = torch.zeros(3)  # 期望输出
w = torch.randn(5, 3, requires_grad=True) # requires_grad=True,需要计算梯度(用于自动求导)
b = torch.randn(3, requires_grad=True)    # requires_grad=True,需要计算梯度(用于自动求导)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y) # 二元交叉熵损失函数 loss = −y⋅log(σ(z))−(1−y)⋅log(1−σ(z))

# .grad_fn 方法 是 PyTorch 中每个 由操作计算得到的 tensor 的公共属性,用于追踪这个 tensor 是通过哪种操作创建出来的
# AddBackward0, 说明 z 是通过一次 加法操作(AddBackward0) 得到的
print(f"Gradient function for z = {z.grad_fn}") 
# BinaryCrossEntropyWithLogitsBackward0, 说明 loss 是通过 binary_cross_entropy_with_logits 这个函数生成的,它本身是复合操作(sigmoid + BCE loss)
print(f"Gradient function for loss = {loss.grad_fn}") 




# ----- 2.计算梯度 -----#
loss.backward()
print(w.grad)
print(b.grad)




# ----- 3.禁用梯度跟踪 -----#
z = torch.matmul(x, w)+b
print(z.requires_grad)

with torch.no_grad():
    z = torch.matmul(x, w)+b
print(z.requires_grad)


# 实现相同结果的另一种方式
z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)





# ----- 4.计算图的更多内容 -----#




# ----- 5.张量梯度和雅可比积(Jacobian Products) -----#

# 之前调用 backward() 时没有传入参数,这其实等价于:backward(torch.tensor(1.0))。这种方式适用于标量值函数(比如训练神经网络时的 loss),它会自动从标量输出开始进行反向传播计算。

# 输入张量 x
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)

# 向量函数 y = f(x),y = [x1^2, x2^2, x3^2]
y = x ** 2

# 定义一个 v 向量,与 y 同形状。这里的 v 即 backward() 函数的参数 gradient
v = torch.tensor([1.0, 0.1, 0.01])

# 计算雅可比积 v^T · J
y.backward(gradient=v)

# 打印结果:这是 v^T * J,即 ∑ v_i * ∂y_i/∂x_j
print("x.grad(即 vᵀ · J) =", x.grad)




# ----- 6.梯度累积 -----#
inp = torch.eye(4, 5, requires_grad=True)
out = (inp+1).pow(2).t()

out.backward(torch.ones_like(out), retain_graph=True)
print(f"First call\n{inp.grad}")

# 当使用相同的参数第二次调用 backward() 时,梯度的值可能会不同。这是因为在进行反向传播时,PyTorch 会累加梯度,也就是说,计算出来的梯度值会被加到计算图中所有叶子节点的 .grad 属性上。
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nSecond call\n{inp.grad}")

# 如果想获得正确的梯度结果,就需要在每次反向传播前将 .grad 清零。在实际训练中,优化器(optimizer)通常会帮我们自动完成这个操作。
inp.grad.zero_()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nCall after zeroing gradients\n{inp.grad}")

六、优化模型参数

        参考:优化模型参数 — PyTorch Tutorials 2.6.0+cu124 文档 - PyTorch 深度学习库

# ----- 1.先决条件 -----#
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor

training_data = datasets.FashionMNIST(
    root="data",
    train=True,
    download=True,
    transform=ToTensor()
)

test_data = datasets.FashionMNIST(
    root="data",
    train=False,
    download=True,
    transform=ToTensor()
)

train_dataloader = DataLoader(training_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)


class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork()




# ----- 2.超参数设置 -----#
learning_rate = 1e-3
batch_size = 64
epochs = 10




# ----- 3.epoch(学习轮次) -----#
'''
每个 epoch 通常包含两个主要部分:
    训练循环(Train Loop):遍历训练数据集,尝试让模型参数逐步收敛到最优;
    验证/测试循环(Validation/Test Loop):遍历验证或测试数据集,用于检查模型性能是否在持续提升。
'''




# ----- 4.损失函数(Loss Function) -----#
'''
常见的损失函数包括:
    nn.MSELoss(均方误差),适用于回归任务;
    nn.NLLLoss(负对数似然损失),适用于分类任务;
    nn.CrossEntropyLoss 是 nn.LogSoftmax 和 nn.NLLLoss 的组合,常用于多类分类任务。
'''
# 初始化损失函数
loss_fn = nn.CrossEntropyLoss()




# ----- 5.优化器(Optimizer) -----#
import torch
'''
在 epoch 的训练循环中,优化过程包括以下三步:
    optimizer.zero_grad(): 重置模型参数的梯度。默认情况下, PyTorch 会在每次 backward() 时将新的梯度累加到计算图中所有叶子节点的 .grad 属性上,为了防止梯度累积,我们需要在每次迭代前手动将梯度清零。
    loss.backward(): 执行反向传播。PyTorch 会计算损失函数相对于参数的梯度,并存储在对应的 .grad 属性中。
    optimizer.step():使用反向传播过程中得到的梯度更新模型参数。
'''
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)




# ----- 6.完整实现 -----#
'''
    关于 批量归一化(BatchNorm) 和 Dropout 在训练和测试阶段的不同处理:
    
    1. **Batch Normalization (批量归一化)**
        训练时: 
            - BN 层根据当前批次计算每个特征的均值和方差。
            - 对每个特征进行归一化, 使其均值为0, 方差为1, 并应用缩放和平移操作。
            - 每个批次的均值和方差会更新并且通过移动平均值保存在模型中,供后续使用。
            
        测试时: 
            - BN 层使用全局均值和方差,而不是当前批次的均值和方差。全局均值和方差 是通过训练阶段中 多个 batch 的均值和方差 累积计算出来的滑动平均结果。
            - 这确保了在推理时,所有的输入数据都受到一致的标准化。
            - 使用训练过程中计算的统计量避免了批次大小的变化对推理结果的影响。
        
    2. **Dropout (丢弃技术)**
        训练时:
            - Dropout 随机丢弃部分神经元的输出,以防止模型过拟合。
            - 在每次训练过程中,神经网络的结构都会有所不同,因为神经元被随机“丢弃”。
            - Dropout 以一定的概率 (如 0.5)丢弃神经元,训练过程中会对输出进行缩放,以保持训练时激活值的期望值。
        
        测试时:
            - Dropout 不再应用,所有神经元的输出都会被使用。
            - 由于训练时丢弃了部分神经元,测试时通常需要对输出进行缩放(乘以 `1 - dropout_rate`),从而使得每个神经元的输出一致。
            - 测试时不再丢弃神经元,以保证推理过程中所有的信息都能得到利用。
        
'''


'''
    定义 train_loop(epoch 的训练循环),用于重复执行优化代码;
    定义 test_loop(epoch 的测试循环),用于评估模型在测试数据上的表现。
'''
def train_loop(dataloader, model, loss_fn, optimizer):
    """
    执行 epoch 的训练循环,对训练集进行遍历,更新模型参数。

    参数:
        dataloader (torch.utils.data.DataLoader): 用于加载训练数据的 DataLoader。
        model (torch.nn.Module): 要训练的神经网络模型。
        loss_fn (torch.nn.modules.loss._Loss): 用于计算预测误差的损失函数。
        optimizer (torch.optim.Optimizer): 用于更新模型参数的优化器。

    返回:
        None
    """
    size = len(dataloader.dataset) # 训练集的样本数量
    # 将模型设置为训练模式 - 对于批量归一化(BatchNorm)和 Dropout 层来说很重要
    # 在当前代码中不是必须的,但为了遵循最佳实践而加入
    model.train()
    for batch, (X, y) in enumerate(dataloader): # X 是输入样本; y 是对应的标签
        # 计算预测值和损失
        pred = model(X)
        loss = loss_fn(pred, y)

        # 反向传播
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

        # 每训练 100 个 batch,打印一次当前损失和进度
        if batch % 100 == 0:
            train_loss = loss.item()          # 获取当前批次损失的数值
            current = (batch+1) * batch_size  # 当前已处理的数据样本数量
            print(f"loss: {train_loss:>7f}  [{current:>5d}/{size:>5d}]")


def test_loop(dataloader, model, loss_fn):
    """
    执行 epoch 的测试循环,对测试集进行遍历,评估模型性能。

    参数:
        dataloader (torch.utils.data.DataLoader): 用于加载测试数据的 DataLoader。
        model (torch.nn.Module): 已训练的神经网络模型。
        loss_fn (torch.nn.modules.loss._Loss): 用于评估模型预测误差的损失函数。

    返回:
        None
    """
    # 将模型设置为评估模式 - 对于批量归一化(BatchNorm)和 Dropout 层来说很重要
    # 在当前代码中不是必须的,但为了遵循最佳实践而加入
    model.eval()
    size = len(dataloader.dataset) # 测试集的样本数量
    num_batches = len(dataloader)  # 测试集中总共有多少个 batch(批次)
    test_loss = 0 # 测试集上每个批次的平均损失
    correct = 0   # 预测正确的样本数

    # 使用 torch.no_grad() 来评估模型,确保在测试模式下不进行梯度计算
    # 同时也能减少不必要的梯度计算和对 requires_grad=True 的张量的内存占用
    with torch.no_grad():
        for X, y in dataloader:
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()

    test_loss /= num_batches
    correct /= size
    print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>7f} \n")


'''
    将已经初始化的损失函数和优化器传递给 train_loop 和 test_loop 函数。
    可以增加 epoch(学习轮次)来观察模型性能是如何逐步提升的。
'''
for t in range(epochs):
    print(f"Epoch {t+1}\n-------------------------------")
    train_loop(train_dataloader, model, loss_fn, optimizer)
    test_loop(test_dataloader, model, loss_fn)
print("Done!")

七、保存和加载模型

        参考:保存和加载模型 — PyTorch 教程 2.6.0+cu124 文档 - PyTorch 深度学习库

# ----- 1.保存和加载模型权重 -----#
import os
import torch
import torchvision.models as models

'''
    PyTorch 模型将学习到的参数存储在一个内部的状态字典中,称为 state_dict。这些参数可以通过 torch.save 方法进行持久化保存。

    这段代码的主要作用是:
    1. 创建一个名为 'model' 的目录,如果该目录已经存在则不做任何操作。
    2. 从 torchvision.models 模块加载 VGG-16 模型,并加载在 ImageNet 数据集(1K 类别版本)上训练得到的预训练权重。
    3. 将模型的参数(即 state_dict) 保存到 'model/model_weights.pth' 文件中,以便后续加载和使用。
'''
os.makedirs('model', exist_ok=True)            # 创建一个名为 'model' 的文件夹,如果文件夹已存在则不会报错。
model = models.vgg16(weights='IMAGENET1K_V1')  # 加载 VGG-16 模型并使用在 ImageNet 数据集上训练的预训练权重。
torch.save(model.state_dict(), 'model/model_weights.pth')  # 将模型的权重(state_dict)保存到指定路径 'model/model_weights.pth'。


'''
    要加载模型权重,首先需要创建一个相同模型的实例,然后使用 load_state_dict() 方法加载参数。
    在下面的代码中,我们设置 weights_only=True 以限制在反序列化过程中执行的功能,仅限于加载权重所必需的部分。在加载权重时,使用 weights_only=True 被认为是一种最佳实践。

    这段代码的目的是加载一个预训练的 VGG-16 模型权重。
    1. 创建了一个未训练的 VGG-16 模型实例.
    2. 使用 `load_state_dict()` 方法加载之前保存的模型权重。最后.
    3. 调用 `model.eval()` 设置模型为评估模式,以禁用训练时特有的操作(如 batch normalization 和 dropout) 
'''

model = models.vgg16()  # 我们没有指定 ``weights``,即创建一个未训练的 VGG-16 模型实例
model.load_state_dict(torch.load('model/model_weights.pth', weights_only=True))  # 加载保存在 'model/model_weights.pth' 文件中的模型权重,且只加载权重
model.eval()  # 设置模型为评估模式,禁用训练时特有的操作(如 batch normalization 和 dropout)
print(model)




# ----- 2.保存和加载包含网络结构的模型 -----#
'''
    当我们加载模型权重时, 需要先实例化模型类, 因为该类定义了神经网络的结构。
    如果我们希望将模型的结构和参数一起保存, 可以将整个模型(model), 而不是仅仅是 model.state_dict(),传给保存函数。
    这种方法底层使用的是 Python 的 pickle 模块来序列化模型, 因此必须在加载模型的代码环境中, 提前定义好这个模型类(class)或确保它可以被导入, 否则就会出现反序列化失败的错误。

    如同 PyTorch 官方文档中关于保存和加载 torch.nn.Module 所描述的那样,保存 state_dict 是推荐的做法(最佳实践)。
'''
torch.save(model, 'model/model.pth')
model = torch.load('model/model.pth', weights_only=False) # 使用 weights_only=False,加载完整的模型结构和模型权重。
import torch
from torch import nn
'''
    不报错原因:
        保存的是像 models.vgg16() 这样的 预定义模型, PyTorch 在加载时可以通过内部路径查找回这个类结构。
'''
model = torch.load('model/model.pth', weights_only=False) # 使用 weights_only=False,加载完整的模型结构和模型权重。

# ----- 1.错误示例 -----#
# 假设你保存的是自定义模型 MyModel

# 加载模型但此文件中没有 MyModel 的定义
model = torch.load('model.pth')  # ❌ 会报错:AttributeError: Can't get attribute 'MyModel'...




# ----- 1.正确做法 -----#
# 保证加载前定义好模型结构
class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        # define layers...

# 然后再加载
model = torch.load('model.pth')  # ✅ 成功

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值