快速了解 PyTorch:入门介绍 – wiki基地


快速了解 PyTorch:入门介绍

引言:迎接深度学习的浪潮

在当今科技飞速发展的时代,人工智能(AI)正以前所未有的速度改变着我们的生活。而深度学习作为AI领域最激动人心的分支之一,已经在图像识别、自然语言处理、语音识别等众多领域取得了突破性进展。要踏入这个领域,掌握一个强大、灵活且易于使用的深度学习框架至关重要。PyTorch,正是一个在学术界和工业界都广受欢迎的选择。

PyTorch 由 Facebook 的人工智能研究院(FAIR)开发并维护,以其“Pythonic”的设计哲学、动态计算图以及强大的社区支持而闻名。相比于一些老牌框架(如早期版本的 TensorFlow),PyTorch 的设计更加直观,与 Python 的原生习惯更为贴近,这使得它特别适合研究人员进行快速原型开发和实验。同时,其在生产环境中的部署能力也在不断增强。

本文旨在为完全没有 PyTorch 基础的读者提供一个快速而全面的入门指南。我们将从 PyTorch 的核心概念——张量(Tensor)——开始,逐步深入到自动微分机制(Autograd)、神经网络的构建模块(torch.nn)、数据的处理、模型的训练流程,以及如何在GPU上加速计算。通过阅读本文,你将能够:

  • 理解 PyTorch 的基本构成和核心特性。
  • 掌握张量的创建、操作和重要属性。
  • 理解自动微分的工作原理及其在训练中的作用。
  • 学会使用 torch.nn 构建简单的神经网络模型。
  • 了解如何使用优化器和损失函数。
  • 熟悉 PyTorch 的数据处理工具。
  • 理解并实现一个简单的模型训练循环。
  • 知道如何利用 GPU 加速计算。
  • 了解模型的保存与加载。

本文力求详细,力求通过理论讲解结合实际代码示例,让你不仅仅是复制代码,更能理解其背后的原理。让我们一起踏上 PyTorch 的学习之旅吧!

第一章:PyTorch 是什么?为什么选择它?

1.1 PyTorch 的定位与特点

PyTorch 是一个开源的机器学习库,主要用于构建和训练神经网络。它基于 Torch 库,但针对 Python 进行了优化。PyTorch 的核心特点包括:

  • Pythonic 设计: PyTorch 的 API 设计非常贴近 Python 的编程习惯,代码易于理解和编写。
  • 动态计算图: 这是 PyTorch 最显著的特点之一。计算图是根据代码的执行动态构建的,这使得调试更加容易,也允许在运行时改变网络结构(例如,在处理变长序列时)。相对而言,TensorFlow 1.x 采用静态计算图,需要先定义完整的计算图,再执行。TensorFlow 2.x 吸取了 PyTorch 的优点,也开始支持动态图(eager execution)。
  • 张量计算: PyTorch 提供强大的张量计算能力,类似于 NumPy,但支持 GPU 加速。
  • 自动微分 (Autograd): 能够自动计算张量上的所有操作的梯度,这是训练神经网络的关键。
  • 丰富的生态系统: PyTorch 拥有强大的库,如 torchvision (计算机视觉)、torchaudio (音频处理)、torchtext (自然语言处理),以及用于部署的 TorchScript 和 mobile 后端。
  • 社区活跃: 拥有庞大且活跃的社区,遇到问题很容易找到帮助和资源。

1.2 为什么选择 PyTorch?

对于初学者和研究人员来说,PyTorch 通常被认为是更容易入门和更灵活的框架。其动态图特性使得实验和调试更加直观。对于需要快速迭代想法、尝试不同模型结构的场景,PyTorch 的优势更加明显。

对于工业界应用,PyTorch 也提供了强大的部署工具,如 TorchScript,可以将模型导出为静态图格式,方便在不同环境中部署。近年来,越来越多的公司开始在生产环境中使用 PyTorch。

当然,选择哪个框架取决于个人偏好、项目需求和团队经验。但无论如何,PyTorch 都是一个值得深入学习的优秀框架。

第二章:安装 PyTorch

安装 PyTorch 非常简单,通常推荐使用 pip 或 conda 包管理器。访问 PyTorch 官方网站 (pytorch.org),选择你的操作系统、包管理器、PyTorch 版本以及是否需要 CUDA 支持(如果你的电脑有NVIDIA显卡,强烈建议安装CUDA版本以利用GPU加速),网站会生成对应的安装命令。

以 conda 为例,安装支持 CUDA 11.8 的最新 PyTorch 版本可能类似于:

bash
conda install pytorch torchvision torchaudio cudatoolkit=11.8 -c pytorch -c conda-forge

如果不需要 GPU 支持(仅 CPU 版本):

bash
conda install pytorch torchvision torchaudio cpuonly -c pytorch

使用 pip 的话,命令会不同,同样参照官网。

安装完成后,可以在 Python 环境中导入 PyTorch 并检查版本和 CUDA 是否可用:

