深度学习框架 PyTorch 入门指南 – wiki基地


PyTorch 入门指南:从零开始构建深度学习之旅

深度学习作为人工智能的核心技术,在计算机视觉、自然语言处理、语音识别等众多领域取得了令人瞩目的成就。而支撑这些技术突破的,是高效且灵活的深度学习框架。在众多框架中,PyTorch 以其“Pythonic”(Pythonic,意即符合 Python 语言习惯,使用起来自然流畅)的特性、动态计算图的灵活性以及强大的社区支持,迅速成为学术界和工业界都青睐的选择。

本篇文章将作为一份详细的 PyTorch 入门指南,带你从零开始,一步步探索 PyTorch 的核心概念和基本用法,为你的深度学习之旅打下坚实基础。无论你是否有其他深度学习框架的经验,本文都力求让你能够轻松上手。

文章大纲:

  1. 引言:为什么选择 PyTorch?
  2. 安装 PyTorch
  3. PyTorch 的核心构建块:张量(Tensor)
    • 什么是张量?
    • 创建张量
    • 张量的属性
    • 张量操作
    • 张量与 NumPy 互转
    • CPU 与 GPU 之间的张量转移
  4. 自动微分:Autograd
    • 什么是自动微分?为什么需要它?
    • 启用梯度追踪
    • 计算梯度
    • 访问梯度
    • 停止梯度追踪
  5. 构建神经网络模块:torch.nn
    • torch.nn 简介
    • 定义神经网络模型 (nn.Module)
    • 常用层介绍 (Linear, ReLU, Sequential 等)
    • 损失函数 (nn.CrossEntropyLoss, nn.MSELoss 等)
    • 优化器 (torch.optim)
  6. 简单的训练流程示例
    • 准备数据
    • 定义模型
    • 定义损失函数和优化器
    • 编写训练循环
  7. 数据加载与预处理:torch.utils.data
    • Dataset 抽象
    • DataLoader 抽象
  8. GPU 加速:利用硬件提升性能
  9. 总结与展望

1. 引言:为什么选择 PyTorch?

在 TensorFlow、Keras、Caffe 等众多深度学习框架中,PyTorch 异军突起,广受好评。这主要归功于它的几个显著优点:

  • Pythonic 特性: PyTorch 的 API 设计非常符合 Python 语言的直觉,易于学习和使用。你可以像编写普通的 Python 代码一样构建和调试模型。
  • 动态计算图 (Dynamic Computation Graph): PyTorch 使用动态计算图,这意味着计算图是在运行时构建的。这使得模型构建更加灵活,尤其是在处理变长序列(如 RNN)、条件控制流或进行模型调试时,它能提供更好的交互性和易用性。相比之下,早期版本的 TensorFlow 使用静态图,需要先定义整个图结构再运行。
  • 易于调试: 由于动态图的特性,你可以像调试普通 Python 程序一样使用 pdb 或 print 语句来检查中间变量和图结构,这大大提高了调试效率。
  • 强大的 GPU 加速: PyTorch 对 GPU 有着一流的支持,能够充分利用现代 GPU 的并行计算能力,显著加速模型训练。
  • 丰富的生态系统和社区支持: PyTorch 拥有活跃的社区,提供了大量的教程、预训练模型和扩展库(如 TorchVision, TorchText, TorchAudio 等),极大地便利了开发工作。
  • 研究友好: 动态图和灵活的 API 使 PyTorch 成为学术研究和原型开发的理想选择,能够快速实验新的模型结构和算法。

总而言之,如果你追求开发效率、易于调试以及研究的灵活性,PyTorch 是一个非常优秀的选择。

2. 安装 PyTorch

安装 PyTorch 通常非常简单,官方网站提供了交互式的安装指令生成器。推荐使用 Conda 或 pip 进行安装,它们能够很好地管理依赖。

