PyTorch核心概念详解 – wiki基地


PyTorch核心概念详解:从基石到实践

PyTorch 作为当今最受欢迎的深度学习框架之一,凭借其灵活性、易用性以及强大的社区支持,在学术界和工业界都得到了广泛应用。无论是初学者想要入门深度学习,还是经验丰富的研究人员探索前沿算法,理解 PyTorch 的核心概念都是至关重要的第一步。本文将深入探讨 PyTorch 的基石组件和核心思想,帮助读者构建一个坚实的基础,从而更自信地驾驭这个强大的工具。

一、 引言:为什么选择 PyTorch?

在深入技术细节之前,我们先简单了解一下 PyTorch 脱颖而出的原因:

  1. Pythonic 与易用性: PyTorch 的 API 设计紧密贴合 Python 的编程习惯,使得熟悉 Python 的开发者能够快速上手。其直观的语法和清晰的结构降低了学习曲线。
  2. 动态计算图 (Dynamic Computational Graph): 这是 PyTorch 最显著的特点之一。与 TensorFlow 1.x 等框架使用的静态计算图不同,PyTorch 的计算图是在运行时动态构建的。这意味着你可以使用标准的 Python 控制流(如循环和条件判断)来构建和修改模型结构,极大地增强了灵活性,特别是在处理变长输入(如 NLP 任务)或进行复杂的模型调试时。
  3. 强大的 GPU 加速: PyTorch 提供了无缝的 CUDA 支持,可以轻松地将计算密集型任务转移到 NVIDIA GPU 上执行,显著加速模型训练和推理过程。
  4. 丰富的生态系统: 围绕 PyTorch 已经形成了一个庞大且活跃的生态系统,包括用于计算机视觉的 TorchVision、处理文本的 TorchText、处理音频的 TorchAudio,以及大量的预训练模型、工具库和研究项目。
  5. 学术界与研究友好: 由于其灵活性,PyTorch 在学术界备受青睐,许多最新的研究成果都是使用 PyTorch 实现的。

理解了 PyTorch 的优势后,让我们正式开始探索其核心概念。

二、 Tensor (张量):PyTorch 的数据基石

在 PyTorch 中,所有的数据都通过 Tensor 来表示。可以将其理解为一个多维数组,类似于 NumPy 的 ndarray,但具备在 GPU 上高效运算的能力,并且是构建计算图和自动求导的基础。

  • 创建 Tensor:

    • 可以直接从 Python 列表或 NumPy 数组创建:
      “`python
      import torch
      import numpy as np

      从列表创建

      data = [[1, 2], [3, 4]]
      x_data = torch.tensor(data)

      从 NumPy 数组创建 (共享内存)

      np_array = np.array(data)
      x_np = torch.from_numpy(np_array)

      创建指定形状和类型的 Tensor

      x_zeros = torch.zeros(2, 3) # 全零
      x_ones = torch.ones(2, 3, dtype=torch.float16) # 全一,指定数据类型
      x_rand = torch.randn(2, 3) # 标准正态分布随机数
      * **Tensor 的属性:**
      * `shape` 或 `size()`: 返回 Tensor 的维度(形状)。
      * `dtype`: 返回 Tensor 中元素的数据类型 (e.g., `torch.float32`, `torch.int64`)。
      * `device`: 返回 Tensor 所在的设备 (e.g., `cpu`, `cuda:0`)。
      * **Tensor 操作:** PyTorch 提供了极其丰富的 Tensor 操作函数,涵盖了数学运算、线性代数、索引、切片、变形等。
      python
      tensor = torch.ones(4, 4)
      # 索引和切片 (类似 NumPy)
      print(f”First row: {tensor[0]}”)
      print(f”First column: {tensor[:, 0]}”)
      print(f”Last column: {tensor[…, -1]}”)
      tensor[:,1] = 0 # 就地修改

    拼接

    t1 = torch.cat([tensor, tensor, tensor], dim=1) # 按列拼接

    矩阵乘法

    y1 = tensor @ tensor.T # .T 是转置
    y2 = torch.matmul(tensor, tensor.T)

    逐元素运算

    z1 = tensor * tensor
    z2 = torch.mul(tensor, tensor)
    z3 = torch.add(tensor, 1) # 加法
    * **NumPy 桥:** PyTorch Tensor 可以轻松地与 NumPy 数组相互转换。
    * `tensor.numpy()`: 将 CPU 上的 Tensor 转换为 NumPy 数组(共享内存)。
    * `torch.from_numpy(ndarray)`: 将 NumPy 数组转换为 PyTorch Tensor(共享内存)。
    * **注意:** 共享内存意味着修改其中一个会影响另一个。如果 Tensor 在 GPU 上,需要先用 `.cpu()` 方法将其移到 CPU。
    * **GPU 加速:** 将 Tensor 移动到 GPU 是实现加速的关键。
    python
    if torch.cuda.is_available():
    device = torch.device(“cuda”) # 获取 CUDA 设备
    tensor = tensor.to(device) # 将 Tensor 移动到 GPU
    print(f”Tensor is on device: {tensor.device}”)
    else:
    device = torch.device(“cpu”)
    print(“CUDA not available, using CPU.”)
    “`
    在 GPU 上的 Tensor 进行的运算将自动在 GPU 上执行。