python
import torch
print(f"PyTorch version: {torch.__version__}")
print(f"Is CUDA available: {torch.cuda.is_available()}")
if torch.cuda.is_available():
print(f"CUDA version: {torch.version.cuda}")
print(f"CUDA device name: {torch.cuda.get_device_name(0)}")

如果 torch.cuda.is_available() 返回 True,恭喜你,你已经可以在 GPU 上运行 PyTorch 了!

第三章:PyTorch 的核心:张量 (Tensor)

张量是 PyTorch 中最基本的数据结构。你可以将其理解为一个多维数组,与 NumPy 的 ndarray 非常相似。然而,PyTorch 的张量可以驻留在 GPU 上,并且能够自动计算梯度,这是它与 NumPy 的根本区别。

3.1 创建张量

有多种方法可以创建张量:

  • 从 Python 列表或 NumPy 数组创建:

    “`python
    import torch
    import numpy as np

    从列表创建

    data = [[1, 2], [3, 4]]
    x_data = torch.tensor(data)
    print(f”From list:\n{x_data}\n”)

    从 NumPy 数组创建

    np_array = np.array(data)
    x_np = torch.from_numpy(np_array)
    print(f”From NumPy:\n{x_np}\n”)
    “`

  • 创建具有特定属性的张量:

    “`python

    创建一个张量,形状与另一个张量相同(默认保留 dtype)

    x_like = torch.ones_like(x_data) # 保留 x_data 的形状和 dtype
    print(f”Ones like:\n{x_like}\n”)

    创建一个张量,形状与另一个张量相同,但指定 dtype

    x_like_float = torch.zeros_like(x_data, dtype=torch.float) # 保留 x_data 的形状,但 dtype 为 float
    print(f”Zeros like (float):\n{x_like_float}\n”)

    创建具有随机值的张量

    shape = (2, 3,)
    rand_tensor = torch.rand(shape) # 值在 [0, 1) 之间均匀分布
    print(f”Random Tensor:\n{rand_tensor}\n”)

    创建具有常量值的张量

    ones_tensor = torch.ones(shape)
    print(f”Ones Tensor:\n{ones_tensor}\n”)

    zeros_tensor = torch.zeros(shape)
    print(f”Zeros Tensor:\n{zeros_tensor}\n”)
    “`

  • 创建未初始化的张量:

    “`python

    创建一个指定形状的未初始化张量

    empty_tensor = torch.empty(shape) # 值是未初始化的,不要依赖其内容
    print(f”Empty Tensor:\n{empty_tensor}\n”)
    “`

3.2 张量的属性

张量有几个重要的属性:

  • shapesize():张量的形状(各维度的大小)。
  • dtype:张量的数据类型(如 torch.float32, torch.int64)。
  • device:张量所在的设备(如 cpu, cuda:0)。

“`python
tensor = torch.rand(3, 4)

print(f”Shape of tensor: {tensor.shape}”)
print(f”Shape of tensor: {tensor.size()}”) # size() 等同于 shape
print(f”Datatype of tensor: {tensor.dtype}”)
print(f”Device tensor is stored on: {tensor.device}”)
“`

3.3 张量的操作