访问 PyTorch 官网 (https://pytorch.org/),选择你的操作系统、包管理器、PyTorch 版本、CUDA 版本(如果你有 NVIDIA GPU)等,它会为你生成相应的安装命令。

使用 Conda 安装(推荐):

Conda 是一个跨平台的包管理器和环境管理器,可以有效避免不同项目之间的依赖冲突。

  1. 首先,确保你已经安装了 Anaconda 或 Miniconda。
  2. 打开终端或命令提示符。
  3. 创建一个新的 Conda 环境(可选,但推荐):
    bash
    conda create -n my_pytorch_env python=3.9
    conda activate my_pytorch_env

    (这里的 my_pytorch_env 是环境名称,python=3.9 是指定的 Python 版本,你可以根据需要修改)
  4. 根据 PyTorch 官网生成的命令进行安装。例如,安装稳定版、支持 CUDA 11.8 的命令可能是:
    bash
    conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia

    如果你没有 GPU 或者不需要 GPU 加速,安装 CPU 版本即可:
    bash
    conda install pytorch torchvision torchaudio cpuonly -c pytorch

使用 pip 安装:

Pip 是 Python 的官方包管理器。

  1. 打开终端或命令提示符。
  2. 根据 PyTorch 官网生成的命令进行安装。例如,安装稳定版、支持 CUDA 11.8 的命令可能是:
    bash
    pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

    安装 CPU 版本:
    bash
    pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu

验证安装:

安装完成后,打开 Python 解释器或运行一个 Python 脚本,输入以下代码:

python
import torch
print(torch.__version__)
print(torch.cuda.is_available()) # 检查 GPU 是否可用

如果能够正确输出 PyTorch 版本号,并且 torch.cuda.is_available() 返回 True (如果你安装了 GPU 版本并且硬件支持),则说明安装成功。

3. PyTorch 的核心构建块:张量(Tensor)

张量是 PyTorch 中最基本的数据结构,它是各种数学运算的载体,也是模型输入、输出以及参数的表示形式。你可以将张量理解为多维数组,类似于 NumPy 的 ndarray,但张量额外提供了对 GPU 加速和自动微分的支持。

什么是张量?

在数学上,张量是矢量和矩阵的推广。
* 标量(Scalar)是一个 0 维张量。
* 矢量(Vector)是一个 1 维张量。
* 矩阵(Matrix)是一个 2 维张量。
* 具有三个或更多维度的数组统称为张量。

在深度学习中,图像通常表示为 3 维张量(通道、高、宽),一批图像则表示为 4 维张量(批量大小、通道、高、宽)。

创建张量

PyTorch 提供了多种创建张量的方法:

1. 直接从 Python 列表或 NumPy 数组创建:

“`python
import torch
import numpy as np

从列表创建

data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)
print(f”从列表创建: \n{x_data}\n”)

从 NumPy 数组创建

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

2. 创建具有特定形状的张量:

“`python

创建一个指定形状的张量,其中的元素未初始化(值不确定)

x_uninitialized = torch.empty(5, 3)
print(f”未初始化张量: \n{x_uninitialized}\n”)

创建一个指定形状的全零张量

x_zeros = torch.zeros(5, 3, dtype=torch.long) # 可以指定数据类型
print(f”全零张量: \n{x_zeros}\n”)

创建一个指定形状的全一张量

x_ones = torch.ones(5, 3, dtype=torch.float32)
print(f”全一张量: \n{x_ones}\n”)
“`

3. 创建随机张量:

“`python

创建一个指定形状的张量,元素服从均匀分布 [0, 1)

x_rand_uniform = torch.rand(5, 3)
print(f”均匀分布随机张量: \n{x_rand_uniform}\n”)

创建一个指定形状的张量,元素服从标准正态分布(均值0,方差1)

x_rand_normal = torch.randn(5, 3)
print(f”正态分布随机张量: \n{x_rand_normal}\n”)
“`

4. 基于现有张量创建新张量:

新张量会保留现有张量的形状和数据类型等属性,但数据是新的。

“`python
x_data = torch.tensor([[1, 2], [3, 4]])

创建一个与 x_data 形状、数据类型相同,但全为1的张量

x_ones_like = torch.ones_like(x_data)
print(f”与 x_data 形状相同的全一张量: \n{x_ones_like}\n”)

创建一个与 x_data 形状、数据类型相同,但为随机数的张量

x_rand_like = torch.rand_like(x_data, dtype=torch.float) # 也可以修改数据类型
print(f”与 x_data 形状相同的随机张量: \n{x_rand_like}\n”)
“`

张量的属性

张量有几个重要的属性:

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

print(f”张量的形状 (Shape): {tensor.shape}”)
print(f”张量的数据类型 (Data type): {tensor.dtype}”)
print(f”张量存储的设备 (Device): {tensor.device}”) # 会显示 ‘cpu’ 或 ‘cuda:0’ 等
“`

  • shape: 张量的维度大小,如 torch.Size([3, 4])
  • dtype: 张量元素的数据类型,如 torch.float32, torch.long 等。默认浮点型是 float32
  • device: 张量所在的设备,'cpu' 表示 CPU,'cuda:0' 表示第一个 GPU。

张量操作

张量支持非常丰富的操作,包括数学运算、索引、切片、变形等。

1. 数学运算:

张量之间的数学运算(加、减、乘、除等)默认是逐元素的(element-wise),前提是它们的形状兼容(符合广播机制)。

“`python
tensor = torch.ones(4, 4)
tensor[:,1] = 0 # 修改一列的值
print(f”原始张量:\n{tensor}\n”)

逐元素相加

tensor_sum = tensor + tensor
print(f”张量相加 (逐元素):\n{tensor_sum}\n”)

另一种逐元素相加方式

tensor_sum_alt = torch.add(tensor, tensor)
print(f”torch.add:\n{tensor_sum_alt}\n”)

逐元素相乘

tensor_mul = tensor * tensor
print(f”张量相乘 (逐元素):\n{tensor_mul}\n”)

另一种逐元素相乘方式

tensor_mul_alt = torch.mul(tensor, tensor)
print(f”torch.mul:\n{tensor_mul_alt}\n”)

矩阵乘法 (@ 或 torch.matmul)

注意:矩阵乘法要求内部维度匹配,这里 (4, 4) @ (4, 4) 是合法的

matrix_mul = tensor.matmul(tensor.T) # tensor.T 是转置
print(f”矩阵乘法:\n{matrix_mul}\n”)

另一种矩阵乘法方式

matrix_mul_alt = torch.matmul(tensor, tensor.T)
print(f”torch.matmul:\n{matrix_mul_alt}\n”)
“`

2. 索引与切片:

张量的索引和切片与 NumPy 类似。

“`python
tensor = torch.arange(16).reshape(4, 4) # 创建一个 4×4 的张量,元素为 0 到 15
print(f”原始张量:\n{tensor}\n”)

访问单个元素 (Python 风格索引)

print(f”访问 [1, 2] 的元素: {tensor[1, 2]}\n”) # 输出 6

切片操作

print(f”切片 第一行: {tensor[0, :]}”)
print(f”切片 第二列: {tensor[:, 1]}”)
print(f”切片 子区域 [0:2, 1:3]:\n{tensor[0:2, 1:3]}\n”)

使用列表或张量进行高级索引

indices = torch.tensor([0, 2])
print(f”高级索引 (行 0 和 2):\n{tensor[indices]}\n”)

mask = tensor > 5 # 创建一个布尔张量作为掩码
print(f”布尔掩码:\n{mask}\n”)
print(f”使用布尔掩码索引:\n{tensor[mask]}\n”) # 结果是一个 1 维张量
“`

3. 形状变换 (Reshaping):

改变张量的形状但保持元素不变。

“`python
tensor = torch.arange(12) # 创建一个 12 元素的 1 维张量
print(f”原始张量: {tensor}\n形状: {tensor.shape}\n”)

reshape

tensor_reshaped = tensor.reshape(3, 4)
print(f”reshape 到 (3, 4):\n{tensor_reshaped}\n形状: {tensor_reshaped.shape}\n”)

view (如果可能,view 共享底层数据,reshape 不一定)

tensor_view = tensor.view(4, 3)
print(f”view 到 (4, 3):\n{tensor_view}\n形状: {tensor_view.shape}\n”)

展平 (flatten)

tensor_flattened = tensor_reshaped.flatten()
print(f”展平后的张量:\n{tensor_flattened}\n形状: {tensor_flattened.shape}\n”)

squeeze/unsqueeze (移除/添加维度大小为 1 的维度)

tensor_1d = torch.tensor([1, 2, 3])
tensor_2d_unsqueeze = tensor_1d.unsqueeze(0) # 在第 0 维增加一个维度
print(f”unsqueeze(0):\n{tensor_2d_unsqueeze}\n形状: {tensor_2d_unsqueeze.shape}\n”) # 形状 (1, 3)

tensor_2d_squeeze = tensor_2d_unsqueeze.squeeze(0) # 移除第 0 维
print(f”squeeze(0):\n{tensor_2d_squeeze}\n形状: {tensor_2d_squeeze.shape}\n”) # 形状 (3,)
“`

4. 其他常用操作:

“`python
tensor = torch.tensor([[1., 2.], [3., 4.]])

print(f”求和 (所有元素): {torch.sum(tensor)}”)
print(f”求和 (沿行): {torch.sum(tensor, dim=0)}”) # [1+3, 2+4] = [4, 6]
print(f”求和 (沿列): {torch.sum(tensor, dim=1)}”) # [1+2, 3+4] = [3, 7]

print(f”均值 (所有元素): {torch.mean(tensor)}”)
print(f”最大值 (所有元素): {torch.max(tensor)}”)
print(f”最大值及其索引 (沿列): {torch.max(tensor, dim=1)}”) # Returns (values, indices)
“`

张量与 NumPy 互转

PyTorch 张量和 NumPy 数组可以方便地相互转换。注意,它们共享底层内存(如果张量在 CPU 上),这意味着修改一个会影响另一个。

“`python

Tensor 转 NumPy

tensor = torch.ones(5)
numpy_array = tensor.numpy()
print(f”Tensor 转 NumPy: {numpy_array}”)
print(type(numpy_array))

修改 Tensor, NumPy 也会改变

tensor.add_(1) # _ 后缀表示in-place operation (原地操作)
print(f”修改 Tensor 后, NumPy: {numpy_array}”)

NumPy 转 Tensor

numpy_array = np.ones(5)
tensor_from_numpy = torch.from_numpy(numpy_array)
print(f”NumPy 转 Tensor: {tensor_from_numpy}”)
print(type(tensor_from_numpy))

修改 NumPy, Tensor 也会改变

np_array += 1
print(f”修改 NumPy 后, Tensor: {tensor_from_numpy}”)
“`

CPU 与 GPU 之间的张量转移

将张量移动到 GPU 可以显著加速计算。前提是你的机器有 NVIDIA GPU 并正确安装了支持 CUDA 的 PyTorch 版本。

“`python

检查 GPU 是否可用

if torch.cuda.is_available():
device = torch.device(“cuda”)
print(f”GPU ‘{torch.cuda.get_device_name(device)}’ 可用”)
else:
device = torch.device(“cpu”)
print(“GPU 不可用,使用 CPU”)

在 CPU 上创建一个张量

tensor_cpu = torch.randn(3, 4)
print(f”张量在设备上: {tensor_cpu.device}”)

将张量移动到 GPU

if torch.cuda.is_available():
tensor_gpu = tensor_cpu.to(device)
print(f”张量移动到 GPU: {tensor_gpu.device}”)

# 在 GPU 上进行计算
result_gpu = tensor_gpu * 2
print(f"在 GPU 上计算结果:\n{result_gpu}\n设备: {result_gpu.device}")

# 将结果移回 CPU
result_cpu = result_gpu.to("cpu")
print(f"结果移回 CPU:\n{result_cpu}\n设备: {result_cpu.device}")

else:
print(“无法在 GPU 上进行示例操作,因为 GPU 不可用。”)

注意:模型和数据都需要在同一个设备上进行计算

例如:model.to(device) 和 inputs.to(device)

“`

重要提示: 进行计算时,参与运算的所有张量必须位于 同一个 设备上(要么都在 CPU,要么都在同一个 GPU)。

4. 自动微分:Autograd

深度学习模型通过梯度下降等优化算法来学习参数。这些算法需要计算模型输出关于模型参数的梯度。PyTorch 的 autograd 模块提供了自动微分功能,极大地简化了这一过程。你不需要手动推导复杂的求导公式。

什么是自动微分?为什么需要它?

自动微分是一种算法技术,用于精确计算函数在某个点上的导数。在深度学习中,我们需要计算损失函数对模型参数的梯度,以便通过反向传播算法更新参数。手动计算这些梯度对于大型复杂的神经网络来说几乎是不可能的,而 autograd 就是为此而生。

autograd 记录了张量的所有操作,并构建一个计算图(computation graph)。在前向传播时,它记录下每一步计算;在调用 .backward() 方法时,它根据这个计算图从输出张量反向计算所有需要梯度的张量的梯度。

启用梯度追踪

默认情况下,张量不追踪其计算历史和梯度。要启用梯度追踪,可以在创建张量时设置 requires_grad=True,或者在张量创建后使用 tensor.requires_grad_(True) 方法。

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

y = torch.zeros(5)
print(f”y: {y}, requires_grad: {y.requires_grad}”)
y.requires_grad_(True) # 使用原地方法设置
print(f”y: {y}, requires_grad: {y.requires_grad}”)

通过操作创建的张量,如果输入的张量有 requires_grad=True,输出张量默认也会追踪梯度

z = x + y
print(f”z = x + y: {z}, requires_grad: {z.requires_grad}”)

如果输入张量都不追踪梯度,输出张量也不会

a = torch.ones(5)
b = torch.zeros(5)
c = a + b
print(f”c = a + b: {c}, requires_grad: {c.requires_grad}”)
“`

计算梯度

假设我们有一个简单的函数 out = x * w + b,我们想计算 out 关于参数 wb 的梯度。

“`python
import torch

创建输入和参数张量

x = torch.tensor([1.0, 2.0, 3.0, 4.0], requires_grad=True)
w = torch.tensor([1.0, 1.0, 1.0, 1.0], requires_grad=True)
b = torch.tensor(1.0, requires_grad=True)

执行前向传播

out = x * w + b
print(f”out: {out}”) # [2.0, 3.0, 4.0, 5.0]

为了计算梯度,我们需要一个标量的输出。通常我们计算损失函数,损失函数通常是标量。

在这里,我们假设 out 的所有元素之和是我们的“损失”

loss = out.sum()
print(f”loss (sum of out): {loss}”) # 2.0 + 3.0 + 4.0 + 5.0 = 14.0

执行反向传播

计算 loss 对所有 requires_grad=True 的张量的梯度

loss.backward()

访问梯度

loss = sum(xi * wi + b)

d(loss) / d(w_i) = d(sum(xi * wi + b)) / d(w_i) = xi

d(loss) / d(b) = d(sum(xi * wi + b)) / d(b) = sum(1) = len(x)

print(f”Gradient of loss with respect to x: {x.grad}”) # Should be [1.0, 1.0, 1.0, 1.0] (d(sum(out))/dx = d(sum(xw+b))/dx = w)
print(f”Gradient of loss with respect to w: {w.grad}”) # Should be [1.0, 2.0, 3.0, 4.0] (d(sum(x
w+b))/dw = x)
print(f”Gradient of loss with respect to b: {b.grad}”) # Should be 4.0 (d(sum(x*w+b))/db = sum(1) = 4)
“`

重要概念:

  • .backward(): 这个方法是触发反向传播的关键。它根据当前的计算图计算叶子节点(那些 requires_grad=True 且不是由其他操作生成的张量,通常是模型的参数)的梯度。如果计算结果是标量,loss.backward() 不需要参数。如果结果是非标量张量,你需要传入一个与该张量形状相同的张量(称为 gradient 参数或 grad_output 参数),表示对该输出张量的“梯度”;实际上,这相当于计算 loss = torch.sum(output * gradient) 对输入的梯度。在训练中,通常处理的是标量损失,所以直接调用 .backward() 即可。
  • .grad: 这是存储计算得到的梯度的属性。它是一个张量,与对应的叶子张量形状相同。在每次进行新的反向传播之前,你应该将 .grad 属性清零,否则梯度会累加。
  • .zero_grad(): 用于将叶子张量的 .grad 属性清零。这是训练循环中非常重要的一步,通常在调用优化器的 .step() 方法之前或之后执行。

停止梯度追踪

有时我们不需要计算梯度的操作,例如在进行模型评估或冻结部分模型参数时。有几种方法可以停止梯度追踪:

  1. torch.no_grad() 上下文管理器: 这是最常用的方法,特别是在推理阶段。在该上下文管理器中的所有操作都不会追踪梯度。

    “`python
    x = torch.tensor([1.0, 2.0], requires_grad=True)
    print(f”x requires_grad: {x.requires_grad}”) # True

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

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

  2. .detach() 方法: 返回一个新的张量,该张量与原张量共享数据,但不追踪梯度。它从计算图中“分离”出来。

    “`python
    x = torch.tensor([1.0, 2.0], requires_grad=True)
    y = x * 2
    z = y.detach()

    print(f”y requires_grad: {y.requires_grad}”) # True
    print(f”z (detached from y) requires_grad: {z.requires_grad}”) # False

    修改 z 不会影响 x 的梯度计算,因为它们已经分离

    z[0] = 100
    print(f”x: {x}”)
    print(f”y: {y}”)
    print(f”z: {z}”)

    如果对 y 调用 backward,会计算到 x 的梯度

    y.sum().backward()

    print(x.grad) # [2.0, 2.0]

    对 z 调用 backward 会报错,因为它不追踪梯度

    z.sum().backward() # RuntimeError

    “`

  3. .requires_grad_(False) 方法: 直接改变张量的 requires_grad 属性(原地操作)。

    python
    x = torch.tensor([1.0, 2.0], requires_grad=True)
    x.requires_grad_(False)
    print(f"x requires_grad after requires_grad_(False): {x.requires_grad}") # False

