资讯动态

PyTorch深度学习入门实战:从环境搭建到模型训练完整指南

发布时间:2026/10/18 15:27:12 来源:尧图企业网站定制
1. 从一张白纸到能跑通第一个模型PyTorch 到底解决了什么问题如果你刚接触深度学习打开搜索引擎输入“PyTorch”大概率会被一堆术语砸晕张量、自动求导、计算图、动态图、GPU 加速、分布式训练……每个词都认识连在一起就不知道在说什么。我刚开始学的时候也一样装完环境跑通第一个import torch就卡了半天更别提理解它为什么能成为学术界和工业界的主流框架之一。PyTorch 本质上是一个基于 Python 的深度学习框架核心能力可以拆成三件事第一它提供了一个叫张量Tensor的数据结构你可以把它理解成“能跑在 GPU 上的多维数组”和 NumPy 的 ndarray 很像但多了自动求导和硬件加速第二它有一套自动微分系统你只需要写前向计算反向传播的梯度它帮你算第三它提供了神经网络模块torch.nn和优化器torch.optim让你用搭积木的方式拼出模型并训练。这套东西解决的核心问题是让研究者把精力放在模型结构设计上而不是手写梯度、手动管理内存、手动做并行。在 PyTorch 出现之前很多人用静态图框架你得先定义一张完整的计算图再喂数据进去跑调试的时候非常痛苦——想看中间某个变量的值不行图还没执行。PyTorch 的动态图机制让计算图在运行时才构建你可以像写普通 Python 代码一样写模型用print调试用pdb断点这对新手和研究者来说友好太多了。这篇文章适合谁看如果你是完全没碰过 PyTorch 的新手我会从环境搭建、张量操作、自动求导、模型定义、训练循环一路讲到底每一步都有可复现的代码如果你已经用过一段时间但总觉得“能跑但不知道为什么这么写”我会在关键节点解释设计动机和常见坑比如requires_grad什么时候该开、optimizer.zero_grad()为什么必须调、model.train()和model.eval()到底改了什么。整篇内容基于我自己的实操经验不是官方文档的翻译而是“一个踩过坑的人告诉你路怎么走”。2. 环境搭建与第一个张量别在第一步就劝退2.1 安装 PyTorch 的正确姿势很多人装 PyTorch 的第一反应是pip install pytorch然后发现装了个寂寞——包名根本不叫这个。正确的包名是torch而且安装命令要根据你的硬件环境有没有 NVIDIA 显卡、CUDA 版本多少来选择。官方推荐去 pytorch.org 的 Get Started 页面勾选配置它会生成对应的命令。我实测下来最稳妥的方式是用 conda 创建独立环境避免和系统 Python 打架conda create -n torch-env python3.10 conda activate torch-env # 以 CUDA 11.8 为例具体命令以官网为准 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118如果你没有独立显卡或者只是想先跑通 CPU 版本直接pip install torch torchvision torchaudio就行。这里有个新手常踩的坑装完之后import torch报错说找不到 DLL 或者版本不兼容大概率是 Python 版本太新或太旧。我试过 Python 3.12 刚出的时候某些 PyTorch 版本还没适配换成 3.10 就正常了。所以如果你不是追求最新特性Python 3.9 到 3.11 是比较稳的区间。验证安装是否成功跑这几行import torch print(torch.__version__) print(torch.cuda.is_available()) # 有显卡且驱动正常应返回 True print(torch.cuda.device_count()) # 显卡数量如果cuda.is_available()返回 False 但你确实有显卡先检查显卡驱动版本是否满足 CUDA 要求再检查装的 PyTorch 是不是 CPU 版本。这两个原因占了九成以上的情况。2.2 张量PyTorch 世界的“万能容器”张量是 PyTorch 里最基础的数据结构。你可以把它想成一个“可以住在 GPU 上的 NumPy 数组”但功能更强。创建张量的方式很多我列几个最常用的import torch # 从 Python 列表创建 a torch.tensor([[1, 2], [3, 4]]) # 全零、全一、随机 b torch.zeros(2, 3) c torch.ones(2, 3) d torch.randn(2, 3) # 标准正态分布 # 从 NumPy 转换 import numpy as np e torch.from_numpy(np.array([1, 2, 3]))张量有几个关键属性你需要时刻关注shape形状、dtype数据类型、device在 CPU 还是 GPU 上。形状不匹配是新手遇到最多的报错来源比如你定义了一个输入维度是 784 的全连接层结果喂进去的数据是[batch, 28, 28]就会报维度错误。解决办法是用x.view(-1, 784)或者x.flatten(1)把图像展平。设备管理也是个大坑。如果你把模型放到了 GPU 上model.cuda()但输入数据还在 CPU 上就会报“expected all tensors on same device”。我的习惯是在训练循环开头就把数据搬到模型所在的设备device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) # 训练时 inputs inputs.to(device) labels labels.to(device)注意torch.tensor()和torch.Tensor()不一样。前者根据输入推断 dtype后者永远返回 float32 且会把整数转成浮点。新手容易混用导致类型错误建议统一用torch.tensor()。2.3 自动求导PyTorch 最核心的魔法自动求导是 PyTorch 的看家本领。你只需要在创建张量时设置requires_gradTrue之后所有基于它的计算都会被记录调用.backward()就能自动算出梯度。举个例子x torch.tensor([2.0, 3.0], requires_gradTrue) y x ** 2 3 * x 1 z y.sum() z.backward() print(x.grad) # dz/dx 2x 3 [7, 9]这里x.grad就是梯度。关键点每次反向传播前要清零梯度否则梯度会累加。这就是为什么训练循环里必须写optimizer.zero_grad()。我见过有人忘了写结果 loss 不降反升排查半天才发现是梯度累积导致的。还有一个容易混淆的概念torch.no_grad()。在推理或评估阶段你不需要计算梯度用这个上下文管理器可以节省显存并加速with torch.no_grad(): output model(input)如果你在评估时忘了加这个显存占用会飙升batch size 稍微大一点就 OOM显存溢出。这是实测下来最常见的显存问题来源之一。3. 从零搭建一个神经网络模块、层与损失函数3.1 nn.Module一切模型的基类PyTorch 里所有神经网络都继承自nn.Module。你至少需要实现两个方法__init__里定义层forward里定义数据怎么流。看一个最简单的全连接网络import torch.nn as nn class SimpleNet(nn.Module): def __init__(self): super().__init__() self.fc1 nn.Linear(784, 128) self.relu nn.ReLU() self.fc2 nn.Linear(128, 10) def forward(self, x): x self.fc1(x) x self.relu(x) x self.fc2(x) return x这里有几个设计细节值得说。第一super().__init__()必须调否则 PyTorch 无法正确注册子模块后面model.parameters()会拿不到参数。第二层定义在__init__里计算逻辑在forward里不要反过来。第三forward方法不要手动调用而是用model(x)因为__call__里还做了 hook 等额外处理。nn.Sequential是另一种更简洁的写法适合层与层之间是简单串联的情况model nn.Sequential( nn.Linear(784, 128), nn.ReLU(), nn.Linear(128, 10) )但如果你需要跳跃连接、多输入多输出还是得用nn.Module自定义。我个人的习惯是原型阶段用 Sequential 快速验证正式项目用 Module 保证灵活性。3.2 损失函数与优化器训练的两条腿损失函数衡量模型输出和真实标签的差距优化器根据梯度更新参数。分类任务最常用交叉熵损失criterion nn.CrossEntropyLoss()注意CrossEntropyLoss内部已经包含了 Softmax所以你的模型最后一层不要加 Softmax直接输出 logits 就行。我见过有人在模型里加了 Softmax又用 CrossEntropyLoss结果 loss 一直很高因为相当于做了两次 Softmax。优化器方面Adam 是默认首选学习率 1e-3 起步optimizer torch.optim.Adam(model.parameters(), lr1e-3)如果你追求更好的泛化性能可以试试 SGD Momentum但需要更细致地调学习率。实测经验Adam 收敛快但有时最终精度不如调好的 SGDSGD 对学习率敏感但配合学习率衰减往往能取得更好的结果。新手建议先用 Adam 跑通再尝试 SGD。优化器适用场景学习率建议特点SGD图像分类、追求极致精度0.01~0.1需调参泛化好Adam大多数任务、快速原型1e-3~1e-4收敛快省心AdamWTransformer 类模型1e-4~5e-5权重衰减更合理RMSpropRNN、强化学习1e-3对非平稳目标适应好3.3 数据加载Dataset 与 DataLoaderPyTorch 的数据加载体系围绕两个类Dataset和DataLoader。Dataset负责“一条数据怎么读”DataLoader负责“怎么批量、怎么打乱、怎么并行加载”。自定义 Dataset 需要实现__len__和__getitem__from torch.utils.data import Dataset, DataLoader class MyDataset(Dataset): def __init__(self, data, labels): self.data data self.labels labels def __len__(self): return len(self.data) def __getitem__(self, idx): return self.data[idx], self.labels[idx] loader DataLoader(MyDataset(X, y), batch_size32, shuffleTrue, num_workers4)num_workers控制并行加载的进程数。注意在 Windows 上num_workers 0有时会报错需要把训练代码放在if __name__ __main__:下面。另外num_workers不是越大越好一般设成 CPU 核心数即可设太大反而因为进程切换开销导致变慢。4. 完整训练流程从数据到模型的每一步4.1 训练循环的标准骨架一个完整的训练循环包含五步前向传播、计算损失、梯度清零、反向传播、参数更新。写成代码就是for epoch in range(num_epochs): model.train() for inputs, labels in train_loader: inputs, labels inputs.to(device), labels.to(device) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() # 验证阶段 model.eval() with torch.no_grad(): for inputs, labels in val_loader: inputs, labels inputs.to(device), labels.to(device) outputs model(inputs) # 计算准确率等指标顺序很重要zero_grad()必须在backward()之前step()必须在backward()之后。我见过有人把zero_grad()放在step()后面结果第一个 batch 的梯度被清零了但后续 batch 的梯度一直在累加训练完全乱套。model.train()和model.eval()的区别在于 BatchNorm 和 Dropout 的行为。训练时 BatchNorm 用当前 batch 的统计量Dropout 随机丢弃神经元评估时 BatchNorm 用滑动平均的统计量Dropout 不生效。如果你忘了切换模式评估结果会波动很大这是非常隐蔽的 bug。4.2 学习率调度与早停学习率太大导致 loss 震荡不收敛太小导致收敛太慢。实践中常用学习率衰减策略scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.1) # 每个 epoch 后调用 scheduler.step()StepLR每 10 个 epoch 把学习率乘以 0.1。还有CosineAnnealingLR、ReduceLROnPlateau等后者根据验证集指标自动调整更智能但需要传入指标scheduler torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, modemin, patience3) scheduler.step(val_loss)早停Early Stopping是防止过拟合的简单有效手段如果验证集 loss 连续 N 个 epoch 不下降就停止训练。我一般设 patience5配合保存最佳模型权重best_loss float(inf) patience_counter 0 if val_loss best_loss: best_loss val_loss torch.save(model.state_dict(), best_model.pth) patience_counter 0 else: patience_counter 1 if patience_counter 5: print(Early stopping) break4.3 模型保存与加载的两种方式PyTorch 保存模型有两种方式保存整个模型和只保存参数。推荐只保存state_dict因为保存整个模型依赖具体的类定义换环境容易出问题# 保存 torch.save(model.state_dict(), model.pth) # 加载 model SimpleNet() # 需要先实例化同样的结构 model.load_state_dict(torch.load(model.pth)) model.eval()加载时如果报“Missing key(s) in state_dict”说明模型结构和保存时不一致如果报“Unexpected key(s)”说明多了参数。常见原因是保存时用了DataParallel或DistributedDataParallelkey 前面多了module.前缀。解决办法是用model.module.state_dict()保存或者加载时去掉前缀。提示torch.load()在 PyTorch 2.6 之后默认weights_onlyTrue如果你保存的不只是权重比如还存了 optimizer 状态加载时需要显式设置weights_onlyFalse但要确保文件来源可信。5. 常见报错与排查技巧实录5.1 显存溢出OOM的排查思路OOM 是训练深度学习模型最常见的报错。排查顺序如下第一检查 batch size 是不是太大减半试试第二检查是否在评估时忘了torch.no_grad()第三检查是否有中间变量一直挂在计算图上没释放比如在循环里累加了 loss 但没 detachtotal_loss loss # 错误loss 还挂在计算图上 total_loss loss.item() # 正确取出标量值第四用torch.cuda.empty_cache()清空缓存但这不是根本解决办法。第五如果模型本身太大可以考虑梯度累积小 batch 多次前向再更新一次或混合精度训练torch.cuda.amp。混合精度训练是我强烈推荐的一个技巧用autocast和GradScaler可以在几乎不影响精度的情况下把显存占用降低 30%~50%训练速度也能提升scaler torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): outputs model(inputs) loss criterion(outputs, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()5.2 梯度消失与梯度爆炸梯度消失表现为底层参数几乎不更新loss 下降极慢梯度爆炸表现为 loss 变成 NaN。排查方法打印每层的梯度范数for name, param in model.named_parameters(): if param.grad is not None: print(name, param.grad.norm().item())如果梯度范数接近 0说明梯度消失可以考虑用 ReLU 替代 Sigmoid、加 BatchNorm、用残差连接。如果梯度范数非常大比如几百几千说明梯度爆炸用梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)这行代码放在loss.backward()之后、optimizer.step()之前。max_norm 一般设 1.0 到 5.0 之间我常用 1.0。5.3 常见报错速查表报错信息可能原因解决办法RuntimeError: expected all tensors on same device数据和模型不在同一设备用.to(device)统一RuntimeError: size mismatch层输入输出维度不匹配检查nn.Linear的 in_featuresRuntimeError: element 0 of tensors does not require grad输入没有设置 requires_grad检查输入张量或模型参数CUDA out of memory显存不足减小 batch size、用 no_grad、混合精度loss is nan学习率太大或梯度爆炸降低学习率、梯度裁剪ValueError: Expected more than 1 value per channelBatchNorm 遇到 batch size 为 1增大 batch size 或用 GroupNorm5.4 几个我踩过的坑第一个坑在__init__里用列表存层。比如self.layers [nn.Linear(10, 10) for _ in range(3)]这样 PyTorch 不会注册这些层model.parameters()拿不到它们优化器就不会更新。正确做法是用nn.ModuleList。第二个坑忘记optimizer.zero_grad()。前面提过但真的太常见了再强调一遍。第三个坑在 DataLoader 的__getitem__里做太重的预处理。数据加载是瓶颈时GPU 利用率会很低。解决办法是把预处理放在__init__里预计算或者用num_workers并行加载。第四个坑随机种子没固定。如果你在复现实验一定要在开头设置import random import numpy as np torch.manual_seed(42) np.random.seed(42) random.seed(42) torch.cuda.manual_seed_all(42)否则每次跑的结果都不一样根本没法对比。6. 从能跑到跑得好一些进阶建议6.1 用 TensorBoard 可视化训练过程光看 loss 数字不够直观用 TensorBoard 可以看到 loss 曲线、准确率曲线、权重分布等from torch.utils.tensorboard import SummaryWriter writer SummaryWriter(runs/experiment1) writer.add_scalar(Loss/train, loss.item(), global_step) writer.add_scalar(Loss/val, val_loss, epoch) writer.close()然后命令行运行tensorboard --logdirruns浏览器打开就能看。这个工具帮我发现过很多问题比如训练 loss 正常下降但验证 loss 很早就开始上升说明过拟合了需要加正则化或早停。6.2 迁移学习站在巨人肩膀上从零训练一个模型往往需要大量数据和算力。实际项目中更常见的是用预训练模型微调import torchvision.models as models model models.resnet18(pretrainedTrue) # 冻结前面的层 for param in model.parameters(): param.requires_grad False # 替换最后一层 model.fc nn.Linear(model.fc.in_features, num_classes)只训练最后一层几分钟就能得到一个不错的结果。如果数据量还可以可以解冻后面几层一起微调学习率设小一点比如 1e-4。6.3 关于调试的个人体会PyTorch 的动态图特性让调试变得非常方便。我的习惯是先用一个极小的数据集比如 10 条数据跑通整个流程确认没有维度错误、设备错误、梯度问题再换全量数据。如果直接上全量数据一个 epoch 跑半小时才发现报错时间全浪费了。另外torch.autograd.set_detect_anomaly(True)可以在反向传播时检测异常帮你定位是哪个操作产生了 NaN 梯度。虽然会拖慢速度但排查问题时非常有用。最后再分享一个小技巧如果你不确定某个操作的输出形状直接在 Python 交互环境里试一下比查文档快得多。PyTorch 的 API 设计很一致试几次就能摸清规律。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