张量支持各种数学运算、逻辑运算、索引、切片、形状变换等,许多操作与 NumPy 类似。

  • 算术运算: 支持元素级运算 (+, -, *, /) 以及矩阵乘法。

    “`python
    tensor = torch.ones(4, 4)
    tensor[:,1] = 0 # 修改第二列为0
    print(f”Original Tensor:\n{tensor}\n”)

    元素级乘法

    print(f”Element-wise multiplication:\n{tensor.mul(tensor)}\n”)
    print(f”Element-wise multiplication (syntax sugar):\n{tensor * tensor}\n”)

    矩阵乘法 (仅当维度兼容时)

    tensor 是 4×4, 需要一个 4xN 或 Nx4 的张量进行乘法

    matrix = torch.ones(4, 2)
    print(f”Matrix multiplication (@):\n{tensor @ matrix}\n”)
    print(f”Matrix multiplication (matmul):\n{tensor.matmul(matrix)}\n”)

    矩阵与标量相乘

    print(f”Scalar multiplication:\n{tensor.mul(5)}\n”)
    print(f”Scalar multiplication (syntax sugar):\n{tensor * 5}\n”)
    “`

  • 索引与切片: 与 NumPy 类似。

    python
    tensor = torch.rand(4, 4)
    print(f"First row: {tensor[0]}\n")
    print(f"First column: {tensor[:, 0]}\n")
    print(f"Last column: {tensor[..., -1]}\n")
    tensor[0, 1] = 100 # 修改元素
    print(f"Modified tensor:\n{tensor}\n")

  • 形状变换: view()reshape() 用于改变张量的形状。view() 要求新旧张量在内存中是连续的,而 reshape() 更灵活,如果不连续会复制数据。对于初学者,通常使用 reshape() 更安全。-1 在形状中表示该维度的大小由其他维度和张量总元素数推断得出。

    “`python
    tensor = torch.ones(4, 4)
    print(f”Original shape: {tensor.shape}\n”)

    将 4×4 的张量展平为 16 维向量

    flat_tensor = tensor.view(-1)
    print(f”Flattened tensor:\n{flat_tensor}\n”)
    print(f”Flattened shape: {flat_tensor.shape}\n”)

    将 16 维向量重新塑形为 2×8 的矩阵

    reshaped_tensor = flat_tensor.reshape(2, 8)
    print(f”Reshaped tensor:\n{reshaped_tensor}\n”)
    print(f”Reshaped shape: {reshaped_tensor.shape}\n”)
    “`

  • 连接 (Concatenation): torch.cat() 用于沿现有维度连接张量。torch.stack() 用于沿新维度堆叠张量。

    “`python
    tensor = torch.ones(4, 4)
    tensor_cat_rows = torch.cat([tensor, tensor, tensor], dim=0) # 在第0维(行)上连接
    print(f”Concatenated rows shape: {tensor_cat_rows.shape}\n”) # 得到 12×4

    tensor_cat_cols = torch.cat([tensor, tensor, tensor], dim=1) # 在第1维(列)上连接
    print(f”Concatenated columns shape: {tensor_cat_cols.shape}\n”) # 得到 4×12

    tensor_stack = torch.stack([tensor, tensor, tensor], dim=0) # 在新维度(第0维)上堆叠
    print(f”Stacked shape: {tensor_stack.shape}\n”) # 得到 3x4x4
    “`

  • NumPy 转换: 张量与 NumPy 数组可以方便地互相转换。注意,CPU 上的张量和 NumPy 数组共享底层内存,修改一个会影响另一个。GPU 上的张量转换为 NumPy 数组时会涉及数据复制。

    “`python

    Tensor to NumPy

    tensor = torch.ones(5)
    numpy_array = tensor.numpy()
    print(f”Tensor:\n{tensor}”)
    print(f”NumPy Array:\n{numpy_array}\n”)

    Changes in the tensor reflect in the NumPy array (on CPU)

    tensor.add_(1) # 原地加1
    print(f”Tensor after adding 1:\n{tensor}”)
    print(f”NumPy Array after tensor changes:\n{numpy_array}\n”) # NumPy Array 也改变了

    NumPy to Tensor

    numpy_array = np.ones(5)
    tensor_from_np = torch.from_numpy(numpy_array)
    print(f”NumPy Array:\n{numpy_array}”)
    print(f”Tensor from NumPy:\n{tensor_from_np}\n”)

    Changes in the NumPy array reflect in the tensor (on CPU)

    np.add(numpy_array, 1, out=numpy_array) # 原地加1
    print(f”NumPy Array after adding 1:\n{numpy_array}”)
    print(f”Tensor from NumPy after NumPy changes:\n{tensor_from_np}\n”) # Tensor 也改变了
    “`

3.4 张量与 GPU

将张量移动到 GPU 上非常简单,使用 .to() 方法:

“`python
if torch.cuda.is_available():
device = “cuda”
else:
device = “cpu”

print(f”Using device: {device}\n”)

tensor = torch.ones(5, device=”cpu”) # 默认创建在 CPU
print(f”Tensor on CPU: {tensor.device}\n”)

tensor = tensor.to(device) # 移动到 GPU (如果可用)
print(f”Tensor on {device}: {tensor.device}\n”)

直接在 GPU 上创建张量

tensor_on_gpu = torch.rand(2, 2, device=device)
print(f”Directly created on {device}:\n{tensor_on_gpu}\n”)
print(f”Device of directly created tensor: {tensor_on_gpu.device}\n”)
“`

记住:在 GPU 上执行操作时,所有参与运算的张量必须位于同一个设备上。将张量从 GPU 移回 CPU 同样使用 .to('cpu').cpu()

第四章:自动微分:Autograd

自动微分是 PyTorch 能够训练神经网络的核心。它通过跟踪张量上的所有操作来构建一个计算图,并利用链式法则自动计算梯度。

4.1 requires_grad

默认情况下,张量不计算梯度。要让张量参与梯度计算,需要设置 requires_grad=True。通常,只有模型的权重和偏置等需要通过梯度更新的参数才需要设置 requires_grad=True

“`python
import torch

x = torch.ones(5, requires_grad=True) # 这个张量需要计算梯度
y = torch.zeros(3) # 这个张量不需要计算梯度
print(f”x requires_grad: {x.requires_grad}”)
print(f”y requires_grad: {y.requires_grad}”)
“`

通过张量进行的操作会形成一个计算图。结果张量的 grad_fn 属性记录了创建它的操作(反向传播时会用到)。