5. 构建神经网络模块:torch.nn

torch.nn 模块是 PyTorch 构建和训练神经网络的核心模块。它提供了各种神经网络层(Modules)、损失函数和一些实用工具。

torch.nn 简介

torch.nn 中的大部分组件都是 nn.Module 的子类。一个 nn.Module 可以包含其他 nn.Module(构成嵌套结构),也可以包含张量(如模型参数)。模型的整个结构就是一个大的 nn.Module

定义神经网络模型 (nn.Module)

构建自定义神经网络通常需要继承 nn.Module 类,并在 __init__ 方法中定义网络的层,在 forward 方法中定义数据在前向传播过程中的流动方式。

“`python
import torch
import torch.nn as nn
import torch.nn.functional as F # 函数式接口,如激活函数

class MySimpleNN(nn.Module):
def init(self, input_size, hidden_size, num_classes):
super(MySimpleNN, self).init() # 调用父类构造函数
# 定义网络层,它们是 nn.Module 的子类
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):
    # 定义数据的前向传播过程
    out = self.fc1(x) # 数据通过第一个全连接层
    out = self.relu(out) # 通过激活函数
    out = self.fc2(out) # 通过第二个全连接层
    return out

实例化模型

input_size = 10
hidden_size = 20
num_classes = 5
model = MySimpleNN(input_size, hidden_size, num_classes)

print(“模型结构:”)
print(model)

模型的参数会自动追踪梯度

print(“\n模型参数 (带 requires_grad=True):”)
for name, param in model.named_parameters():
if param.requires_grad:
print(f”{name}: {param.shape}”)

假设有一个输入数据 (批量大小 x 输入特征数)

dummy_input = torch.randn(64, input_size) # Batch size = 64

将数据通过模型进行前向传播

output = model(dummy_input)
print(f”\n输入形状: {dummy_input.shape}”)
print(f”输出形状: {output.shape}”) # Batch size x num_classes (64, 5)
“`