三、 Autograd:自动微分引擎

Autograd 是 PyTorch 的核心功能之一,它为 Tensor 上的所有操作提供了自动微分能力。这是训练神经网络(本质上是利用梯度下降优化参数)的关键。

  • 计算图 (Computational Graph): PyTorch 使用动态计算图。当你对设置了 requires_grad=True 的 Tensor 执行操作时,PyTorch 会自动记录这些操作,构建一个由 Function 对象组成的有向无环图 (DAG)。叶子节点是输入 Tensor,根节点是输出 Tensor。每个非叶子节点代表一个操作(Function),并保存了其对应的梯度计算方法。
  • requires_grad 属性:
    • 一个 Tensor 的 requires_grad 属性默认为 False
    • 如果希望 PyTorch 跟踪对某个 Tensor 的操作并计算梯度,需要将其 requires_grad 设置为 True。通常,模型的参数(权重和偏置)会自动设置 requires_grad=True
      python
      x = torch.ones(2, 2, requires_grad=True)
      print(x.requires_grad) # Output: True
      y = x + 2
      print(y.requires_grad) # Output: True (由 requires_grad=True 的 Tensor 计算得到)
      z = y * y * 3
      out = z.mean()
      print(out.requires_grad) # Output: True
  • grad_fn 属性:
    • 如果一个 Tensor 是由用户直接创建的(叶子节点),它的 grad_fnNone
    • 如果一个 Tensor 是某个操作的结果,且该操作的任何输入 Tensor 的 requires_grad=True,那么这个 Tensor 会有一个 grad_fn 属性。该属性指向创建这个 Tensor 的 Function 对象(例如 AddBackward0, MulBackward0, MeanBackward0),用于反向传播时计算梯度。
      python
      print(x.grad_fn) # Output: None (用户创建)
      print(y.grad_fn) # Output: <AddBackward0 object at 0x...>
      print(out.grad_fn)# Output: <MeanBackward0 object at 0x...>
  • .backward() 方法:

    • 当你在一个标量 Tensor(例如损失函数 loss)上调用 .backward() 时,Autograd 会从这个标量开始,沿着计算图反向传播,计算图中所有 requires_grad=True 的叶子节点相对于该标量的梯度。
    • 计算得到的梯度会累加到对应 Tensor 的 .grad 属性中。
      “`python
      out.backward() # 计算梯度

    查看 x 的梯度 d(out)/dx

    print(x.grad)

    Output:

    tensor([[4.5000, 4.5000],

    [4.5000, 4.5000]])

    这里的梯度计算过程大致是:`out = (1/4) * Σ z_i`,`z_i = 3 * y_i^2`,`y_i = x_i + 2`。 `d(out)/dz_i = 1/4`。`d(z_i)/dy_i = 6 * y_i`。`d(y_i)/dx_i = 1`。 根据链式法则,`d(out)/dx_i = d(out)/dz_i * d(z_i)/dy_i * d(y_i)/dx_i = (1/4) * (6 * y_i) * 1 = (3/2) * y_i`。 由于 `x` 初始是全1矩阵,`y = x + 2` 是全3矩阵,所以 `d(out)/dx_i = (3/2) * 3 = 4.5`。
    * **梯度累加与 `.zero_grad()`:**
    * PyTorch 的梯度是**累加 (accumulate)** 的。这意味着如果你连续多次调用 `.backward()` 而不清除梯度,新的梯度会加到 `.grad` 属性上。
    * 在训练循环中,每个 batch 开始优化之前,必须手动清除上一步计算的梯度,否则梯度会错误地累积。这通常通过优化器(Optimizer)的 `.zero_grad()` 方法完成。
    * **禁用梯度跟踪:**
    * 有时我们不需要计算梯度,例如在模型评估(Inference)或更新模型参数之后。禁用梯度跟踪可以减少内存消耗并加速计算。
    * 使用 `torch.no_grad()` 上下文管理器:
    python
    with torch.no_grad():
    # 在这个块内的所有计算都不会被跟踪
    y = x + 2
    print(y.requires_grad) # Output: False
    * 使用 Tensor 的 `.detach()` 方法:创建一个新的 Tensor,它与原始 Tensor 共享数据,但不参与梯度计算。python
    x_detached = x.detach()
    print(x_detached.requires_grad) # Output: False
    “`