“`python
z = x + 2
print(f”z: {z}\n”)
print(f”z.grad_fn: {z.grad_fn}\n”) # z 是通过加法创建的,所以 grad_fn 是

a = z * z * 3
print(f”a: {a}\n”)
print(f”a.grad_fn: {a.grad_fn}\n”) # a 是通过乘法创建的,所以 grad_fn 是

v = a.mean() # 计算平均值,得到一个标量
print(f”v: {v}\n”)
print(f”v.grad_fn: {v.grad_fn}\n”) # v 是通过平均值操作创建的,所以 grad_fn 是

注意:如果一个张量没有任何操作依赖于 requires_grad=True 的张量,它也不需要梯度

b = torch.randn(5) # b 不跟踪梯度
c = b + 2 # c 也不跟踪梯度
print(f”c requires_grad: {c.requires_grad}”)
“`

4.2 计算梯度:.backward()

当计算图构建完成后,对于一个标量输出张量(例如,损失函数的结果),可以通过调用其 .backward() 方法来计算相对于图叶子节点(那些 requires_grad=True 的张量)的梯度。

“`python
v.backward() # 对标量 v 执行反向传播

梯度会累积到对应张量的 .grad 属性中

print(f”Gradient of x with respect to v: {x.grad}\n”)

数学上验证:

v = a.mean() = (1/5) * sum(a)

a = 3 * z * z

z = x + 2

v = (1/5) * sum(3 * (x + 2)^2)

dv/dx_i = (1/5) * d(3 * (x_i + 2)^2) / dx_i

= (1/5) * 3 * 2 * (x_i + 2) * 1

= (6/5) * (x_i + 2)

因为 x = torch.ones(5),所以 x_i = 1

dv/dx_i = (6/5) * (1 + 2) = (6/5) * 3 = 18/5 = 3.6

验证输出:

x = tensor([1., 1., 1., 1., 1.], requires_grad=True)

z = x + 2 = tensor([3., 3., 3., 3., 3.], grad_fn=)

a = z * z * 3 = tensor([27., 27., 27., 27., 27.], grad_fn=)

v = a.mean() = tensor(27., grad_fn=)

x.grad = tensor([3.6000, 3.6000, 3.6000, 3.6000, 3.6000])

结果一致。

“`

如果输出张量是向量(非标量),调用 .backward() 需要传入一个与该张量形状相同的张量作为参数,这个参数代表“梯度向量”(通常是 torch.ones_like(output_tensor)),其作用类似于计算雅可比矩阵与该向量的乘积。但在大多数深度学习场景中,我们计算的是标量损失函数的梯度,所以直接调用 .backward() 即可。

4.3 停止梯度跟踪:torch.no_grad().detach()

在训练过程中,有些操作我们不希望记录到计算图中,例如在评估模型或更新优化器参数时。可以使用 torch.no_grad() 上下文管理器或 .detach() 方法来阻止梯度跟踪。

  • torch.no_grad() 在该上下文管理器内部创建或操作的张量将不会被跟踪梯度。常用于模型评估阶段。

    “`python
    x = torch.ones(5, requires_grad=True)
    print(f”x requires_grad: {x.requires_grad}”)

    with torch.no_grad():
    y = x + 2
    print(f”y requires_grad within no_grad: {y.requires_grad}”) # False

    z = x + 3
    print(f”z requires_grad outside no_grad: {z.requires_grad}”) # True
    “`

  • .detach() 从当前的计算图中分离张量,返回一个 新的 张量,这个新张量不再跟踪梯度,且与原张量共享底层数据(除非形状改变等原因导致复制)。常用于在需要张量的值但不需要梯度的场景,比如在训练循环中记录损失值。

    “`python
    x = torch.ones(5, requires_grad=True)
    print(f”x requires_grad: {x.requires_grad}”)

    y = x.detach() # y 是 x 的一个副本,但不跟踪梯度
    print(f”y requires_grad: {y.requires_grad}”) # False
    print(f”y is x: {y is x}”) # False (y 是新的张量)
    print(f”y == x: {(y == x).all()}”) # True (值相同)

    注意:修改 detach() 后的张量可能会影响原张量(如果共享内存)

    但由于 y 不跟踪梯度,这些修改不会记录在计算图中

    “`

4.4 梯度清零:optimizer.zero_grad()tensor.grad.zero_()

梯度是会累积的。在每次反向传播之前,必须将所有参数的梯度清零,否则本次计算的梯度会与上次计算的梯度累加。通常使用优化器的 zero_grad() 方法来完成这个任务。

“`python

假设 model 是一个神经网络模型

假设 optimizer 是一个优化器

假设 loss 是本次迭代计算的损失

1. 梯度清零

optimizer.zero_grad() # 或者手动对每个需要梯度的参数执行 param.grad.zero_()

2. 前向传播

outputs = model(inputs)
loss = criterion(outputs, labels)

3. 反向传播

loss.backward()

4. 更新参数

optimizer.step()
“`
这一步是训练循环中的关键,将在后面详细介绍。

第五章:构建神经网络:torch.nn

torch.nn 是 PyTorch 中构建神经网络的核心模块。它提供了各种预定义的神经网络层(如全连接层、卷积层、循环层)、激活函数、损失函数等。

