Pytorch知识点2
Pytorch知识点
- 1、官方教程
- 2、张量
- 🧱 0、数组概念
- 🧱 1. 创建张量
- 📐 2. 张量形状与维度
- 🔢 3. 张量数据类型
- ➗ 4. 张量的数学与逻辑操作
- 🔄 5. 张量的就地操作
- 📦 6. 复制张量
- 🚀 7. 将张量移动到加速器(如 GPU)
- 🔄 8. 操作张量的形状
- 🔗 9. PyTorch 与 NumPy 的桥接
- 张量汇总1
- 1. **张量基础**
- 2. **张量属性**
- 3. **张量操作**
- 4. **设备管理**
- 5. **形状调整**
- 6. **与 NumPy 互操作**
- 7. **其他重要功能**
- 总结
- 张量汇总2
- **一、张量基础**
- **二、内存布局(Strides)**
- **三、张量操作**
- **四、原地操作**
- **五、设备与加速**
- **六、自动求导(Autograd)**
- **七、与NumPy互操作**
- **八、最佳实践**
- 3、数据集(PyTorch)
- 3.1 加载已有数据集格式
- 3.2 自定义数据集
- 3.3 变换(transforms)
- 4、构建神经网络
- **1.核心类:`torch.nn.Module` 与 `torch.nn.Parameter`**
- **1. `torch.nn.Module`**
- **2. `torch.nn.Parameter`**(访问参数)
- 3. torch.functional
- 4. torch.optim
- **2. 常用层类型**
- **线性层 (`torch.nn.Linear`)**
- **卷积层 (`torch.nn.Conv2d`)**
- **循环层 (`torch.nn.LSTM`, `torch.nn.GRU`)**
- **Transformer**
- **3. 其他重要层与函数**
- **数据操作层**
- **激活函数**
- **损失函数**
- **4. 模型训练流程**
- **5. 模型保存与加载**
- **6. 关键技巧**
- 5、torch.autograd 自动微分
- 1.计算图
- 2.grad_fn、grad
- 例子
- 3.backward()
- 4.grad_fn、grad和backward()
- **1. `grad_fn`**
- **2. `grad`**
- **3. `backward()`**
- **协同工作流程**
- **三者的关系**
- **常见问题**
- **Q1: 为什么非叶子节点的 `grad` 默认为空?**
- **Q2: 如何强制保留非叶子节点的梯度?**
- **Q3: 向量输出如何调用 `backward()`?**
- **总结**
- 5.Autograd汇总
- **Autograd 的核心作用**
- **Autograd 的使用示例**
- **训练中的 Autograd 实践**
- **Autograd 的控制与优化**
- **总结**
- 6.动态图
- 6、Python with 语句
- 一、`with` 语句的执行流程
- 二、`with` 语句的等价替换代码
- 三、代码示例:文件操作的展开对比
- 7、with torch.no_grad() 禁用梯度跟踪
- 一、为什么需要 `torch.no_grad()`?
- 二、`with torch.no_grad():` 的具体行为
- 三、代码示例:对比有无 `no_grad` 的差异
- 示例 1:禁用梯度后,新 Tensor 的 `requires_grad` 行为
- 示例 2:禁用梯度对内存和计算速度的影响(推理场景)
- 四、常见使用场景
- 五、注意事项
- 六、与 `model.eval()` 的关系
- 总结
- 8、model.train()和model.eval()
- 0.汇总
- 1. **`model.train()`**
- 2. **`model.eval()`**
- 3. **`model.test()`(自定义方法)**
- 4.关键区别总结
- 5.最佳实践
- 9、设置训练设备accelerator
- 10、优化器(torch.optim)
- **1. 核心功能**
- **2. 常见优化器**
- **2.1 SGD(随机梯度下降)**
- **2.2 Adam(自适应矩估计)**
- **2.3 RMSprop**
- **2.4 Adagrad**
- **3. 优化器核心操作**
- **3.1 初始化优化器**
- **3.2 `zero_grad()`**
- **3.3 `loss.backward()`**
- **3.4 `step()`**
- **3.5 `state_dict()` 和 `load_state_dict()`**
- **4. 学习率调整(`lr_scheduler`)**
- **4.1 StepLR**
- **4.2 ReduceLROnPlateau**
- **4.3 CosineAnnealingLR**
- **5. 完整训练流程示例**
- **6. 高级用法**
- **6.1 为不同参数组设置不同超参数**
- **6.2 添加钩子(Hook)**
- **7. 总结**
- 11、TensorBoard 可视化模型、数据和训练
- 12、Captum(次要)
- 13、深度学习书籍
- 14、入门实战示例
- 1、TorchVision-FashionMNIST
- 2、TorchText--NLP
- 3、强化学习
- 4、秘籍--PyTorch 代码示例集
1、官方教程
官方的三部教程:
阅读循序:
1.使用 PyTorch 进行深度学习:60 分钟闪电战
https://pytorch.ac.cn/tutorials/beginner/deep_learning_60min_blitz.html
2.学习基础知识
https://pytorch.ac.cn/tutorials/beginner/basics/intro.html
3.PyTorch 入门 - YouTube 系列
https://pytorch.ac.cn/tutorials/beginner/introyt/introyt_index.html
官方教程:
中文:
https://pytorch.ac.cn/
https://pytorch.ac.cn/tutorials/beginner/basics/quickstart_tutorial.html
https://pytorch.ac.cn/tutorials/beginner/basics/tensorqs_tutorial.html
https://github.com/pytorch/tutorials.git
其他:https://www.runoob.com/pytorch/pytorch-datasets.html
英文:
https://pytorch.org/
https://docs.pytorch.org/tutorials/
快速入门参考:https://blog.csdn.net/weixin_44986037/article/details/129843027
2、张量
参考:
https://pytorch.ac.cn/tutorials/beginner/introyt/tensors_deeper_tutorial.html#
https://pytorch.ac.cn/tutorials/beginner/blitz/tensor_tutorial.html
《深入理解 PyTorch 张量教程》是 PyTorch 官方提供的一篇详细教程,旨在帮助初学者深入理解 torch.Tensor
类的核心概念和操作。以下是该教程的中文总结:
(注意,view 是 PyTorch 中的版本,对应于 Numpy 的 reshape)
🧱 0、数组概念
详细请参考:链接https://blog.csdn.net/weixin_44986037/article/details/148317613?
在 NumPy 中,维数(Dimensions)=维度(Dimensions)=秩(Rank)、轴(Axis)、形状(Shape) 是描述数组结构的关键概念。它们相互关联但侧重点不同,以下是详细解释和对比:
总结关系
- 秩 = 轴的数量 = 维数(维度数);
- 形状描述各轴的长度,其元组长度等于秩;
- 轴用于指定操作的方向(如
axis=0
表示行方向)。
例如,一个形状为 (3, 4, 5)
的数组:
- 秩为 3(3 个轴);
- 轴 0 长度为 3,轴 1 长度为 4,轴 2 长度为 5 。
概念解释
- 维数 / 维度 (Dimensions)=秩(Rank):指数组存在的方向数量(即数组是几维的),指的是数组的秩(rank)即数组的
轴(axis)数量
,也就是数组嵌套的层数
。例如,一维数组的维度是1,二维数组的维度是2,以此类推。 - 轴(Axes):轴是数组的一个特定方向,
每个维度对应一个轴,轴从外向内编号(从0开始)
。在一维数组中,只有一个轴(轴0);在二维数组中,有两个轴,轴0表示行,轴1表示列;在三维数组中,轴0通常表示深度,轴1表示行,轴2表示列。三维数组:轴0是最外层(如多个二维数组堆叠),轴1是行方向,轴2是列方向。 - 形状(Shape):形状是描述数组在
每个维度上元素数量的元组
,其长度等于秩。描述每个维度的大小的元组,形状的元素数 = 维数/秩。形状是一个元组,它表示数组在每个轴上的大小。例如, 一维数组[1, 2, 3]
的形状为(3,)
(1 个轴,长度为 3);二维数组[[1, 2], [3, 4]]
的形状为(2, 2)
(2 个轴,每个轴长度为 2);三维数组[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
的形状为(2, 2, 2)
(3 个轴,每个轴长度为 2)。
🧱 1. 创建张量
- 基础创建:使用
torch.empty()
创建一个未初始化的张量。
x = torch.empty(3, 4)print(x)
这将创建一个形状为 3x4 的张量,内容为未初始化的值。
-
常见初始化方法:
-
torch.zeros(2, 3)
:创建一个 2x3 的零张量。 -
torch.ones(2, 3)
:创建一个 2x3 的全一张量。 -
torch.rand(2, 3)
:创建一个 2x3 的随机浮点数张量,值在 [0, 1) 之间。
-
-
随机种子:使用
torch.manual_seed()
设置随机种子,确保结果可复现。
torch.manual_seed(1729)random_tensor = torch.rand(2, 3)print(random_tensor)
📐 2. 张量形状与维度
- 查看形状:使用
.shape
属性查看张量的形状。
x = torch.empty(2, 2, 3)print(x.shape)
输出:torch.Size([2, 2, 3])
- 与其他张量相同形状:使用
torch.*_like()
方法创建与另一个张量相同形状的张量。([PyTorch 文档][1])
zeros_like_x = torch.zeros_like(x)print(zeros_like_x.shape)
🔢 3. 张量数据类型
- 指定数据类型:在创建张量时使用
dtype
参数指定数据类型。
a = torch.ones((2, 3), dtype=torch.int16)print(a)
- 类型转换:使用
.to()
方法将张量转换为其他数据类型。
b = a.to(torch.float32)print(b)
➗ 4. 张量的数学与逻辑操作
- 常见操作:对张量进行加、减、乘、除、指数等操作。
ones = torch.ones(2, 2)twos = ones * 2fours = twos ** 2print(fours)
- 广播机制:当进行操作的两个张量形状不同时,PyTorch 会自动扩展其中一个张量的维度,使其与另一个张量匹配,这称为广播机制。
rand = torch.rand(2, 4)doubled = rand * (torch.ones(1, 4) * 2)print(doubled)
🔄 5. 张量的就地操作
- 就地操作:使用带有
_
后缀的方法,如.add_()
、.mul_()
,可以直接修改原张量的值,而不是返回一个新的张量。
a = torch.ones(2, 2)b = torch.rand(2, 2)a.add_(b)print(a)
- 注意:就地操作会影响计算图,可能会影响梯度计算。
📦 6. 复制张量
- 复制张量:使用
.clone()
方法创建张量的副本。
a = torch.ones(2, 2)b = a.clone()print(b)
- 分离计算图:使用
.detach()
方法从计算图中分离张量。
c = a.detach()print(c)
🚀 7. 将张量移动到加速器(如 GPU)
- 检查加速器:使用
torch.cuda.is_available()
检查是否有可用的 GPU。
if torch.cuda.is_available():device = torch.device("cuda")else:device = torch.device("cpu")
- 移动张量:使用
.to(device)
方法将张量移动到指定设备。
tensor = tensor.to(device)
🔄 8. 操作张量的形状
- 添加维度:使用
.unsqueeze()
方法在指定位置添加一个维度。([arxiv.org][2])
a = torch.rand(3, 226, 226)b = a.unsqueeze(0)print(b.shape)
- 移除维度:使用
.squeeze()
方法移除维度为 1 的维度。
c = torch.rand(1, 20)d = c.squeeze(0)print(d.shape)
- 重塑形状:使用
.reshape()
方法改变张量的形状。
output3d = torch.rand(6, 20, 20)input1d = output3d.reshape(6 * 20 * 20)print(input1d.shape)
🔗 9. PyTorch 与 NumPy 的桥接
- 从 NumPy 转换为 PyTorch 张量:使用
torch.from_numpy()
方法将 NumPy 数组转换为 PyTorch 张量。
import numpy as npnumpy_array = np.ones((2, 3))pytorch_tensor = torch.from_numpy(numpy_array)print(pytorch_tensor)
- 从 PyTorch 张量转换为 NumPy 数组:使用
.numpy()
方法将 PyTorch 张量转换为 NumPy 数组。
numpy_array = pytorch_tensor.numpy()print(numpy_array)
张量汇总1
以下是 PyTorch 官方教程 张量简介 的核心内容汇总:
1. 张量基础
- 定义:张量是 PyTorch 的核心数据抽象,类似于 NumPy 的多维数组,但支持 GPU 加速和自动梯度计算 。
- 创建方法:
torch.empty()
:分配内存但不初始化。torch.zeros()
、torch.ones()
、torch.rand()
:分别创建全零、全一、随机值(0-1 之间)的张量。torch.tensor(data)
:直接从 Python 列表/元组创建,支持嵌套结构。- 示例:
x = torch.rand(2, 3) # 创建 2x3 的随机张量 some_constants = torch.tensor([[3.14, 2.7], [1.6, 0.007]]) # 从数据直接创建
2. 张量属性
- 形状(Shape):通过
.shape
查看维度,如x.shape
返回torch.Size([2, 3])
。 - 数据类型(Dtype):默认为
torch.float32
,可通过dtype
参数指定,如:a = torch.ones((2, 3), dtype=torch.int16) # 创建 16 位整数类型张量
- 设备(Device):默认在 CPU 上,可通过
device
参数指定 GPU,如:if torch.cuda.is_available():gpu_tensor = torch.rand(2, 2, device="cuda") # 移动到 GPU
3. 张量操作
- 数学运算:
- 支持标量和逐元素运算(加减乘除、幂等),如:
ones = torch.zeros(2, 2) + 1 # 全零张量加 1 twos = torch.ones(2, 2) * 2 # 全一张量乘 2
- 广播机制:允许形状不同的张量进行运算,规则与 NumPy 一致。例如:
rand = torch.rand(2, 4) doubled = rand * (torch.ones(1, 4) * 2) # 1x4 张量广播至 2x4
- 支持标量和逐元素运算(加减乘除、幂等),如:
- 就地操作:通过下划线
_
后缀修改原张量,如:a = torch.tensor([1, 2]) a.add_(torch.tensor([3, 4])) # a 现在为 [4, 6]
4. 设备管理
- 检查设备可用性:
if torch.cuda.is_available(): # 或 torch.mps.is_available()(Mac)device = torch.device("cuda") else:device = torch.device("cpu")
- 张量移动:使用
.to(device)
在 CPU/GPU 间切换:tensor = tensor.to("cuda") # 移动到 GPU
5. 形状调整
- 增减维度:
unsqueeze(dim)
:增加指定维度(大小为 1)。squeeze(dim)
:删除指定维度(大小必须为 1)。- 示例:
img = torch.rand(3, 226, 226) # 单张图像 batch_img = img.unsqueeze(0) # 添加批次维度 -> [1, 3, 226, 226]
- 重塑形状:
reshape()
改变张量形状但保留元素数量:output3d = torch.rand(6, 20, 20) input1d = output3d.reshape(6*20*20) # 转为一维
6. 与 NumPy 互操作
- 双向转换:
torch.from_numpy()
:从 NumPy 数组创建张量。.numpy()
:将张量转换为 NumPy 数组。
- 共享内存:转换后的对象共享底层内存,修改会互相影响:
numpy_array = np.ones((2, 3)) pytorch_tensor = torch.from_numpy(numpy_array) numpy_array[1, 1] = 23 # pytorch_tensor 的对应值也会变为 23
7. 其他重要功能
- 随机种子:通过
torch.manual_seed()
确保结果可复现。 - 克隆与分离:
clone()
:复制张量及其梯度计算历史。detach()
:分离张量以停止梯度跟踪(用于推理阶段)。
- 内存优化:使用
out
参数指定输出张量,避免重复分配内存:result = torch.zeros(2, 2) torch.add(a, b, out=result) # 结果直接写入 result
总结
该教程详细介绍了 PyTorch 张量的创建、操作、设备管理及与 NumPy 的交互,是理解 PyTorch 基础运算的关键资源。完整示例代码可通过页面链接下载 。
张量汇总2
以下是PyTorch官方教程《深入理解张量》(https://pytorch.ac.cn/tutorials/beginner/introyt/tensors_deeper_tutorial.html) 的核心内容汇总:
一、张量基础
-
创建张量
- 直接创建:
torch.tensor([[1, 2], [3, 4]])
- 初始化方法:
torch.zeros()
(全零)torch.ones()
(全1)torch.rand()
(随机值)torch.randn()
(标准正态分布)
- 从NumPy转换:
torch.from_numpy(numpy_array)
- 直接创建:
-
关键属性
- 数据类型:
.dtype
(如torch.float32
) - 维度:
.shape
或.size()
- 存储设备:
.device
(CPU/GPU) - 布局:
.layout
(默认为torch.strided
)
- 数据类型:
二、内存布局(Strides)
-
核心概念
- Strides表示沿每个维度移动时内存中需跳过的元素数量。
- 示例:张量
shape=(3,4)
的默认 strides 为(4,1)
,即:- 沿行移动:跳4个元素(到下一行)
- 沿列移动:跳1个元素(到下一列)
-
内存视图
tensor.storage()
展示底层一维数据存储。- 转置操作(如
tensor.T
)不复制数据,仅修改strides(例:转置后 strides 变为(1,4)
)。
三、张量操作
-
索引与切片
- 类NumPy语法:
tensor[:, 1:3]
- 高级索引:
tensor[[0, 2], [1, 3]]
- 类NumPy语法:
-
形状操作
- 重塑:
.view()
(需连续内存)和.reshape()
(自动处理非连续) - 压缩/扩展维度:
.squeeze()
/.unsqueeze()
- 重塑:
-
数学运算
- 逐元素运算:
+
,*
,torch.sin()
- 矩阵乘法:
torch.matmul()
或@
- 聚合操作:
.sum()
,.mean()
- 逐元素运算:
-
广播机制
- 自动扩展不同形状张量(如
(3,1) * (1,4)
→(3,4)
)。
- 自动扩展不同形状张量(如
四、原地操作
- 标识
- 后缀下划线(如
.add_()
)表示原地修改。
- 后缀下划线(如
- 注意事项
- 节省内存但破坏计算图历史,不推荐在梯度计算中使用。
五、设备与加速
- 设备迁移
- 显式指定:
tensor.to(device='cuda')
- 跨设备复制:
.cpu()
/.cuda()
- 显式指定:
- 性能优势
- GPU并行加速大规模计算。
六、自动求导(Autograd)
- 梯度跟踪
requires_grad=True
启用梯度计算(如torch.tensor(..., requires_grad=True)
)。
- 计算梯度
.backward()
自动计算梯度并存储于.grad
属性。
- 分离计算图
.detach()
或with torch.no_grad():
禁止梯度跟踪。
七、与NumPy互操作
- 无缝转换
torch.from_numpy(np_array)
→ 张量tensor.numpy()
→ NumPy数组
- 内存共享
- 转换后的对象共享底层内存(修改一方影响另一方)。
八、最佳实践
- 优先向量化操作(避免Python循环)
- 警惕原地操作(尤其在Autograd中)
- 利用设备迁移(将计算密集型任务移至GPU)
- 注意内存连续性(影响
view()
等操作)
该教程通过代码示例详细解释了张量的底层原理(如内存布局)及高效操作技巧,是深入理解PyTorch核心数据结构的必备内容。
3、数据集(PyTorch)
参考:
https://pytorch.ac.cn/tutorials/beginner/basics/data_tutorial.html
https://pytorch.ac.cn/tutorials/beginner/basics/quickstart_tutorial.html
https://github.com/pytorch/tutorials/blob/main/beginner_source/basics/data_tutorial.py
https://pytorch.ac.cn/tutorials/beginner/nn_tutorial.html#mnist-data-setup
数据集:
https://pytorch.ac.cn/vision/stable/datasets.html
PyTorch 提供特定领域的库,例如 TorchText、TorchVision 和 TorchAudio
,所有这些库都包含数据集。在本教程中,我们将使用 TorchVision 数据集。
torchvision.datasets 模块包含许多真实世界视觉数据的 Dataset 对象,例如 CIFAR、COCO(完整列表在此)。在本教程中,我们使用 FashionMNIST 数据集。每个 TorchVision Dataset 都包含两个参数:transform 和 target_transform,分别用于修改样本和标签。
3.1 加载已有数据集格式
处理数据样本的代码可能会变得杂乱且难以维护;理想情况下,我们希望将数据集代码与模型训练代码解耦,以提高可读性和模块化。PyTorch 提供了两种数据原语:torch.utils.data.DataLoader 和 torch.utils.data.Dataset,它们允许您使用预加载的数据集以及您自己的数据。Dataset 存储样本及其对应的标签,而 DataLoader 则在 Dataset 周围封装了一个迭代器,以便于访问样本。
PyTorch 领域库提供了许多预加载的数据集(例如 FashionMNIST),这些数据集继承自 torch.utils.data.Dataset 并实现了特定于特定数据的功能。它们可用于原型设计和模型基准测试。您可以在此处找到它们:图像数据集、文本数据集和音频数据集
代码参考:
-
Dataset
我们使用以下参数加载 FashionMNIST Datasettraining_data = datasets.FashionMNIST(root="data",train=True,download=True,transform=ToTensor(), )例如: import torch from torch.utils.data import Dataset from torchvision import datasets from torchvision.transforms import ToTensor import matplotlib.pyplot as plttraining_data = datasets.FashionMNIST(root="/Users/umr/MyData/vspro/data_set",train=True,download=True,transform=ToTensor() )test_data = datasets.FashionMNIST(root="/Users/umr/MyData/vspro/data_set",train=False,download=True,transform=ToTensor() )# 返回的格式: return image, label
root是下载到的文件夹
train 是否是训练数据, 否则就是测试数据
download是否要进行下载root 是训练/测试数据存储的路径,
train 指定训练或测试数据集,
download=True 会在 root 路径下数据不存在时从互联网下载。
transform 和 target_transform 指定特征和标签变换使用 DataLoaders 准备数据进行训练
Dataset 一次检索一个样本的数据集特征和标签。在训练模型时,我们通常希望以“迷你批量”的形式传递样本,在每个 epoch 重新打乱数据以减少模型过拟合,并使用 Python 的 multiprocessing 来加速数据检索。DataLoader 是一个迭代器,它通过简单的 API 为我们抽象了这种复杂性。
from torch.utils.data import DataLoadertrain_dataloader = DataLoader(training_data, batch_size=64, shuffle=True) test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
-
遍历 DataLoader
我们已将数据集加载到 DataLoader 中,并可以根据需要遍历数据集。下面的每次迭代都会返回一批 train_features 和 train_labels(分别包含 batch_size=64 个特征和标签)。因为我们指定了 shuffle=True,所以在遍历所有批量后,数据会被打乱(如需对数据加载顺序进行更精细的控制,请参阅采样器 (Samplers))。# Display image and label. train_features, train_labels = next(iter(train_dataloader)) print(f"Feature batch shape: {train_features.size()}") print(f"Labels batch shape: {train_labels.size()}") img = train_features[0].squeeze() label = train_labels[0] plt.imshow(img, cmap="gray") plt.show() print(f"Label: {label}")
3.2 自定义数据集
为您的文件创建自定义数据集
自定义 Dataset 类必须实现三个函数:init、len 和 getitem。请看这个实现;FashionMNIST 图像存储在目录 img_dir 中,而它们的标签则单独存储在 CSV 文件 annotations_file 中。
在接下来的部分,我们将详细介绍这些函数中的每一个。
#################################################################
# Creating a Custom Dataset for your files
# ---------------------------------------------------
#
# A custom Dataset class must implement three functions: `__init__`, `__len__`, and `__getitem__`.
# Take a look at this implementation; the FashionMNIST images are stored
# in a directory ``img_dir``, and their labels are stored separately in a CSV file ``annotations_file``.
#
# In the next sections, we'll break down what's happening in each of these functions.import os
import pandas as pd
from torchvision.io import read_imageclass CustomImageDataset(Dataset):def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):self.img_labels = pd.read_csv(annotations_file)self.img_dir = img_dirself.transform = transformself.target_transform = target_transformdef __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])image = read_image(img_path)label = self.img_labels.iloc[idx, 1]if self.transform:image = self.transform(image)if self.target_transform:label = self.target_transform(label)return image, label
init
init 函数在实例化 Dataset 对象时运行一次。我们初始化包含图像的目录、标注文件以及两个变换(将在下一节详细介绍)。
labels.csv 文件如下所示
tshirt1.jpg, 0
tshirt2.jpg, 0
......
ankleboot999.jpg, 9
len
len 函数返回数据集中样本的数量。
示例
def __len__(self):return len(self.img_labels)
getitem
getitem 函数加载并返回给定索引 idx 处的数据集样本。根据索引,它确定图像在磁盘上的位置,使用 read_image 将其转换为张量,从 self.img_labels 中的 csv 数据检索相应标签,对其调用变换函数(如果适用),并以元组形式返回张量图像和相应标签。
return image, label
3.3 变换(transforms)
数据并不总是以机器学习算法训练所需的最终处理形式出现。我们使用 变换(transforms) 对数据进行一些处理,使其适合训练。
所有 TorchVision 数据集都有两个参数 -transform 用于修改特征,target_transform 用于修改标签 - 它们接受包含变换逻辑的可调用对象。 torchvision.transforms 模块提供了几个常用的现成变换。
FashionMNIST 特征采用 PIL Image 格式,标签是整数。为了训练,我们需要将特征转换为归一化张量,将标签转换为独热编码张量。为了实现这些变换,我们使用 ToTensor 和 Lambda。
import torch
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambdads = datasets.FashionMNIST(root="data",train=True,download=True,transform=ToTensor(),target_transform=Lambda(lambda y: torch.zeros(10, dtype=torch.float).scatter_(0, torch.tensor(y), value=1))
)
4、构建神经网络
参考:
https://pytorch.ac.cn/tutorials/beginner/basics/buildmodel_tutorial.html
https://pytorch.ac.cn/tutorials/beginner/introyt/modelsyt_tutorial.html
https://pytorch.ac.cn/tutorials/beginner/nn_tutorial.html
在本教程开始时,我们承诺将通过示例解释 torch.nn
、torch.optim
、Dataset
和 DataLoader
。现在我们总结一下我们所学到的:
参考:https://pytorch.ac.cn/tutorials/beginner/nn_tutorial.html
-
torch.nn
:Module
:创建一个可调用对象,其行为类似函数,但也可以包含状态(如神经网络层的权重)。它知道自己包含哪些Parameter
,可以将其所有梯度清零,遍历它们进行权重更新等。Parameter
:一个张量的包装器,它告诉Module
它拥有在反向传播期间需要更新的权重。只有设置了requires_grad
属性的张量才会被更新。functional
:一个模块(通常按惯例导入到F
命名空间中),包含激活函数、损失函数等,以及卷积层和线性层等无状态版本的层。
-
torch.optim
:包含优化器,例如SGD
,它们在反向传播步骤中更新Parameter
的权重。 -
Dataset
:具有__len__
和__getitem__
方法的对象的抽象接口,包括 PyTorch 提供的类,如TensorDataset
。 -
DataLoader
:接受任何Dataset
并创建一个迭代器,该迭代器返回批次数据。
神经网络由对数据执行操作的层/模块组成。torch.nn 命名空间提供了构建自己的神经网络所需的所有构建块。PyTorch 中的每个模块都继承自 nn.Module。神经网络本身就是一个由其他模块(层)组成的模块。这种嵌套结构使得构建和管理复杂的架构变得容易。
定义类
我们通过继承 nn.Module 来定义我们的神经网络,并在 init 中初始化神经网络层。每个 nn.Module 子类都在 forward 方法中实现对输入数据的操作。
要使用模型,我们将输入数据传递给它。这将执行模型的 forward 方法,以及一些后台操作。不要直接调用 model.forward()!
在输入上调用模型会返回一个二维张量,其中 dim=0 对应于每个类别的 10 个原始预测值,dim=1 对应于每个输出的单个值。通过将其传递给 nn.Softmax 模块的一个实例,我们可以获得预测概率。dim 参数表示值必须沿哪个维度求和为 1。
mport os
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms#############################################
# Get Device for Training
# -----------------------
# We want to be able to train our model on an `accelerator <https://pytorch.org/docs/stable/torch.html#accelerators>`__
# such as CUDA, MPS, MTIA, or XPU. If the current accelerator is available, we will use it. Otherwise, we use the CPU.device = torch.accelerator.current_accelerator().type if torch.accelerator.is_available() else "cpu"
print(f"Using {device} device")##############################################
# Define the Class
# -------------------------
# We define our neural network by subclassing ``nn.Module``, and
# initialize the neural network layers in ``__init__``. Every ``nn.Module`` subclass implements
# the operations on input data in the ``forward`` method.class NeuralNetwork(nn.Module):def __init__(self):super().__init__()self.flatten = nn.Flatten()self.linear_relu_stack = nn.Sequential(nn.Linear(28*28, 512),nn.ReLU(),nn.Linear(512, 512),nn.ReLU(),nn.Linear(512, 10),)def forward(self, x):x = self.flatten(x)logits = self.linear_relu_stack(x)return logits##############################################
# We create an instance of ``NeuralNetwork``, and move it to the ``device``, and print
# its structure.model = NeuralNetwork().to(device)
print(model)##############################################
# To use the model, we pass it the input data. This executes the model's ``forward``,
# along with some `background operations <https://github.com/pytorch/pytorch/blob/270111b7b611d174967ed204776985cefca9c144/torch/nn/modules/module.py#L866>`_.
# Do not call ``model.forward()`` directly!
#
# Calling the model on the input returns a 2-dimensional tensor with dim=0 corresponding to each output of 10 raw predicted values for each class, and dim=1 corresponding to the individual values of each output.
# We get the prediction probabilities by passing it through an instance of the ``nn.Softmax`` module.X = torch.rand(1, 28, 28, device=device)
logits = model(X)
pred_probab = nn.Softmax(dim=1)(logits)
y_pred = pred_probab.argmax(1)
print(f"Predicted class: {y_pred}")
1.核心类:torch.nn.Module
与 torch.nn.Parameter
1. torch.nn.Module
- 定位:PyTorch 中所有神经网络模型的基类,用于封装模型组件(如层)及其行为(如参数管理、前向传播)。
- 关键特性:
- 参数注册:自动跟踪子模块(如
Linear
、Conv2d
层)的可学习参数。 - 层次化结构:支持嵌套子模块,可通过
model.layer_name
访问任意层。 - 前向传播:需自定义
forward()
方法定义计算逻辑,backward()
由 Autograd 自动处理。 - torch.nn.Module
所有神经网络模块的基类,用于封装模型结构和行为。
子类需定义 init()(声明层)和 forward()(计算逻辑)。
- 参数注册:自动跟踪子模块(如
2. torch.nn.Parameter
(访问参数)
- 定位:
torch.Tensor
的子类,用于表示模型的可学习参数(如权重、偏置)。 - 关键特性:
- 自动注册:当作为
Module
的属性赋值时(如self.weight = nn.Parameter(torch.randn(3, 2))
),会自动添加到Module.parameters()
列表中。 - 梯度跟踪:默认启用
requires_grad=True
,自动参与反向传播。 - torch.nn.Parameter
Tensor 的子类,表示可学习参数(如权重、偏置)。
当赋值给 Module 属性时,自动注册到模型参数列表中,可通过 parameters() 访问。
常见层类型
- 自动注册:当作为
以下为 PyTorch 官方教程 中关于 构建深度学习模型 的核心内容汇总:
模型基础结构
torch.nn.Module
是所有 PyTorch 模型的基类,用于封装模型组件及其参数。torch.nn.Parameter
是Tensor
的子类,表示可学习的参数。当分配为Module
的属性时,会自动加入模型参数列表,可通过model.parameters()
访问。- 模型需定义两个核心方法:
__init__()
:初始化层(如线性层、卷积层等)。forward()
:定义数据流向(如输入经过各层的计算过程)。
- 示例代码:定义了一个简单模型
TinyModel
,包含两个线性层和激活函数,并展示了参数访问方法 (https://pytorch.ac.cn/tutorials/beginner/introyt/modelsyt_tutorial.html)。
3. torch.functional
在 PyTorch 中,torch.nn.functional
(通常简写为 F
)是一个包含各种无状态函数的重要模块,用于实现神经网络操作。它与 torch.nn
模块(包含有状态的类)相互补充,共同构建神经网络。以下是核心要点:
1. 核心概念
- 无状态函数:不包含可学习参数(如权重/偏置),仅实现计算操作
- 纯函数:相同输入 → 相同输出,无内部状态
- 使用场景:
- 激活函数(ReLU, sigmoid)
- 池化操作(max_pool, avg_pool)
- 损失函数(cross_entropy, mse_loss)
- 标准化(dropout, layer_norm)
实现无需学习参数的层或操作:
- 激活函数:
F.relu()
,F.sigmoid()
,F.tanh()
,F.softmax()
- 池化操作:
F.max_pool2d()
,F.avg_pool2d()
- 正则化:
F.dropout()
,F.batch_norm()
(需手动传入 weight/bias) - 损失函数:
F.cross_entropy()
,F.mse_loss()
,F.l1_loss()
2. 与 torch.nn
模块的区别
特性 | torch.nn.functional (F) | torch.nn 模块 |
---|---|---|
状态 | 无状态(无参数) | 有状态(包含可训练参数) |
使用方式 | 直接函数调用 F.relu(x) | 类实例化 nn.ReLU() |
参数存储 | 不管理参数 | 自动管理参数(通过 parameters() ) |
典型用途 | 简单操作/自定义层 | 标准网络层(卷积/线性层等) |
4. torch.optim
torch.optim
是 PyTorch 中用于优化神经网络模型参数的核心模块,它实现了多种优化算法(如 SGD、Adam、RMSprop 等),通过计算损失函数对参数的梯度并更新模型权重,以最小化损失函数。以下是关于 torch.optim
的详细解析:
核心功能**
- 优化算法实现:提供多种优化算法(如 SGD、Adam、Adagrad、RMSprop 等)。
- 动态学习率调整:支持学习率调度器(如
lr_scheduler
)动态调整学习率。 - 参数更新:通过梯度反向传播更新模型参数。
核心作用
- 参数优化:自动更新模型权重以最小化损失函数
- 梯度管理:高效计算和应用梯度
- 学习率控制:支持动态学习率调整
- 算法实现:提供多种优化算法(SGD, Adam 等)
- 状态维护:跟踪优化器内部状态(如动量缓存)
优化算法实现
优化器 | 适用场景 | 特点 |
---|---|---|
SGD | 基础优化任务 | 支持动量(momentum)、Nesterov加速 |
Adam | 深度学习主流选择 (默认推荐) | 自适应学习率,结合动量 |
AdamW | 带权重衰减的优化 | 修复标准Adam中权重衰减实现问题(更好泛化) |
RMSprop | RNN/LSTM | 自适应调整学习率(每个参数单独调整) |
Adagrad | 稀疏数据特征学习 | 为频繁特征分配小学习率 |
Adadelta | 替代Adagrad | 无需初始学习率 |
LBFGS | 小规模全批处理 | 准牛顿方法(内存消耗大但收敛快) |
2. 常用层类型
线性层 (torch.nn.Linear
)
- 实现全连接操作:输入与权重矩阵相乘并加偏置。
- 参数:
in_features
(输入特征数)、out_features
(输出特征数)、bias
(是否启用偏置)。 - 示例:
Linear(3, 2)
表示输入维度为3,输出维度为2的线性变换 (https://pytorch.ac.cn/tutorials/beginner/introyt/modelsyt_tutorial.html)。
卷积层 (torch.nn.Conv2d
)
- 用于处理具有空间相关性的数据(如图像)。
- 参数:
in_channels
(输入通道数)、out_channels
(输出通道数)、kernel_size
(卷积核大小)。 - 示例:
Conv2d(1, 6, 5)
表示输入为1通道(灰度图),输出6个特征图,卷积核大小5x5 (https://pytorch.ac.cn/tutorials/beginner/introyt/modelsyt_tutorial.html)。 - 典型流程:卷积 → 激活(如ReLU) → 池化(如最大池化)。
循环层 (torch.nn.LSTM
, torch.nn.GRU
)
- 处理序列数据(如文本、时间序列)。
- 示例:
LSTMTagger
模型用于词性标注,包含嵌入层、LSTM层和线性分类层 (https://pytorch.ac.cn/tutorials/beginner/introyt/modelsyt_tutorial.html)。
Transformer
- 支持自注意力机制,适用于自然语言处理(NLP)任务。
- PyTorch 提供
torch.nn.Transformer
及其组件(如编码器、解码器层),可灵活构建模型(如BERT)(https://pytorch.ac.cn/tutorials/beginner/introyt/modelsyt_tutorial.html)。
3. 其他重要层与函数
数据操作层
- 最大池化 (
MaxPool2d
):通过取局部最大值减少张量尺寸。 - 归一化层 (
BatchNorm1d
):标准化中间张量,加速训练并允许使用更高学习率。 - Dropout 层:训练时随机屏蔽部分输入,防止过拟合。
激活函数
- 非线性函数(如 ReLU、Sigmoid、Softmax)使模型能拟合复杂函数。
- 示例:
F.relu()
、F.softmax()
。
损失函数
- 常见类型:均方误差(MSE)、交叉熵损失(CrossEntropyLoss)、负对数似然损失(NLLLoss)。
4. 模型训练流程
- 训练循环步骤:
- 从
DataLoader
获取数据。 - 将优化器梯度归零。
- 前向传播(推理预测结果)。
- 计算损失。
- 反向传播(
loss.backward()
)。 - 更新参数(
optimizer.step()
)(https://pytorch.ac.cn/tutorials/beginner/introyt/modelsyt_tutorial.html)。
- 从
5. 模型保存与加载
- 保存模型:
torch.save(model.state_dict(), "model.pth")
。 - 加载模型:先实例化模型结构,再调用
model.load_state_dict(torch.load("model.pth"))
(https://pytorch.ac.cn/tutorials/beginner/introyt/modelsyt_tutorial.html)。
6. 关键技巧
- 参数初始化:合理设置权重初始化方法(如 Xavier、Kaiming)可改善训练效果。
- 设备迁移:通过
model.to(device)
将模型移至 GPU(device="cuda"
)或 CPU(device="cpu"
)。 - 模型评估模式:调用
model.eval()
禁用 Dropout 和 BatchNorm 的训练行为 (https://pytorch.ac.cn/tutorials/beginner/introyt/modelsyt_tutorial.html)。
以上内容总结了 PyTorch 构建深度学习模型的核心概念与代码实践,适合初学者快速掌握模型定义、训练及优化方法。
5、torch.autograd 自动微分
参考:
https://pytorch.ac.cn/tutorials/beginner/basics/autogradqs_tutorial.html
https://pytorch.ac.cn/tutorials/beginner/introyt/autogradyt_tutorial.html#what-do-we-need-autograd-for
对于一个小的两层网络来说,手动实现后向传播并不是什么大问题,但对于大型复杂网络来说,这很快就会变得非常麻烦。值得庆幸的是,我们可以使用 自动微分 来自动化神经网络中后向传播的计算。PyTorch 中的 autograd 包正是提供了这个功能。使用 autograd 时,网络的前向传播将定义一个 计算图 ;图中的节点是张量,边是根据输入张量生成输出张量的函数。然后,通过这个图进行反向传播,你可以轻松地计算梯度。在底层,每个原始的 autograd 算子实际上是作用于张量的两个函数。forward 函数根据输入张量计算输出张量。backward 函数接收输出张量关于某个标量值的梯度,并计算输入张量关于同一标量值的梯度。参考
1.计算图
https://pytorch.ac.cn/tutorials/beginner/blitz/autograd_tutorial.html
训练神经网络时,最常用的算法是反向传播。在该算法中,根据损失函数相对于给定参数的梯度来调整参数(模型权重)。
注意:Parameters
参数
你可以在创建张量时设置 requires_grad 的值,也可以稍后使用 x.requires_grad_(True) 方法设置。
x = torch.ones(5) # input tensor
w = torch.randn(5, 3, requires_grad=True)我们只能获取计算图叶子节点的 grad 属性,
这些叶子节点的 requires_grad 属性设置为 True。
对于图中所有其他节点,梯度将不可用。出于性能考虑,我们只能在给定图上使用 backward 进行一次梯度计算。
如果我们需要在同一个图上进行多次 backward 调用,
我们需要将 retain_graph=True 传递给 backward 调用。
我们应用于张量以构建计算图的函数实际上是 Function 类的一个对象。此对象知道如何执行函数在前向方向的计算,以及如何在反向传播步骤中计算其导数。对反向传播函数的引用存储在张量的 grad_fn 属性中。你可以在文档中找到更多关于 Function 的信息。
请注意,只有计算图的叶子节点才会计算梯度
。例如,如果你尝试 print(c.grad),你会得到 None。在这个简单的例子中,只有输入是叶子节点,因此只有它计算了梯度。
注意
PyTorch 中的 DAG 是动态的 需要注意的一点是,图是每次从头开始重新创建的;在每次调用 .backward() 后,autograd 开始填充一个新的图。这正是允许你在模型中使用控制流语句的原因;如果需要,你可以在每次迭代时改变形状、大小和操作。
2.grad_fn、grad
每个与我们的张量一起存储的 grad_fn 都可以让你通过其 next_functions 属性一直回溯到其输入。我们可以看到,对 d 的此属性进行深入探究,会显示所有先前张量的梯度函数。注意,a.grad_fn 报告为 None,表明这是函数的输入,本身没有历史记录。
有了所有这些机制,我们如何获取导数呢?你可以在输出上调用 backward() 方法,并检查输入的 grad 属性以查看梯度。
请注意,只有计算图的叶子节点才会计算梯度。例如,如果你尝试 print(c.grad),你会得到 None。在这个简单的例子中,只有输入是叶子节点,因此只有它计算了梯度。
a = torch.linspace(0., 2. * math.pi, steps=25, requires_grad=True)
print(a)b = torch.sin(a)
print(b)c = 2 * b
print(c)d = c + 1
print(d)
out = d.sum()
print(out)# **********************rint('d:')
print(d.grad_fn)
print(d.grad_fn.next_functions)
print(d.grad_fn.next_functions[0][0].next_functions)
print(d.grad_fn.next_functions[0][0].next_functions[0][0].next_functions)
print(d.grad_fn.next_functions[0][0].next_functions[0][0].next_functions[0][0].next_functions)
print('\nc:')
print(c.grad_fn)
print('\nb:')
print(b.grad_fn)
print('\na:')
print(a.grad_fn)# ***************************
out.backward()
print(a.grad)
输出:
tensor([0.0000, 0.2618, 0.5236, 0.7854, 1.0472, 1.3090, 1.5708, 1.8326, 2.0944,2.3562, 2.6180, 2.8798, 3.1416, 3.4034, 3.6652, 3.9270, 4.1888, 4.4506,4.7124, 4.9742, 5.2360, 5.4978, 5.7596, 6.0214, 6.2832],requires_grad=True)
tensor([ 0.0000e+00, 2.5882e-01, 5.0000e-01, 7.0711e-01, 8.6603e-01,9.6593e-01, 1.0000e+00, 9.6593e-01, 8.6603e-01, 7.0711e-01,5.0000e-01, 2.5882e-01, -8.7423e-08, -2.5882e-01, -5.0000e-01,-7.0711e-01, -8.6603e-01, -9.6593e-01, -1.0000e+00, -9.6593e-01,-8.6603e-01, -7.0711e-01, -5.0000e-01, -2.5882e-01, 1.7485e-07],grad_fn=<SinBackward0>)
tensor([ 0.0000e+00, 5.1764e-01, 1.0000e+00, 1.4142e+00, 1.7321e+00,1.9319e+00, 2.0000e+00, 1.9319e+00, 1.7321e+00, 1.4142e+00,1.0000e+00, 5.1764e-01, -1.7485e-07, -5.1764e-01, -1.0000e+00,-1.4142e+00, -1.7321e+00, -1.9319e+00, -2.0000e+00, -1.9319e+00,-1.7321e+00, -1.4142e+00, -1.0000e+00, -5.1764e-01, 3.4969e-07],grad_fn=<MulBackward0>)
tensor([ 1.0000e+00, 1.5176e+00, 2.0000e+00, 2.4142e+00, 2.7321e+00,2.9319e+00, 3.0000e+00, 2.9319e+00, 2.7321e+00, 2.4142e+00,2.0000e+00, 1.5176e+00, 1.0000e+00, 4.8236e-01, -3.5763e-07,-4.1421e-01, -7.3205e-01, -9.3185e-01, -1.0000e+00, -9.3185e-01,-7.3205e-01, -4.1421e-01, 4.7684e-07, 4.8236e-01, 1.0000e+00],grad_fn=<AddBackward0>)
tensor(25., grad_fn=<SumBackward0>)
d:
<AddBackward0 object at 0x1075ee920>
((<MulBackward0 object at 0x1075ee950>, 0), (None, 0))
((<SinBackward0 object at 0x1075ee950>, 0), (None, 0))
((<AccumulateGrad object at 0x1075ee920>, 0),)
()c:
<MulBackward0 object at 0x1075ee950>b:
<SinBackward0 object at 0x1075ee950>a:
None
有了所有这些机制,我们如何获取导数呢?
你可以在输出上调用 backward() 方法,
并检查输入的 grad 属性以查看梯度out.backward()
print(a.grad)
plt.plot(a.detach(), a.grad.detach())
tensor([ 2.0000e+00, 1.9319e+00, 1.7321e+00, 1.4142e+00, 1.0000e+00,5.1764e-01, -8.7423e-08, -5.1764e-01, -1.0000e+00, -1.4142e+00,-1.7321e+00, -1.9319e+00, -2.0000e+00, -1.9319e+00, -1.7321e+00,-1.4142e+00, -1.0000e+00, -5.1764e-01, 2.3850e-08, 5.1764e-01,1.0000e+00, 1.4142e+00, 1.7321e+00, 1.9319e+00, 2.0000e+00])[<matplotlib.lines.Line2D object at 0x7f12e5cc5240>]
例子
参考:https://pytorch.ac.cn/tutorials/beginner/introyt/autogradyt_tutorial.html#what-do-we-need-autograd-for
BATCH_SIZE = 16
DIM_IN = 1000
HIDDEN_SIZE = 100
DIM_OUT = 10class TinyModel(torch.nn.Module):def __init__(self):super(TinyModel, self).__init__()self.layer1 = torch.nn.Linear(DIM_IN, HIDDEN_SIZE)self.relu = torch.nn.ReLU()self.layer2 = torch.nn.Linear(HIDDEN_SIZE, DIM_OUT)def forward(self, x):x = self.layer1(x)x = self.relu(x)x = self.layer2(x)return xsome_input = torch.randn(BATCH_SIZE, DIM_IN, requires_grad=False)
ideal_output = torch.randn(BATCH_SIZE, DIM_OUT, requires_grad=False)model = TinyModel()##########################################################################
print(model.layer2.weight[0][0:10]) # just a small slice
print(model.layer2.weight.grad)##########################################################################
optimizer = torch.optim.SGD(model.parameters(), lr=0.001)prediction = model(some_input)loss = (ideal_output - prediction).pow(2).sum()
print(loss)######################################################################
loss.backward()
print(model.layer2.weight[0][0:10])
print(model.layer2.weight.grad[0][0:10])########################################################################
optimizer.step()
print(model.layer2.weight[0][0:10])
print(model.layer2.weight.grad[0][0:10])######################################################################
print(model.layer2.weight.grad[0][0:10])for i in range(0, 5):prediction = model(some_input)loss = (ideal_output - prediction).pow(2).sum()loss.backward()print(model.layer2.weight.grad[0][0:10])optimizer.zero_grad(set_to_none=False)print(model.layer2.weight.grad[0][0:10])
如果我们查看模型的层,可以检查权重的数值,并验证尚未计算任何梯度
print(model.layer2.weight[0][0:10]) # just a small slice
print(model.layer2.weight.grad)
tensor([ 0.0920, 0.0916, 0.0121, 0.0083, -0.0055, 0.0367, 0.0221, -0.0276,-0.0086, 0.0157], grad_fn=<SliceBackward0>)
None
现在,让我们调用 loss.backward(),看看会发生什么
loss.backward()
print(model.layer2.weight[0][0:10])
print(model.layer2.weight.grad[0][0:10])
tensor([ 0.0920, 0.0916, 0.0121, 0.0083, -0.0055, 0.0367, 0.0221, -0.0276,-0.0086, 0.0157], grad_fn=<SliceBackward0>)
tensor([12.8997, 2.9572, 2.3021, 1.8887, 5.0710, 7.3192, 3.5169, 2.4319,0.1732, -5.3835])
我们可以看到,每个学习权重的梯度都已计算出来,但权重保持不变,因为我们还没有运行优化器。优化器负责根据计算出的梯度更新模型权重。
optimizer.step()
print(model.layer2.weight[0][0:10])
print(model.layer2.weight.grad[0][0:10])
tensor([ 0.0791, 0.0886, 0.0098, 0.0064, -0.0106, 0.0293, 0.0186, -0.0300,-0.0088, 0.0211], grad_fn=<SliceBackward0>)
tensor([12.8997, 2.9572, 2.3021, 1.8887, 5.0710, 7.3192, 3.5169, 2.4319,0.1732, -5.3835])
你应该看到 layer2 的权重已经改变了。
关于这个过程,重要的一点是:在调用 optimizer.step() 后,你需要调用 optimizer.zero_grad(),否则每次运行 loss.backward() 时,学习权重的梯度都会累积
print(model.layer2.weight.grad[0][0:10])
for i in range(0, 5):
prediction = model(some_input)
loss = (ideal_output - prediction).pow(2).sum()
loss.backward()
print(model.layer2.weight.grad[0][0:10])
optimizer.zero_grad(set_to_none=False)
print(model.layer2.weight.grad[0][0:10])
tensor([12.8997, 2.9572, 2.3021, 1.8887, 5.0710, 7.3192, 3.5169, 2.4319,0.1732, -5.3835])
tensor([ 19.2095, -15.9459, 8.3306, 11.5096, 9.5471, 0.5391, -0.3370,8.6386, -2.5141, -30.1419])
tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
运行上面的单元格后,你应该会看到,在多次运行 loss.backward() 后,大多数梯度的幅度会大得多。如果在运行下一个训练批次之前未能将梯度归零,将导致梯度以这种方式爆炸,从而导致错误和不可预测的学习结果。
3.backward()
通过调用backward(),我们可以对某个Variable(譬如说y)进行一次自动求导,但如果我们再对这个Variable进行一次backward()操作,会发现程序报错。这是因为PyTorch默认做完一次自动求导后,就把计算图丢弃了。我们可以通过设置retain_graph来实现多次求导。
backward &retain_graph时:
注意,当我们第二次使用相同参数调用 backward 时,梯度的值是不同的。这是因为在进行 backward 传播时,PyTorch 会累加梯度,即计算出的梯度值会添加到计算图所有叶子节点的 grad 属性中。如果你想计算正确的梯度,你需要先将 grad 属性清零。在实际训练中,优化器会帮助我们完成此操作
。参考:https://pytorch.ac.cn/tutorials/beginner/basics/autogradqs_tutorial.html
inp = torch.eye(4, 5, requires_grad=True)
out = (inp+1).pow(2).t()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"First call\n{inp.grad}")
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nSecond call\n{inp.grad}")
inp.grad.zero_()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nCall after zeroing gradients\n{inp.grad}")
输出
First call
tensor([[4., 2., 2., 2., 2.],[2., 4., 2., 2., 2.],[2., 2., 4., 2., 2.],[2., 2., 2., 4., 2.]])Second call
tensor([[8., 4., 4., 4., 4.],[4., 8., 4., 4., 4.],[4., 4., 8., 4., 4.],[4., 4., 4., 8., 4.]])Call after zeroing gradients
tensor([[4., 2., 2., 2., 2.],[2., 4., 2., 2., 2.],[2., 2., 4., 2., 2.],[2., 2., 2., 4., 2.]])
优化器:
4.grad_fn、grad和backward()
在 PyTorch 中,grad_fn
、grad
和 backward()
是 torch.autograd
模块的核心组成部分,用于实现自动求导和反向传播。以下是它们的详细说明:
grad_fn 构建计算图(张量计算方式),backward() 触发求导计算,grad 存储结果,共同实现高效的梯度计算。
1. grad_fn
作用:
grad_fn
是一个张量的属性(Tensor.grad_fn
),用于记录该张量的梯度计算方式(即它是通过什么操作生成的)。
- 非叶子节点:由用户定义的操作(如加法、乘法等)生成的张量会有一个
grad_fn
,指向具体的梯度计算函数(如AddBackward0
、MulBackward0
等)。 - 叶子节点:用户直接创建的张量(如模型参数)或不需要梯度的张量,其
grad_fn
为None
。
示例:
x = torch.tensor(2.0, requires_grad=True) # 叶子节点
y = x ** 2 + 3 * x + 1 # 非叶子节点
print(y.grad_fn) # 输出: <PowBackward0 object at 0x...>
y
的grad_fn
是PowBackward0
,表示它是通过幂运算生成的。
- 定义:
grad_fn
是张量的一个属性,记录了该张量的计算来源(即它是通过哪些操作生成的)。 - 作用:
- 构建动态计算图,跟踪张量之间的依赖关系,为反向传播提供梯度计算路径。
- 叶子节点(用户手动创建的张量)的
grad_fn
为None
,而非叶子节点(通过运算生成的张量)的grad_fn
会指向创建它的Function
对象 。
- 示例:
x = torch.tensor([1.0, 2.0], requires_grad=True) # 叶子节点,grad_fn=None y = x * 3 # 非叶子节点,grad_fn=<MulBackward>
2. grad
作用:
grad
是一个张量的属性(Tensor.grad
),用于存储该张量的梯度值。
- 叶子节点:如果张量是叶子节点(
is_leaf=True
),则grad
会保存反向传播计算出的梯度。 - 非叶子节点:默认情况下,非叶子节点的梯度不会被保存(因为 PyTorch 会释放计算图以节省内存),但可以通过
.retain_grad()
强制保留。
示例:
x = torch.tensor(2.0, requires_grad=True) # 叶子节点
y = x ** 2 + 3 * x + 1
y.backward() # 反向传播
print(x.grad) # 输出 dy/dx = 2x + 3 = 7
- 定义:
grad
是张量的一个属性,存储其梯度值(即损失函数对该张量的偏导数)。 - 作用:
- 只有叶子节点且
requires_grad=True
的张量才会存储梯度,非叶子节点默认不保存梯度(除非显式调用.retain_grad()
)。 - 调用
backward()
后,grad
会被填充为计算得到的梯度值 。
- 只有叶子节点且
- 注意事项:
- 多次调用
backward()
会导致梯度累加,因此需要手动调用optimizer.zero_grad()
或tensor.grad.zero_()
清零 。
- 多次调用
3. backward()
作用:
backward()
是触发反向传播的方法,用于计算梯度。
- 调用方式:通常对损失函数(标量)调用
.backward()
,PyTorch 会自动从输出节点开始反向传播,计算所有叶子节点的梯度。 - 向量/矩阵输出:如果输出不是标量,需要传入
grad_tensors
参数(一个与输出形状相同的张量),指定梯度权重。
关键行为:
- 动态计算图:PyTorch 的计算图是动态的(每次前向传播会重新构建计算图)。
- 梯度累加:默认情况下,多次调用
backward()
会累加梯度(需手动调用.zero_grad()
清零)。 - 内存释放:默认在
backward()
后释放计算图(通过retain_graph=False
控制)。
示例:
x = torch.tensor(2.0, requires_grad=True)
y = x ** 2 + 3 * x + 1
y.backward() # 计算 dy/dx
print(x.grad) # 输出 7.0
- 定义:
backward()
是触发反向传播的函数,自动计算梯度并填充到对应的grad
属性中。 - 作用:
- 从当前张量(通常是损失值)出发,沿着计算图回溯,利用链式法则计算所有叶子节点的梯度 。
- 仅计算满足以下条件的张量梯度:
- 是叶子节点;
requires_grad=True
;- 依赖于当前张量 。
- 注意事项:
backward()
不返回梯度值,而是直接修改grad
属性;若需要返回梯度值,可使用torch.autograd.grad()
。
协同工作流程
三者的协同工作流程
- 前向计算:
创建计算图,非叶子节点记录grad_fn
(如AddBackward
,MulBackward
)。 - 反向传播:
调用loss.backward()
,从loss
开始回溯计算图,计算所有叶子节点的梯度并存储到grad
中 。 - 优化更新:
使用优化器(如SGD
)根据grad
更新模型参数,随后清零梯度以避免累加 。
示例:
x = torch.tensor([1.0, 2.0], requires_grad=True)
y = x * 3
z = y.sum()
z.backward() # 计算梯度
print(x.grad) # 输出: tensor([3., 3.])
三者的关系
概念 | 作用 |
---|---|
grad_fn | 记录张量的梯度计算方式(如加法、乘法等),用于反向传播时的链式法则。 |
grad | 存储张量的梯度值,仅对叶子节点有效(非叶子节点默认不保存)。 |
backward() | 触发反向传播,利用 grad_fn 计算梯度,并将结果存储在 grad 中(仅叶子节点)。 |
特性 | backward() | grad | grad_fn |
---|---|---|---|
类型 | 方法 | 属性 | 属性 |
作用 | 启动反向计算 | 存储梯度值 | 记录操作(张量计算方式:如加法、乘法等)历史 |
存在位置 | 输出张量调用 | 叶子节点张量 | 非叶子节点张量 |
内容 | N/A | 梯度值(Tensor) | 操作函数(Function) |
是否可写 | 不可写 | 可修改(谨慎!) | 只读 |
常见问题
Q1: 为什么非叶子节点的 grad
默认为空?
A: 为了节省内存,PyTorch 默认只保留叶子节点的梯度。非叶子节点的梯度在反向传播后会被释放,除非调用 .retain_grad()
。
Q2: 如何强制保留非叶子节点的梯度?
A: 调用 .retain_grad()
:
a = x ** 2 # 非叶子节点
a.retain_grad()
a.backward()
print(a.grad) # 输出非空
Q3: 向量输出如何调用 backward()
?
A: 需要传入 grad_tensors
参数(权重):
x = torch.tensor([1.0, 2.0], requires_grad=True)
y = x ** 2 # y 是向量 [1, 4]
y.backward(torch.tensor([1.0, 1.0])) # 等价于求 dy[0]/dx[0] + dy[1]/dx[1]
print(x.grad) # 输出 [2.0, 4.0]
总结
grad_fn
:记录梯度计算方式,是反向传播的“路径”。grad
:存储梯度值,仅叶子节点可用。backward()
:触发反向传播,根据grad_fn
计算梯度并填充到grad
中。
通过这三者的协作,PyTorch 实现了高效的自动微分机制,简化了深度学习模型的训练过程。
5.Autograd汇总
以下是网址内容的汇总,结合知识库信息整理而成:
Autograd 的核心作用
-
动态计算与梯度追踪
PyTorch 的torch.autograd
是其自动微分引擎,通过动态记录计算过程(如张量操作)构建计算图,支持在复杂模型(如包含分支或循环的模型)中高效计算梯度。这种动态机制相比静态图框架(如 TensorFlow 1.x)提供了更高的灵活性 。 -
梯度计算原理
Autograd 基于链式法则自动计算损失函数对模型参数的偏导数(梯度),这是神经网络反向传播的核心。例如,若模型输出 y = M ( x ) y = M(x) y=M(x),损失函数 L ( y ) L(y) L(y) 的梯度 ∂ L ∂ x \frac{\partial L}{\partial x} ∂x∂L 会通过链式法则分解为局部导数的乘积 。- 核心概念
Autograd 作用:
自动计算神经网络中的梯度(偏导数)
支持动态计算图,允许模型有决策分支/循环
是反向传播和模型训练的核心机制 - 工作原理:
张量设置 requires_grad=True 时跟踪计算历史
每个操作记录在 grad_fn 属性中
调用 .backward() 时反向传播计算梯度
- 核心概念
Autograd 的使用示例
-
张量与
requires_grad
标志
需要计算梯度的张量需设置requires_grad=True
。例如,创建输入张量a = torch.linspace(0, 2π, steps=25, requires_grad=True)
,后续操作(如b = torch.sin(a)
)会记录梯度函数grad_fn
。 -
反向传播与梯度获取
通过调用.backward()
计算梯度,叶子节点(如输入张量a
)的.grad
属性存储梯度值。例如,对d = c.sum()
调用d.backward()
,可获取a.grad
,其值与理论导数 $ 2\cos(a) $ 一致 。 -
非叶子节点的梯度限制
中间张量(如c
或b
)默认不存储梯度,需显式调用.retain_grad()
才能访问其梯度 。
训练中的 Autograd 实践
-
模型参数的梯度计算
在torch.nn.Module
中定义的层(如Linear
)会自动追踪权重梯度。例如,模型输出后通过loss.backward()
计算所有参数的梯度,并利用优化器(如 SGD)更新权重 。 -
梯度累积与清零
多次调用.backward()
会导致梯度累加,需在每次优化前调用optimizer.zero_grad()
避免梯度爆炸。例如,重复 5 次反向传播后,梯度值会显著增大,归零后恢复初始状态 。
Autograd 的控制与优化
-
启用/禁用 Autograd
- 直接修改张量的
requires_grad
标志 。 - 使用
torch.no_grad()
上下文管理器或装饰器临时禁用梯度计算(如推理阶段)。 - 通过
detach()
创建无梯度历史的副本,用于脱离计算图(如可视化或数据转换)。
- 直接修改张量的
-
就地操作(In-place Operations)的限制
Autograd 禁止对需要梯度的叶子张量执行就地操作(如torch.sin_()
),以避免破坏计算历史导致梯度计算错误 。 -
性能分析工具
Autograd 内置性能分析器(torch.autograd.profiler
),可记录张量操作的时间开销(如 CPU/GPU 上的耗时),帮助优化模型 。
总结
Autograd 通过动态计算图和自动微分机制,简化了复杂模型的梯度计算,成为 PyTorch 构建高效机器学习项目的核心工具。其灵活性(如支持动态控制流)和易用性(如自动参数追踪)显著降低了实现神经网络的门槛 。
6.动态图
关键行为:
- 动态计算图:PyTorch 的计算图是动态的(每次前向传播会重新构建计算图)。
- 梯度累加:默认情况下,多次调用
backward()
会累加梯度(需手动调用.zero_grad()
清零)。 - 内存释放:默认在
backward()
后释放计算图(通过retain_graph=False
控制)。
6、Python with 语句
Python 的 with
语句本质是通过**上下文管理器(Context Manager)**自动管理资源的语法糖。其底层逻辑可通过手动调用上下文管理器的 __enter__
和 __exit__
方法,并结合 try...finally
块实现等价替换。以下是详细的展开逻辑和代码示例:
Python 中的 with 语句是一种用于资源管理的语法糖,核心作用是确保在代码块执行前后,资源能被正确获取和释放。它的本质依赖「上下文管理器(Context Manager)」,能自动处理资源的分配与清理,避免因忘记释放资源(如文件未关闭、网络连接未断开)导致的程序错误或性能问题。
一、with
语句的执行流程
with
语句的完整执行流程可分解为以下 5 步(假设代码为 with A() as B: C
):
- 执行
A()
创建上下文管理器对象(记为cm
)。 - 调用
cm.__enter__()
方法,返回值赋给变量B
(即as
后的变量)。 - 执行代码块
C
(即with
缩进内的代码)。 - 无论代码块
C
是否抛出异常,最终都会调用cm.__exit__(exc_type, exc_val, exc_tb)
方法:exc_type
:异常类型(无异常时为None
)。exc_val
:异常对象(无异常时为None
)。exc_tb
:异常追踪信息(无异常时为None
)。
- 若
__exit__
返回True
,则抑制代码块中的异常(不向外传播);若返回False
,则异常会继续向上抛出。
二、with
语句的等价替换代码
根据上述流程,with
语句可手动展开为以下结构(完全等价):
# 原始 with 语句
with context_expr as var:block# 等价的手动展开代码
cm = context_expr # 1. 创建上下文管理器对象
var = cm.__enter__() # 2. 调用 __enter__,赋值给 var
try:block # 3. 执行代码块
finally:# 4. 无论是否异常,调用 __exit__exc_type, exc_val, exc_tb = sys.exc_info() # 获取异常信息(若有)# 调用 __exit__,并处理异常抑制逻辑suppress = cm.__exit__(exc_type, exc_val, exc_tb)# 若 __exit__ 返回 True,则抑制异常(不抛出)if suppress:exc_type = exc_val = exc_tb = None# 否则,异常会被自动抛出(由 finally 后的代码处理)
三、代码示例:文件操作的展开对比
# 传统写法(需手动关闭)
file = open("test.txt", "r")
try:content = file.read()
finally:file.close() # 必须手动关闭,否则可能占用资源# with 写法(自动关闭)
with open("test.txt", "r") as file:content = file.read() # 代码块结束后,file 会自动关闭
原始 with
写法
with open("test.txt", "r") as f:content = f.read() # 代码块执行
# 代码块结束后,f 自动关闭(由 __exit__ 处理)
手动展开后的等价代码
import sys# 1. 创建上下文管理器(文件对象)
cm = open("test.txt", "r")
try:# 2. 调用 __enter__,返回文件对象并赋值给 ff = cm.__enter__()try:# 3. 执行代码块content = f.read()finally:# 4. 无论是否异常,调用 __exit__ 关闭文件exc_type, exc_val, exc_tb = sys.exc_info()# 调用文件对象的 __exit__(内部调用 f.close())suppress = cm.__exit__(exc_type, exc_val, exc_tb)# 若 __exit__ 返回 True,抑制异常if suppress:exc_type = exc_val = exc_tb = None
finally:# 确保上下文管理器对象被清理(如文件未关闭时强制关闭)pass
7、with torch.no_grad() 禁用梯度跟踪
参考:https://pytorch.ac.cn/tutorials/beginner/basics/autogradqs_tutorial.html
with torch.no_grad():
是 PyTorch 中用于临时禁用自动梯度计算的上下文管理器,核心作用是在代码块执行期间关闭梯度记录,从而减少内存消耗并提升计算速度。它是 PyTorch 自动微分(AutoGrad)机制的重要工具,常见于模型推理(预测)、数据预处理等不需要梯度更新的场景。
一、为什么需要 torch.no_grad()
?
PyTorch 的自动梯度(AutoGrad)机制会在每次 Tensor 操作时记录计算图(Computation Graph),用于后续反向传播(Backward)时计算梯度。但在以下场景中,梯度计算是冗余的:
- 模型推理(预测):只需前向传播得到输出,无需更新模型参数。
- 数据预处理/中间计算:例如特征提取、归一化等辅助操作,不涉及模型训练。
- 节省内存:梯度计算需要存储中间结果(如各节点的梯度值),关闭梯度可显著减少内存占用(尤其是大模型或批量数据场景)。
二、with torch.no_grad():
的具体行为
当进入 with torch.no_grad():
代码块时,PyTorch 会临时禁用自动梯度计算,具体表现为:
-
新创建的 Tensor 默认
requires_grad=False
块内新建的 Tensor(如x = torch.tensor([1.0])
)会被自动标记为不需要梯度(x.requires_grad
为False
)。 -
已有 Tensor 的梯度计算被跳过
即使块外的 Tensor 原本requires_grad=True
(如模型参数),块内对其的操作也不会记录梯度(即不会构建计算图)。 -
反向传播(
backward()
)被禁用
若在块内尝试调用loss.backward()
,会直接报错(因无梯度信息可计算)。
三、代码示例:对比有无 no_grad
的差异
以下通过具体代码演示 torch.no_grad()
的效果:
示例 1:禁用梯度后,新 Tensor 的 requires_grad
行为
import torch# 块外:默认 requires_grad=False(普通 Tensor)
x = torch.tensor([2.0])
print(x.requires_grad) # 输出:False# 块外:模型参数(通常 requires_grad=True)
w = torch.tensor([3.0], requires_grad=True)
print(w.requires_grad) # 输出:True# 进入 no_grad 块
with torch.no_grad():# 块内新建 Tensor:自动 requires_grad=Falsey = torch.tensor([4.0])print(y.requires_grad) # 输出:False# 块内操作已有 Tensor(w):不记录梯度z = w * x # 等价于 z = w.mul(x)print(z.requires_grad) # 输出:False(因无梯度记录)# 块外:w 的 requires_grad 恢复为 True(不受块影响)
print(w.requires_grad) # 输出:True
示例 2:禁用梯度对内存和计算速度的影响(推理场景)
import torch
from time import time# 模拟一个大模型(如 ResNet)
model = torch.nn.Sequential(torch.nn.Linear(1000, 2000),torch.nn.ReLU(),torch.nn.Linear(2000, 1000)
)
input_data = torch.randn(1000, 1000) # 批量数据(1000 样本)# 无 no_grad:需要记录梯度(内存占用高,速度慢)
start = time()
output = model(input_data)
loss = output.sum()
loss.backward() # 反向传播(假设误操作)
print(f"无 no_grad 耗时:{time()-start:.4f}s")# 有 no_grad:禁用梯度(内存占用低,速度快)
start = time()
with torch.no_grad():output = model(input_data) # 前向传播不记录梯度
# 尝试反向传播会报错(因无梯度信息)
# loss = output.sum()
# loss.backward() # 报错:Trying to backward through a tensor that does not require gradients
print(f"有 no_grad 耗时:{time()-start:.4f}s")
输出(示意):
无 no_grad 耗时:0.1234s # 耗时更长,内存占用更高
有 no_grad 耗时:0.0456s # 耗时更短,内存占用更低
实现机制图解
普通前向传播:
输入 → [计算图构建] → 输出↳ 保存中间变量用于反向传播no_grad 模式:
输入 → [纯计算] → 输出↳ 不构建计算图,不保存中间结果
四、常见使用场景
with torch.no_grad():
主要用于以下不需要梯度计算的场景:
-
模型推理(预测)
部署模型时,只需前向传播得到预测结果,无需反向更新参数。例如:model.eval() # 切换到推理模式(关闭 Dropout/BatchNorm 等) with torch.no_grad():for batch in test_dataloader:outputs = model(batch)predictions = torch.argmax(outputs, dim=1)
-
数据预处理/特征提取
例如从原始数据中提取特征(如用预训练模型提取图像特征),不需要梯度:with torch.no_grad():features = pretrained_model(raw_data) # 提取特征
-
梯度验证或调试
临时禁用梯度,验证模型前向计算是否正常(避免反向传播干扰)。
五、注意事项
- 不影响已有 Tensor 的
requires_grad
属性:no_grad
仅临时禁用梯度计算,不会修改块外 Tensor 的requires_grad
标记(块外 Tensor 仍保持原有状态)。 - 与
model.eval()
的区别:model.eval()
用于切换模型状态(如关闭 Dropout、固定 BatchNorm 统计量),而no_grad()
用于禁用梯度计算,二者常配合使用(推理时既需要模型状态正确,也需要禁用梯度)。 - 反向传播必须在块外:若需要计算梯度(如训练时),需确保
backward()
在no_grad
块外调用。
六、与 model.eval()
的关系
操作 | 作用 | 是否互斥 |
---|---|---|
with torch.no_grad() | 禁用梯度计算 | × |
model.eval() | 关闭Dropout/BatchNorm等特殊层 | × |
最佳实践:通常同时使用二者
model.eval() with torch.no_grad():# 执行推理或评估
with torch.no_grad():
是 PyTorch 中的一个上下文管理器,用于临时禁用梯度计算,具体作用如下:
-
禁用梯度跟踪
在with torch.no_grad():
包裹的代码块中,所有张量操作都不会记录梯度(即不会构建计算图),即使输入张量的requires_grad=True
,输出张量的requires_grad
也会被强制设为False
。这避免了反向传播所需的内存开销,显著减少显存占用并加速计算 。 -
适用场景
主要用于模型推理(如预测阶段)或模型评估(如验证集测试),因为这些场景不需要梯度计算 。此外,在更新模型参数时(如手动优化器步骤),也可通过此上下文管理器避免梯度被意外跟踪 。 -
与
@torch.no_grad()
的关系
with torch.no_grad():
是上下文管理器形式,作用范围仅限代码块;而@torch.no_grad()
是装饰器形式,作用于整个函数或方法 。两者功能相同,均通过局部禁用梯度计算优化资源 。 -
注意事项
- 不会影响模型参数的
requires_grad
属性(即可训练参数集合不变)。 - 若需切换模型到评估模式(如关闭 Dropout 或 BatchNorm 的训练行为),需额外调用
model.eval()
。
示例代码:
model = MyModel().eval() # 切换评估模式 with torch.no_grad(): # 禁用梯度计算output = model(input_tensor) # 推理过程无需构建计算图
- 不会影响模型参数的
总结
with torch.no_grad():
本质是:
临时关闭autograd引擎 → 执行代码 → 恢复原状态
它是PyTorch的推理加速器,通过避免不必要的梯度计算
with torch.no_grad():
是 PyTorch 中控制自动梯度的核心工具,通过临时关闭梯度计算,能有效减少内存消耗并提升计算效率,是模型推理、数据预处理等场景的“性能优化利器”。理解其行为和适用场景,能帮助你更高效地编写 PyTorch 代码。
8、model.train()和model.eval()
0.汇总
Dropout 层通过在 训练期间 随机设置输入张量的部分来工作——Dropout
层在推理时总是关闭的。这迫使模型针对这个掩蔽或缩减的数据集进行学习。 (请注意,我们在训练前总是调用参考:https://pytorch.ac.cn/tutorials/beginner/introyt/modelsyt_tutorial.html
model.train(),在推理前调用 model.eval(),因为这些函数会被 nn.BatchNorm2d 和 nn.Dropout
等层使用,以确保它们在不同阶段的正确行为。)参考
(注意,view 是 PyTorch 中的版本,对应于 Numpy 的 reshape)
model.train()、model.eval()和mode.test
-
模型的训练模式(train mode):
model.train()
:在使用 pytorch 构建神经网络的时候,训练过程中会在程序上方添加一句model.train( ),作用是启用 batch normalization 和 dropout 。如果模型中有BN层(Batch Normalization)和 Dropout ,需要在训练时添加 model.train( )。model.train( ) 是保证 BN 层能够用到每一批数据的均值和方差。对于 Dropout,model.train( ) 是随机取一部分网络连接来训练更新参数。
-
模型评估方法有两种:
-
model.eval()
:将模型设置为评估模式,用于在验证集或测试集上评估模型性能。在评估模式下,模型会将dropout
和batch normalization
等层设置为不工作,这样可以保证每次评估得到的结果是一致的。同时,评估模式下还可以通过设置torch.no_grad()
来禁用梯度计算,避免浪费计算资源。 -
model.test()
:该方法在pytorch
中并不存在,可能是因为评估模式下已经能够满足测试需求。在一些框架中,test
模式通常用于模型的推理阶段,即将模型应用到真实数据上进行预测。与评估模式类似,test
模式也会将dropout
和batch normalization
等层设置为不工作,以保证每次预测结果一致。但是,test
模式通常需要考虑一些其他因素,例如数据增强(data augmentation
)、模型融合(model ensemble
)等。
-
训练模式与评估模式的区别
在Pytorch中,模型的训练模式(train mode)和评估模式(eval mode)之间有几个关键区别。
- Batch Normalization和Dropout层的行为不同:在训练模式下,Batch Normalization和Dropout被启用,能够提高模型的泛化能力。而在评估模式下,他们被禁用,以确保模型的输出稳定和可重复。
- 梯度计算和权重更新不同:在训练模式下,模型会自动计算每个参数的梯度,并通过优化器进行权重更新。而在评估模式下,模型只进行前向传播,并不进行梯度计算和权重更新。
- 总结与对比
- 如果模型中有 BN 层(Batch Normalization)和 Dropout,需要在训练时添加 model.train(),在测试时添加 model.eval( )。
- 其中
model.train( ) 是保证 BN 层用每一批数据的均值和方差,而 model.eval( ) 是保证 BN 用全部训练数据的均值和方差
; - 而对于 Dropout,model.train( ) 是随机取一部分网络连接来训练更新参数,而 model.eval( ) 是利用到了所有网络连接。
获取当前模式的方法:
在Pytorch中,可以通过以下两种方法来检查模型的当前模式
- 通过model.training属性来检查:model.training属性返回一个布尔值,表示当前模型是否处于训练模式。如果返回True,则表示模型处于训练模式;如果返回False,则表示模型处于评估模式。
- 通过model.mode属性来检查:model.mode是Pytorch中自定义的模型属性,用于表示模型的模式。可以通过自定义的model.set_mode()方法来设置模型的模式。在set_mode()方法中,可以根据需要设置model.training属性,并更新model.mode属性。通过model.get_mode()方法可以获取当前模型的模式。
在深度学习框架(如PyTorch)中,model.train()
、model.eval()
和自定义的 model.test()
用于控制模型在不同阶段的行为。以下是详细解释:
1. model.train()
model.train()
的作用是将模型设置为训练模式。此时,模型中的某些层(如 Dropout、BatchNorm 等)会启用与训练相关的特性:
-
Dropout 层:会随机“关闭”部分神经元(按设定的概率),防止过拟合。
-
BatchNorm 层:会根据当前批次数据的均值和方差计算归一化参数,并更新全局的均值/方差统计量(通过移动平均)。
-
作用:
将模型设置为训练模式,启用训练相关的特定行为。 -
关键影响:
- Dropout层:按照设定的概率随机丢弃神经元,防止过拟合。
- Batch Normalization层:使用当前批次的均值和方差进行归一化,并更新全局统计量(移动平均)。
-
使用场景:
在训练循环中调用,确保模型以训练模式处理数据。model.train() # 设置训练模式 for data, labels in train_loader:outputs = model(data)loss = criterion(outputs, labels)loss.backward()optimizer.step()
2. model.eval()
model.eval()
的作用是将模型设置为评估模式(推理模式)。此时,模型中的训练相关特性会被关闭:
- Dropout 层:不再随机失活神经元(所有神经元保持激活),确保推理结果的稳定性。
- BatchNorm 层:不再更新全局统计量,而是使用训练阶段累积的均值/方差(如
running_mean
和running_var
)进行归一化,避免推理时因批次数据波动导致结果不稳定。 - 作用:
将模型设置为评估模式,关闭训练特有的行为。 - 关键影响:
- Dropout层:禁用随机丢弃,使用所有神经元。
- Batch Normalization层:使用训练阶段积累的全局均值和方差(而非当前批次),保持输出稳定。
- 自动梯度计算:通常与
torch.no_grad()
配合使用,减少内存占用。
- 使用场景:
在验证/测试/推理阶段调用,确保结果可复现且一致。model.eval() # 设置评估模式 with torch.no_grad(): # 禁用梯度计算for data, labels in test_loader:outputs = model(data)accuracy = calculate_accuracy(outputs, labels)
3. model.test()
(自定义方法)
PyTorch 等框架没有内置的 model.test()
方法。它通常是用户自定义的方法,用于实现测试阶段的逻辑(如加载测试数据、计算指标等)。
- 注意:
PyTorch没有内置的model.test()
方法。这是一个用户自定义的扩展,通常用于:- 封装测试逻辑:计算指标(如准确率、F1分数)。
- 特殊测试行为:例如在特定数据集上评估模型。
- 典型实现:
class MyModel(nn.Module):def __init__(self):super().__init__()self.layer = nn.Linear(10, 2)def forward(self, x):return self.layer(x)def test(self, test_loader):self.eval() # 切换到评估模式total_correct = 0with torch.no_grad():for data, labels in test_loader:outputs = self(data)preds = outputs.argmax(dim=1)total_correct += (preds == labels).sum().item()accuracy = total_correct / len(test_loader.dataset)print(f"Test Accuracy: {accuracy:.4f}")
- 调用方式:
model = MyModel() model.load_state_dict(torch.load("model.pth")) model.test(test_loader) # 执行自定义测试逻辑
4.关键区别总结
方法 | 模式 | Dropout/BN 行为 | 梯度计算 | 主要用途 |
---|---|---|---|---|
model.train() | 训练模式 | Dropout激活;BN用当前批次统计 | 启用 | 训练阶段 |
model.eval() | 评估模式 | Dropout关闭;BN用全局固定统计 | 通常禁用 | 验证/测试/推理 |
model.test() | 自定义逻辑 | 需手动调用 eval() | 通常禁用 | 封装测试流程 |
5.最佳实践
- 训练阶段:
model.train() # 明确设置训练模式 # 训练循环...
- 测试阶段:
model.eval() # 切换到评估模式 with torch.no_grad():# 测试循环...
- 自定义测试:
在模型中实现test()
方法,内部调用eval()
和torch.no_grad()
,并包含指标计算逻辑。
9、设置训练设备accelerator
详细参考:参考
我们希望能够在 加速器 上训练我们的模型,例如 CUDA、MPS、MTIA 或 XPU。如果当前加速器可用,我们将使用它。否则,我们使用 CPU。
参考:https://pytorch.ac.cn/tutorials/beginner/basics/buildmodel_tutorial.html
device = torch.accelerator.current_accelerator().type if torch.accelerator.is_available() else "cpu"
print(f"Using {device} device")
10、优化器(torch.optim)
torch.optim
是 PyTorch 中用于优化神经网络模型参数的核心模块,它实现了多种优化算法(如 SGD、Adam、RMSprop 等),通过计算损失函数对参数的梯度并更新模型权重,以最小化损失函数。以下是关于 torch.optim
的详细解析:
1. 核心功能
- 优化算法实现:提供多种优化算法(如 SGD、Adam、Adagrad、RMSprop 等)。
- 动态学习率调整:支持学习率调度器(如
lr_scheduler
)动态调整学习率。 - 参数更新:通过梯度反向传播更新模型参数。
2. 常见优化器
2.1 SGD(随机梯度下降)
- 特点:基础优化算法,适用于大多数简单任务。
- 公式:
θ t + 1 = θ t − η ⋅ ∇ θ J ( θ t ) \theta_{t+1} = \theta_t - \eta \cdot \nabla_\theta J(\theta_t) θt+1=θt−η⋅∇θJ(θt) - 参数:
lr
:学习率(控制步长)。momentum
:动量因子(加速收敛,默认 0)。dampening
:动量阻尼(默认 0)。nesterov
:是否使用 Nesterov 动量(默认False
)。
- 示例:
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
2.2 Adam(自适应矩估计)
- 特点:结合动量法和自适应学习率,适合大规模数据和复杂模型。
- 参数:
lr
:学习率(默认 0.001)。betas
:一阶矩和二阶矩的平滑系数(默认(0.9, 0.999)
)。eps
:数值稳定性项(默认1e-8
)。weight_decay
:权重衰减(L2 正则化)。
- 示例:
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
2.3 RMSprop
- 特点:Adagrad 的改进版,解决学习率过早衰减的问题。
- 参数:
lr
:学习率。alpha
:平滑常数(默认 0.99)。eps
:数值稳定性项。
- 示例:
optimizer = torch.optim.RMSprop(model.parameters(), lr=0.01)
2.4 Adagrad
- 特点:自适应优化算法,为每个参数分配不同的学习率。
- 参数:
lr
:学习率。lr_decay
:学习率衰减(默认 0)。
- 示例:
optimizer = torch.optim.Adagrad(model.parameters(), lr=0.01)
3. 优化器核心操作
3.1 初始化优化器
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
3.2 zero_grad()
- 功能:清除所有参数的梯度,避免梯度累积。
- 示例:
optimizer.zero_grad()
3.3 loss.backward()
- 功能:计算损失函数对参数的梯度(反向传播)。
3.4 step()
- 功能:根据梯度更新参数。
- 示例:
optimizer.step()
3.5 state_dict()
和 load_state_dict()
- 功能:保存和加载优化器状态(如动量、学习率等)。
- 示例:
# 保存状态 torch.save(optimizer.state_dict(), "optimizer.pth") # 加载状态 optimizer.load_state_dict(torch.load("optimizer.pth"))
4. 学习率调整(lr_scheduler
)
4.1 StepLR
- 功能:按固定周期衰减学习率。
- 示例:
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)
4.2 ReduceLROnPlateau
- 功能:根据验证集性能动态调整学习率。
- 示例:
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min', patience=5)
4.3 CosineAnnealingLR
- 功能:余弦退火策略,周期性调整学习率。
- 示例:
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50)
5. 完整训练流程示例
import torch
import torch.nn as nn
import torch.optim as optim# 定义模型
class SimpleNet(nn.Module):def __init__(self):super().__init__()self.fc = nn.Linear(10, 2)def forward(self, x):return self.fc(x)model = SimpleNet()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)# 模拟数据
inputs = torch.randn(32, 10) # 32个样本,10维特征
targets = torch.randint(0, 2, (32,)) # 32个标签# 训练循环
for epoch in range(10):optimizer.zero_grad()outputs = model(inputs)loss = criterion(outputs, targets)loss.backward()optimizer.step()print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")
6. 高级用法
6.1 为不同参数组设置不同超参数
optimizer = optim.Adam([{'params': model.base.parameters(), 'lr': 1e-2},{'params': model.classifier.parameters(), 'lr': 1e-3}
], lr=1e-2) # 默认学习率
6.2 添加钩子(Hook)
- 功能:在优化器状态加载/保存前后插入自定义逻辑。
- 示例:
optimizer.register_load_state_dict_pre_hook(my_pre_hook) optimizer.register_load_state_dict_post_hook(my_post_hook)
7. 总结
- 选择优化器:
- 简单任务:SGD(可加动量)。
- 复杂任务:Adam(默认选择)。
- 大规模数据:RMSprop 或 Adagrad。
- 调参建议:
- 学习率(
lr
):通常在[1e-5, 1e-1]
范围内搜索。 - 动量(
momentum
):SGD 中常用 0.9。 - 权重衰减(
weight_decay
):防止过拟合,通常设为1e-4
或1e-5
。
- 学习率(
如果需要更具体的优化器配置或调度策略的示例,请告诉我!
11、TensorBoard 可视化模型、数据和训练
https://pytorch.ac.cn/tutorials/intermediate/tensorboard_tutorial.html
https://pytorch.ac.cn/tutorials/beginner/introyt/tensorboardyt_tutorial.html
PyTorch 集成了 TensorBoard,这是一个专门用于可视化神经网络训练结果的工具。本教程将介绍 TensorBoard 的一些功能,使用 Fashion-MNIST 数据集,该数据集可以使用 torchvision.datasets 加载到 PyTorch 中。
在本教程中,我们将学习如何
-
读取数据并进行适当的变换 (与之前的教程几乎相同)。
-
设置 TensorBoard。
-
向 TensorBoard 写入数据。
-
使用 TensorBoard 检查模型架构。
-
使用 TensorBoard 创建我们在上一个教程中创建的可视化的交互式版本,并减少代码量
12、Captum(次要)
参考:https://pytorch.ac.cn/tutorials/beginner/introyt/captumyt.html
Captum (在拉丁语中意为“理解”) 是一个构建于 PyTorch 之上的开源、可扩展的模型可解释性库。
随着模型复杂性的增加以及由此导致的透明度不足,模型可解释性方法变得越来越重要。模型理解既是一个活跃的研究领域,也是机器学习在各行各业实际应用的重点领域。Captum 提供了最先进的算法,包括集成梯度 (Integrated Gradients),为研究人员和开发者提供一种简单的方式来理解哪些特征对模型的输出有所贡献。
完整的文档、API 参考和针对特定主题的一系列教程可在 captum.ai 网站上找到。
引言
Captum 对模型可解释性的方法基于归因。Captum 中提供了三种类型的归因
特征归因 (Feature Attribution) 旨在根据生成特定输出的输入特征来解释该输出。解释一篇影评是积极还是消极,通过影评中的某些词语来理解,就是特征归因的一个例子。
层归因 (Layer Attribution) 检查模型隐藏层在接收特定输入后的活动情况。检查卷积层对输入图像的空间映射输出就是一个层归因的例子。
神经元归因 (Neuron Attribution) 类似于层归因,但侧重于单个神经元的活动。
13、深度学习书籍
https://course.fastai.net.cn/
我们首先只使用 PyTorch 张量操作创建一个模型。我们假设你已经熟悉神经网络的基础知识。(如果你不熟悉,可以在 course.fast.ai 上学习)。(参考:https://pytorch.ac.cn/tutorials/beginner/nn_tutorial.html)
面向初学者的 NLP 入门(fastai中的):https://www.kaggle.com/code/jhoward/getting-started-with-nlp-for-absolute-beginners
14、入门实战示例
https://pytorch.ac.cn/tutorials/beginner/basics/quickstart_tutorial.html
https://pytorch.ac.cn/tutorials/beginner/nn_tutorial.html#mnist-data-setup
https://pytorch.ac.cn/pytorch-domains(PyTorch 提供特定领域的库,例如 TorchText、TorchVision 和 TorchAudio
)
1、TorchVision-FashionMNIST
FashionMNIST示例:
https://pytorch.ac.cn/tutorials/beginner/basics/quickstart_tutorial.html
https://github.com/pytorch/tutorials/blob/master/beginner_source/basics/quickstart_tutorial.py
数据集:TorchVision
https://pytorch.ac.cn/vision/stable/datasets.html
PyTorch 提供特定领域的库,例如 TorchText、TorchVision 和 TorchAudio
,所有这些库都包含数据集。在本教程中,我们将使用 TorchVision 数据集。
torchvision.datasets 模块包含许多真实世界视觉数据的 Dataset 对象,例如 CIFAR、COCO(完整列表在此)。在本教程中,我们使用 FashionMNIST 数据集。每个 TorchVision Dataset 都包含两个参数:transform 和 target_transform,分别用于修改样本和标签。
2、TorchText–NLP
https://pytorch.ac.cn/text/stable/index.html
https://pytorch.ac.cn/text/stable/index.html (数据集)
本教程是一个三部分系列的一部分
- 从零开始学自然语言处理:使用字符级 RNN 对名字进行分类
- 从零开始学自然语言处理:使用字符级 RNN 生成名字
- 从零开始学自然语言处理:使用序列到序列网络和注意力机制进行翻译
RNN和LSTM原理参考
循环神经网络令人惊叹的有效性 展示了大量现实生活中的示例
理解 LSTM 网络 专门关于 LSTM,但也提供关于 RNN 的通用信息
https://colah.github.io/posts/2015-08-Understanding-LSTMs/
https://karpathy.github.io/2015/05/21/rnn-effectiveness/
3、强化学习
https://pytorch.ac.cn/tutorials/intermediate/reinforcement_q_learning.html
强化学习 (DQN) 教程
使用 TorchRL 的强化学习 (PPO) 教程
训练玩马里奥的 RL 智能体
Pendulum: 使用 TorchRL 编写环境和变换
4、秘籍–PyTorch 代码示例集
秘籍是短小精悍、可操作的示例,展示了如何使用特定的 PyTorch 功能,与我们的完整教程不同。
https://pytorch.ac.cn/tutorials/recipes/recipes_index.html
将动态量化应用于简单的 LSTM 模型。
https://pytorch.ac.cn/tutorials/recipes/recipes/dynamic_quantization.html