常用层介绍

torch.nn 提供了各种预定义的层:

  • nn.Linear(in_features, out_features): 全连接层(或称为线性层、密集层)。对输入应用线性变换:y = xW^T + b
  • nn.Conv1d, nn.Conv2d, nn.Conv3d: 卷积层,用于处理序列、图像、视频等网格状数据。
  • nn.MaxPool1d, nn.MaxPool2d, nn.MaxPool3d: 最大池化层,用于下采样。
  • nn.ReLU, nn.Sigmoid, nn.Tanh, nn.Softmax 等: 激活函数。注意,nn.functional 也提供了函数式接口,例如 F.relu(x)
  • nn.LSTM, nn.GRU, nn.RNN: 循环神经网络层,用于处理序列数据。
  • nn.BatchNorm1d, nn.BatchNorm2d 等: 批量归一化层,有助于稳定训练。
  • nn.Dropout: Dropout 层,用于正则化,防止过拟合。
  • nn.Sequential(*layers): 一个容器,按顺序将多个模块组合成一个更大的模块。当模型是一个简单的、顺序的层堆叠时非常方便。

“`python

使用 nn.Sequential 构建一个简单的网络

model_sequential = nn.Sequential(
nn.Linear(10, 20),
nn.ReLU(),
nn.Linear(20, 5),
nn.Sigmoid() # 假设最后一层需要 sigmoid 输出概率
)

print(“\nSequential 模型结构:”)
print(model_sequential)

dummy_input = torch.randn(64, 10)
output_seq = model_sequential(dummy_input)
print(f”\nSequential 模型输出形状: {output_seq.shape}”)
“`