nn.Module 是所有神经网络模块(层、整个模型)的基类。一个模型或一层通常继承自 nn.Module,并在 __init__ 方法中定义其子模块或参数,在 forward 方法中定义前向传播的计算过程。

5.1 定义一个简单的神经网络

让我们定义一个简单的全连接神经网络,包含一个输入层、一个隐藏层和一个输出层。

“`python
import torch
import torch.nn as nn
import torch.nn.functional as F # F 模块通常包含函数式的操作,如激活函数、池化等

class SimpleNN(nn.Module):
def init(self, input_size, hidden_size, num_classes):
super(SimpleNN, self).init() # 调用父类构造函数
self.fc1 = nn.Linear(input_size, hidden_size) # 定义第一个全连接层 (输入 -> 隐藏)
self.relu = nn.ReLU() # 定义 ReLU 激活函数
self.fc2 = nn.Linear(hidden_size, num_classes) # 定义第二个全连接层 (隐藏 -> 输出)

def forward(self, x):
    # x 是输入张量
    out = self.fc1(x) # 通过第一个全连接层
    out = self.relu(out) # 应用 ReLU 激活函数
    out = self.fc2(out) # 通过第二个全连接层
    # 对于分类问题,通常在输出层之后不加 softmax,因为交叉熵损失函数 (nn.CrossEntropyLoss) 内部包含了 softmax
    return out

实例化模型

input_size = 784 # 例如,一个展平的 28×28 图像
hidden_size = 128
num_classes = 10 # 例如,10 个类别的分类问题

model = SimpleNN(input_size, hidden_size, num_classes)
print(model) # 打印模型结构
“`

5.2 nn.Module 的核心机制

  • __init__(self, ...) 在这里定义网络中需要学习参数的层或子模块(例如 nn.Linear, nn.Conv2d, nn.BatchNorm2d 等)。这些层被注册为模型的子模块。
  • forward(self, x) 在这里定义输入 x 通过网络各层的计算路径。这是模型的前向传播过程。
  • parameters() nn.Module 的一个重要方法,会返回模型中所有需要学习的参数(那些 requires_grad=True 的张量)。优化器需要这些参数来更新模型。
  • .to(device) 可以方便地将整个模型的所有参数和缓冲区移动到指定设备(CPU 或 GPU)。

“`python

将模型移动到 GPU (如果可用)

if torch.cuda.is_available():
device = torch.device(‘cuda’)
else:
device = torch.device(‘cpu’)

model.to(device)
print(f”\nModel moved to device: {next(model.parameters()).device}\n”) # 检查模型参数所在的设备

打印模型的参数 (会包含 require_grad=True 的所有张量)

print(“Model Parameters:”)
for name, param in model.named_parameters():
if param.requires_grad:
print(f”Layer: {name} | Size: {param.size()} | Requires_grad: {param.requires_grad} | Device: {param.device}”)
“`

第六章:训练的关键:损失函数与优化器

训练神经网络的目标是最小化损失函数,即衡量模型预测结果与真实标签之间差距的函数。优化器的作用是根据损失函数计算出的梯度来更新模型参数,使得损失函数逐渐减小。

6.1 损失函数 (nn.Module 的子类)

PyTorch 在 torch.nn 模块中提供了各种常用的损失函数。它们通常继承自 nn.Module,因此可以像层一样使用,并且通常需要先实例化。

  • nn.CrossEntropyLoss 常用于多类别分类问题。它内部集成了 Softmax 激活函数和负对数似然损失。输入是模型的原始输出(logits),目标是类别索引。
  • nn.MSELoss 均方误差损失,常用于回归问题。
  • nn.BCELoss 二进制交叉熵损失,常用于二分类问题。输入需要经过 Sigmoid 激活。
  • nn.BCEWithLogitsLoss 二进制交叉熵损失,但输入是原始 logits,内部集成了 Sigmoid。更稳定,常用于二分类。

“`python

示例:使用 CrossEntropyLoss

criterion = nn.CrossEntropyLoss()

假设 model_outputs 是模型对一批数据的预测输出 (例如,batch_size x num_classes)

假设 labels 是这批数据的真实类别标签 (例如,batch_size)

model_outputs = torch.randn(64, 10, requires_grad=True) # 示例:64个样本,10个类别

labels = torch.randint(0, 10, (64,)) # 示例:随机生成 64 个 0-9 的标签

计算损失

loss = criterion(model_outputs, labels)

print(f”\nExample Loss: {loss}”)

“`

6.2 优化器 (torch.optim)

torch.optim 模块提供了各种优化算法,如随机梯度下降 (SGD)、Adam、Adagrad 等。优化器需要知道需要更新哪些参数以及学习率等超参数。

  • torch.optim.SGD 随机梯度下降。可以设置动量 (momentum)。
  • torch.optim.Adam Adam 优化器,一种自适应学习率方法,通常效果不错且无需过多调参。
  • torch.optim.AdamW Adam 的改进版,通常用于权重衰减。

实例化优化器时,通常传入 model.parameters() 和学习率。

