PyTorch 深度学习入门与全面指南

本文档将全面、系统地介绍 PyTorch 的核心概念、基础用法和完整的工作流程,从张量的基本操作到神经网络的训练与保存,旨在为初学者提供一份详尽的参考手册。


1. PyTorch 简介、作用与安装

1.1 PyTorch 是什么?

PyTorch 是一个开源的机器学习框架,主要用于构建和训练深度学习模型。它以其动态计算图和简洁的 Python 接口而闻名,特别适合研究和快速原型开发。

  • 动态计算图:PyTorch 的计算图是动态构建的,这意味着计算图会在每次前向传播时根据输入数据重新构建。这使得 PyTorch 在处理可变长度序列(如自然语言处理任务)或具有复杂控制流的模型时非常灵活和直观。

1.2 PyTorch 的核心作用

PyTorch 提供了高效的多维数组运算能力,并内置了自动微分(Autograd)引擎,这使得它可以:

  • 构建和训练神经网络:自动计算梯度,简化了反向传播过程。

  • 利用 GPU 加速:高效利用 GPU 进行并行计算,大幅提升训练速度。

  • 数据处理:提供强大的数据加载工具,简化数据集管理。

1.3 如何安装 PyTorch

推荐使用官方网站提供的安装命令,它会根据你的操作系统、包管理器和 CUDA 版本生成最合适的安装指令。

  • 访问官网https://pytorch.org/get-started/locally/

  • 选择配置:选择你的系统、包管理器(如 Conda 或 pip)、Python 版本和 CUDA 版本。

  • 运行命令

    # 以 Conda 和 CUDA 12.1 为例
    conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia
    

    如果你没有 GPU,选择 "CPU" 版本即可。

2. PyTorch 张量与 NumPy 的核心区别

PyTorch 的核心数据结构是 张量(Tensor),它与 NumPy 的 ndarray 极为相似,但在功能上,PyTorch 张量具有显著的优势。

特性 PyTorch 张量 NumPy 数组 概念解释
GPU 加速 支持。是深度学习加速的关键。 不支持。只能在 CPU 上运行。 **GPU(图形处理器)拥有数以千计的核心,擅长并行处理大规模矩阵运算,这正是深度学习的核心。PyTorch 可以将张量无缝地转移到 GPU 上,从而将训练速度提升数十倍甚至数百倍。
自动微分 内置 autograd 引擎,可自动计算梯度。 不支持。需要手动实现梯度计算。 自动微分是深度学习优化的基石。它能自动追踪所有操作,并计算出每个参数对最终损失的导数(梯度),这是反向传播算法所必需的。NumPy 没有这个功能,你需要手动计算和实现梯度更新。
动态计算图 采用动态计算图**,灵活易于调试。 无计算图概念,操作立即执行。 PyTorch 的计算图在运行时动态构建,这使得你可以使用标准的 Python 控制流(如 iffor 循环)来构建网络。相比之下,TensorFlow 1.x 的静态计算图需要在运行前完全定义好,调试起来更困难。
互操作性 与 NumPy 数组可以方便且高效地转换。 不具备此功能。 你可以轻松地将 PyTorch 张量转换为 NumPy 数组,反之亦然。这种转换非常高效,因为它们通常共享底层的内存,避免了数据拷贝。

3. PyTorch 张量的基础操作

3.1 张量的数据类型与创建

张量可以存储不同类型的数据,其数据类型可以通过 dtype 指定。

import torch
import numpy as np

# 创建一个 32 位浮点数张量(默认)
t1 = torch.tensor([1.0, 2.0])
print(f"默认数据类型: {t1.dtype}") # torch.float32

# 指定为 64 位整数
t2 = torch.tensor([1, 2], dtype=torch.int64)
print(f"指定数据类型: {t2.dtype}") # torch.int64

# 从 NumPy 数组创建,两者共享内存
n_array = np.array([3, 4])
t3 = torch.from_numpy(n_array)
print(f"从 NumPy 创建: {t3}")

