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.shapetensor.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 返回张量所在的设备(如 cpucuda: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 的关键区别

  1. 设备支持: PyTorch Tensors 默认在 CPU 上创建,但可以使用 .cuda().to('cuda') 轻松移动到 GPU 上进行高性能计算。NumPy $ndarray$ 只能在 CPU 上运行。
  2. 梯度追踪: PyTorch Tensors 可以使用 requires_grad=True 启用 自动求导 (Autograd),这是构建神经网络的核心功能,而 NumPy 不具备此功能。
  3. 原地操作 (In-place Operations): PyTorch 提供了许多以 _ 结尾的方法(例如 add_()),这些是原地操作,会直接修改张量本身的值。在涉及 Autograd 时使用原地操作需要小心,可能会破坏计算图。

数据集和数据加载器

  • DatasetDataloader是什么?用于管理和加载数据的

  • Dataset包含对数据基本的操作,比如按路径读取源和标签,获取样本方法,获取数据集大小方法等。

  • Dataloader 可以实现小批量传递样本。DataLoader 是一个可迭代对象,它通过简单的 API 为我们抽象了这些复杂性。

  • 我们可以自己继承DatasetDataloader写我们的数据集和数据加载器,但通常我们用开源库。

  • 当我们实例化我们的数据集时,我们需要告诉它一些事情。

    • 数据要存放的文件的系统路径。
    • 我们是否正在使用此集合进行训练;大多数数据集将分为训练集和测试集。
    • 如果我们还没有下载数据集,是否希望下载它。
    • 我们想要应用于数据的变换。

    一旦你的数据集准备就绪,你就可以将其交给 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——模块可以嵌套——事实上,即使是 Conv2dLinear 层类也继承自 torch.nn.Module
  • 模型将有一个 __init__() 函数,在其中它实例化其层,并加载任何可能需要的 数据工件(例如,NLP 模型可能会加载词汇表)。
  • 模型将有一个 forward() 函数。这是实际发生计算的地方:输入通过网络层和各种函数生成输出。
  • 除此之外,你可以像其他 Python 类一样构建你的模型类,添加任何你需要支持模型计算的属性和方法。

  • 用于训练的设备选择;.to(device)

  • nn.Flattennn.Linearnn.ReLUnn.Softmax......

  • 模型参数的查看

自动微分torch.autograd

  • 手动实现后向传播对于一个小型两层网络来说不算什么,但对于大型复杂网络来说,它会变得非常混乱。

值得庆幸的是,我们可以使用 自动微分 来自动计算神经网络中的后向传播。PyTorch 中的 autograd 包正是提供了这项功能。在使用 autograd 时,网络的正向传播将定义一个计算图;图中的节点将是张量,边将是生成输出张量(从输入张量)的函数。通过该图进行反向传播就可以轻松计算梯度。

这听起来很复杂,但实际上使用起来相当简单。每个张量代表计算图中的一个节点。如果 x 是一个张量,并且 x.requires_grad=True,那么 x.grad 是另一个张量,它保存了 x 相对于某个标量值的梯度。

  • 在底层,每个原始 autograd 运算符实际上是两个操作张量的函数forward 函数根据输入张量计算输出张量。backward 函数接收输出张量相对于某个标量值的梯度,并计算输入张量相对于该相同标量值的梯度。

  • 使用 loss.backward() 计算,然后从 w.gradb.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}$

深入类比:流水线作业

您可以将这个过程想象成一个三站式流水线作业

  1. 清零站 (zero_grad): 擦干净参数 $\theta$ 上的旧标记(梯度)。
  2. 标记站 (backward): 基于计算图,计算新的错误方向(梯度),并标记在参数 $\theta$ 上。
  3. 调整站 (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)。