“`python
import torch.optim as optim

实例化一个 SGD 优化器

optimizer_sgd = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

实例化一个 Adam 优化器

optimizer_adam = optim.Adam(model.parameters(), lr=0.001)

在实际训练中,通常只选择一种优化器

“`

6.3 训练循环的关键步骤回顾

在每次训练迭代(一个 batch)中,典型的训练循环包含以下步骤:

  1. 前向传播: 将输入数据通过模型,得到预测输出。
  2. 计算损失: 使用损失函数比较预测输出和真实标签,得到损失值。
  3. 梯度清零: 清除之前计算的梯度。这是因为 PyTorch 的梯度会累积。
  4. 反向传播: 调用 loss.backward() 计算损失相对于模型参数的梯度。
  5. 参数更新: 调用 optimizer.step() 根据梯度更新模型参数。

我们将在后面的完整示例中看到这些步骤的代码实现。

第七章:数据处理:torch.utils.data

在实际应用中,训练数据通常存储在文件或数据库中。PyTorch 提供了 torch.utils.data 模块来帮助高效地加载和处理数据。

  • Dataset 代表整个数据集。需要继承 torch.utils.data.Dataset 类,并实现两个魔术方法:
    • __len__(self):返回数据集的大小。
    • __getitem__(self, idx):根据索引 idx 获取一个样本(数据和标签)。
  • DataLoader 负责数据的批量加载、打乱 (shuffle) 和并行加载 (num_workers)。它接收一个 Dataset 对象,并提供一个迭代器,每次迭代返回一个批次的数据。

“`python

示例:创建一个简单的自定义 Dataset (用于说明概念,实际应用中数据会从文件加载)

from torch.utils.data import Dataset, DataLoader

class CustomDataset(Dataset):
def init(self, num_samples=100):
# 假设我们有一些随机数据和标签
self.data = torch.randn(num_samples, 10) # 100个样本,每个样本10维
self.labels = torch.randint(0, 2, (num_samples,)) # 100个样本,标签是 0 或 1 (二分类)

def __len__(self):
    return len(self.data)

def __getitem__(self, idx):
    # 返回第 idx 个样本的数据和标签
    sample_data = self.data[idx]
    sample_label = self.labels[idx]
    return sample_data, sample_label

实例化数据集

dataset = CustomDataset(num_samples=1000)
print(f”Dataset size: {len(dataset)}”)

实例化 DataLoader

batch_size = 32
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=0) # shuffle=True 在每个 epoch 开始时打乱数据,num_workers>0 可以使用多进程加载数据

迭代 DataLoader

print(“\nIterating through DataLoader batches:”)
for i, (inputs, labels) in enumerate(dataloader):
print(f”Batch {i}: Inputs shape {inputs.shape}, Labels shape {labels.shape}”)
if i >= 2: # 打印前3个批次作为示例
break
“`

使用 DataLoader 的好处在于:

  • 批量处理: 神经网络通常以批次为单位进行训练。
  • 数据打乱: 防止模型学习数据的顺序,提高泛化能力。
  • 并行加载: 在 CPU 上预加载数据,减少 GPU 等待时间。

第八章:整合实践:一个简单的训练示例

现在,我们将前面介绍的概念整合起来,实现一个完整的、简单的线性回归模型训练过程。

问题: 学习一个线性关系 y = 2x + 1,加入一些噪声。

“`python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader

— 1. 数据准备 —

创建一个简单的线性数据集,带噪声

class LinearDataset(Dataset):
def init(self, num_samples=100):
self.x = torch.randn(num_samples, 1) # 输入 x 是 100×1 的张量
self.y = 2 * self.x + 1 + torch.randn(num_samples, 1) * 0.1 # 目标 y = 2x + 1 + 噪声

def __len__(self):
    return len(self.x)

def __getitem__(self, idx):
    return self.x[idx], self.y[idx]

实例化数据集和 DataLoader

dataset = LinearDataset(num_samples=1000)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)

— 2. 模型定义 —

定义一个简单的线性回归模型

class LinearRegression(nn.Module):
def init(self):
super(LinearRegression, self).init()
self.linear = nn.Linear(1, 1) # 输入特征1个,输出特征1个

def forward(self, x):
    return self.linear(x)

实例化模型

model = LinearRegression()

— 3. 损失函数和优化器 —

对于回归问题,使用均方误差损失

criterion = nn.MSELoss()

使用 SGD 优化器,学习率 0.01

optimizer = optim.SGD(model.parameters(), lr=0.01)

— 4. 训练循环 —

num_epochs = 100

print(“Starting training…”)
for epoch in range(num_epochs):
total_loss = 0
# 迭代 DataLoader 获取每个 batch 的数据
for inputs, targets in dataloader:
# 将数据移动到合适设备(如果使用 GPU)
# inputs, targets = inputs.to(device), targets.to(device)

    # 前向传播
    outputs = model(inputs)
    loss = criterion(outputs, targets)

    # 梯度清零
    optimizer.zero_grad()

    # 反向传播
    loss.backward()

    # 参数更新
    optimizer.step()

    total_loss += loss.item() # 累加每个 batch 的损失

# 打印每个 epoch 的平均损失
avg_loss = total_loss / len(dataloader)
if (epoch + 1) % 10 == 0 or epoch == 0: # 每10个epoch打印一次,以及第一个epoch
    print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}')

print(“Training finished!”)

— 5. 查看学习到的参数 —

print(“\nLearned parameters:”)

model.linear 是 nn.Linear 模块的实例

weight 是 W (权重), bias 是 b (偏置)

由于是 y = wx + b,我们期望 W 接近 2,b 接近 1

for name, param in model.named_parameters():
if param.requires_grad:
print(f”{name}: {param.data.numpy()}”) # .data 获取张量值,.numpy() 转为 numpy 方便打印
“`

