接触 PyTorch 也有一段时间了,从最初的“照着教程敲代码”,到后来能比较顺畅地搭模型、调训练循环。这篇文章不是官方文档的翻译,而是我站在使用者角度,对 PyTorch 底层设计的一些个人理解,希望能帮到同样在入门的朋友。
一句话概括 PyTorch
PyTorch 就是一个带自动微分的 NumPy,外加一个模块化的神经网络组件库。
如果你能理解这句话,PyTorch 的大半就掌握了。剩下的是各种 API 的熟练度问题。
核心概念一:张量(Tensor)
Tensor 是PyTorch中一切高层概念的基石,是建设高层建筑所需要的基础材料,理解Tensor对于熟练使用PyTorch至关重要。在PyTorch中,模型的输入输出和模型的权重参数都是用Tensor来表示的。可以认为Tensor 本质就是 n 维数组,和 NumPy 的 ndarray 几乎一一对应,但它多了两个关键能力:GPU 加速和自动微分。
Tensor初始化
1 | import torch |
如果想生成固定shape的tensor,有一些函数可以直接指定想要生成的张量的维度
1 | x = torch.zeros(3, 4) # 全 0 |
Tensor的属性
1 | x = torch.tensor([1, 2, 3]) |
Tensor的操作
PyTorch支持约100多种不同类型的tensor操作:转置,索引,切片,常规的数学运算,线性代数领域的运算,随机采样等。每种运算都支持在GPU上进行加速运算,这是PyTorch的框架所支持的。
1 | tensor = torch.rand(3, 4) |
就地操作
1 | tensor = torch.rand(3, 4) |
Tensor与Numpy进行桥接
存储在CPU上的Tensor可以与Numpy进行桥接,他们共享相同的内存,所以对其中一个进行修改,另一个也会被修改。
1 | import numpy as np |
核心概念二:自动微分(Autograd)
这是 PyTorch 的灵魂。自动微分的意思是:你只需要定义前向计算过程,PyTorch 会自动帮你把梯度算出来。
1 | x = torch.tensor([2.0], requires_grad=True) # 开启梯度追踪 |
关键在于 requires_grad=True。设置之后,PyTorch 会在前向计算的过程中偷偷记录计算图,等 backward() 时再从后往前把每个中间变量的梯度算出来。
我最初一直困惑的是:梯度到底是怎么”自动”算出来的?后来想明白了,其实就是链式法则。PyTorch 把 y = x ** 2 + 3 * x 拆成一张图:
1 | x -> x**2 -> + -> y |
backward() 就是沿着这张图反向走一遍,把 dy/dx 逐层传回去。所谓的”深度学习框架”,本质就是替你维护了这张图和链式求导。
计算图的释放
有一个细节值得注意:每次 backward() 之后,计算图会被释放,所以 loss.backward() 只能在训练循环里调用一次。如果需要多次反向(比如某些 GAN 的写法),要传 retain_graph=True。这个细节常常是新手报错 RuntimeError: Trying to backward through the graph a second time 的根源。
核心概念三:动态计算图
这是 PyTorch 区别于老式框架(如 TensorFlow 1.x 的静态图)最重要的设计。
- 静态图:先定义好完整的计算图,再喂数据执行。优点是可以预先优化,缺点是图的定义和调试都很痛苦,Python 的
if、for都得想办法塞进图 API 里。 - 动态图:执行即构建。每跑一次前向,图就现场搭一次。写模型就跟写普通 Python 代码一样,
if、for、函数调用随手就用。
1 | def forward(self, x, use_dropout=True): |
我的个人理解:动态图的本质是让”搭网络”这件事回归到”写程序”,框架不再强迫你用一套声明式 DSL。这对研究、调试、快速迭代极其友好——想加一个分支?写个 if 就完了。
核心概念四:nn.Module 模块化
nn.Module 是搭模型的”积木”基类。任何网络都可以看作模块的嵌套组合。
1 | import torch.nn as nn |
我理解 nn.Module 真正做的事有三件:
- 自动注册子模块和参数:
self.fc1 = nn.Linear(...)一旦赋值,model.parameters()就会自动收集到fc1的权重,不用手动管理。 - 统一前向接口:
forward定义后,model(x)自动帮你处理一些细节(比如切换训练/评估模式)。 - 管理状态:
.train()/.eval()、.to(device)、.state_dict()等一整套生命周期操作。
一个完整的训练循环
训练的本质其实非常简单,就五步,每步一行:
1 | optimizer = torch.optim.SGD(model.parameters(), lr=0.01) |
这五行代码我建议手写十遍。深度学习看似高大上,落到训练这一层,就是”前向算损失,反向算梯度,沿着梯度反方向走一步”。
其中 optimizer.zero_grad() 是最容易忽略的一步——PyTorch 的梯度是累加的,不清零的话每轮梯度会叠加,训练直接崩掉。这也是个人踩过的坑。
个人总结
最后说说我对 PyTorch 的整体观感:
- 它首先是个科学的玩具,其次才是工业工具。 动态图 + Python 生态让它成为研究首选,部署上虽有 TorchScript 等方案,但历史包袱比某些框架重。
- 理解自动微分 > 记住 API。 只要想清楚”计算图 + 链式法则”,遇到任何自定义损失、自定义层的需求,心里就有底。
- 先会套模板,再拆模板。 训练循环、
nn.Module的写法都是高度模板化的,先跑通,再去抠每一行的原理,是效率最高的学习路径。
一句话收尾:PyTorch 让”从想法到训练出模型”的路径变得足够短,剩下的,就是你的数据和想法了。