PyTorch 进阶指南:从张量操作到模型训练全流程
发布时间:2026/9/28 21:08:55来源:尧图网络
昨天我们入门了深度学习的核心概念和 PyTorch 的 Tensor 基础操作。但如果只停留在创建张量、做做加减乘除离真正训练一个模型还差得远。今天我们继续深入——掌握张量的形状操作与拼接理解自动微分的原理走通模型训练的五步循环最后用一个线性回归案例把所有知识串起来。这篇文章是从会用 PyTorch到能训练模型的关键一跃。一、张量的形状操作在深度学习中数据在不同层之间流动时形状经常需要变换。把一个图像张量从[批量, 通道, 高, 宽]变成[批量, 高×宽×通道]把一个特征矩阵转置——这些都需要形状操作。查看形状import torch t torch.randn(2, 3, 4) print(t.shape) # torch.Size([2, 3, 4]) print(t.size()) # torch.Size([2, 3, 4]).shape是属性.size()是方法两者结果完全一样。看到torch.Size([2, 3, 4])就知道这个张量有 3 个维度分别有 2、3、4 个元素。修改形状t torch.randn(2, 3, 4) # 24个元素 # reshape改变形状不改变数据 t1 t.reshape(6, 4) # 变成 [6, 4] t2 t.reshape(2, 12) # 变成 [2, 12] # view同样改变形状但要求内存连续 t3 t.view(6, 4) # 变成 [6, 4]reshape和view看起来一样但有一个关键区别view要求张量的物理内存是连续的。如果一个张量经过了维度交换transpose等操作物理内存不再连续view就会报错而reshape仍然可以工作它会自动处理内存问题。判断内存是否连续可以用.contiguous属性。如果需要让不连续的张量变连续调用.contiguous()方法即可t torch.randn(2, 3) t_t t.transpose(0, 1) # 交换维度后内存不连续 # t_t.view(6) # 报错 t_t.contiguous().view(6) # 正常工作实际项目中推荐统一使用reshape它更安全不会因为内存不连续而报错。但如果你追求极致性能view不涉及数据拷贝比reshape更快——在确定内存连续的情况下用view是更好的选择。理解两者的底层差异比记住用哪个更重要。一个常见场景CNN 的全连接层之前需要把[batch, channels, height, width]的四维张量变成[batch, channels × height × width]的二维张量。这时就用reshape(batch, -1)——-1表示自动推断这一维的大小非常方便。增删维度# unsqueeze增加一个大小为1的维度 t torch.randn(3, 4) t1 t.unsqueeze(0) # [1, 3, 4] 在第0维增加 t2 t.unsqueeze(2) # [3, 1, 4] 在第2维增加 # squeeze删除大小为1的维度 t3 t1.squeeze(0) # [3, 4] 删除第0维 t4 t1.squeeze() # [3, 4] 删除所有大小为1的维度unsqueeze在深度学习中极为常用——比如把一个样本[3, 224, 224]变成[1, 3, 224, 224]加上 batch 维度就是用unsqueeze(0)完成的。squeeze则是反向操作去掉多余的维度。调整维度顺序t torch.randn(2, 3, 4) # transpose交换两个维度 t1 t.transpose(1, 2) # [2, 4, 3] 交换第1和第2维 # permute批量交换所有维度 t2 t.permute(2, 0, 1) # [4, 2, 3] 按指定顺序重排所有维度transpose只能交换两个维度permute可以一次性重排所有维度。在图像处理中permute特别常用——PyTorch 默认图片格式是[C, H, W]通道、高、宽但很多数据加载器给出的是[H, W, C]就需要用permute(2, 0, 1)来转换。形状操作在深度学习中的使用频率非常高。一个典型的 CNN 数据流是这样的输入图片[B, 3, 224, 224]经过卷积层变成[B, 64, 56, 56]进入全连接层前用reshape展平为[B, 64×56×56]输出后再用reshape恢复形状。如果形状搞错了PyTorch 报错信息通常很清晰——它会告诉你期望的形状和实际的形状根据报错信息调整即可。二、张量的拼接深度学习中经常需要把多个张量拼到一起——比如把多个 batch 的特征拼接或者把不同层的输出合并。cat同维度拼接a torch.randn(2, 3) b torch.randn(2, 3) # dim0上下拼接沿行方向不产生新维度 c1 torch.cat([a, b], dim0) # [4, 3] # dim1左右拼接沿列方向不产生新维度 c2 torch.cat([a, b], dim1) # [2, 6]cat的特点是不会产生新的维度——两个[2, 3]的张量沿 dim0 拼接结果是[4, 3]维度数不变还是二维。stack新维度拼接a torch.randn(2, 3) b torch.randn(2, 3) # stack产生一个新的维度 c torch.stack([a, b], dim0) # [2, 2, 3] 新增了第0维stack和cat的区别在于stack会产生一个新的维度。两个[2, 3]的张量用stack拼接结果是[2, 2, 3]——多了一个维度。这就像把两张照片叠成一摞多出了第几张这个维度。简单记忆cat是拼接stack是堆叠。不想增加维度用cat想增加维度用stack。选择cat还是stack取决于你的需求。比如你有 4 张图片[3, 224, 224]想组成一个 batch——那就用stack把它们堆叠成[4, 3, 224, 224]新增了 batch 维度。但如果你已经有两个 batch[32, 3, 224, 224]和[16, 3, 224, 224]想合成一个[48, 3, 224, 224]的大 batch——那就用cat沿 dim0 拼接不增加维度。理解了是否需要新维度这个判断标准选 cat 还是 stack 就不会纠结了。三、张量的自动微分自动微分是 PyTorch 的灵魂功能也是深度学习能自动训练的核心。理解了它你就理解了 PyTorch 为什么能自动更新模型参数。梯度是什么梯度是多个参数的偏导数组成的一个向量。对于损失函数L f(w₁, w₂, ..., wₙ)梯度就是[∂L/∂w₁, ∂L/∂w₂, ..., ∂L/∂wₙ]。梯度的方向是损失增加最快的方向所以参数更新时要沿着梯度的反方向走——这就是梯度下降。自动微分的四步流程import torch # 第一步在张量中设置 requires_gradTrue w torch.tensor([1.0, 2.0, 3.0], requires_gradTrue) # 第二步前向传播构建计算图PyTorch自动记录运算过程 y w * 2 1 loss y.sum() # 第三步反向传播自动计算梯度 loss.backward() # 第四步获取梯度结果 print(w.grad) # tensor([2., 2., 2.])设置requires_gradTrue后PyTorch 会自动跟踪这个张量的所有运算构建一个计算图——记录每一步操作的函数和依赖关系。调用.backward()时PyTorch 沿着计算图反向追溯用链式法则自动算出每个参数的梯度结果存在.grad属性中。上面例子中y w * 2 1loss y.sum()所以∂loss/∂w 2——梯度确实是[2, 2, 2]。一个关键陷阱梯度清零# 梯度会累加必须每次反向传播前清零 w torch.tensor([1.0], requires_gradTrue) # 第一轮 y1 (w * 3).sum() y1.backward() print(w.grad) # tensor([3.]) # 第二轮——不清零的话梯度会累加 y2 (w * 3).sum() y2.backward() print(w.grad) # tensor([6.]) 错误是336梯度每轮训练会自动累加——这是 PyTorch 的设计在某些场景如梯度累积用小 batch 模拟大 batch有用但在常规训练中会导致梯度越来越大必须每次训练前清零w.grad.zero_() # 手动清零 # 或用优化器清零更常用 optimizer.zero_grad()这个陷阱几乎每个 PyTorch 初学者都踩过——训练几轮发现 loss 越来越大模型预测越来越离谱debug 半天最后发现是梯度没清零。养成习惯写训练循环的第一行永远是optimizer.zero_grad()就像出门前先检查钥匙一样自然。还有一个细节值得注意.backward()默认只能对标量只有一个元素的张量调用。如果你的 loss 是一个向量需要传入梯度参数loss.backward(gradienttorch.ones_like(loss))。但在实际项目中损失函数通常输出标量所以这个问题不常见。四、模型训练的五步循环有了前面的基础现在来看 PyTorch 模型训练的核心流程。不管网络多复杂训练过程都遵循这五步循环# 训练循环 for epoch in range(epochs): for x, y in dataloader: # 1. 梯度清零 optimizer.zero_grad() # 2. 前向传播 output model(x) # 3. 计算损失 loss criterion(output, y) # 4. 反向传播自动计算梯度 loss.backward() # 5. 参数更新沿梯度反方向更新参数 optimizer.step()五步的含义梯度清零zero_grad清空上一轮累积的梯度。这是最容易被遗忘但最重要的一步——忘了清零梯度就会像滚雪球一样越滚越大模型直接崩掉。前向传播model(x)把数据送入模型得到预测结果。这一步是用当前参数算一遍预测。计算损失criterion(output, y)用损失函数衡量预测值和真实值的差距。分类问题常用交叉熵损失回归问题常用均方误差。反向传播loss.backward()PyTorch 自动从 loss 出发沿计算图反向计算每个参数的梯度。你不需要手动求导框架全部搞定。参数更新optimizer.step()优化器根据梯度更新参数沿着梯度的反方向走一步。学习率决定了步子大小。这五步构成了深度学习训练的最小完整循环。理解了它你看任何 PyTorch 训练代码都不会迷路——不管多复杂的模型核心都是这个循环。还有一个概念需要注意epoch vs batch vs iteration。一个 epoch 是把所有训练数据跑完一遍一个 batch 是一次送入模型的一批数据如 32 条一个 iteration 是一次前向反向传播的过程。如果 1000 条数据、batch_size32一个 epoch 大约需要 32 个 iteration。五步循环的每一轮就是一个 iteration。另外训练循环中经常需要加上model.train()和model.eval()的切换。model.train()告诉模型处于训练模式Dropout 会随机丢弃神经元、BatchNorm 会更新运行统计量model.eval()告诉模型处于评估模式Dropout 关闭、BatchNorm 使用固定统计量。训练时调用model.train()验证或测试时调用model.eval()这也是一个容易遗漏的细节。五、实战案例线性回归理论讲完了用一个完整的线性回归案例把所有知识串起来。这个案例涵盖了从数据创建到模型保存的完整流程。完整流程import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset from sklearn.datasets import make_regression from torch.utils.tensorboard import SummaryWriter # 1. 数据创建 X, y make_regression(n_samples500, n_features5, noise10, random_state42) X torch.FloatTensor(X) y torch.FloatTensor(y).unsqueeze(1) # 增加维度 [500] → [500, 1] # 2. TensorBoard 可视化 writer SummaryWriter(runs/linear_regression) # 3. 构建 DataLoader设置批次 dataset TensorDataset(X, y) dataloader DataLoader(dataset, batch_size32, shuffleTrue) # 4. 创建模型 model nn.Linear(5, 1) # 5个特征 → 1个输出 # 5. 创建损失函数 criterion nn.MSELoss() # 6. 创建优化器 optimizer torch.optim.SGD(model.parameters(), lr0.001) # 7. 模型训练 for epoch in range(100): total_loss 0 for batch_x, batch_y in dataloader: optimizer.zero_grad() # 梯度清零 output model(batch_x) # 前向传播 loss criterion(output, batch_y) # 计算损失 loss.backward() # 反向传播 optimizer.step() # 参数更新 total_loss loss.item() writer.add_scalar(Loss, total_loss / len(dataloader), epoch) if (epoch 1) % 20 0: print(fEpoch {epoch1}, Loss: {total_loss/len(dataloader):.4f}) # 8. 保存模型 torch.save(model.state_dict(), linear_model.pth) # 9. 加载模型 loaded_model nn.Linear(5, 1) loaded_model.load_state_dict(torch.load(linear_model.pth)) loaded_model.eval() # 10. 结果预测 with torch.no_grad(): # 预测时不需要计算梯度 test_x torch.FloatTensor([[1.0, 2.0, 3.0, 4.0, 5.0]]) prediction loaded_model(test_x) print(f预测结果: {prediction.item():.4f})流程解读这个案例覆盖了十个步骤每一步都有讲究数据创建用make_regression生成回归数据这是 sklearn 提供的便捷函数。注意unsqueeze(1)把标签从一维变成二维——这是 PyTorch 的要求nn.Linear的输入输出都需要是二维的[batch_size, features]。DataLoader负责把数据分批次送入模型。batch_size32表示每次送 32 条样本shuffleTrue表示每个 epoch 打乱数据顺序。分批训练既节省内存又能让梯度更有随机性。模型创建用nn.Linear(5, 1)创建一个线性层——5 个输入特征1 个输出值这就是最简单的神经网络。复杂的模型也只是把多个层组合起来。优化器用 SGD随机梯度下降学习率 0.001。学习率的选择和前面线性回归中学到的一样——太大震荡太小太慢。除了 SGDPyTorch 还提供了 Adam、RMSprop 等优化器其中 Adam 是最常用的万能优化器通常不需要太多调参就能取得不错的效果。模型保存与加载用state_dict()保存模型参数不是保存整个模型加载时先创建相同结构的模型再加载参数。这是 PyTorch 推荐的保存方式——更灵活、更安全。保存整个模型torch.save(model, ...)虽然在代码上更简洁但它依赖于 Python 的 pickle 序列化加载时必须保证模型类的定义在同一个路径下迁移性差。而state_dict只保存参数数值不依赖类定义更适合实际部署。预测时用torch.no_grad()包裹——告诉 PyTorch 不需要构建计算图、不需要计算梯度节省内存和计算。model.eval()把模型切换到评估模式影响 Dropout 和 BatchNorm 的行为。这两个在预测时缺一不可——no_grad()省内存eval()保证行为正确。TensorBoard在案例中承担了训练监控的角色。通过writer.add_scalar(Loss, loss_value, epoch)把每轮的 loss 写入日志启动 TensorBoard 后就能在浏览器中看到 loss 的下降曲线。如果曲线平滑下降说明训练正常如果曲线剧烈震荡或停滞就需要排查问题。这种可视化训练过程的能力在调参时极其重要。TensorBoard 可视化案例中用到了TensorBoard它是 TensorFlow 生态中诞生但已被 PyTorch 广泛采用的可视化工具。通过SummaryWriter把训练过程中的 loss 写入日志然后在终端启动 TensorBoard 就能看到 loss 曲线的变化趋势——是下降还是震荡有没有收敛。tensorboard --logdirruns/linear_regression打开浏览器就能看到实时更新的训练曲线。在调参时TensorBoard 是你的仪表盘——学习率合不合适、模型有没有过拟合看一眼曲线就知道了。六、学习心得与建议第一形状操作是 PyTorch 的基本功。深度学习代码中一半的 bug 都和形状有关——维度对不上、batch 维度忘了加、通道顺序搞反了。把 reshape、unsqueeze、permute 这些操作练到肌肉记忆调试效率会大幅提升。第二自动微分是 PyTorch 的灵魂。你不需要手动推导梯度、不需要实现反向传播——requires_gradTruebackward()搞定一切。但你要理解它的工作原理计算图、链式法则、梯度累加。理解了这些遇到梯度爆炸、梯度消失等问题时才知道怎么排查。第三五步训练循环要背下来。zero_grad → forward → loss → backward → step这五步是所有 PyTorch 训练代码的骨架。不管模型多复杂核心都是这个循环。把它刻在脑子里读任何训练代码都能快速抓住主线。第四线性回归案例是最好的起点。虽然它简单但包含了完整的训练流程——数据加载、模型定义、损失函数、优化器、训练循环、模型保存加载、预测。后面学 CNN、RNN、Transformer变化的只是模型结构外围的训练流程几乎一模一样。把这个案例吃透后面学任何模型都是换汤不换药。第五善用 TensorBoard。很多初学者训练完才知道效果不好却不知道问题出在哪。TensorBoard 能让你实时看到 loss 曲线的变化——如果 loss 一直不降可能是学习率太小或模型太简单如果 loss 突然飙升可能是梯度爆炸或学习率太大如果 loss 降到一定程度就不动了可能是陷入了局部最优。训练不盲目先让过程可视化。写在最后从昨天的 Tensor 基础到今天的模型训练全流程我们完成了 PyTorch 的核心入门。张量的形状操作和拼接是工具自动微分是引擎五步训练循环是骨架线性回归案例是验证——四者合在一起就是一个完整的 PyTorch 模型训练知识体系。回顾这一路从机器学习概述到线性回归从 KNN 到决策树与集成学习从聚类到深度学习概念再到今天的 PyTorch 模型训练——我们走过了一条从浅到深、从理论到实践的完整学习路径。每一篇文章都不是孤立的它们层层递进前一篇为后一篇铺路后一篇在前一篇的基础上深入。深度学习的大门已经打开接下来的路——CNN 处理图像、RNN 处理文本、Transformer 大杀四方——都是在今天这个基础上搭建更复杂的模型结构。骨架已经搭好剩下的就是往上面添砖加瓦。如果这篇文章对你有帮助欢迎点赞收藏。下一篇我们将用 PyTorch 搭建更复杂的神经网络模型敬请关注。
网站建设高端定制企业官网