PyTorch Learning 笔记
目录
Pytorch学习笔记
本来我是不想记笔记的,但是我学着学着发现,如果不简要记些东西,下次查看的时候难以连贯思路,因此写一个我的学习路径和思考。
张量
-
前置知识:Numpy,机器学习基础
-
多维数组
-
形状的理解
-
主要属性
-
在深度学习中的作用
PyTorch 张量常见功能映射表
| 功能类别 | 数学/NumPy 概念 | PyTorch 对应函数/方法 | 常见用途和说明 |
|---|---|---|---|
| I. 创建与初始化 | 零矩阵 | torch.zeros(shape) |
创建一个所有元素都为 0 的张量。 |
| '1'矩阵 | torch.ones(n, m) |
创建一个 $n \times m$ 的全'1'矩阵。 | |
| 单位矩阵 | torch.eye(n, m) |
创建一个 $n \times m$ 的单位矩阵。 | |
| 随机数 | torch.rand(shape) |
创建服从 $[0, 1)$ 均匀分布的张量。 | |
| 正态分布随机数 | torch.randn(shape) |
创建服从标准正态分布 ($\mu=0, \sigma=1$) 的张量。 | |
| 基于现有张量创建 | torch.ones_like(tensor) |
根据另一个张量的形状创建新张量。 | |
| 从 Python/NumPy 创建 | torch.tensor(data), torch.from_numpy(ndarray) |
将 Python 列表、数组或 NumPy 转换成张量。 | |
| II. 形状和维度操作 | 形状查询 | tensor.shape 或 tensor.size() |
返回张量的形状(元组)。 |
维度重塑 (NumPy: reshape) |
tensor.view(new_shape) 或 tensor.reshape(new_shape) |
改变张量的形状。view 要求内存连续,reshape 更灵活。 |
|
| 增加/减少维度 | torch.squeeze(), torch.unsqueeze(dim) |
移除(维度为 1)或增加维度(如将向量转为 Batch )。 | |
维度转置 (NumPy: T) |
tensor.permute(dims), tensor.transpose(dim0, dim1) |
交换张量的维度位置(常用于 $HWC \to CWH$)。 | |
张量拼接 (NumPy: concatenate) |
torch.cat((t1, t2), dim=0) |
沿指定维度将多个张量拼接起来(维度增加)。 | |
张量堆叠 (NumPy: stack) |
torch.stack((t1, t2), dim=0) |
沿一个新的维度将多个张量堆叠起来(维度数量增加)。 | |
| 基于索引的元素分散操作 | target.scatter_(dim, index, src,value (optional)) |
将一个 src 张量(源数据)中的值,根据一个 index 张量(索引)的指示,分散写入(覆盖或累加)到调用该方法的 目标张量(self,即 target)的指定位置;如果不使用 src 张量,可以传入一个标量值,所有写入操作都使用这个固定值。 | |
| III. 数学运算 | 元素级加减乘除 | t1 + t2, t1 * t2, torch.add(t1, t2, out=t3) |
对应元素进行运算。 |
矩阵乘法 (NumPy: @ 或 dot) |
torch.matmul(t1, t2) 或 t1 @ t2 |
适用于矩阵、向量乘法,也支持 Batch 矩阵乘法。 | |
向量内积 (NumPy: dot) |
torch.dot(vec1, vec2) |
仅用于一维张量(向量)。 | |
求和 (NumPy: sum) |
torch.sum(tensor, dim=None) |
对所有元素或指定维度求和。 | |
| 平均值 | torch.mean(tensor, dim=None) |
对所有元素或指定维度求平均值。 | |
| IV. 梯度控制与设备管理 | 设备查询 | tensor.device |
返回张量所在的设备(如 cpu 或 cuda:0)。 |
| 设备移动 | tensor.to('cuda'), tensor.cpu() |
将张量在 CPU 和 GPU 之间移动。 | |
| 梯度追踪 | tensor.requires_grad |
布尔值,指示是否需要计算梯度(默认为 False)。 |
|
| 停止梯度追踪 | with torch.no_grad(): 或 tensor.detach() |
在不需要计算梯度时(如评估阶段)使用,节省计算和内存。 | |
| V. 索引和切片 | 标量值 | tensor.item() |
从只包含一个元素的张量中获取 Python 标量值。 |
| 标准索引/切片 | tensor[0], tensor[:, 2:5] |
与 NumPy 索引规则相同。 | |
| 掩码索引 | tensor[tensor > 5] |
使用布尔张量作为索引(获取满足条件的元素)。 |
PyTorch 与 NumPy 的关键区别
- 设备支持: PyTorch Tensors 默认在 CPU 上创建,但可以使用
.cuda()或.to('cuda')轻松移动到 GPU 上进行高性能计算。NumPy $ndarray$ 只能在 CPU 上运行。 - 梯度追踪: PyTorch Tensors 可以使用
requires_grad=True启用 自动求导 (Autograd),这是构建神经网络的核心功能,而 NumPy 不具备此功能。 - 原地操作 (In-place Operations): PyTorch 提供了许多以
_结尾的方法(例如add_()),这些是原地操作,会直接修改张量本身的值。在涉及 Autograd 时使用原地操作需要小心,可能会破坏计算图。
数据集和数据加载器
-
Dataset和Dataloader是什么?用于管理和加载数据的 类。 -
Dataset包含对数据基本的操作,比如按路径读取源和标签,获取样本方法,获取数据集大小方法等。 -
Dataloader可以实现小批量传递样本。DataLoader是一个可迭代对象,它通过简单的 API 为我们抽象了这些复杂性。 -
我们可以自己继承
Dataset和Dataloader写我们的数据集和数据加载器,但通常我们用开源库。 -
当我们实例化我们的数据集时,我们需要告诉它一些事情。
- 数据要存放的文件的系统路径。
- 我们是否正在使用此集合进行训练;大多数数据集将分为训练集和测试集。
- 如果我们还没有下载数据集,是否希望下载它。
- 我们想要应用于数据的变换。
一旦你的数据集准备就绪,你就可以将其交给
DataLoader。 -
为什么要Dataset和Dataloader?
Transform 转换
-
什么是
TorchVision -
数据并不总是以机器学习算法训练所需的最终处理形式提供。我们使用 transforms 来对数据进行一些操作,使其适合训练。
-
比如,
torchvision.transforms是一个工具包,里面实现了各种转化的方法, - 常见操作示例:
ToTensor(): 将 PIL Image 或 NumPy 数组转换成 PyTorch Tensor。Normalize(mean, std): 对 Tensor 进行标准化处理 (减去均值mean,除以标准差std)。Resize(): 改变图像尺寸。RandomCrop(): 随机裁剪,用于数据增强。Compose(): 将多个变换操作串联起来。
构建神经网络
-
前置知识:神经网络
-
torch.nn是 PyTorch 的神经网络模块,提供了: -
预定义层:全连接层、卷积层、池化层等
- 损失函数:各种损失计算
- 容器:组织网络结构的容器
nn.Sequential -
工具函数:初始化、正则化等
-
nn.Module;定义我们自己的神经网络:在__init__中初始化神经网络层。每个nn.Module子类都在forward方法中实现对输入数据的操作。
一个典型 PyTorch 模型的结构。
- 它继承自
torch.nn.Module——模块可以嵌套——事实上,即使是Conv2d和Linear层类也继承自torch.nn.Module。 - 模型将有一个
__init__()函数,在其中它实例化其层,并加载任何可能需要的 数据工件(例如,NLP 模型可能会加载词汇表)。 - 模型将有一个
forward()函数。这是实际发生计算的地方:输入通过网络层和各种函数生成输出。 -
除此之外,你可以像其他 Python 类一样构建你的模型类,添加任何你需要支持模型计算的属性和方法。
-
用于训练的设备选择;
.to(device) -
nn.Flatten、nn.Linear、nn.ReLU、nn.Softmax...... -
模型参数的查看
自动微分torch.autograd
- 手动实现后向传播对于一个小型两层网络来说不算什么,但对于大型复杂网络来说,它会变得非常混乱。
值得庆幸的是,我们可以使用 自动微分 来自动计算神经网络中的后向传播。PyTorch 中的 autograd 包正是提供了这项功能。在使用 autograd 时,网络的正向传播将定义一个计算图;图中的节点将是张量,边将是生成输出张量(从输入张量)的函数。通过该图进行反向传播就可以轻松计算梯度。
这听起来很复杂,但实际上使用起来相当简单。每个张量代表计算图中的一个节点。如果 x 是一个张量,并且 x.requires_grad=True,那么 x.grad 是另一个张量,它保存了 x 相对于某个标量值的梯度。
-
在底层,每个原始 autograd 运算符实际上是两个操作张量的函数。forward 函数根据输入张量计算输出张量。backward 函数接收输出张量相对于某个标量值的梯度,并计算输入张量相对于该相同标量值的梯度。
-
使用
loss.backward()计算,然后从w.grad和b.grad中检索值。 -
禁用梯度跟踪:默认情况下,所有
requires_grad=True的张量都会跟踪它们的计算历史并支持梯度计算。但是,在某些情况下我们不需要这样做,例如,当我们训练好模型并只想将其应用于某些输入数据时,即我们只想通过网络进行前向计算。我们可以将计算代码用torch.no_grad()块包围来停止跟踪计算。 -
PyTorch 使用一种称为动态计算图 (Dynamic Computation Graph) 的机制。
-
前向传播记录: 每当数据 (
Tensor) 流经模型中的一个操作(如矩阵乘法、卷积、加法、激活函数等)时,PyTorch 都会在后台实时记录这个操作,以及该操作的输入 Tensor 和输出 Tensor,从而构建一个有向无环图 (DAG)。 -
图的节点与边: 图中的节点是 Tensor(数据)和 Function(操作),边表示数据流动的方向。
loss.backward()、optimizer.step()、optimizer.zero_grad() 等,它们操作的不是模型本身,而是模型参数所处的环境或计算过程。
我们把这个环境或过程称为 PyTorch 的自动微分系统。
1. 核心操作对象:动态计算图(The Graph)
这个“图”就是 PyTorch 在前向传播时构建的动态计算图。
- 图的构成: 图记录了从输入数据到最终 $Loss$ 值的所有张量操作。图上的叶子节点就是模型参数 $\theta$。
2. 方法之间的关系:依赖与解耦
这些方法彼此之间没有直接的函数调用联系(比如 loss.backward() 不会调用 optimizer.step()),但它们是时间上连续且功能上依赖的关系。
| 方法 | 操作对象 | 核心动作 | 关系总结 |
|---|---|---|---|
loss.backward() |
计算图 | 沿着计算图反向传播,计算出 $\nabla L$,并将其存入参数 $\theta$ 的 .grad 属性中。 |
读图,写 $\theta.\mathbf{grad}$ |
optimizer.step() |
参数 $\theta$ | 读取 $\theta.\mathbf{grad}$(即 $\nabla L$),执行优化算法,并更新参数 $\theta$ 的值。 | 读 $\theta.\mathbf{grad}$,写 $\theta$ |
optimizer.zero_grad() |
参数 $\theta$ | 将所有 $\theta$ 的 .grad 属性清零或设为 None。 |
写 $\theta.\mathbf{grad}$ |
深入类比:流水线作业
您可以将这个过程想象成一个三站式流水线作业:
- 清零站 (
zero_grad): 擦干净参数 $\theta$ 上的旧标记(梯度)。 - 标记站 (
backward): 基于计算图,计算新的错误方向(梯度),并标记在参数 $\theta$ 上。 - 调整站 (
step): 根据参数 $\theta$ 上的标记(梯度),调整参数本身的位置和数值。
优化模型参数
-
损失函数的使用(比如
nn.loss_fn = nn.CrossEntropyLoss()) -
优化器的使用。
optimizer = torch.optim.Adam(model.parameters(),lr=0.001);以及优化器的操作
训练
-
训练函数和测试函数的编写
-
epoch和batch的理解;理解train函数是按批次(batch)处理的
-
损失 (Loss)
- 指标定义: 损失函数(如交叉熵损失 $L$)衡量的是模型预测值 $\hat{y}$ 与真实值 $y$ 之间的差异程度。在评估中,我们关注的是平均损失。
- 计算逻辑:
- 在
for X, y in dataloader:循环中,loss_fn(pred, y).item()计算的是当前这一个批次(Batch)数据上的平均损失(PyTorch 的损失函数默认通常会对批次内所有样本的损失求平均)。
- 在
保存和加载模型
-
保存和加载模型权重
-
PyTorch 模型将其学习到的参数存储在一个内部状态字典中,称为
state_dict。可以使用torch.save方法将它们持久化。 - 要加载模型权重,需要先创建一个同模型的实例,然后使用
load_state_dict()方法加载参数。 - 在下面的代码中,我们将
weights_only设置为True,以将反序列化期间执行的函数限制为仅加载权重所必需的函数。在加载权重时,将weights_only设置为True被认为是最佳实践。
python
model = models.vgg16() # we do not specify ``weights``, i.e. create untrained model
model.load_state_dict(torch.load('model_weights.pth', weights_only=True))
model.eval()
-
保存和加载带形状的模型
-
在加载模型权重时,我们需要先实例化模型类,因为类定义了网络的结构。如果我们想将此类的结构与模型一起保存,那么我们可以将
model(而不是model.state_dict())传递给保存函数。 -
python torch.save(model, 'model.pth') -
然后,我们可以按照下面的方法加载模型。
python model = torch.load('model.pth', weights_only=False)
补充
enumerate()
enumerate() 函数接受一个可迭代对象(如列表 list、元组 tuple、字符串 str 等),并将其元素与一个计数器(索引)组合成一个个元组 (索引, 值),然后返回一个可枚举对象(enumerate object)。