运行这段代码,你会看到损失值在逐渐下降,并且最终学习到的 linear.weight 应该接近 2,linear.bias 应该接近 1。

这个示例包含了深度学习模型训练的全部核心步骤:数据加载、模型定义、损失函数、优化器以及训练循环。虽然模型和数据非常简单,但这个流程适用于更复杂的问题和网络结构。

第九章:利用 GPU 加速计算

在深度学习中,GPU 加速是至关重要的。PyTorch 使得利用 GPU 变得非常容易。

核心方法是使用 .to(device)device 可以是 'cpu''cuda' (默认是 cuda:0)。

要使用 GPU,你需要:

  1. 检查 CUDA 是否可用: torch.cuda.is_available()
  2. 定义设备: device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  3. 将模型移动到设备: model.to(device)
  4. 将数据移动到设备: 在训练循环中,将每个 batch 的输入和标签移动到设备:inputs, targets = inputs.to(device), targets.to(device)

重要: 所有参与计算的张量和模型必须在同一个设备上。如果你有多个 GPU,可以使用 torch.cuda.set_device(gpu_id) 指定当前使用的 GPU,或者使用 nn.DataParallel 来分布式训练。对于初学者,先专注于单 GPU 使用即可。

修改上面的线性回归示例以支持 GPU:

“`python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader

— 设置设备 —

device = torch.device(‘cuda’ if torch.cuda.is_available() else ‘cpu’)
print(f”Using device: {device}”)

— 数据准备 (同上) —

class LinearDataset(Dataset):
def init(self, num_samples=100):
self.x = torch.randn(num_samples, 1)
self.y = 2 * self.x + 1 + torch.randn(num_samples, 1) * 0.1

def __len__(self):
    return len(self.x)

def __getitem__(self, idx):
    return self.x[idx], self.y[idx]

dataset = LinearDataset(num_samples=1000)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)

— 模型定义 (同上) —

class LinearRegression(nn.Module):
def init(self):
super(LinearRegression, self).init()
self.linear = nn.Linear(1, 1)

def forward(self, x):
    return self.linear(x)

实例化模型 并移动到设备

model = LinearRegression().to(device)

— 损失函数和优化器 (同上) —

criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

— 训练循环 (修改数据移动到设备) —

num_epochs = 100

print(“Starting training…”)
for epoch in range(num_epochs):
total_loss = 0
for inputs, targets in dataloader:
# 将数据移动到设备
inputs = inputs.to(device)
targets = targets.to(device)

    # 前向传播
    outputs = model(inputs)
    loss = criterion(outputs, targets)

    # 梯度清零
    optimizer.zero_grad()

    # 反向传播
    loss.backward()

    # 参数更新
    optimizer.step()

    # loss 是一个位于 device 上的张量,.item() 将其转为 Python 数字
    total_loss += loss.item()

avg_loss = total_loss / len(dataloader)
if (epoch + 1) % 10 == 0 or epoch == 0:
    print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}')

print(“Training finished!”)

— 查看学习到的参数 (先将模型移回 CPU 或将参数复制到 CPU numpy) —

print(“\nLearned parameters:”)

如果模型在 GPU 上,需要将其移回 CPU 或复制参数到 CPU

model.to(‘cpu’) # 将整个模型移回 CPU
for name, param in model.named_parameters():
if param.requires_grad:
print(f”{name}: {param.data.numpy()}”)
“`

将模型和数据移动到 GPU 后,计算将在 GPU 上执行,显著提高训练速度,特别是对于大型模型和数据集。

第十章:模型的保存与加载

训练好的模型需要保存以便后续使用(如进行推理或继续训练)。PyTorch 通常推荐保存和加载模型的 状态字典 (state_dict),而不是整个模型对象。状态字典是一个 Python 字典,存储了模型所有参数(权重和偏置)以及其他缓冲区 (buffers) 的状态。

10.1 保存模型

“`python

假设 model 是已经训练好的模型

指定保存路径

model_path = “linear_regression_model.pth”

保存模型的状态字典

torch.save(model.state_dict(), model_path)

print(f”\nModel state_dict saved to {model_path}”)
“`

.pth.pt 是 PyTorch 模型文件的常用后缀。

10.2 加载模型

