PyTorch 零基础入门教程:从 Tensor 到构建你的第一个神经网络
人工智能和深度学习是当今科技领域最热门的方向之一。而在众多的深度学习框架中,PyTorch 以其灵活性、易用性和动态计算图特性,受到了学术界和工业界的广泛青睐。如果你对深度学习充满好奇,渴望亲手构建智能模型,那么学习 PyTorch 是一个绝佳的起点。
本教程旨在为你提供一个 PyTorch 的零基础入门指南。我们将从 PyTorch 最基础的数据结构——Tensor(张量)开始,逐步深入到自动求导、构建神经网络模块、数据处理,最终让你能够搭建并训练一个简单的深度学习模型。无论你是否有深度学习背景,只要具备一定的 Python 基础,你就能跟随本教程逐步掌握 PyTorch 的核心概念和使用方法。
本文预计字数较多,内容详实,建议分多次阅读或边阅读边实践。
第一章:踏入 PyTorch 的世界——安装与初探
在开始激动人心的深度学习之旅前,我们首先需要在你的电脑上搭建 PyTorch 环境。
1.1 环境准备:Python 的重要性
PyTorch 是一个 Python 库,因此你需要先安装 Python。推荐使用 Anaconda 或 Miniconda,它们能帮助你轻松管理 Python 环境和各种库。安装 Anaconda/Miniconda 后,你可以创建一个独立的虚拟环境,避免与系统中其他 Python 项目产生冲突。
“`bash
创建一个名为 ‘pytorch_env’ 的新环境,并指定 Python 版本为 3.8 或更高
conda create -n pytorch_env python=3.8
激活新环境
conda activate pytorch_env
“`
1.2 安装 PyTorch
安装 PyTorch 最便捷的方式是访问 PyTorch 官方网站 (https://pytorch.org/) 的安装向导。根据你的操作系统、包管理器(pip 或 conda)、Python 版本以及是否需要 GPU 支持(CUDA),网站会为你生成相应的安装命令。
重要提示:
- GPU 支持 (CUDA): 如果你的电脑配备了 NVIDIA 显卡,并且希望利用 GPU 进行计算加速,请确保选择对应你的 CUDA 版本的 PyTorch。GPU 计算能极大地提升训练速度。如果不确定或没有独立显卡,可以选择仅 CPU 版本。
- 包管理器: 使用
conda
通常更方便,因为它能处理更多的依赖关系,尤其是 CUDA 相关的。
以使用 conda 安装支持 CUDA 11.3 的 PyTorch 为例(请根据官网向导获取最新且适合你的命令):
“`bash
确保你已激活了之前创建的环境 (conda activate pytorch_env)
复制官网生成的命令并执行
conda install pytorch torchvision torchaudio cudatoolkit=11.3 -c pytorch
“`
如果选择 pip 安装 CPU 版本:
“`bash
确保你已激活了之前创建的环境 (conda activate pytorch_env)
pip install torch torchvision torchaudio
“`
安装可能需要一些时间,取决于你的网络状况。
1.3 验证安装
安装完成后,启动 Python 解释器或 Jupyter Notebook,输入以下代码来验证 PyTorch 是否成功安装:
“`python
import torch
print(f”PyTorch 版本: {torch.version}”)
检查是否支持 CUDA (GPU)
if torch.cuda.is_available():
print(f”CUDA 版本: {torch.version.cuda}”)
print(f”当前设备名称: {torch.cuda.get_device_name(0)}”)
print(“GPU 可用,PyTorch 将优先使用 GPU 进行计算。”)
else:
print(“GPU 不可用,PyTorch 将使用 CPU 进行计算。”)
“`
如果都能正确输出信息,恭喜你!PyTorch 已经成功安装并可以使用了。
第二章:PyTorch 的基石——Tensor (张量)
在 PyTorch 中,所有的数据处理和模型运算都围绕着一个核心数据结构进行:Tensor(张量)。张量可以看作是多维数组,它是 NumPy 的 ndarray
的 PyTorch 版本,但增加了对 GPU 计算和自动求导的强大支持。
如果你熟悉 NumPy,会发现 Tensor 的很多操作与 NumPy 数组非常相似。
2.1 创建 Tensor
PyTorch 提供了多种创建 Tensor 的方法:
-
从 Python 列表或 NumPy 数组创建:
“`python
import torch
import numpy as np从列表创建
data = [[1, 2], [3, 4]]
tensor_from_list = torch.tensor(data)
print(“从列表创建:\n”, tensor_from_list)
print(“形状:”, tensor_from_list.shape)
print(“数据类型:”, tensor_from_list.dtype)从 NumPy 数组创建
numpy_array = np.array(data)
tensor_from_numpy = torch.from_numpy(numpy_array) # 注意 from_numpy 创建的 tensor 和 numpy 数组共享内存
print(“\n从 NumPy 数组创建:\n”, tensor_from_numpy)
print(“形状:”, tensor_from_numpy.shape)
print(“数据类型:”, tensor_from_numpy.dtype)修改 tensor_from_numpy 会影响 numpy_array
tensor_from_numpy[0, 0] = 100
print(“修改 tensor_from_numpy 后:\n”, tensor_from_numpy)
print(“原始 numpy_array 是否改变:\n”, numpy_array) # 会改变
“` -
创建特定形状和值的 Tensor:
“`python
创建一个 3×4 的全 1 张量
ones_tensor = torch.ones(3, 4)
print(“\n全 1 张量:\n”, ones_tensor)创建一个 3×4 的全 0 张量
zeros_tensor = torch.zeros(3, 4)
print(“\n全 0 张量:\n”, zeros_tensor)创建一个 3×4 的随机张量 (值在 0 到 1 之间均匀分布)
rand_tensor = torch.rand(3, 4)
print(“\n随机张量 (rand):\n”, rand_tensor)创建一个 3×4 的随机张量 (服从标准正态分布)
randn_tensor = torch.randn(3, 4)
print(“\n随机张量 (randn):\n”, randn_tensor)创建一个未初始化的张量 (值是随机的,取决于内存状态)
通常用于后续会被覆盖的场景,可以稍微快一点
empty_tensor = torch.empty(2, 3)
print(“\n空张量 (未初始化):\n”, empty_tensor)
“` -
创建指定形状和数据类型的 Tensor (类似于其他 Tensor):
“`python
创建一个形状与 rand_tensor 相同,但值为 0 的张量
zeros_like_rand = torch.zeros_like(rand_tensor)
print(“\n形状与 rand_tensor 相同且全 0:\n”, zeros_like_rand)创建一个形状与 ones_tensor 相同,但值为 1 的张量
ones_like_ones = torch.ones_like(ones_tensor)
print(“\n形状与 ones_tensor 相同且全 1:\n”, ones_like_ones)
“`
2.2 Tensor 的属性
Tensor 有许多有用的属性,可以帮助我们了解它的信息:
“`python
import torch
tensor = torch.rand(3, 4)
print(f”形状 (Shape): {tensor.shape}”)
print(f”数据类型 (Dtype): {tensor.dtype}”)
print(f”设备 (Device): {tensor.device}”) # Tensor 所在的设备 (CPU 或 GPU)
“`
2.3 Tensor 的操作
Tensor 支持大量的操作,包括算术运算、索引、切片、形状变换等,与 NumPy 类似。
-
算术运算:
“`python
tensor1 = torch.tensor([[1, 2], [3, 4]])
tensor2 = torch.tensor([[5, 6], [7, 8]])加法 (逐元素)
add_result = tensor1 + tensor2
或使用函数形式
add_result_func = torch.add(tensor1, tensor2)
print(“加法结果:\n”, add_result)乘法 (逐元素)
mul_result = tensor1 * tensor2
或使用函数形式
mul_result_func = torch.mul(tensor1, tensor2)
print(“逐元素乘法结果:\n”, mul_result)矩阵乘法
注意形状要求: (m x n) @ (n x p) -> (m x p)
matrix_mul_result = tensor1 @ tensor2 # 使用 @ 运算符
或使用函数形式
matrix_mul_result_func = torch.matmul(tensor1, tensor2)
print(“矩阵乘法结果:\n”, matrix_mul_result)原地操作 (操作后修改自身,通常以 _ 结尾)
tensor1.add_(tensor2) # tensor1 = tensor1 + tensor2
print(“原地加法后的 tensor1:\n”, tensor1)
“` -
索引与切片:
“`python
tensor = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])访问特定元素 (与 Python 列表和 NumPy 类似)
print(“第一个元素:”, tensor[0, 0].item()) # 使用 .item() 获取单个标量值
访问第一行
print(“第一行:”, tensor[0, :])
访问第一列
print(“第一列:”, tensor[:, 0])
访问子区域 (前两行,后两列)
print(“子区域:\n”, tensor[:2, 1:])
使用整数张量进行索引
indices = torch.tensor([0, 2])
print(“根据索引张量提取行:\n”, tensor[indices, :])
“` -
形状变换:
“`python
tensor = torch.ones(4, 4)
print(“原始张量形状:”, tensor.shape)重塑张量 (view 或 reshape)
view 要求内存是连续的,而 reshape 不要求
reshaped_tensor = tensor.view(16) # 展平为一维张量
print(“展平后的张量形状 (view):”, reshaped_tensor.shape)reshaped_tensor_2d = tensor.view(2, 8) # 重塑为 2×8
print(“重塑为 2×8 的张量形状 (view):\n”, reshaped_tensor_2d)使用 -1 自动推断维度
reshaped_tensor_auto = tensor.view(-1, 2) # 自动推断行数,列数为 2
print(“重塑为自动行数,2列的张量形状 (view):\n”, reshaped_tensor_auto.shape)转置 (交换维度)
transposed_tensor = tensor.t() # 仅适用于 2D 张量
print(“转置后的张量形状 (t):”, transposed_tensor.shape)更通用的转置 (permute)
tensor_3d = torch.randn(2, 3, 4) # 形状 (batch, height, width)
print(“原始 3D 张量形状:”, tensor_3d.shape)将形状变为 (height, width, batch)
permuted_tensor = tensor_3d.permute(1, 2, 0)
print(“Permute 后的张量形状:”, permuted_tensor.shape)
“` -
Tensor 与 NumPy 的转换:
前面提到过
torch.from_numpy()
,它创建的 Tensor 和 NumPy 数组共享内存。另一个常用的方法是.numpy()
,它将 Tensor 转换为 NumPy 数组。“`python
tensor = torch.ones(5)
numpy_array = tensor.numpy()
print(“Tensor 转 NumPy:\n”, numpy_array)注意:如果 Tensor 在 GPU 上,需要先移动到 CPU 才能转换为 NumPy
if torch.cuda.is_available():
tensor_gpu = torch.ones(5).cuda()
# numpy_array_fail = tensor_gpu.numpy() # 会报错
numpy_array_success = tensor_gpu.cpu().numpy()
print(“GPU Tensor 转 NumPy (先移到 CPU):\n”, numpy_array_success)
``
torch.from_numpy()和
.numpy()` 之间的共享内存特性意味着修改其中一个会影响另一个,这在某些情况下很方便,但也需要注意。
2.4 Tensor 设备管理:CPU vs. GPU
深度学习训练通常需要大量的计算,而 GPU 能够提供显著的加速。PyTorch 允许你轻松地将 Tensor 在 CPU 和 GPU 之间移动。
“`python
import torch
默认创建的 Tensor 在 CPU 上
tensor = torch.rand(3, 4)
print(f”默认设备: {tensor.device}”)
如果 GPU 可用,将 Tensor 移动到 GPU
if torch.cuda.is_available():
device = torch.device(“cuda”) # 定义 GPU 设备
tensor_gpu = tensor.to(device) # 或简写为 tensor.cuda()
print(f”移动到 GPU 后的设备: {tensor_gpu.device}”)
# 在 GPU 上进行计算 (所有参与运算的 Tensor 必须在同一个设备上)
tensor_gpu_2 = torch.rand(3, 4, device=device) # 创建时直接指定设备
result_gpu = tensor_gpu + tensor_gpu_2
print(f"GPU 上的加法结果设备: {result_gpu.device}")
# 将 Tensor 移动回 CPU
tensor_cpu_back = result_gpu.to("cpu") # 或简写为 result_gpu.cpu()
print(f"移动回 CPU 后的设备: {tensor_cpu_back.device}")
else:
print(“GPU 不可用,所有计算将在 CPU 上进行。”)
建议在实际项目中,先判断是否有 GPU,然后将模型和数据都移动到 GPU
device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”)
model.to(device)
data = data.to(device)
labels = labels.to(device)
“`
小结: Tensor 是 PyTorch 的核心数据结构,掌握其创建、属性、基本操作和设备管理是学习 PyTorch 的第一步,也是最重要的一步。
第三章:深度学习的魔法——自动求导 (Autograd)
深度学习模型的训练过程依赖于反向传播算法,而反向传播的核心是计算损失函数相对于模型参数的梯度。PyTorch 的 autograd
模块正是负责自动完成这个复杂求导过程的强大工具。
有了 autograd
,你无需手动编写导数公式,PyTorch 会根据你在 Tensor 上执行的操作自动构建一个计算图,并在调用 .backward()
方法时遍历这个图来计算所有需要的梯度。
3.1 requires_grad
标志
只有那些需要计算梯度的 Tensor(通常是模型的权重和偏置)才需要启用梯度计算。通过设置 Tensor 的 requires_grad=True
属性来标记它们。
“`python
import torch
默认情况下,Tensor 的 requires_grad 是 False
x = torch.tensor([1., 2., 3.])
print(f”x 的 requires_grad: {x.requires_grad}”) # False
创建时直接指定 requires_grad=True
y = torch.tensor([1., 2., 3.], requires_grad=True)
print(f”y 的 requires_grad: {y.requires_grad}”) # True
通过 .requires_grad_(True) 方法修改
z = torch.tensor([4., 5., 6.])
z.requires_grad_(True) # 原地修改 requires_grad 属性
print(f”z 的 requires_grad: {z.requires_grad}”) # True
通过操作生成的 Tensor 如果其输入的 requires_grad 为 True,则其 requires_grad 也为 True
a = torch.tensor([1., 2.], requires_grad=True)
b = a * 2
print(f”b = a * 2, b 的 requires_grad: {b.requires_grad}”) # True
即使输入有 requires_grad=True,某些操作也可能导致输出为 False (例如,转换为 NumPy 数组)
c = b.detach() # detach() 方法创建一个新的 tensor,与原 tensor 不共享计算图历史
print(f”c = b.detach(), c 的 requires_grad: {c.requires_grad}”) # False
或者使用 with torch.no_grad(): 上下文管理器
with torch.no_grad():
d = a * 3
print(f”在 no_grad() 内计算 d = a * 3, d 的 requires_grad: {d.requires_grad}”) # False
``
torch.no_grad()` 通常用于模型的推理(Inference)阶段,因为在这个阶段我们不需要计算梯度,关闭梯度计算可以节省内存和计算资源。
3.2 计算图与梯度计算
当对一个 requires_grad=True
的 Tensor 执行操作时,PyTorch 会在后台构建一个计算图,记录这些操作。每个操作都会创建一个新的 Tensor,并记住它是如何由其输入 Tensor 计算得来的。
“`python
import torch
假设我们要计算函数 f(x) = x^2 + 2x + 1 在 x=3 处的导数
x = torch.tensor(3.0, requires_grad=True) # 需要计算梯度的变量,必须是浮点数类型
构建计算过程,PyTorch 会记录这些操作
y = x + 1
z = y * y
loss = z # 在深度学习中,这通常是损失函数的值
调用 backward() 方法计算梯度
注意: backward() 只能在一个标量 Tensor 上调用 (即只有一个元素的 Tensor)
如果是多维 Tensor,通常需要指定一个 grad_output 参数,表示对输出的梯度 (链式法则)
对于损失函数,我们通常假设它是标量 (整个 batch 的平均损失),直接调用即可
loss.backward()
访问梯度 (.grad 属性)
梯度会累积在叶子节点 (即 requires_grad=True 的输入 Tensor) 的 .grad 属性中
print(f”函数 loss 对 x 的梯度: {x.grad}”)
理论上,f(x) = (x+1)^2 = x^2 + 2x + 1
f'(x) = 2x + 2
在 x=3 时,f'(3) = 2*3 + 2 = 8
PyTorch 计算的结果与理论值一致
“`
重要事项:
- 梯度累积: 梯度在
.grad
属性中是 累积 的。这意味着如果你多次调用.backward()
而不先清除.grad
,梯度会叠加。在训练循环中,每次计算新的梯度前,都需要使用optimizer.zero_grad()
(稍后介绍)或手动将.grad
属性清零。 - 非叶子节点: 默认情况下,非叶子节点的梯度在计算后会被释放,以节省内存。如果你需要保留非叶子节点的梯度,可以使用
.retain_grad()
方法。
3.3 停止梯度追踪
在某些情况下,比如在模型评估或推理阶段,我们不需要计算梯度。这时可以使用 torch.no_grad()
上下文管理器来停止梯度追踪,或者使用 .detach()
方法创建一个新的、不带梯度历史的 Tensor。
“`python
import torch
x = torch.tensor(2.0, requires_grad=True)
y = x * x
print(f”y.requires_grad: {y.requires_grad}”) # True
with torch.no_grad():
z = y * 3
print(f”z.requires_grad inside no_grad: {z.requires_grad}”) # False
从 y 中分离出一个新的 tensor,不带梯度历史
w = y.detach()
print(f”w = y.detach(), w.requires_grad: {w.requires_grad}”) # False
此时如果在 w 上进行操作,不会影响 y 的梯度计算
w_new = w + 10
y.backward() # 可以正常执行,梯度会累积到 x.grad
w_new.backward() # 会报错,因为 w_new 的 requires_grad 为 False
“`
autograd
是 PyTorch 能够高效进行深度学习训练的关键。理解 requires_grad
、计算图以及 .backward()
的工作原理,是你迈向构建和训练神经网络的重要一步。
第四章:构建神经网络——torch.nn
模块
PyTorch 的 torch.nn
模块提供了构建神经网络所需的各种层(Modules)和工具。它是构建复杂模型的核心。nn.Module
是所有神经网络模块的基类。
4.1 nn.Module
简介
nn.Module
封装了神经网络的层、整个模型、甚至是更复杂的计算单元。继承 nn.Module
的类必须实现两个核心方法:
__init__(self, ...)
: 在这里定义模型的各个层(例如,线性层、卷积层、激活函数等)。通常将它们注册为类的属性。forward(self, x)
: 定义模型的前向传播逻辑。输入x
通过在__init__
中定义的层进行计算,并返回输出。
PyTorch 会自动追踪在 nn.Module
子类中定义的、继承自 nn.Module
的属性,并将它们的参数注册为模型的参数。
4.2 构建一个简单的线性回归模型
让我们用 nn.Module
来构建一个最简单的模型:线性回归 y = wx + b
。
“`python
import torch
import torch.nn as nn
定义线性回归模型类
class LinearRegressionModel(nn.Module):
def init(self, input_dim, output_dim):
# 调用父类的构造函数
super(LinearRegressionModel, self).init()
# 定义一个线性层 (全连接层)
# nn.Linear(in_features, out_features)
# 会自动创建权重 W 和偏置 b,并初始化它们
self.linear = nn.Linear(input_dim, output_dim)
def forward(self, x):
# 定义前向传播过程
out = self.linear(x)
return out
实例化模型
input_size = 1 # 输入特征维度
output_size = 1 # 输出特征维度
model = LinearRegressionModel(input_size, output_size)
print(“模型结构:”)
print(model)
查看模型的参数 (W 和 b)
print(“\n模型参数:”)
for name, param in model.named_parameters():
if param.requires_grad:
print(f”层: {name}, 参数形状: {param.shape}”)
# print(f”参数值:\n{param.data}”) # param.data 是参数的具体值 (Tensor)
“`
nn.Linear
层在 __init__
中自动创建了权重 Tensor (weight
) 和偏置 Tensor (bias
),并将它们的 requires_grad
属性设置为 True
,因为这些是需要在训练过程中通过梯度下降更新的参数。
4.3 常用层和激活函数
torch.nn
提供了丰富的层和激活函数:
- 线性层 (Linear Layer):
nn.Linear(in_features, out_features)
- 卷积层 (Convolutional Layers):
nn.Conv1d
,nn.Conv2d
,nn.Conv3d
(用于处理序列、图像、视频等) - 池化层 (Pooling Layers):
nn.MaxPool1d
,nn.MaxPool2d
,nn.MaxPool3d
,nn.AvgPool2d
(用于降采样) - 循环神经网络层 (RNN Layers):
nn.RNN
,nn.LSTM
,nn.GRU
(用于处理序列数据) - Transformer 层:
nn.TransformerEncoderLayer
,nn.TransformerDecoderLayer
,nn.Transformer
- 归一化层 (Normalization Layers):
nn.BatchNorm1d
,nn.BatchNorm2d
,nn.LayerNorm
- 激活函数 (Activation Functions):
nn.ReLU
,nn.Sigmoid
,nn.Tanh
,nn.Softmax
- Dropout 层:
nn.Dropout
(用于防止过拟合)
示例:构建一个简单的全连接神经网络 (多层感知机 – MLP)
“`python
import torch
import torch.nn as nn
class SimpleMLP(nn.Module):
def init(self, input_dim, hidden_dim, output_dim):
super(SimpleMLP, self).init()
# 第一个线性层:输入 -> 隐藏层
self.linear1 = nn.Linear(input_dim, hidden_dim)
# 激活函数:ReLU
self.relu = nn.ReLU()
# 第二个线性层:隐藏层 -> 输出
self.linear2 = nn.Linear(hidden_dim, output_dim)
def forward(self, x):
# 前向传播过程
out = self.linear1(x)
out = self.relu(out) # 在线性层输出后应用激活函数
out = self.linear2(out)
return out
实例化模型
input_size = 784 # 例如,展开的 MNIST 图像
hidden_size = 128
output_size = 10 # 例如,10 个类别的分类问题
mlp_model = SimpleMLP(input_size, hidden_size, output_size)
print(“\nMLP 模型结构:”)
print(mlp_model)
“`
4.4 损失函数 (Loss Functions)
损失函数(或称代价函数、目标函数)衡量模型的预测输出与真实标签之间的差异。在训练过程中,我们希望最小化损失函数的值。torch.nn
也提供了常用的损失函数:
- 均方误差损失 (Mean Squared Error Loss):
nn.MSELoss()
(用于回归问题) - 交叉熵损失 (Cross Entropy Loss):
nn.CrossEntropyLoss()
(用于多分类问题,内部包含 Softmax) - 二元交叉熵损失 (Binary Cross Entropy Loss):
nn.BCELoss()
(用于二分类问题,输入需要先经过 Sigmoid) - 负对数似然损失 (Negative Log Likelihood Loss):
nn.NLLLoss()
(用于多分类问题,输入需要是 log-softmax 输出)
示例:
“`python
import torch.nn as nn
import torch
假设预测输出 (logits) 和真实标签
对于分类问题,预测输出通常是未经 Softmax 的原始分数
predictions = torch.randn(10, 5) # Batch size = 10, 5 个类别
真实标签通常是类别索引 (Long 类型)
labels = torch.randint(0, 5, (10,)) # 10 个样本的标签,范围 [0, 4]
使用交叉熵损失
criterion_ce = nn.CrossEntropyLoss()
loss_ce = criterion_ce(predictions, labels)
print(f”\n交叉熵损失: {loss_ce.item()}”)
对于回归问题
predictions_reg = torch.randn(10, 1) # Batch size = 10, 输出维度 1
labels_reg = torch.randn(10, 1) # 真实值
使用均方误差损失
criterion_mse = nn.MSELoss()
loss_mse = criterion_mse(predictions_reg, labels_reg)
print(f”均方误差损失: {loss_mse.item()}”)
“`
4.5 优化器 (Optimizers)
优化器的作用是根据计算出的梯度来更新模型的参数(权重和偏置),以最小化损失函数。torch.optim
模块提供了各种优化算法:
- 随机梯度下降 (Stochastic Gradient Descent):
torch.optim.SGD(params, lr=...)
- Adam:
torch.optim.Adam(params, lr=...)
- Adagrad, Adadelta, RMSprop, etc.
实例化优化器时,需要告知它优化哪些参数(通常是 model.parameters()
返回的迭代器),以及设置学习率 (lr
) 等超参数。
示例:
“`python
import torch.optim as optim
假设 model 已经定义好 (如上面的 SimpleMLP)
optimizers 需要知道优化哪些参数
model.parameters() 会返回模型中所有 requires_grad=True 的参数
optimizer_sgd = optim.SGD(mlp_model.parameters(), lr=0.01)
optimizer_adam = optim.Adam(mlp_model.parameters(), lr=0.001)
print(“\n优化器已创建 (SGD 和 Adam)”)
“`
选择合适的优化器和学习率是训练模型的关键,通常需要通过实验来确定。Adam 是一种常用的、在很多任务上表现不错的优化器。
小结: torch.nn
和 torch.optim
模块是构建和训练神经网络的基础。通过组合不同的层、激活函数、损失函数和优化器,我们可以构建出各种复杂的模型来解决不同的问题。
第五章:训练你的第一个模型——训练循环
有了模型、损失函数和优化器,我们就可以开始训练了。训练过程通常是一个迭代循环(Epoch),每个 Epoch 中又包含对数据集的多次迭代(Batch)。
在一个典型的训练迭代(处理一个 Batch 的数据)中,需要执行以下步骤:
- 前向传播 (Forward Pass): 将输入数据送入模型,获得预测输出。
- 计算损失 (Calculate Loss): 使用损失函数计算预测输出与真实标签之间的差异。
- 清零梯度 (Zero Gradients): 清除之前迭代计算的梯度(因为梯度会累积)。
- 反向传播 (Backward Pass): 调用损失的
.backward()
方法,计算损失相对于模型所有requires_grad=True
参数的梯度。 - 更新参数 (Optimizer Step): 调用优化器的
.step()
方法,根据计算出的梯度和优化算法来更新模型的参数。
让我们以一个简单的模拟数据线性回归为例,展示完整的训练循环。
5.1 准备模拟数据
首先,我们生成一些符合 y = 2x + 1
关系的数据,并加入一些噪声。
“`python
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt # 用于可视化
模拟数据
true_weight = 2.0
true_bias = 1.0
生成 x 值
X = torch.randn(100, 1) * 10 # 100 个样本,每个样本 1 个特征,范围 [-10, 10]
生成 y 值,y = 2x + 1 + noise
y = true_weight * X + true_bias + torch.randn(100, 1) * 2 # 加入高斯噪声
print(“模拟数据形状:”)
print(“X:”, X.shape)
print(“y:”, y.shape)
简单可视化数据点
plt.scatter(X.numpy(), y.numpy())
plt.xlabel(“X”)
plt.ylabel(“y”)
plt.title(“模拟数据”)
plt.show()
“`
5.2 定义模型、损失函数和优化器
使用前面定义的 LinearRegressionModel
,以及 MSE 损失和 SGD 优化器。
“`python
定义模型、损失函数和优化器
input_size = 1
output_size = 1
model = LinearRegressionModel(input_size, output_size)
criterion = nn.MSELoss() # 均方误差损失适用于回归问题
optimizer = optim.SGD(model.parameters(), lr=0.01) # 学习率为 0.01 的随机梯度下降
print(“\n模型、损失函数和优化器已实例化。”)
“`
5.3 编写训练循环
“`python
训练参数
num_epochs = 1000 # 训练轮次
print(f”\n开始训练 ({num_epochs} epochs)…”)
记录损失以便观察训练过程
loss_history = []
for epoch in range(num_epochs):
# — 核心训练步骤 —
# 1. 前向传播: 计算预测值
# model(X) 实际上是调用了 model.forward(X)
predictions = model(X)
# 2. 计算损失
loss = criterion(predictions, y)
# 3. 清零梯度: 在新的反向传播之前,清空之前累积的梯度
# 否则,梯度会累加到现有的 .grad 属性中
optimizer.zero_grad()
# 4. 反向传播: 计算每个参数的梯度
loss.backward()
# 5. 更新参数: 根据梯度和优化器算法更新参数
optimizer.step()
# ---------------------
# 记录损失
loss_history.append(loss.item())
# 打印训练信息 (可选)
if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
print(“训练结束。”)
观察最终学到的参数 (权重和偏置)
print(“\n训练后模型的参数:”)
for name, param in model.named_parameters():
if param.requires_grad:
print(f”层: {name}, 参数值: {param.data.numpy()}”)
可视化损失下降曲线
plt.plot(loss_history)
plt.xlabel(“Epoch”)
plt.ylabel(“Loss”)
plt.title(“训练损失曲线”)
plt.show()
可视化训练结果
predicted_y = model(X).detach().numpy() # detach() 停止梯度追踪,转为 NumPy
plt.scatter(X.numpy(), y.numpy(), label=’真实数据’)
plt.plot(X.numpy(), predicted_y, color=’red’, label=’预测直线’)
plt.xlabel(“X”)
plt.ylabel(“y”)
plt.title(“训练结果”)
plt.legend()
plt.show()
“`
运行上述代码,你会看到损失值在训练过程中逐渐下降,并且最终学到的权重和偏置参数会接近我们模拟数据时使用的真实值 (2.0 和 1.0)。
5.4 在 GPU 上训练 (可选)
如果你的环境支持 GPU,并且想利用 GPU 进行加速,只需要简单地将模型和数据移动到 GPU 设备上。
“`python
检查是否有 GPU
device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”)
print(f”\n使用的设备: {device}”)
将模型移动到设备
model.to(device)
将数据移动到设备
X = X.to(device)
y = y.to(device)
训练循环与之前相同,因为 Tensor 在哪个设备上,运算就在哪个设备上进行
… (复制上面 5.3 中的训练循环代码) …
print(“在指定设备上训练结束。”)
最终模型参数仍在 GPU 上,需要移回 CPU 才能转 NumPy 或后续处理
for name, param in model.named_parameters():
if param.requires_grad:
print(f”层: {name}, 参数值: {param.data.cpu().numpy()}”)
如果要可视化,预测结果也需要移回 CPU
predicted_y = model(X).detach().cpu().numpy()
plt.scatter(X.cpu().numpy(), y.cpu().numpy(), label=’真实数据’) # 数据也需要移回 CPU
plt.plot(X.cpu().numpy(), predicted_y, color=’red’, label=’预测直线’)
plt.xlabel(“X”)
plt.ylabel(“y”)
plt.title(“GPU 训练结果”)
plt.legend()
plt.show()
“`
将模型和数据移动到 GPU 是使用 PyTorch 进行大规模深度学习训练的标准做法。
小结: 训练循环是将模型、损失函数和优化器结合起来,通过迭代计算和参数更新,使模型逐步学习数据特征并最小化损失的过程。这是深度学习的核心。
第六章:处理真实数据——Dataset 和 DataLoader
在实际应用中,数据通常存储在文件(如图像文件、文本文件、CSV 文件等)中,并且数据集往往非常庞大。一次性加载所有数据到内存是不可行的。PyTorch 提供了 torch.utils.data
模块来高效地处理数据,特别是通过 Dataset
和 DataLoader
。
Dataset
: 表示整个数据集。它需要实现两个方法:__len__(self)
: 返回数据集的大小(样本数量)。__getitem__(self, index)
: 根据给定的索引获取一个样本(通常是数据和其对应的标签)。
DataLoader
: 负责从Dataset
中加载数据,并提供批处理(Batching)、打乱数据(Shuffling)、多进程加载(Multiprocessing)等功能,使得训练过程更加高效。
6.1 使用内置 Dataset (例如 FashionMNIST)
PyTorch 的 torchvision
、torchaudio
、torchtext
等库提供了很多常用的公开数据集作为内置 Dataset
,方便我们学习和实验。
“`python
import torch
from torch.utils.data import Dataset, DataLoader
from torchvision import datasets # 用于加载图像数据集
from torchvision.transforms import ToTensor # 用于将 PIL Image 或 NumPy 转为 Tensor
下载并加载 FashionMNIST 训练集
root: 数据集存放路径
train: 是否是训练集 (True) 或测试集 (False)
download: 如果数据不存在,是否下载
transform: 对样本进行的转换 (例如,转为 Tensor)
training_data = datasets.FashionMNIST(
root=”data”,
train=True,
download=True,
transform=ToTensor() # 将图像转换为 Tensor
)
下载并加载 FashionMNIST 测试集
test_data = datasets.FashionMNIST(
root=”data”,
train=False,
download=True,
transform=ToTensor()
)
print(f”\n训练集大小: {len(training_data)} 样本”)
print(f”测试集大小: {len(test_data)} 样本”)
获取一个样本
img, label = training_data[0]
print(f”第一个样本的图像形状: {img.shape}”) # 形状为 [C, H, W] = [1, 28, 28]
print(f”第一个样本的标签: {label}”) # 标签是类别索引 (0-9)
打印类别名称 (FashionMNIST 的类别)
labels_map = {
0: “T-Shirt”, 1: “Trouser”, 2: “Pullover”, 3: “Dress”, 4: “Coat”,
5: “Sandal”, 6: “Shirt”, 7: “Sneaker”, 8: “Bag”, 9: “Ankle Boot”,
}
print(f”第一个样本对应的类别名称: {labels_map[label]}”)
“`
6.2 使用 DataLoader
创建 DataLoader
实例来迭代访问数据集。
“`python
定义 DataLoader 参数
batch_size = 64 # 每次训练迭代处理的样本数量
创建 DataLoader
train_dataloader = DataLoader(training_data, batch_size=batch_size, shuffle=True) # 训练集通常需要打乱
test_dataloader = DataLoader(test_data, batch_size=batch_size, shuffle=False) # 测试集通常不需要打乱
print(f”\nDataLoader 已创建,batch size: {batch_size}”)
迭代 DataLoader
print(“迭代一个 batch:”)
next(iter(dataloader)) 获取 DataLoader 中的第一个 batch
train_features, train_labels = next(iter(train_dataloader))
print(f”Batch 特征形状: {train_features.shape}”) # [batch_size, channels, height, width]
print(f”Batch 标签形状: {train_labels.shape}”) # [batch_size]
接下来,就可以在一个训练循环中迭代 train_dataloader 了
for batch_features, batch_labels in train_dataloader:
… 训练步骤 …
“`
6.3 自定义 Dataset (可选,但非常重要)
当你处理自己的数据时,通常需要创建自定义的 Dataset
类。你需要继承 torch.utils.data.Dataset
并实现 __len__
和 __getitem__
方法。
例如,处理一个包含图像路径和对应标签的 CSV 文件:
“`python
import os
import pandas as pd
from torchvision.io import read_image # 用于读取图像文件
class CustomImageDataset(Dataset):
def init(self, annotations_file, img_dir, transform=None, target_transform=None):
# 读取包含图像路径和标签的 CSV 文件
self.img_labels = pd.read_csv(annotations_file)
self.img_dir = img_dir # 图像文件所在的目录
self.transform = transform # 数据转换
self.target_transform = target_transform # 标签转换
def __len__(self):
# 返回数据集中的样本数量
return len(self.img_labels)
def __getitem__(self, idx):
# 获取指定索引的样本
img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0]) # 假设 CSV 第一列是文件名
image = read_image(img_path) # 读取图像文件
label = self.img_labels.iloc[idx, 1] # 假设 CSV 第二列是标签
# 应用数据转换
if self.transform:
image = self.transform(image)
if self.target_transform:
label = self.target_transform(label)
return image, label
示例用法 (假设你有一个 images/ 目录和 annotations.csv 文件)
创建一个模拟的 annotations.csv
import csv
with open(‘annotations.csv’, ‘w’, newline=”) as f:
writer = csv.writer(f)
writer.writerow([‘image_name’, ‘label’])
writer.writerow([‘img001.png’, 0])
writer.writerow([‘img002.png’, 1])
# … 添加更多行
custom_dataset = CustomImageDataset(
annotations_file=’annotations.csv’,
img_dir=’images/’, # 你的图像文件目录
transform=ToTensor()
)
custom_dataloader = DataLoader(custom_dataset, batch_size=32, shuffle=True)
print(“\n自定义数据集和 DataLoader 已创建。”)
“`
自定义 Dataset
提供了极大的灵活性,使你能够处理各种格式和组织方式的数据。
小结: Dataset
和 DataLoader
是处理大规模和复杂数据集的关键工具。它们使得以批处理方式加载数据、进行数据增强和高效训练成为可能。
第七章:保存和加载模型
训练好的模型是宝贵的,我们通常需要保存模型以备将来使用(如进行推理、继续训练或部署)。PyTorch 提供了灵活的保存和加载机制。
最常见的保存方法是保存模型的状态字典 (state_dict)。状态字典是一个 Python 字典,它存储了模型所有参数(权重和偏置)的映射关系。保存状态字典的好处是它只保存模型的学习到的参数,与具体的模型类结构分离,因此加载时需要先创建模型类的实例,然后加载状态字典。
7.1 保存模型状态字典
“`python
import torch
import torch.nn as nn
假设你已经训练好了一个模型
class MyModel(nn.Module):
def init(self):
super(MyModel, self).init()
self.linear = nn.Linear(10, 5)
def forward(self, x):
return self.linear(x)
model = MyModel() # 实例化模型
假设模型已经过训练…
print(“模型状态字典 (训练前可能只有默认初始化值):\n”, model.state_dict())
定义保存路径
model_path = “my_model_state_dict.pth”
保存模型状态字典
torch.save(model.state_dict(), model_path)
print(f”\n模型状态字典已保存到 {model_path}”)
“`
7.2 加载模型状态字典
加载模型状态字典时,需要先创建模型的实例,然后使用 model.load_state_dict()
方法加载保存的状态字典。
“`python
import torch
import torch.nn as nn
需要先定义与保存时相同的模型类结构
class MyModel(nn.Module):
def init(self):
super(MyModel, self).init()
self.linear = nn.Linear(10, 5)
def forward(self, x):
return self.linear(x)
实例化一个空的模型
loaded_model = MyModel()
定义保存时使用的路径
model_path = “my_model_state_dict.pth”
加载状态字典
state_dict = torch.load(model_path)
将状态字典加载到模型中
loaded_model.load_state_dict(state_dict)
将模型设置为评估模式 (通常在加载模型后进行推理时需要)
loaded_model.eval() # 例如,会关闭 Dropout 和 BatchNorm 的训练行为
print(f”\n模型状态字典已从 {model_path} 加载。”)
print(“加载后的模型状态字典:\n”, loaded_model.state_dict())
现在可以使用 loaded_model 进行预测
dummy_input = torch.randn(1, 10) # 一个 batch_size 为 1 的输入
在评估模式下,不需要计算梯度,使用 torch.no_grad()
with torch.no_grad():
output = loaded_model(dummy_input)
print(“使用加载的模型进行预测的输出形状:”, output.shape)
“`
注意:
- 加载状态字典时,模型实例的结构必须与保存状态字典时的模型结构完全一致,否则
load_state_dict()
会报错(除非你指定strict=False
,但这可能会导致一些参数没有加载)。 - 在加载模型进行推理时,务必调用
model.eval()
将模型设置为评估模式,并在进行预测的代码块外围使用with torch.no_grad():
来关闭梯度计算。
7.3 保存和加载整个模型 (不常用,不推荐用于跨平台或版本)
PyTorch 也允许保存整个模型对象本身,但这不推荐作为长期存储或跨平台/版本加载的方法,因为它会序列化模型类定义,可能引入兼容性问题。
“`python
保存整个模型对象 (包括结构和参数)
torch.save(model, “my_whole_model.pth”)
print(“\n整个模型对象已保存。”)
加载整个模型对象
loaded_whole_model = torch.load(“my_whole_model.pth”)
print(“整个模型对象已加载。”)
loaded_whole_model.eval()
“`
除非有特殊原因,否则请优先使用保存和加载状态字典的方法。
小结: 保存和加载模型是模型训练后的重要步骤。掌握保存/加载模型状态字典的方法,是实际项目开发中必不可少的技能。
第八章:接下来学什么?
恭喜你!通过本教程的学习,你已经掌握了 PyTorch 的核心概念:Tensor、Autograd、nn.Module
、optim
、Dataset
、DataLoader
以及模型的保存和加载。你已经具备了构建和训练一个简单神经网络的基础能力。
但这仅仅是深度学习世界的冰山一角。要进一步提升你的能力,你可以继续深入学习以下内容:
- 更复杂的网络结构:
- 卷积神经网络 (CNN): 用于图像识别、目标检测等视觉任务。
- 循环神经网络 (RNN), 长短期记忆网络 (LSTM), 门控循环单元 (GRU): 用于处理序列数据,如文本、时间序列。
- **Transformer: ** 在自然语言处理和计算机视觉领域都取得了巨大成功。
- 高级训练技巧:
- 学习率调度 (Learning Rate Scheduling)
- 正则化 (Regularization, e.g., Dropout, Weight Decay)
- 批量归一化 (Batch Normalization) 和层归一化 (Layer Normalization)
- 迁移学习 (Transfer Learning) 和模型微调 (Fine-tuning)
- 计算机视觉和自然语言处理:
- 使用
torchvision
库进行图像数据处理和预训练模型 (resnet
,vgg
等)。 - 使用
torchtext
或更现代的库如Hugging Face Transformers
进行文本数据处理和预训练语言模型 (BERT
,GPT
等)。
- 使用
- 模型部署:
- 将 PyTorch 模型转换为 TorchScript 或 ONNX 格式,以便在生产环境中部署。
- PyTorch Ecosystem:
- 了解 PyTorch Lightning、Fastai 等简化训练流程的高级库。
- 使用 TensorBoard 或 Visdom 进行训练可视化。
- 深入数学原理:
- 回顾线性代数、微积分(特别是链式法则)和概率统计的基础知识,这将帮助你更深入地理解模型和算法。
资源推荐:
- PyTorch 官方文档: 最权威、最全面的参考资料。
- PyTorch 官方教程: 涵盖了从入门到各种进阶主题的详细教程。
- 深度学习相关书籍: 如《深度学习》(花书)、《动手学深度学习》。
- 在线课程: Coursera、Udemy、DataCamp 等平台有许多高质量的深度学习和 PyTorch 课程。
- 开源项目: 阅读和理解 GitHub 上优秀的 PyTorch 开源项目代码。
结语
从 Tensor 的基本操作到构建和训练一个简单的神经网络,你已经完成了 PyTorch 入门的旅程。你现在掌握了使用 PyTorch 构建、训练和评估深度学习模型的核心流程。
深度学习是一个快速发展的领域,保持好奇心,持续学习和实践是成功的关键。勇敢地去尝试构建更复杂的模型,解决实际问题吧!PyTorch 将是你强大的盟友。
祝你在深度学习的道路上一切顺利!