损失函数 (nn.CrossEntropyLoss, nn.MSELoss 等)

损失函数(或称准则 criterion)用于衡量模型的输出与真实标签之间的差异。torch.nn 提供了许多常用的损失函数:

  • nn.CrossEntropyLoss(): 常用于多分类问题,结合了 LogSoftmax 和负对数似然损失 (NLLLoss)。输入是模型的原始输出(通常是 logits),目标是类别索引。
  • nn.MSELoss(): 均方误差损失,常用于回归问题。
  • nn.L1Loss(): 平均绝对误差损失,常用于回归问题。
  • nn.BCELoss(): 二元交叉熵损失,常用于二分类问题。输入是经过 sigmoid 激活后的概率,目标是 0 或 1。
  • nn.BCEWithLogitsLoss(): 结合了 Sigmoid 和 BCELoss,输入是原始 logits,目标是 0 或 1。推荐用于二分类,因为它在数值上更稳定。

“`python

示例:使用交叉熵损失

loss_fn = nn.CrossEntropyLoss()

假设模型输出了一个批量大小为 3 的数据,有 5 个类别

dummy_output = torch.randn(3, 5) # 模型的原始输出 (logits)

对应的真实类别标签 (每个样本一个类别索引)

dummy_labels = torch.tensor([1, 4, 0]) # 批量大小为 3

loss = loss_fn(dummy_output, dummy_labels)
print(f”\n交叉熵损失示例: {loss}”)
“`

