PyTorch 入门指南:从零开始构建深度学习之旅
深度学习作为人工智能的核心技术,在计算机视觉、自然语言处理、语音识别等众多领域取得了令人瞩目的成就。而支撑这些技术突破的,是高效且灵活的深度学习框架。在众多框架中,PyTorch 以其“Pythonic”(Pythonic,意即符合 Python 语言习惯,使用起来自然流畅)的特性、动态计算图的灵活性以及强大的社区支持,迅速成为学术界和工业界都青睐的选择。
本篇文章将作为一份详细的 PyTorch 入门指南,带你从零开始,一步步探索 PyTorch 的核心概念和基本用法,为你的深度学习之旅打下坚实基础。无论你是否有其他深度学习框架的经验,本文都力求让你能够轻松上手。
文章大纲:
- 引言:为什么选择 PyTorch?
- 安装 PyTorch
- PyTorch 的核心构建块:张量(Tensor)
- 什么是张量?
- 创建张量
- 张量的属性
- 张量操作
- 张量与 NumPy 互转
- CPU 与 GPU 之间的张量转移
- 自动微分:Autograd
- 什么是自动微分?为什么需要它?
- 启用梯度追踪
- 计算梯度
- 访问梯度
- 停止梯度追踪
- 构建神经网络模块:
torch.nn
torch.nn
简介- 定义神经网络模型 (
nn.Module
) - 常用层介绍 (
Linear
,ReLU
,Sequential
等) - 损失函数 (
nn.CrossEntropyLoss
,nn.MSELoss
等) - 优化器 (
torch.optim
)
- 简单的训练流程示例
- 准备数据
- 定义模型
- 定义损失函数和优化器
- 编写训练循环
- 数据加载与预处理:
torch.utils.data
Dataset
抽象DataLoader
抽象
- GPU 加速:利用硬件提升性能
- 总结与展望
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 是一个跨平台的包管理器和环境管理器,可以有效避免不同项目之间的依赖冲突。
- 首先,确保你已经安装了 Anaconda 或 Miniconda。
- 打开终端或命令提示符。
- 创建一个新的 Conda 环境(可选,但推荐):
bash
conda create -n my_pytorch_env python=3.9
conda activate my_pytorch_env
(这里的my_pytorch_env
是环境名称,python=3.9
是指定的 Python 版本,你可以根据需要修改) - 根据 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 的官方包管理器。
- 打开终端或命令提示符。
- 根据 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
关于参数 w
和 b
的梯度。
“`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(xw+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()
方法之前或之后执行。
停止梯度追踪
有时我们不需要计算梯度的操作,例如在进行模型评估或冻结部分模型参数时。有几种方法可以停止梯度追踪:
-
torch.no_grad()
上下文管理器: 这是最常用的方法,特别是在推理阶段。在该上下文管理器中的所有操作都不会追踪梯度。“`python
x = torch.tensor([1.0, 2.0], requires_grad=True)
print(f”x requires_grad: {x.requires_grad}”) # Truewith torch.no_grad():
y = x * 2
print(f”y requires_grad in no_grad: {y.requires_grad}”) # Falsez = x * 3
print(f”z requires_grad outside no_grad: {z.requires_grad}”) # True
“` -
.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
“`
-
.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)都包含以下步骤:
- 前向传播 (Forward Pass): 将输入数据送入模型,获得模型的预测输出。
- 计算损失 (Calculate Loss): 使用损失函数比较模型输出和真实标签,计算当前的损失值。
- 清零梯度 (Zero Gradients): 在反向传播之前,将模型参数的梯度清零,以防止梯度累加。
- 反向传播 (Backward Pass): 调用
loss.backward()
,根据计算图自动计算损失函数对模型所有requires_grad=True
参数的梯度。 - 优化器步进 (Optimizer Step): 调用
optimizer.step()
,根据计算出的梯度和优化算法来更新模型参数。
在实际应用中,数据通常会被分成多个小批量(mini-batches),训练循环会在每个 epoch 中遍历所有的小批量。
7. 数据加载与预处理:torch.utils.data
在实际项目中,数据集通常很大,无法一次性加载到内存,并且需要进行各种预处理(如归一化、数据增强)。PyTorch 提供了 torch.utils.data
模块来帮助高效地处理这些任务。其核心是 Dataset
和 DataLoader
抽象。
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 示例结束.”)
“`
使用 Dataset
和 DataLoader
是处理实际数据集的标准做法。torchvision.datasets
等库已经提供了许多常用数据集的 Dataset
实现。
8. GPU 加速:利用硬件提升性能
深度学习模型的训练通常计算量巨大,GPU 的并行计算能力可以大幅提升训练速度。利用 GPU 在 PyTorch 中非常简单:
- 检查 GPU 是否可用:
torch.cuda.is_available()
。 - 创建设备对象:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
。 - 将模型移动到设备:
model.to(device)
。 - 将数据移动到设备:
inputs.to(device)
,labels.to(device)
。
请确保模型和所有相关数据(输入、标签)都在同一个设备上。优化器、损失函数等对象不需要移动到设备。
“`python
示例代码已穿插在前面章节中,这里不再重复展示完整的训练循环。
核心就是 model.to(device) 和 inputs.to(device)/labels.to(device)。
“`
对于多 GPU 训练,PyTorch 提供了 nn.DataParallel
和 nn.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 构建和训练简单深度学习模型的能力。最重要的是动手实践,尝试修改代码、解决实际问题,并在官方文档和社区中寻找答案。
祝你在深度学习的世界里探索愉快!