尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

PyTorch核心入门:Tensor、autograd与神经网络实战

PyTorch核心入门:Tensor、autograd与神经网络实战 在深度学习项目中PyTorch 已经成为科研和工业界使用频率最高的框架之一。很多人卡在第一步并不是模型结构看不懂而是不清楚张量、自动求导和训练循环之间是怎么配合的。这篇文章围绕 PyTorch 最核心的三个知识点展开Tensor 张量、autograd 自动求导、torch.nn 神经网络模块从环境搭建到训练一个可用的小模型全部走通。内容会给出可运行的源码、参数说明、常见坑以及模型部署时浮点精度的选型建议适合刚入门深度学习、准备用 PyTorch 做项目或者正在复习基础的同学。1. 先理解 PyTorch 的 3 个核心知识点很多教程会把 PyTorch 讲成一系列 API 的堆叠结果就是看的时候全懂关了页面自己写就报错。更合适的方式是先抓主线PyTorch 的代码通常围绕“定义张量 - 计算损失 - 反向传播 - 更新参数”这条链路展开而这条链路背后正好是三个核心知识点。1.1 TensorNumPy 的替代品也是模型的“数据容器”Tensor 是 PyTorch 中最基础的数据结构。从使用上看它和 NumPy 的 ndarray 很相似支持多维数组、切片、数学运算、广播机制。但 Tensor 多了两个非常重要的能力可以在 GPU 上运行并且可以自动记录计算路径以支持梯度回传。在深度学习模型里输入数据、模型参数、中间特征、损失值本质上都是 Tensor。比如一张 32x32 的彩色图片在 PyTorch 中通常表示为形状为[3, 32, 32]的张量分别对应通道数、高度、宽度。模型训练时数据被拆成一个个 batch每个 batch 的形状是[batch_size, 3, 32, 32]第一维表示这一批有多少张图。Tensor 容易让人误解的地方在于默认创建的整数张量不支持梯度浮点张量也要显式设置requires_gradTrue才会被自动求导机制跟踪。并不是所有张量都需要梯度通常只有模型参数和需要反向传播的中间变量才需要。1.2 autogradPyTorch 的自动求导引擎深度学习训练的本质是求损失函数对模型参数的梯度然后按照梯度方向更新参数。如果手写偏导公式线性模型还能应付到了多层神经网络、卷积、注意力机制几乎无法维护。PyTorch 通过autograd解决这个问题。只要把参与计算的 Tensor 设置requires_gradTrue并且整个计算过程都是基于 Tensor 的算子PyTorch 就会在计算时自动构建一张计算图。调用loss.backward()后梯度会沿着这张图逐层回传每个需要梯度的张量都会在.grad属性中保存对应的梯度值。理解 autograd 的关键是“动态计算图”思想每一次正向传播都会重新构建计算图这意味着网络结构可以在运行时改变这也是 PyTorch 调试起来比静态图框架更直观的原因。1.3 torch.nn搭建网络和损失函数的模块化工具箱只用 Tensor 和自动求导也能训练模型但代码会很啰嗦。torch.nn把常用的网络层、损失函数、激活函数、容器封装成了可以直接调用的模块。nn.Module是所有神经网络模型的基类。自定义网络时通常继承它在__init__里定义子层在forward里定义数据的前向计算过程。nn.Linear是全连接层nn.Conv2d是卷积层nn.ReLU是激活函数。模型准备好之后还需要一个优化器比如torch.optim.SGD或torch.optim.Adam用来根据梯度更新参数。这三块合起来就构成了 PyTorch 项目最常见的骨架数据进来变成 TensorTensor 经过nn.Module前向计算得到预测值损失函数计算误差backward()回传梯度优化器step()更新参数。2. 环境准备先确认 Python 和 CUDA再装 PyTorch环境配置是新手第一个容易翻车的地方。很多报错看起来是代码问题实际上是在安装阶段就把 CPU 版本和 GPU 版本搞混了。在动手写模型前建议按照下面的顺序检查环境。2.1 确认基础环境PyTorch 是一个 Python 库安装前需要确认三项信息Python 版本。PyTorch 官方对 Python 版本有最低要求目前建议使用 Python 3.8 及以上版本推荐 3.10 或 3.11。是否有 NVIDIA GPU。如果没有独显可以安装 CPU 版本训练小模型也够用。如果有 NVIDIA 显卡需要确认驱动是否支持对应的 CUDA 版本。包管理工具。常见的是 pip 和 conda任选其一即可。在终端执行以下命令确认环境python --version nvidia-smi如果nvidia-smi显示显卡驱动信息说明机器上有 NVIDIA 显卡。需要记录右上角显示的 CUDA Version比如 “CUDA Version: 11.8”这个值是驱动支持的最高 CUDA 版本不一定等于 PyTorch 运行时使用的 CUDA 版本但可以作为选版本的依据。2.2 安装 PyTorch最稳妥的方式是打开 PyTorch 官网选择你的操作系统、包管理器、CUDA 版本官网会生成对应的安装命令。不要直接在终端执行pip install torch因为 pip 默认安装的可能不是你要的 CUDA 版本。常见安装命令示例如下# CPU 版 pip install torch torchvision torchaudio # CUDA 11.8 版 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # CUDA 12.1 版 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121这里的cu118、cu121表示 PyTorch 编译时使用的 CUDA 版本不是系统驱动版本。只要显卡驱动足够新就能运行这些 CUDA 版本的 PyTorch。如果使用 conda可以这样安装conda install pytorch torchvision torchaudio pytorch-cuda12.1 -c pytorch -c nvidia环境创建建议用 conda 独立管理避免多个项目之间包版本互相干扰conda create -n pytorch python3.10 conda activate pytorch注意安装时如果网络速度很慢不要反复中断重试。可以先配置 pip 国内镜像再用镜像下载 PyTorch 的 wheel 包。但使用镜像时要确认镜像仓库是否包含你需要的 CUDA 版本否则还是建议从官方源安装。2.3 验证安装结果安装完成后在 Python 环境中执行import torch print(torch.__version__) print(torch.cuda.is_available())如果torch.cuda.is_available()返回False说明当前安装的是 CPU 版本或者 CUDA 驱动、版本不匹配。可以再执行print(torch.version.cuda) print(torch.backends.cudnn.version())这些信息可以帮助判断问题出在安装版本还是驱动上。下面是一个常见环境对照表场景推荐配置说明学习入门Windows/Linux CPU 版 PyTorch小模型可训练成本低科研实验Linux NVIDIA 显卡 CUDA 版需要频繁训练 CNN/Transformer生产部署GPU 推理 TensorRT 或 ONNX 优化还需要考虑批处理和延迟Mac 电脑Apple Silicon 可使用 MPS 加速torch.backends.mps.is_available()3. 用 Tensor 完成第一个张量操作安装完成后不要急着去写神经网络。先用 Tensor 把数据处理能力练熟。深度学习项目里数据清洗和预处理通常要花一半以上的时间而 Tensor 操作是这些工作的基础。3.1 创建张量PyTorch 提供了多种创建 Tensor 的方式下面代码演示的是最常用用法import torch # 从 Python 列表创建 data [[1, 2], [3, 4]] t torch.tensor(data) print(t) # 创建全零张量 zeros torch.zeros(3, 4) # 创建全一张量 ones torch.ones(2, 3) # 创建随机张量 rand_t torch.rand(3, 3) # 创建与某张量相同 shape 的张量 like_t torch.zeros_like(t) # 创建区间张量 range_t torch.arange(0, 10, 2) # 创建对角矩阵 eye_t torch.eye(3) print(zeros.shape, ones.shape, range_t)输出类似tensor([[1, 2], [3, 4]]) torch.Size([3, 4]) torch.Size([2, 3]) tensor([0, 2, 4, 6, 8])这段代码覆盖了大多数日常操作。需要注意torch.tensor会复制数据而torch.as_tensor在某些情况下可以避免复制性能更优但无法保证共享内存实际项目里要按场景选择。3.2 张量运算和索引Tensor 支持加减乘除、矩阵乘法、拼接、切分、压缩维度等操作。下面这些是深度学习中出现频率最高的a torch.tensor([[1., 2.], [3., 4.]]) b torch.tensor([[5., 6.], [7., 8.]]) # 对应元素乘法 print(a * b) # 矩阵乘法 print(a b) print(torch.matmul(a, b)) # 拼接 c torch.cat([a, b], dim0) # 第 0 维拼接 d torch.cat([a, b], dim1) # 第 1 维拼接 # 改变形状 e a.view(4, 1) f a.reshape(4, 1) # 增加/删除维度 g a.unsqueeze(0) # shape [1, 2, 2] h a.squeeze() # 删除长度为 1 的维度 # 获取最大值索引 pred torch.tensor([0.1, 0.8, 0.3]) print(pred.argmax())矩阵乘法是神经网络前向计算的核心。a b要求a的最后一位长度等于b的倒数第二位长度不满足就会报维度错误。很多新手看到mat1 and mat2 shapes cannot be multiplied这类报错通常是忘了调整输入的维度。3.3 Tensor 和 NumPy 的转换Tensor 和 NumPy 数组的互转有两种方式import numpy as np # NumPy 转 Tensor np_array np.array([1, 2, 3]) tensor_from_np torch.from_numpy(np_array) # Tensor 转 NumPy tensor torch.tensor([1, 2, 3]) np_from_tensor tensor.numpy()这里有一个隐蔽的坑在 CPU 上torch.from_numpy生成的 Tensor 与 NumPy 数组共享内存修改其中一个会影响另一个。如果不想共享需要使用clone()。在 GPU 上的 Tensor 不能直接转 NumPy必须先cpu()再转。4. autograd让反向传播自动完成自动求导是 PyTorch 区别于 NumPy 的最大特性。理解它之后训练循环就能看懂一大半。4.1 requires_grad 和梯度看下面这个最小示例import torch x torch.tensor(3.0, requires_gradTrue) y x ** 2 2 * x 1 print(y) y.backward() print(x.grad)x的初始值是 3函数y x^2 2x 1的导数是2x 2在x3处的梯度是8。执行y.backward()后x.grad就会得到tensor(8.)。这个过程的核心是PyTorch 在计算y时记录了x的运算路径backward()触发链式求导把梯度写到x.grad中。4.2 梯度只有叶子节点才保存默认情况下backward()只给requires_gradTrue的叶子张量保存梯度。中间变量虽然参与了计算但不保留梯度。比如a torch.tensor(2.0, requires_gradTrue) b a * 3 c b * b c.backward() print(a.grad) # tensor(12.) print(b.grad) # None如果希望查看中间变量的梯度需要在反向传播前调用b.retain_grad()但这是调试时才需要的操作正常训练不要滥用。4.3 梯度累计和清零PyTorch 的梯度默认是累加的。多次调用backward()梯度会自动叠加到.grad上。这在某些特殊场景有用比如模拟小 batch 的梯度累积但也会带来麻烦如果不手动清零多次迭代的梯度会叠加导致参数更新方向错误。标准训练循环中通常在每次参数更新前清零梯度optimizer.zero_grad() loss.backward() optimizer.step()也可以直接操作model.zero_grad()。两者效果类似但optimizer.zero_grad()只清零优化器管理的参数更精确。5. 搭建并训练第一个神经网络现在把前三个知识点串起来写一个完整的最小训练项目。为了不依赖外部数据集下载这里手动生成一组二维数据训练一个两层的多层感知机MLP完成二分类。这个例子虽然简单但包含了神经网络训练的所有关键步骤。5.1 生成数据集import torch import torch.nn as nn import torch.optim as optim import matplotlib.pyplot as plt # 生成两个类别 torch.manual_seed(42) num_samples 200 # 类别 1中心在 (2, 2) 附近 class1 torch.randn(num_samples, 2) torch.tensor([2.0, 2.0]) # 类别 2中心在 (-2, -2) 附近 class2 torch.randn(num_samples, 2) torch.tensor([-2.0, -2.0]) X torch.cat([class1, class2], dim0).float() y torch.cat([torch.zeros(num_samples), torch.ones(num_samples)]).long() print(X.shape, y.shape)这里用torch.randn从标准正态分布采样加上中心偏移后得到可分的两类数据。5.2 定义模型自定义网络继承nn.Moduleclass MLP(nn.Module): def __init__(self, in_dim, hidden_dim, out_dim): super(MLP, self).__init__() self.fc1 nn.Linear(in_dim, hidden_dim) self.relu nn.ReLU() self.fc2 nn.Linear(hidden_dim, out_dim) def forward(self, x): x self.fc1(x) x self.relu(x) x self.fc2(x) return x model MLP(in_dim2, hidden_dim16, out_dim2) print(model)输出会显示模型的层级结构。forward是前向传播输入x先经过一个线性层再经过 ReLU 激活最后经过输出层。out_dim2表示二分类输出两个 logits。5.3 定义损失函数和优化器分类问题使用交叉熵损失优化器选择最常见的 Adamcriterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr0.01)model.parameters()返回所有需要训练的参数优化器会根据参数名和梯度更新这些参数。lr是学习率过大可能导致不收敛过小则收敛慢。5.4 训练循环训练循环分为三步正向传播算预测值计算损失反向传播更新参数。完整代码如下epochs 100 batch_size 32 dataset_size X.shape[0] indices torch.arange(dataset_size) for epoch in range(epochs): # 打乱数据 perm torch.randperm(dataset_size) X_shuffled X[perm] y_shuffled y[perm] total_loss 0.0 for i in range(0, dataset_size, batch_size): # 取一个 batch batch_X X_shuffled[i:i batch_size] batch_y y_shuffled[i:i batch_size] # 前向传播 outputs model(batch_X) loss criterion(outputs, batch_y) # 反向传播和参数更新 optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() * batch_X.size(0) avg_loss total_loss / dataset_size if (epoch 1) % 20 0: print(fEpoch [{epoch 1}/{epochs}], Loss: {avg_loss:.4f})这里使用了torch.randperm打乱数据顺序这是训练中防止模型记忆数据顺序的常规操作。5.5 验证模型效果训练完成后用全量数据计算准确率with torch.no_grad(): outputs model(X) _, predicted torch.max(outputs, 1) accuracy (predicted y).sum().item() / len(y) print(fAccuracy: {accuracy:.4f})预期能到 95% 以上因为数据线性可分。torch.no_grad()表示在这个上下文内不计算梯度推理阶段应该始终使用可以节省内存和计算资源。如果希望可视化决策边界可以生成网格点并绘制import numpy as np x_min, x_max X[:, 0].min() - 1, X[:, 0].max() 1 y_min, y_max X[:, 1].min() - 1, X[:, 1].max() 1 xx, yy np.meshgrid(np.arange(x_min, x_max, 0.05), np.arange(y_min, y_max, 0.05)) grid torch.tensor(np.c_[xx.ravel(), yy.ravel()], dtypetorch.float32) with torch.no_grad(): logits model(grid) _, pred torch.max(logits, 1) pred pred.numpy().reshape(xx.shape) plt.contourf(xx, yy, pred, alpha0.6) plt.scatter(X[y 0][:, 0], X[y 0][:, 1], colorblue) plt.scatter(X[y 1][:, 0], X[y 1][:, 1], colorred) plt.show()这个可视化能直观看到模型学习到的边界。6. 训练过程中常见的坑和排查路径很多 PyTorch 报错不是语法问题而是数据 shape、设备、梯度设置不一致导致的。下面把最常见的问题整理成表格。问题现象常见原因检查方式处理建议mat1 and mat2 shapes cannot be multiplied输入维度和线性层不匹配打印x.shape和nn.Linear(in, out)的in值确认输入最后一维等于线性层输入维度CUDA out of memorybatch size 太大显存不足查看nvidia-smi的显存占用降低 batch size或使用torch.cuda.empty_cache()device不匹配模型在 GPU数据在 CPU或反之打印model.weight.device和x.device统一使用.to(cuda)或.to(cpu)loss一直不变学习率设置不合理或没有调用zero_grad打印每个 epoch 的 loss调整 lr检查优化器是否在每个 batch 前清零梯度Expected dtype ... but found dtype ...标签类型和损失函数要求不一致打印y.dtype标签通常使用long类型如y.long()梯度为None参数requires_gradFalse打印model.fc1.weight.requires_grad确认模型参数未冻结输入张量可设置requires_grad但不是必须安装后import torch报错环境中存在多个 Python 版本执行which python换用 conda 环境或删除冲突包这里有一条排查顺序建议先看报错信息最后一行确认是 shape 错误、设备错误还是数据类型错误再打印关键变量x.shape、y.shape、x.device、y.dtype最后检查模型结构和优化器配置。在训练中如果 loss 出现 NaN通常有三种原因学习率过大、输入数据包含 NaN、损失函数数值不稳定。可以先把lr调小 10 倍再检查数据是否经过标准化。7. 模型部署相关的浮点精度选型fp32、fp16、bf16、tf32训练跑通之后下一个阶段通常是模型部署或者加速推理。热搜词里有一个高频主题fp32、fp16、bf16、tf32 浮点数格式详解与实战选型这里展开说明因为它是 PyTorch 从训练走向生产时必须理解的知识点。7.1 四种浮点格式的本质区别浮点数的核心矛盾是位宽越小占用的显存和计算量越低但能表示的数值范围和精度也越有限。fp32 是单精度浮点数用 32 位存储训练模型时的默认精度兼容性最好。fp16 是半精度浮点数用 16 位存储由 1 位符号、5 位指数、10 位尾数组成计算快但数值范围小容易出现梯度下溢或溢出。bf16 是 Brain Floating Point同样用 16 位存储但指数位取 8 位尾数只有 7 位因此数值范围接近 fp32但精度下降更多。tf32 是 TensorFloat32它不是通用数据格式而是 NVIDIA Ampere 架构 GPU 在矩阵运算时的一种加速模式用 19 位存储相当于在 fp32 的输入输出基础上截断尾数。一张表可以快速对比格式位数指数位尾数位数值范围精度适用场景fp3232823大高默认训练精度fp1616510小中混合精度训练、GPU 推理加速bf161687大较低大模型训练常与 fp32 混合使用tf3219810大约等于 fp32略低于 fp32Ampere 架构 GPU 的矩阵加速7.2 在 PyTorch 中如何开启fp16 在 PyTorch 中有两种方式。一种是手动把模型和数据转换为half()model.half() x x.half()但这样容易出现梯度溢出。更好的方式是使用 PyTorch 的自动混合精度AMP功能from torch.cuda.amp import autocast, GradScaler scaler GradScaler() for inputs, labels in dataloader: optimizer.zero_grad() with autocast(): outputs model(inputs) loss criterion(outputs, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()GradScaler的作用是在反向传播前放大梯度避免 fp16 下梯度过小变成 0参数更新前再缩放回去这是混合精度训练的标准做法。bf16 在autocast下通常默认可用但如果手动使用bfloat16()转换需要在能支持 bf16 的 GPU 上运行。tf32 的开启方式因框架版本而异在 PyTorch 中可以通过设置 cuDNN 和 matmul 精度来控制torch.backends.cuda.matmul.allow_tf32 True torch.backends.cudnn.allow_tf32 True7.3 选型建议模型推理时选择哪个格式要看业务对精度的容忍度和硬件支持情况如果目标是跑通流程、调试逻辑直接用默认的 fp32不要追求花哨优化。如果显存有限且模型是 CNN 这类对噪声相对不敏感的结构可以尝试 fp16 推理注意比较输出差异。如果训练大模型推荐混合使用 bf16 和 fp32 优化器状态能显著减少显存占用。如果部署在 NVIDIA 数据中心级 GPU 上且不需要离线转换可以研究 TensorRT 动态精度缩放而不是手动指定所有层。一定要在真实数据上评估精度差异。不要只因为一个测试图片结果正确就认为精度无损需要统计整个验证集上的指标变化。8. 一周学习路线和最佳实践到这里你已经具备了使用 PyTorch 的基本闭环能力。接下来如何在一周内继续巩固这里给出一条可执行的路线。天数学习重点输出物第 1 天完成环境安装熟练 Tensor 创建、索引、变形、广播写 10 种不同 shape 的张量操作第 2 天掌握 autograd理解梯度计算图和zero_grad手写二次函数的梯度并验证第 3 天使用nn.Linear、nn.ReLU、交叉熵损失搭建 MLP训练并记录 loss 曲线第 4 天掌握 Dataset 和 DataLoader替换真实数据集用 torchvision 加载 MNIST 或 FashionMNIST第 5 天搭建 CNN理解卷积层和池化层 shape 变化在 MNIST 上训练一个 CNN目标准确率 95%第 6 天训练技巧学习率调整、Dropout、BatchNorm、保存与加载模型对比不同设置的实验记录第 7 天部署基础ONNX 导出、精度转换、CPU/GPU 推理导出一个模型并写推理脚本这个路线不需要全部做完才能开始下一步但每一步都要有可运行的代码和验证结果。比起记 API更重要的是把所有东西串起来形成工程直觉。8.1 写 PyTorch 代码的一些最佳实践第一区分训练和推理模式。训练时模型默认启用 Dropout、BatchNorm 等依赖当前 batch 统计量的行为推理前需要调用model.eval()并在推理代码外层使用torch.no_grad()。第二不要在每个 epoch 之后重新定义模型。很多新手会把模型和优化器定义在循环内部每轮都重建模型这样参数永远是从头训练。模型、优化器、损失函数应该在训练循环外定义。第三保存模型时不要只保存state_dict。建议同时保存优化器状态、epoch、loss 等信息方便中断后继续训练torch.save({ epoch: epoch, model_state_dict: model.state_dict(), optimizer_state_dict: optimizer.state_dict(), loss: avg_loss, }, checkpoint.pt)加载时使用torch.load并传map_location到指定设备。第四随机种子要在数据生成、模型初始化前设置。否则每次运行结果不一致不利于调试torch.manual_seed(42) np.random.seed(42)第五数据处理要规范化。比如 MNIST 图片输入范围是 0 到 255需要除以 255 变成 0 到 1 之间再进一步标准化。如果不做梯度很容易异常。8.2 从学习环境到生产环境的差距学习时只用 CPU、单卡、小模型生产环境往往还要求日志、监控、多卡、性能测试和回滚机制。从写好一个 notebook 到部署一个服务还要补齐以下内容配置外置。不要硬编码 batch size、学习率、模型路径建议用 yaml 或环境变量管理。日志和监控。记录每个 epoch 的 loss、准确率、显存占用、吞吐量训练失败时能看到完整回放。模型版本管理。使用torch.save加时间戳或 commit id 保存避免同一个文件被覆盖。推理接口封装。输入要做预处理、归一化、batch 组包输出要做后处理。回滚策略。如果线上模型效果下降能快速切换回旧版本。这些点不需要在第一天全部掌握但在学习的过程中要有意识地往工程化方向靠而不是永远只写单文件脚本。9. 常用资源与下一步方向当你能独立完成一个小模型训练和导出后下一步可以围绕下面几个方向深入数据部分深入torch.utils.data.Dataset、DataLoader、数据增强、分布式数据加载。模型部分阅读torchvision里经典模型的源码研究 ResNet 的残差结构。训练部分学习学习率调度器torch.optim.lr_scheduler、梯度裁剪、混合精度训练。部署部分尝试 ONNX 导出、使用 TensorRT 或 OpenVINO 加速理解量化原理。框架原理阅读 autograd 的源码或文档理解Function如何自定义反向传播。推荐从torchvision中的经典模型源码开始读。不要只看别人训练好的权重自己真正把nn.Module的初始化、forward、返回结构梳理一遍对后续改模型结构非常有帮助。如果遇到新问题建议优先查 PyTorch 官方文档和 GitHub issues。很多报错在搜索时加上你的 PyTorch 版本号和 CUDA 版本命中率会高很多。最后要和你说的是PyTorch 学习不像背单词不需要把所有 API 都记住。只要能独立完成“数据处理 - 定义模型 - 训练 - 评估 - 保存”这一整条链路就已经进入了深度学习工程的大门。剩下的细节都是在一次次 debug 中逐渐补上的。
返回列表