四、 torch.nn:构建神经网络的核心模块

torch.nn 模块是 PyTorch 构建神经网络的核心。它提供了定义网络层、损失函数以及容器的类。

  • nn.Module:
    • 所有神经网络模块的基类。自定义的网络层或整个模型都应该继承自 nn.Module
    • 核心方法:
      • __init__(): 定义模型的层(如卷积层、线性层)和其他组件。在这里实例化的 nn.Module 子模块(如 nn.Linear, nn.Conv2d)会被自动注册。
      • forward(): 定义数据通过网络的前向传播逻辑。输入数据,返回输出。当你调用 model(input_data) 时,实际上是调用了 model.forward(input_data)
    • nn.Module 会自动管理其包含的参数 (nn.Parameter)。nn.Parameter 是一种特殊的 Tensor,当它被赋值给 nn.Module 的属性时,会自动设置 requires_grad=True 并被添加到模块的参数列表(可通过 module.parameters()module.named_parameters() 访问)。
  • 常用的层 (nn.Layer):
    • nn.Linear(in_features, out_features): 全连接层(密集层)。
    • nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0): 二维卷积层。类似地还有 nn.Conv1d, nn.Conv3d
    • nn.MaxPool2d(kernel_size, stride=None, padding=0): 二维最大池化层。类似地还有 nn.AvgPool2d
    • nn.ReLU(), nn.Sigmoid(), nn.Tanh(), nn.Softmax(dim=None): 常用的激活函数。
    • nn.BatchNorm2d(num_features), nn.Dropout(p=0.5): 正则化层。
    • nn.Embedding(num_embeddings, embedding_dim): 词嵌入层,常用于 NLP。
    • nn.LSTM(), nn.GRU(): 循环神经网络层。
  • 容器 (nn.Container):
    • nn.Sequential: 一个顺序容器。模块将按照它们在构造函数中传递的顺序添加到容器中。输入数据会依次通过每个模块。
      python
      model = nn.Sequential(
      nn.Linear(784, 128),
      nn.ReLU(),
      nn.Linear(128, 10)
      )
  • 自定义 nn.Module:
    “`python
    import torch.nn as nn
    import torch.nn.functional as F # 函数式接口,通常无参数

    class SimpleNet(nn.Module):
    def init(self):
    super(SimpleNet, self).init() # 必须调用父类构造函数
    # 定义层
    self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
    self.relu1 = nn.ReLU()
    self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
    self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)
    self.relu2 = nn.ReLU()
    self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
    # 假设输入是 28×28 图像,经过两次 2×2 池化后变为 7×7
    self.fc1 = nn.Linear(64 * 7 * 7, 128)
    self.relu3 = nn.ReLU()
    self.fc2 = nn.Linear(128, 10) # 输出 10 个类别

    def forward(self, x):
        # 定义前向传播逻辑
        x = self.pool1(self.relu1(self.conv1(x)))
        x = self.pool2(self.relu2(self.conv2(x)))
        x = x.view(-1, 64 * 7 * 7) # 展平特征图
        x = self.relu3(self.fc1(x))
        x = self.fc2(x) # 通常最后一层不加激活,留给损失函数处理(如 CrossEntropyLoss)
        return x
    

    实例化模型

    net = SimpleNet()
    print(net) # 打印模型结构

    打印模型参数

    for name, param in net.named_parameters():

    if param.requires_grad:

    print(name, param.data.shape)

    “`