# 从 PyTorch 张量转为 NumPy 数组
n_array_new = t3.numpy()
3.2 张量的维度操作与拼接
  • view()reshape():用于改变张量的形状。

    • view():如果可能,返回一个共享底层内存的视图。如果新的形状与原张量内存布局连续,则可以使用。

    • reshape():更通用的方法。如果可以,它会返回一个视图;否则,它会返回一个新的副本。

    x = torch.randn(2, 4)
    y = x.view(8)         # 将 2x4 张量展平为一维(1x8)
    z = x.reshape(4, 2)   # 改变形状为 4x2
    print(f"原张量形状: {x.shape}")
    print(f"view 后的形状: {y.shape}")
    print(f"reshape 后的形状: {z.shape}")
    
  • unsqueeze()squeeze():用于添加或移除单个维度。

    • unsqueeze(dim):在指定的 dim 位置增加一个维度,大小为 1。

    • squeeze():移除所有大小为 1 的维度。

    x = torch.randn(5)      # 形状: (5,)
    y = x.unsqueeze(0)      # 在第 0 维增加一个维度,形状: (1, 5)
    z = y.squeeze()         # 移除所有大小为 1 的维度,形状: (5,)
    
  • cat()stack():用于拼接张量。

    • cat(tensors, dim):沿着指定的 dim 拼接张量。所有张量在该维度以外的形状必须相同。

    • stack(tensors, dim):沿着指定的 dim 创建一个新的维度来拼接张量。所有张量的形状必须完全相同。

    t1 = torch.randn(2, 3)
    t2 = torch.randn(2, 3)
    t_cat = torch.cat([t1, t2], dim=0)    # 按行拼接,形状: (4, 3)
    t_stack = torch.stack([t1, t2], dim=0) # 增加一个新维度来拼接,形状: (2, 2, 3)
    
3.3 索引、切片与广播

PyTorch 的索引和切片语法与 NumPy 相同。

  • 索引与切片

    t = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
    print(t[0])       # 访问第一行:tensor([1, 2, 3])
    print(t[1, 2])    # 访问第二行第三列的元素:tensor(6)
    print(t[:, 1:])   # 访问所有行的第二列及之后
    # tensor([[2, 3],
    #         [5, 6],
    #         [8, 9]])
    
    print(t[:2, 1])   # 访问前两行的第二列
    # tensor([2, 5])
    
  • 花式索引(Fancy Indexing):使用张量或列表作为索引,可以访问非连续的元素。

    indices = torch.tensor([0, 2])
    print(t[indices]) # 访问第0行和第2行
    # tensor([[1, 2, 3],
    #         [7, 8, 9]])
  • 广播(Broadcasting)

PyTorch 的广播机制允许张量在特定条件下自动扩展其形状,以便进行元素级运算。当两个张量的形状不兼容时,PyTorch 会尝试自动广播以进行运算。

基本规则

1. 每个张量至少有一个维度。

2. 从尾部维度开始比较,维度大小必须相等,或者其中一个为 1,或者其中一个不存在。

例如:一个 (3, 1) 的张量可以与一个 (1, 4) 的张量相加,因为它们可以被广播为 (3, 4)

t1 = torch.tensor([1, 2, 3])       # 形状: (3)
t2 = torch.tensor([[10], [20]])     # 形状: (2, 1)
result = t1 + t2 # t1 被广播为 (2, 3), t2 被广播为 (2, 3)
# 结果:
# tensor([[11, 12, 13],
#         [21, 22, 23]])
3.4 聚合、克隆与赋值
  • 聚合操作:如 sum()mean()max() 等,可以指定 dim 参数进行维度聚合。

    t = torch.tensor([[1, 2], [3, 4]])
    print(t.sum())          # 所有元素总和:tensor(10)
    print(t.mean(dim=0))    # 按列求平均:tensor([2., 3.])
    print(t.mean().item())  # 平均值,`.item()`用于取出单个元素:2.5
    
    
    t = torch.tensor([10, 20, 5, 30])
    max_val, max_idx = torch.max(t, dim=0)
    print(f"最大值: {max_val.item()}, 索引: {max_idx.item()}") # 最大值: 30, 索引: 3
  • 按维度聚合:这些操作都可以指定 dim 参数,沿着某个维度进行操作。

    t = torch.tensor([[1, 2], [3, 4]])
    print(t.sum(dim=0))  # 沿着第0维(列)求和:tensor([4, 6])
    print(t.mean(dim=1)) # 沿着第1维(行)求平均:tensor([1.5000, 3.5000])
  • 克隆与赋值

.clone():创建一个张量的新副本。新副本与原张量拥有独立的内存,改变其中一个不会影响另一个。

原地操作:一些操作会直接在张量上进行修改,而不是返回新的张量。通常,这些操作的名称以 _ 结尾(例如 add_())。

  • t1 = torch.tensor([1, 2])
    t2 = t1.clone()   # 创建独立副本
    t1.add_(5)        # 原地加法,t1 被修改
    print(t1)         # tensor([6, 7])
    t2[0] = 30
    print(t1)         # tensor([6, 7])
    print(t2)         # tensor([30, 2])
    
3.5 张量与 GPU 的交互

PyTorch 允许张量在 CPU 和 GPU 之间轻松转换,这对于利用 GPU 加速至关重要。

# 检查 GPU 是否可用
if torch.cuda.is_available():
    device = torch.device('cuda')
    print("Using GPU.")
else:
    device = torch.device('cpu')
    print("Using CPU.")

# 将张量移动到 GPU
t_cpu = torch.randn(3, 3)
t_gpu = t_cpu.to(device)