优化器 (torch.optim)

优化器负责根据计算得到的梯度来更新模型的参数。torch.optim 提供了各种优化算法:

  • optim.SGD(params, lr, momentum=0): 随机梯度下降(Stochastic Gradient Descent),包括动量(Momentum)版本。
  • optim.Adam(params, lr, betas=(0.9, 0.999), eps=1e-08, weight_decay=0): Adam 优化器,目前非常流行且效果不错的自适应学习率优化器。
  • optim.Adagrad(), optim.RMSprop() 等: 其他各种优化器。

实例化优化器时,需要将模型的参数 (model.parameters()) 传递给它,并设置学习率 (lr) 等超参数。

“`python

假设 model 是上面定义的 MySimpleNN 实例

定义优化器,例如 Adam

optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

SGD 示例

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

“`

6. 简单的训练流程示例

现在,我们将前面学到的知识整合起来,构建一个简单的训练循环。这个示例是深度学习训练中最基本、最重要的模式。

假设我们有一个二分类问题,输入是 10 维特征,输出是 1 维(表示属于类别的概率),使用 Sigmoid 和 BCEWithLogitsLoss。

“`python
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F # 用于函数式激活函数

— 1. 准备数据 —

通常这里会加载数据集 (如 MNIST, CIFAR10)

为了示例,我们创建一些随机的假数据

input_size = 10
num_samples = 100

随机输入数据 (批量大小 x 输入特征数)

X_train = torch.randn(num_samples, input_size)

随机生成二分类标签 (0 或 1)

y_train = torch.randint(0, 2, (num_samples,)).float().unsqueeze(1) # 确保是 float 类型,形状为 (num_samples, 1)

print(f”训练数据形状: {X_train.shape}, 标签形状: {y_train.shape}”)

将数据移动到 GPU (如果可用)

device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”)
X_train = X_train.to(device)
y_train = y_train.to(device)
print(f”数据移动到设备: {device}”)

— 2. 定义模型 —

class SimpleBinaryClassifier(nn.Module):
def init(self, input_size):
super(SimpleBinaryClassifier, self).init()
self.layer1 = nn.Linear(input_size, 32)
self.layer2 = nn.Linear(32, 1) # 二分类输出 1 维

def forward(self, x):
    x = F.relu(self.layer1(x))
    x = self.layer2(x) # 输出 logits (未经过 sigmoid)
    return x

model = SimpleBinaryClassifier(input_size).to(device) # 将模型移动到设备
print(“\n模型结构:”)
print(model)

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

BCELossWithLogitsLoss 适用于二分类原始输出 (logits) 和 0/1 标签

criterion = nn.BCEWithLogitsLoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)

— 4. 编写训练循环 —

num_epochs = 100 # 训练的总轮数
batch_size = 10 # 每批数据大小

创建 DataLoader (后面会详细介绍)

这里为了简化,我们直接使用全部数据作为一批进行演示

实际中会使用 torch.utils.data.DataLoader 来分批加载数据

print(“\n开始训练…”)
for epoch in range(num_epochs):
# 前向传播
outputs = model(X_train)
loss = criterion(outputs, y_train)

# 反向传播和优化
optimizer.zero_grad() # 清零之前的梯度
loss.backward() # 计算梯度
optimizer.step() # 更新模型参数

# 打印训练信息
if (epoch + 1) % 10 == 0:
    # 计算训练精度 (可选,但常见)
    with torch.no_grad(): # 评估时不追踪梯度
        predicted = (torch.sigmoid(outputs) > 0.5).float() # 将 logits 转换为概率并阈值化
        accuracy = (predicted == y_train).sum().item() / y_train.size(0)

    print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}, Accuracy: {accuracy:.4f}')

print(“训练结束.”)

— 5. (可选) 保存和加载模型 —

保存整个模型

torch.save(model, ‘simple_model.pth’)

加载整个模型

loaded_model = torch.load(‘simple_model.pth’)

loaded_model.eval() # 设置为评估模式

保存模型参数 (推荐,更灵活)

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

加载模型参数

new_model = SimpleBinaryClassifier(input_size).to(device)

new_model.load_state_dict(torch.load(‘simple_model_state_dict.pth’))

new_model.eval() # 设置为评估模式

“`