五、 torch.optim:优化算法库

在计算出损失(Loss)和梯度之后,我们需要一种机制来根据梯度更新模型的参数(权重和偏置),以最小化损失。这就是优化器(Optimizer)的作用。torch.optim 模块提供了各种常用的优化算法。

  • 常用的优化器:
    • optim.SGD: 随机梯度下降(Stochastic Gradient Descent),可以附加动量(momentum)、权重衰减(weight decay)等。
    • optim.Adam: Adam 优化器,一种自适应学习率的优化算法,通常收敛速度较快,是目前广泛使用的优化器之一。
    • optim.RMSprop: RMSprop 优化器,也是一种自适应学习率算法。
    • optim.Adagrad, optim.AdamW 等。
  • 使用方法:

    1. 创建优化器实例,传入需要优化的模型参数 (model.parameters()) 和学习率 (lr) 等超参数。
    2. 在训练循环中:
      • optimizer.zero_grad(): 清除上一步遗留的梯度。必须在计算损失和调用 .backward() 之前执行。
      • loss.backward(): 计算损失相对于模型参数的梯度。
      • optimizer.step(): 根据计算出的梯度和优化算法的规则,更新模型参数。

    “`python
    import torch.optim as optim

    假设 net 是我们定义的 SimpleNet 实例

    定义损失函数和优化器

    criterion = nn.CrossEntropyLoss() # 交叉熵损失,常用于多分类
    optimizer = optim.Adam(net.parameters(), lr=0.001)

    — 模拟训练循环中的一步 —

    假设 inputs 和 labels 是一个 batch 的数据

    inputs = torch.randn(64, 1, 28, 28) # 假设 batch size = 64
    labels = torch.randint(0, 10, (64,))

    1. 清零梯度

    optimizer.zero_grad()

    2. 前向传播

    outputs = net(inputs)

    3. 计算损失

    loss = criterion(outputs, labels)

    4. 反向传播计算梯度

    loss.backward()

    5. 更新参数

    optimizer.step()

    print(f”Loss: {loss.item()}”) # .item() 获取标量 Tensor 的 Python 数值

    — 训练循环结束 —

    “`

六、 损失函数 (Loss Functions):衡量模型表现

损失函数用于衡量模型预测值与真实标签之间的差异。训练的目标就是最小化损失函数的值。损失函数通常也定义在 torch.nn 模块中。

  • 常用的损失函数:

    • nn.MSELoss: 均方误差损失(Mean Squared Error Loss),常用于回归任务。loss = mean((input - target)^2)
    • nn.CrossEntropyLoss: 交叉熵损失。它内部结合了 nn.LogSoftmaxnn.NLLLoss(负对数似然损失),非常适合用于多分类任务。输入通常是未经 Softmax 的原始 logits,目标是类别索引。
    • nn.BCELoss: 二元交叉熵损失(Binary Cross Entropy Loss),用于二分类任务。输入需要先经过 Sigmoid 激活。
    • nn.BCEWithLogitsLoss: 结合了 Sigmoid 和 BCELoss,比分开使用更数值稳定,用于二分类任务,输入是原始 logits。
    • nn.L1Loss: L1 损失(Mean Absolute Error Loss),也用于回归任务。
  • 使用: 损失函数通常是一个可调用的对象。将模型的输出和目标标签传入即可计算损失值(一个标量 Tensor)。
    “`python
    # 回归任务示例
    output_reg = torch.randn(10, 1, requires_grad=True)
    target_reg = torch.randn(10, 1)
    loss_fn_mse = nn.MSELoss()
    loss_mse = loss_fn_mse(output_reg, target_reg)
    print(f”MSE Loss: {loss_mse.item()}”)

    分类任务示例 (已在优化器部分展示)

    output_cls = torch.randn(64, 10, requires_grad=True) # 64 个样本,10 个类别 logits
    target_cls = torch.randint(0, 10, (64,)) # 目标类别索引
    loss_fn_ce = nn.CrossEntropyLoss()
    loss_ce = loss_fn_ce(output_cls, target_cls)
    print(f”Cross Entropy Loss: {loss_ce.item()}”)
    “`

