
个人主页 for_ever_love__ 欢迎各位大佬莅临其他栏目: 大模型开发从0到1 其他栏目: iOS项目总结大全 其他栏目: 我想学python了 其他栏目: iOS UI 文章目录PyTorch 张量与 autograd——自动求导怎么工作一、安装与验证二、TensorPyTorch 的核心数据结构与 NumPy 的关键差异矩阵运算和 NumPy 几乎一样三、autograd自动求导的心脏3.1 requires_grad告诉 PyTorch盯着这个变量3.2 backward()一键算所有梯度3.3 计算图长什么样3.4 非标量输出需要传 gradient四、梯度累加新手第一大坑五、torch.no_grad()推理与参数更新必用detach()把张量从图上摘下来六、in-place 操作的陷阱七、实战用 autograd 重写线性回归八、手写 autograd 验证它到底在算什么九、设备管理CPU / GPU / MPS十、常见坑与注意事项十一、本篇小结PyTorch 张量与 autograd——自动求导怎么工作承上上一篇《神经网络与反向传播原理》我们手推了每一个梯度写得很爽也很累。本篇本篇把它交给 PyTorchTensor、requires_grad、backward 自动求导。启下下一篇《用 nn.Module 搭建你的第一个神经网络》用积木化组件搭真正的网络。学完这一节你能动手做用 autograd 十几行重写之前四十行的训练代码避开梯度累加、in-place 修改、推理忘关 no_grad 这三大坑正确把张量与模型搬到 GPU / MPS 设备并统一 dtype上一篇我们手写了两层网络的前向、反向、梯度检查——写得很爽但也很累。真实模型有几十亿参数、上百层手推梯度是不可能的。PyTorch 的 autograd自动求导就是来解放你的你只写前向它自动帮你算所有梯度。但要真正用对 autograd必须理解它怎么工作——否则你会踩到梯度没清零“in-place 报错”推理时显存爆了这些经典坑。这一篇把它讲透。一、安装与验证pipinstalltorch torchvisionimporttorchprint(torch.__version__)print(CUDA 可用:,torch.cuda.is_available())print(MPS(Mac GPU) 可用:,torch.backends.mps.is_available())print(设备:,torch.device(cudaiftorch.cuda.is_available()elsempsiftorch.backends.mps.is_available()elsecpu))没有 GPU 也能学完整个专栏——基础实验 CPU 完全够用只是大模型训练慢。二、TensorPyTorch 的核心数据结构Tensor 就是能在 GPU 上跑、能自动求导的多维数组可以理解为 NumPy 的升级版。importtorchimportnumpyasnp# 创建atorch.tensor([1.0,2.0,3.0])btorch.zeros(2,3)ctorch.ones(2,3)dtorch.randn(2,3)# 标准正态etorch.arange(0,10,2)# [0,2,4,6,8]ftorch.eye(3)# 单位矩阵print(a,a.shape,a.dtype)# 与 NumPy 互转np_arrnp.array([[1,2],[3,4]])ttorch.from_numpy(np_arr)# numpy → tensor共享内存backt.numpy()# tensor → numpy# 常用属性和 NumPy 一致xtorch.randn(3,4)print(x.shape,x.dtype,x.device,x.requires_grad)与 NumPy 的关键差异NumPyPyTorch TensorGPU 加速❌✅.cuda()/.to(mps)自动求导❌✅requires_gradTrue维度排列HWC图像常用CHW卷积要求默认 dtypefloat64float32# 类型转换深度学习几乎都用 float32xtorch.tensor([1,2,3])# int64print(x.dtype)print(x.float().dtype)# float32print(x.to(torch.float32).dtype)# 形状操作ytorch.randn(2,3,4)print(y.reshape(6,4).shape)# 或 y.view(6,4)print(y.unsqueeze(0).shape)# (1,2,3,4) 加一维batch 维度常用print(y.squeeze().shape)# 去掉所有长度为1的维print(y.permute(2,0,1).shape)# (4,2,3) 维度重排图像 CHW↔HWC矩阵运算和 NumPy 几乎一样Atorch.randn(3,4)Btorch.randn(4,5)print((A B).shape)# (3,5) 矩阵乘法print(torch.matmul(A,B).shape)print((A*A).shape)# 逐元素相乘形状不变# 求和/均值深度学习里算 loss 常用print(A.sum(),A.mean(),A.mean(dim0).shape)# dim0 沿行方向压缩 → (4,)三、autograd自动求导的心脏3.1 requires_grad告诉 PyTorch盯着这个变量xtorch.tensor([2.0],requires_gradTrue)wtorch.tensor([3.0],requires_gradTrue)btorch.tensor([1.0],requires_gradTrue)yw*xb# y 3*2 1 7print(y ,y)print(y.grad_fn ,y.grad_fn)# AddBackward0 ← PyTorch 已记录了运算只要输入的requires_gradTrue运算结果就会自动带上grad_fn——这是构建计算图的痕迹。3.2 backward()一键算所有梯度y.backward()# 从 y 开始反向传播print(dy/dx ,x.grad)# 3.0 ( w)print(dy/dw ,w.grad)# 2.0 ( x)print(dy/db ,b.grad)# 1.0和我们手推的完全一致。这就是 autograd它在前向时记录每张运算卡片backward()时按链式法则倒着走一遍。3.3 计算图长什么样atorch.tensor([1.0],requires_gradTrue)btorch.tensor([2.0],requires_gradTrue)ca*b# c 2dc1# d 3ed**2# e 9print(c.grad_fn:,c.grad_fn)# MulBackwardprint(d.grad_fn:,d.grad_fn)# AddBackwardprint(e.grad_fn:,e.grad_fn)# PowBackwarde.backward()print(de/da ,a.grad)# 手推: de/da 2*d*b 2*3*2 12print(de/db ,b.grad)# 手推: de/db 2*d*a 2*3*1 6# 验算手算链式法则print(手算 da:,2*(a*b1).item()*b.item())3.4 非标量输出需要传 gradientxtorch.randn(3,requires_gradTrue)yx*2print(y)# 向量# 向量对向量求导会报错需要指定上游梯度try:y.backward()exceptRuntimeErroraserr:print(报错:,err)# 正确做法传一个同形状的权重向量通常是 loss 的梯度y.backward(torch.tensor([1.0,1.0,1.0]))print(x.grad)# [2,2,2]# 实践中更常见先聚合成标量x2torch.randn(3,requires_gradTrue)loss(x2*2).sum()# 标量loss.backward()print(x2.grad)# [2,2,2]实践中几乎总是先.sum()或.mean()得到标量 loss再 backward。四、梯度累加新手第一大坑wtorch.tensor([1.0],requires_gradTrue)forstepinrange(3):yw*2y.backward()print(fstep{step}: grad {w.grad.item()})# 期望每次都是 2.0实际是 2, 4, 6 —— 梯度被累加了PyTorch 默认累加梯度grad new_grad不会自动清零。因为有些场景需要累加比如大 batch 拆成小 batch 模拟。正确写法wtorch.tensor([1.0],requires_gradTrue)optimizer_lr0.1forstepinrange(3):ifw.gradisnotNone:w.grad.zero_()# ← 清零或者用 optimizer.zero_grad()yw*2y.backward()withtorch.no_grad():# 更新参数时不该被记录进计算图w-optimizer_lr*w.gradprint(fstep{step}: w {w.item():.3f}, grad {w.grad.item()})忘记zero_grad()是深度学习最常见的 bug——表现为 loss 诡异、训练不收敛。五、torch.no_grad()推理与参数更新必用xtorch.randn(3,requires_gradTrue)withtorch.no_grad():yx*2print(no_grad 下 y.requires_grad ,y.requires_grad)# False# 用途 1参数更新前面见过# 用途 2推理/评估省显存、提速defpredict(model,x):withtorch.no_grad():# 推理时不建图显存占用大减returnmodel(x)# 用途 3把张量转成普通数值withtorch.no_grad():valx.sum().item()print(item():,val,type(val))为什么推理必须用no_grad()因为构建计算图要保存每一层的中间激活这部分显存开销巨大。推理时不需要梯度省掉它能让 batch 大一倍。detach()把张量从图上摘下来xtorch.randn(2,requires_gradTrue)yx*3zy.detach()# z 与 y 共享数据但不再参与求导print(y.requires_grad:,y.requires_grad)# Trueprint(z.requires_grad:,z.requires_grad)# False# 典型场景GAN、强化学习中需要截断梯度流六、in-place 操作的陷阱xtorch.randn(3,requires_gradTrue)yx**2# ❌ 危险in-place 修改叶子节点try:x.add_(1.0)# 带下划线 in-placey.backward()exceptRuntimeErrorase:print(报错:,str(e)[:90])# ✅ 安全写法xtorch.randn(3,requires_gradTrue)y(x1.0)**2# 用 out-of-placey.sum().backward()print(安全:,x.grad)规则带下划线的方法add_、mul_、zero_是 in-place会修改原张量。不要对需要求导的叶子张量做 in-place 修改。不过.zero_()用在.grad上是安全的梯度不需要再被求导。七、实战用 autograd 重写线性回归上一篇手写版要 40 行PyTorch 版只要十几行importtorch torch.manual_seed(42)# 1) 造数据n,d200,3X_npnp.random.randn(n,d)w_truenp.array([[2.0],[-3.0],[1.5]])y_npX_np w_true0.5np.random.randn(n,1)*0.3Xtorch.from_numpy(X_np).float()ytorch.from_numpy(y_np).float()# 2) 初始化参数requires_gradTrue 让 PyTorch 自动追踪Wtorch.randn(d,1,requires_gradTrue)btorch.zeros(1,requires_gradTrue)# 3) 训练lr,epochs0.1,300forepinrange(epochs):ifW.gradisnotNone:W.grad.zero_();b.grad.zero_()y_predX Wb# 前向losstorch.mean((y_pred-y)**2)# MSEloss.backward()# 自动反向withtorch.no_grad():# 更新W-lr*W.grad b-lr*b.gradifep%600orepepochs-1:print(fepoch{ep:3d}loss{loss.item():.4f})print(\n学到的 W:,W.detach().flatten().numpy().round(3))print(真实的 W:,w_true.flatten())print(学到的 b:,b.detach().numpy().round(3), 真实 b: [0.5])对比手写版梯度那几行最易出错的部分全没了loss.backward()一行搞定。八、手写 autograd 验证它到底在算什么为了确信 autograd 不是黑魔法我们做一次梯度检查defcheck_autograd():torch.manual_seed(0)Wtorch.randn(3,1,requires_gradTrue)btorch.zeros(1,requires_gradTrue)Xstorch.randn(10,3)ystorch.randn(10,1)losstorch.mean((Xs Wb-ys)**2)loss.backward()# 数值梯度eps1e-4foriinrange(3):W_plusW.detach().clone();W_plus[i]eps W_minusW.detach().clone();W_minus[i]-eps lptorch.mean((Xs W_plusb.detach()-ys)**2)lmtorch.mean((Xs W_minusb.detach()-ys)**2)numeric(lp-lm).item()/(2*eps)print(fW[{i}] 解析{W.grad[i].item():.6f}数值{numeric:.6f}f误差{abs(W.grad[i].item()-numeric):.2e})check_autograd()误差在1e-6量级——autograd 算的就是链式法则和我们在阶段 2 手推的一模一样。九、设备管理CPU / GPU / MPSdevicetorch.device(cudaiftorch.cuda.is_available()elsempsiftorch.backends.mps.is_available()elsecpu)print(使用设备:,device)# 把张量/模型搬到设备上x_cputorch.randn(2,3)x_devx_cpu.to(device)print(x_dev.device:,x_dev.device)# 常见错误一个在 CPU 一个在 GPUtry:ifdevice.typecuda:badx_cpux_devexceptRuntimeErrorase:print(跨设备运算报错预期)训练时的标准写法X, y X.to(device), y.to(device)和model.to(device)。数据类型也要匹配atorch.randn(2,3,dtypetorch.float32)btorch.randn(2,3,dtypetorch.float64)try:abexceptRuntimeErrorase:print(dtype 不匹配报错:,str(e)[:60])# 解决统一 .float() 或 .to(torch.float32)十、常见坑与注意事项坑现象解决忘记zero_grad()梯度累加loss 诡异每步optimizer.zero_grad()推理没用no_grad()显存爆、速度慢with torch.no_grad():in-place 改叶子张量RuntimeError用 out-of-place 运算设备不一致Expected all tensors on same device统一.to(device)dtype 不一致Expected Float got Double统一.float()对向量直接 backwardgrad can be implicitly created only for scalar先.sum()或传 gradient用.data改参数绕过追踪可能出错用torch.no_grad()忘了.item()打印出 tensor(…)标量取.item()记忆口诀zero_grad→forward→loss→backward→step更新五步循环。十一、本篇小结Tensor 能上 GPU 能自动求导的数组API 与 NumPy 高度相似矩阵乘法、*、reshape、permute。requires_gradTrue让 PyTorch 追踪运算前向时自动建图每个输出带grad_fn。loss.backward()沿计算图倒着应用链式法则把所有requires_grad变量的梯度填进.grad。梯度默认累加必须手动清零推理/更新用torch.no_grad()省显存避免 in-place 修改叶子张量。我们用 autograd 重写了线性回归并用数值梯度验证——autograd 算的就是我们手推的链式法则。设备管理统一device和dtypeX.to(device)/.float()。下一篇用 nn.Module 搭建你的第一个神经网络不再手动定义W1/b1/W2/b2用 PyTorch 的nn.Linear、nn.ReLU、nn.Sequential搭积木学会参数管理、forward()写法并搭出一个真正的多层感知机MLP。本篇是《大模型开发从 0 到 1》专栏第 23 篇阶段 4「深度学习与 PyTorch」第 2 篇。专栏文章按「分类专栏」归类顺序学习体验最佳。