这个训练循环是深度学习的核心。每一次迭代(epoch)或每一个批次(batch)都包含以下步骤:

  1. 前向传播 (Forward Pass): 将输入数据送入模型,获得模型的预测输出。
  2. 计算损失 (Calculate Loss): 使用损失函数比较模型输出和真实标签,计算当前的损失值。
  3. 清零梯度 (Zero Gradients): 在反向传播之前,将模型参数的梯度清零,以防止梯度累加。
  4. 反向传播 (Backward Pass): 调用 loss.backward(),根据计算图自动计算损失函数对模型所有 requires_grad=True 参数的梯度。
  5. 优化器步进 (Optimizer Step): 调用 optimizer.step(),根据计算出的梯度和优化算法来更新模型参数。

在实际应用中,数据通常会被分成多个小批量(mini-batches),训练循环会在每个 epoch 中遍历所有的小批量。

7. 数据加载与预处理:torch.utils.data

在实际项目中,数据集通常很大,无法一次性加载到内存,并且需要进行各种预处理(如归一化、数据增强)。PyTorch 提供了 torch.utils.data 模块来帮助高效地处理这些任务。其核心是 DatasetDataLoader 抽象。

  • Dataset: 代表数据集。你需要继承 torch.utils.data.Dataset 类,并重写两个方法:
    • __len__(self): 返回数据集的大小(样本数量)。
    • __getitem__(self, idx): 根据索引 idx 获取一个样本及其对应的标签。
  • DataLoader: 封装了 Dataset,负责批量加载数据、打乱数据以及多进程加载等。

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