加载模型需要先实例化一个与保存模型时具有相同结构的模型对象,然后加载状态字典到这个新创建的模型中。

“`python

首先,实例化一个与保存模型时具有相同结构的空白模型

loaded_model = LinearRegression() # 确保结构与训练时一致

加载状态字典

loaded_model.load_state_dict(torch.load(model_path))

将加载的模型设置为评估模式 (dropout 和 batchnorm 等层在训练和评估模式下行为不同)

loaded_model.eval() # 切换到评估模式

print(“\nModel loaded successfully!”)

可以在加载的模型上进行推理

例如,预测一个新输入的值

new_x = torch.tensor([[5.0]]) # 注意输入形状和数据类型与训练时一致

在评估模式下,通常不需要计算梯度,可以使用 torch.no_grad()

with torch.no_grad():
prediction = loaded_model(new_x)

print(f”Prediction for x=5.0: {prediction.item()}”)

如果需要在 GPU 上加载和推理,确保模型和数据都在 GPU 上

loaded_model.to(device)

new_x = new_x.to(device)

with torch.no_grad():

prediction = loaded_model(new_x)

“`

为什么推荐保存/加载 state_dict?

  • 更灵活:你可以只加载部分参数,方便进行迁移学习。
  • 更安全:保存整个模型对象时,文件会包含模型的类定义,这使得在不同项目或不同版本的 PyTorch 中加载时可能出错。保存 state_dict 只保存参数数据。

保存整个模型对象:

虽然不推荐,但也可以保存和加载整个模型对象:

“`python

保存整个模型对象

torch.save(model, “entire_model.pth”)

加载整个模型对象

loaded_entire_model = torch.load(“entire_model.pth”)

loaded_entire_model.eval()

注意:加载整个模型对象时,需要确保模型类的定义在当前环境中是可用的。

“`

第十一章:PyTorch 生态与进阶方向

PyTorch 拥有一个不断壮大的生态系统,提供各种工具和库来支持深度学习的各个方面:

  • torchvision 包含了流行的数据集(如 MNIST, ImageNet)、模型架构(如 ResNet, VGG)以及图像转换工具。
  • torchaudio 提供了音频数据集、模型和音频处理工具。
  • torchtext 用于自然语言处理任务的数据集、模型和文本处理工具(目前处于维护模式,推荐使用第三方库如 Hugging Face 的 transformers)。
  • TorchScript: 用于将 PyTorch 模型转换为可序列化、可优化并在 C++ 环境中运行的静态图表示,方便生产部署。
  • PyTorch Mobile: 将 PyTorch 模型部署到移动设备上。
  • PyTorch Ecosystem Projects: 社区贡献的各种库,涵盖强化学习、图神经网络、概率编程等。

入门 PyTorch 后,可以进一步学习:

  • 更复杂的网络架构: 卷积神经网络 (CNN) 用于图像,循环神经网络 (RNN) / Transformer 用于序列数据。
  • 迁移学习: 利用预训练模型解决新任务。
  • 数据增强: 提高模型的泛化能力。
  • 模型评估指标: 除了损失函数,还有准确率、精确率、召回率、F1 分数等。
  • 可视化工具: 如 TensorBoard (与 PyTorch 集成良好)。
  • 分布式训练: 在多个 GPU 或多台机器上训练模型。
  • 模型部署: 使用 TorchScript 等工具将模型部署到生产环境。

第十二章:总结与展望

恭喜你完成了 PyTorch 入门的核心内容学习!我们从 PyTorch 的基础——张量开始,深入了解了其核心特性 Autograd,学习了如何构建神经网络模型 nn.Module,掌握了训练所需的损失函数和优化器,了解了数据加载 DataLoader,并通过一个完整的线性回归示例将这些概念串联起来。我们还学习了如何在 GPU 上加速计算以及如何保存和加载模型。

PyTorch 以其易用性、灵活性和强大的功能,已经成为深度学习领域的主流框架之一。本文为你打开了通往 PyTorch 世界的大门,但这仅仅是一个开始。深度学习是一个充满活力且快速发展的领域,不断学习和实践是掌握它的关键。

下一步行动建议:

  1. 动手实践: 运行本文中的代码示例,尝试修改参数,观察结果。
  2. 挑战更复杂的任务: 尝试使用 PyTorch 实现一个简单的图像分类器(如 MNIST 数据集),或一个简单的文本分类器。
  3. 阅读官方文档和教程: PyTorch 官方文档非常详细且不断更新,是最好的学习资源之一。官方教程涵盖了各种主题和应用领域。
  4. 学习经典模型: 了解并尝试实现一些经典的神经网络模型架构。
  5. 参与社区: 加入 PyTorch 论坛、GitHub 仓库或相关的社区,与其他学习者交流,获取帮助。

深度学习之旅充满挑战,但也充满乐趣和成就感。希望本文能为你提供一个坚实的基础,助你在探索 PyTorch 和深度学习的道路上越走越远!祝你学习顺利!


发表评论

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

滚动至顶部