# 将张量移回 CPU
t_cpu_new = t_gpu.to('cpu')

4. PyTorch 深度学习核心组件

4.1 自动微分(Autograd)

这是 PyTorch 的核心功能。通过设置 requires_grad=True,PyTorch 会追踪张量上的所有操作,以便在反向传播时计算梯度。

# 1. 创建需要求梯度的张量
x = torch.tensor(3.0, requires_grad=True)

# 2. 定义函数
y = x**2 + 5 * x

# 3. 反向传播,计算 y 对 x 的梯度
y.backward()

# 4. 访问梯度值
# y' = 2x + 5,当 x=3 时,y' = 2*3 + 5 = 11.0
print(x.grad) # 结果为 tensor(11.)
4.2 数据集与数据加载器(Datasets & DataLoaders)

这两个类是 PyTorch 高效处理数据的关键。

  • Dataset:代表数据集,需要自定义类并实现 __len__(返回数据总数)和 __getitem__(根据索引返回一个样本)方法。

  • DataLoader:一个迭代器,用于将数据集分批次(batch)加载到内存中,并支持多线程并行加载数据打乱等功能。

from torch.utils.data import Dataset, DataLoader

# 自定义数据集
class CustomDataset(Dataset):
    def __init__(self, data, labels):
        self.data = data
        self.labels = labels
    def __len__(self):
        return len(self.data)
    def __getitem__(self, idx):
        return self.data[idx], self.labels[idx]

# 使用 DataLoader
dataset = CustomDataset(torch.randn(100, 10), torch.randint(0, 2, (100,)))
data_loader = DataLoader(dataset, batch_size=16, shuffle=True)

# 遍历数据加载器
for inputs, labels in data_loader:
    print(f"批次数据形状: {inputs.shape}, 批次标签形状: {labels.shape}")
    break
4.3 模型构建与管理

PyTorch 推荐通过继承 torch.nn.Module 来定义神经网络。

  • 模型定义:在 __init__ 中定义网络层,在 forward 中定义前向传播过程。

    import torch.nn as nn
    
    class SimpleModel(nn.Module):
        def __init__(self):
            super().__init__()
            # 定义一个线性层(全连接层),输入10个特征,输出5个特征
            self.fc1 = nn.Linear(10, 5)
            # 定义一个激活函数
            self.relu = nn.ReLU()
            # 定义另一个线性层,输入5个特征,输出1个特征
            self.fc2 = nn.Linear(5, 1)
    
        def forward(self, x):
            # 定义数据流向,即前向传播的顺序
            out = self.fc1(x)
            out = self.relu(out)
            out = self.fc2(out)
            return out
    
  • 优化器(Optimizer):管理和更新模型参数,如 AdamSGD 等。

        model.parameters():返回所有可训练的参数。

        torch.optim:提供各种优化算法,如 SGD、Adam,用于根据梯度更新参数。

  • import torch.optim as optim
    model = SimpleModel()
    # 将模型的所有参数传递给优化器
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    

5. PyTorch 训练循环

  • 清零梯度:在每次反向传播前,将优化器中的梯度清零,optimizer.zero_grad()

  • 前向传播:将输入数据传递给模型,计算预测值。

  • 计算损失:使用损失函数(如 nn.CrossEntropyLoss)计算预测值与真实值之间的差异。

  • 反向传播:调用 loss.backward() 计算每个参数的梯度。

  • 更新参数:调用 optimizer.step(),根据梯度和优化算法更新模型参数。

这是训练神经网络的核心流程,通常在一个 for 循环中完成。

# 假设 model, data_loader, optimizer, loss_fn 已定义
for epoch in range(num_epochs):
    # 遍历数据加载器中的所有批次
    for inputs, labels in data_loader:
        # 1. 清除梯度:在每次反向传播前,将之前计算的梯度清零
        optimizer.zero_grad()
        # 2. 前向传播:将输入数据传递给模型,计算预测值
        outputs = model(inputs)
        # 3. 计算损失:使用损失函数(如 nn.MSELoss)计算预测值与真实值之间的差异
        loss = loss_fn(outputs, labels)
        # 4. 反向传播:计算每个参数对损失的梯度
        loss.backward()
        # 5. 更新参数:根据梯度和优化算法更新模型参数
        optimizer.step()

6. 模型保存与加载

为了在训练后使用或继续训练模型,保存和加载参数是必不可少的。

  • 保存:推荐只保存模型的参数字典(state_dict),因为它更轻量且灵活。

    # 只保存模型的参数
    torch.save(model.state_dict(), 'model_params.pth')
    
  • 加载:先实例化模型,再加载参数。

    # 实例化一个新的模型
    new_model = SimpleModel()
    # 加载已保存的参数
    new_model.load_state_dict(torch.load('model_params.pth'))
    

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