示例:自定义一个简单的 Dataset

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]

创建一些假数据

sample_data = torch.randn(100, 20) # 100个样本,每个20维特征
sample_labels = torch.randint(0, 2, (100,)) # 100个二分类标签

实例化自定义 Dataset

dataset = CustomDataset(sample_data, sample_labels)

实例化 DataLoader

batch_size: 每批数据大小

shuffle: 是否在每个 epoch 开始时打乱数据

num_workers: 使用多少个子进程加载数据 (提高效率,0 表示主进程加载)

dataloader = DataLoader(dataset, batch_size=10, shuffle=True, num_workers=0)

print(“\n使用 DataLoader 遍历数据:”)

训练循环中,通常这样遍历数据:

for epoch in range(2): # 遍历两个 epoch
print(f”— Epoch {epoch+1} —“)
# DataLoader 变成可迭代对象,每次循环返回一个批次的数据和标签
for i, (inputs, labels) in enumerate(dataloader):
# inputs 和 labels 已经是张量,且是批量的形式
# 在这里进行模型的前向传播、损失计算、反向传播和参数更新
# inputs = inputs.to(device) # 记得将数据移动到设备
# labels = labels.to(device)
# … 训练代码 …
print(f” Batch {i+1}, inputs shape: {inputs.shape}, labels shape: {labels.shape}”)

    # 简单示例,实际中会用模型进行计算
    if i >= 2: break # 只打印前3个批次作为示例

print(“DataLoader 示例结束.”)
“`

使用 DatasetDataLoader 是处理实际数据集的标准做法。torchvision.datasets 等库已经提供了许多常用数据集的 Dataset 实现。

8. GPU 加速:利用硬件提升性能

深度学习模型的训练通常计算量巨大,GPU 的并行计算能力可以大幅提升训练速度。利用 GPU 在 PyTorch 中非常简单:

  1. 检查 GPU 是否可用: torch.cuda.is_available()
  2. 创建设备对象: device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  3. 将模型移动到设备: model.to(device)
  4. 将数据移动到设备: inputs.to(device)labels.to(device)

请确保模型和所有相关数据(输入、标签)都在同一个设备上。优化器、损失函数等对象不需要移动到设备。

“`python

示例代码已穿插在前面章节中,这里不再重复展示完整的训练循环。

核心就是 model.to(device) 和 inputs.to(device)/labels.to(device)。

“`

对于多 GPU 训练,PyTorch 提供了 nn.DataParallelnn.parallel.DistributedDataParallel(推荐用于更复杂的场景)等模块。这超出了入门指南的范畴,但在你需要处理大型模型和数据集时会非常有用。

9. 总结与展望

恭喜你完成了 PyTorch 入门之旅!本文详细介绍了 PyTorch 的核心概念和基本用法,包括:

  • PyTorch 的优势及其安装。
  • 张量(Tensor)的创建、属性和基本操作,以及与 NumPy 的互转和设备转移。
  • 自动微分(Autograd)的工作原理、梯度追踪和计算。
  • 使用 torch.nn 构建神经网络模型,包括定义 Module、常用层、损失函数和优化器。
  • 一个简单的端到端训练流程示例。
  • 使用 torch.utils.data 进行数据加载和预处理。
  • 如何利用 GPU 加速训练。

这仅仅是 PyTorch 功能的冰山一角。要更深入地学习和应用 PyTorch,你可以继续探索以下主题:

  • 更复杂的模型: 卷积神经网络 (CNN)、循环神经网络 (RNN)、Transformer 等。
  • 更多 torch.nn 层和模块: BatchNorm, Dropout, Embedding 等。
  • 模型保存与加载: 了解 state_dict 的用法。
  • 模型评估: 如何在验证集或测试集上评估模型性能。
  • 学习率调度器: 在训练过程中动态调整学习率。
  • 数据增强: 使用 torchvision.transforms 等进行图像预处理和增强。
  • 迁移学习: 如何使用预训练模型。
  • 分布式训练: 在多 GPU 或多台机器上训练模型。
  • TorchVision, TorchText, TorchAudio 等: 针对特定领域的官方库。
  • PyTorch 生态系统: TensorBoard (可视化工具)、PyTorch Lightning (高级封装库)、Hugging Face Transformers (自然语言处理库) 等。

掌握本指南中的基础知识,你就已经具备了使用 PyTorch 构建和训练简单深度学习模型的能力。最重要的是动手实践,尝试修改代码、解决实际问题,并在官方文档和社区中寻找答案。

祝你在深度学习的世界里探索愉快!


发表评论

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

滚动至顶部