七、 数据加载与处理 (torch.utils.data)

高效地加载和预处理数据对于训练深度学习模型至关重要,尤其是当数据集非常庞大无法一次性载入内存时。torch.utils.data 模块提供了两个关键类:DatasetDataLoader

  • Dataset:

    • 一个抽象类,代表一个数据集。你需要继承它并实现两个核心方法:
      • __len__(): 返回数据集的大小(样本数量)。
      • __getitem__(idx): 根据索引 idx 获取并返回一个样本(通常是数据和标签组成的元组或字典)。这里可以包含数据的读取、预处理(如图像变换、文本分词)等逻辑。
        “`python
        from torch.utils.data import Dataset, DataLoader
        from torchvision import transforms # 常用图像变换

    class CustomImageDataset(Dataset):
    def init(self, annotations_file, img_dir, transform=None, target_transform=None):
    # self.img_labels = pd.read_csv(annotations_file) # 加载标签
    self.img_dir = img_dir
    self.transform = transform # 数据预处理
    self.target_transform = target_transform # 标签预处理
    # … 初始化,加载文件列表等 …
    self.image_files = […] # 假设这是图像文件路径列表
    self.labels = […] # 假设这是对应的标签列表

    def __len__(self):
        return len(self.image_files) # 返回数据集大小
    
    def __getitem__(self, idx):
        img_path = os.path.join(self.img_dir, self.image_files[idx])
        # image = read_image(img_path) # 读取图像 (e.g., using PIL)
        image = ... # 加载图像数据
        label = self.labels[idx]
        if self.transform:
            image = self.transform(image) # 应用图像变换
        if self.target_transform:
            label = self.target_transform(label) # 应用标签变换
        return image, label # 返回一个样本
    

    ``
    * **
    DataLoader:**
    * 接收一个
    Dataset对象,并提供一个可迭代对象,用于按批次 (batch) 加载数据。
    * **核心功能:**
    * **批处理 (Batching):** 将多个样本组合成一个 batch。
    * **打乱 (Shuffling):** 在每个 epoch 开始时打乱数据顺序,有助于模型泛化 (
    shuffle=True)。
    * **并行加载 (Multiprocessing):** 使用多个子进程并行加载数据,避免数据加载成为训练瓶颈 (
    num_workers > 0)。
    * **自定义 Collate Function:** 定义如何将单个样本组合成一个 batch (
    collate_fn`)。

    “`python

    假设我们有一个 CustomImageDataset 实例

    train_dataset = CustomImageDataset(…)

    创建 DataLoader

    train_dataloader = DataLoader(
    dataset=train_dataset,
    batch_size=64, # 每个 batch 的样本数
    shuffle=True, # 每个 epoch 打乱数据
    num_workers=4 # 使用 4 个子进程加载数据 (根据 CPU 核心数调整)
    )

    在训练循环中使用 DataLoader

    num_epochs = 10
    for epoch in range(num_epochs):
    print(f”Epoch {epoch+1}\n——————————-“)
    for batch, (X, y) in enumerate(train_dataloader):
    # 将数据移动到正确的设备 (CPU or GPU)
    X, y = X.to(device), y.to(device)

        # ... 模型训练步骤 (前向传播, 计算损失, 反向传播, 更新参数) ...
    
        if batch % 100 == 0:
            loss, current = loss.item(), batch * len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{len(train_dataset):>5d}]")
    

    “`

八、 模型保存与加载

训练好的模型需要被保存下来,以便后续进行评估、部署或继续训练。PyTorch 提供了方便的接口来保存和加载模型状态。

  • 保存/加载整个模型:
    “`python
    # 保存
    torch.save(model, ‘model.pth’)

    加载

    model = torch.load(‘model.pth’)
    model.eval() # 设置为评估模式
    这种方法简单直接,但不够灵活,可能在代码重构或不同版本的 PyTorch 之间存在兼容性问题。
    * **保存/加载模型的状态字典 (State Dictionary) - 推荐方式:**
    * 状态字典 (`state_dict`) 是一个 Python 字典,它将模型的每个层映射到其可学习的参数(权重和偏置)张量。只保存模型的参数,不保存模型结构。
    python

    保存 state_dict

    torch.save(model.state_dict(), ‘model_state_dict.pth’)

    加载 state_dict

    1. 先创建模型实例 (需要有模型的类定义)

    model = SimpleNet() # 或者你的模型类

    2. 加载 state_dict

    model.load_state_dict(torch.load(‘model_state_dict.pth’))

    3. 设置模式 (训练或评估)

    model.eval() # 如果用于推理

    model.train() # 如果要继续训练

    这种方式更推荐,因为它更轻量、更灵活,也更容易迁移。
    * **保存包含优化器状态等的检查点 (Checkpoint):**
    在训练过程中,除了模型参数,可能还需要保存优化器的状态(如学习率、Adam 的动量信息等)、当前的 epoch、损失等,以便中断后能完全恢复训练。
    python

    保存 Checkpoint

    checkpoint = {
    ‘epoch’: epoch,
    ‘model_state_dict’: model.state_dict(),
    ‘optimizer_state_dict’: optimizer.state_dict(),
    ‘loss’: loss,
    # … 其他需要保存的信息
    }
    torch.save(checkpoint, ‘checkpoint.pth’)

    加载 Checkpoint

    model = SimpleNet()
    optimizer = optim.Adam(model.parameters()) # 先创建实例

    checkpoint = torch.load(‘checkpoint.pth’)
    model.load_state_dict(checkpoint[‘model_state_dict’])
    optimizer.load_state_dict(checkpoint[‘optimizer_state_dict’])
    epoch = checkpoint[‘epoch’]
    loss = checkpoint[‘loss’]

    model.train() # 或者 model.eval()
    “`

九、 总结与展望

本文详细介绍了 PyTorch 的几个核心概念:

  • Tensor: 多维数组,PyTorch 的基本数据单元,支持 GPU 加速。
  • Autograd: 自动微分引擎,通过动态计算图实现梯度的自动计算。
  • torch.nn: 构建神经网络的核心模块,提供了 nn.Module 基类、各种网络层和损失函数。
  • torch.optim: 优化算法库,用于根据梯度更新模型参数。
  • 损失函数: 衡量模型预测与真实值差异的标准。
  • torch.utils.data: 高效加载和处理数据集的工具 (DatasetDataLoader)。
  • 模型保存与加载: 持久化模型状态的方法。

这些概念是相互关联、协同工作的。Tensor 是数据流的基础;Autograd 在 Tensor 操作之上构建计算图并计算梯度;nn.Module 定义了包含可学习参数(也是 Tensor)的网络结构;损失函数计算出一个标量 Tensor;torch.optim 利用 Autograd 计算出的梯度来更新 nn.Module 中的参数;DataLoader 则负责高效地将数据(Tensor)喂给模型。

掌握了这些核心概念,你就拥有了使用 PyTorch 构建、训练和评估深度学习模型的坚实基础。当然,PyTorch 的世界远不止于此,还有分布式训练、模型部署(TorchServe, ONNX)、JIT 编译(TorchScript)、更高级的 Autograd 功能以及庞大的生态系统等待探索。但理解这些核心构件,无疑是开启 PyTorch 深度学习之旅最重要的一步。希望本文能为你提供清晰的指引,祝你在 PyTorch 的学习和实践中取得成功!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部