PyTorch 深度学习笔记(五)张量拼接与自动微分——构建神经网络基础
📅 2026/8/4 6:03:03
👁️ 阅读次数
📝 编程学习
系列导读:本系列共 6 篇,从 PyTorch 框架入门到实战案例,带你系统掌握深度学习开发。上一篇:张量运算与变形——数值计算与索引进阶
一、张量拼接操作
1.1 cat / concat:沿现有维度拼接
torch.cat沿现有维度连接一系列张量,不改变维度数。
importtorch# 初始化数据data1=torch.randint(0,6,(2,3))data2=torch.randint(0,6,(2,3))print(data1.shape)# torch.Size([2, 3])print(data2.shape)# torch.Size([2, 3])# 按 dim=0 拼接(行方向,垂直堆叠)data=torch.cat([data1,data2],dim=0)print(data.shape)# torch.Size([4, 3])# 按 dim=1 拼接(列方向,水平拼接)data=torch.cat([data1,data2],dim=1)print(data.shape)# torch.Size([2, 6])# 按 dim=2 拼接(需要三维张量)data1=torch.randint(0,6,(2,3,4))data2=torch.randint(0,6,(2,3,4))data=torch.cat([data1,data2],dim=2)print(data.shape)# torch.Size([2, 3, 8])关键规则:拼接维度可以不一致,但其他维度必须相同。
1.2 stack:在新维度上拼接
torch.stack在新维度上连接张量,增加一个新维度,所有输入张量形状必须完全相同。
data1=torch.randint(0,10,[2,3])data2=torch.randint(0,10,[2,3])# dim=0:新维度在最前面 → (2, 2, 3)new_data=torch.stack([data1,data2],dim=0)print(new_data.shape)# torch.Size([2, 2, 3])# dim=1:新维度在中间 → (2, 2, 3)new_data=torch.stack([data1,data2],dim=1)print(new_data.shape)# torch.Size([2, 2, 3])# dim=2:新维度在最后 → (2, 3, 2)new_data=torch.stack([data1,data2],dim=2)print(new_data.shape)# torch.Size([2, 3, 2])经典应用:
- 单通道灰度图
[H, W],三张 stack 模拟 RGB →[3, H, W] - 多张图片张量
[C, H, W],stack 得到批次[B, C, H, W]
1.3 chunk / split:拆分张量
化整为零,各个击破:大拆小,小运行。
data=torch.tensor([[1,2,3],[4,5,6],[7,8,9],[11,12,13]])# chunk:均分成 chunks 份chunks=torch.chunk(data,chunks=2,dim=0)print('第1块:',chunks[0].shape)# torch.Size([2, 3])print('第2块:',chunks[1].shape)# torch.Size([2, 3])# split:按指定大小拆分splits=torch.split(data,[1,1,2],dim=0)print(splits[0].shape)# torch.Size([1, 3])print(splits[1].shape)# torch.Size([1, 3])print(splits[2].shape)# torch.Size([2, 3])1.4 stack 与 chunk 互逆操作
# 生成 64 张 3 通道 32x32 的图片张量imgs=[]foriinrange(64):img=torch.randn(3,32,32)# 3 通道,32 高,32 宽imgs.append(img)# stack 拼接:64 张 → (64, 3, 32, 32)imgs2=torch.stack(imgs,dim=0)print(imgs2.shape)# torch.Size([64, 3, 32, 32])# chunk 拆分:(64, 3, 32, 32) → 64 张 (1, 3, 32, 32)imgs_restore=torch.chunk(imgs2,chunks=64,dim=0)print(len(imgs_restore))# 64print(imgs_restore[0].shape)# torch.Size([1, 3, 32, 32])# squeeze 降维:(1, 3, 32, 32) → (3, 32, 32)imgs_new=imgs_restore[0].squeeze(0)print(imgs_new.shape)# torch.Size([3, 32, 32])二、自动微分模块 autograd ⭐⭐⭐
2.1 什么是自动微分
自动微分 = 自动计算梯度 = 自动计算导数
训练神经网络时,PyTorch 会根据模型构建计算图(Computational Graph):
- 跟踪哪些数据通过哪些操作组合产生输出
- 通过反向传播算法,根据损失函数的梯度调整参数(模型权重)
torch.autograd引擎支持任意计算图的自动梯度计算
2.2 计算图可视化
importtorchfromtorchvizimportmake_dot# 简单模型x=torch.randn(1,requires_grad=True)y=x**2# 生成计算图图片make_dot(y,params={"x":x}).render("y=x²的计算图",format="png")2.3 梯度基本概念
| 概念 | 解释 |
|---|---|
| 梯度 | 对函数求导的值 |
| 梯度下降法 | 求最优梯度,使损失函数最小 |
| 链式法则 | 复合函数求导,反向传播的核心 |
| 反向传播 | 从后向前求导,传播梯度 |
梯度经典语录:
- 在某一点上,对函数求导得到的值就是该点的梯度
- 在平面上,梯度就是某一点上的斜率
- 反向传播传播的是梯度
- 链式法则中梯度相乘,就是传说中的梯度传播
三、梯度基本计算
3.1 关键前提
PyTorch 不支持向量张量对向量张量的求导,只支持标量张量对向量张量的求导。
x是张量 →y必须是标量(一个值)才能求导
3.2 requires_grad:梯度开关
# 默认不开启自动求导x=torch.arange(9).view(3,3)print(x.requires_grad)# False# 开启自动求导x=torch.rand(3,3,requires_grad=True)print(x.requires_grad)# True3.3 标量张量求导
# 定义变量(开启梯度追踪)x=torch.tensor(10,requires_grad=True,dtype=torch.float32)print("x -->",x)# 定义曲线:y = x²y=x**2print("y -->",y)# 查看梯度函数类型print(y.grad_fn)# <PowBackward0 object># 计算梯度(y 是标量,直接 backward)y.backward()# x.grad 就是导数值print("x 的梯度值是:",x.grad)# 2*10 = 20.0backward() 执行过程:
- 从输出
y出发,初始化上游梯度dy/dy = 1 - 进入
PowBackward0算子,套用导数公式dy/dx = 2x - 梯度流入
AccumulateGrad节点,把算出的2x存入x.grad - 执行完毕后,
x.grad才有具体数值
3.4 向量张量求导
# 定义向量张量x=torch.tensor([10,20],requires_grad=True,dtype=torch.float32)print("x -->",x)# 定义曲线y=x**2print("y -->",y)# y 是向量,先转成标量再求导y.sum().backward()# x.grad 保存每个元素的梯度print("x.grad -->",x.grad)# tensor([20., 40.])四、梯度计算注意事项
4.1 不能将求导张量转 NumPy
x=torch.tensor([10,20],requires_grad=True,dtype=torch.float64)# ❌ 报错!不能直接转 NumPy# print(x.numpy())# RuntimeError: Can't call numpy() on Tensor that requires grad.# ✅ 先用 detach() 脱离计算图x2=x.detach()print(x2.numpy())detach() 的作用:
- 产生一个新的张量,与原始张量共享数据
- 新张量不会自动微分(requires_grad=False)
x.data_ptr()和x2.data_ptr()相同(共享内存)id(x)和id(x2)不同(不同 Python 对象)
4.2 梯度累加问题
x=torch.tensor(2.0,requires_grad=True)y=x**2y.backward()print(x.grad)# 4.0# 再次 backward 会累加!y=x**2y.backward()print(x.grad)# 8.0(累加了)# 解决方法:每次 backward 前清零x.grad.zero_()五、拼接与微分总结
| 操作 | 方法 | 说明 | 关键参数 |
|---|---|---|---|
| cat | torch.cat(tensors, dim) | 沿现有维度拼接 | dim 指定拼接轴 |
| stack | torch.stack(tensors, dim) | 在新维度拼接 | 增加一个新维度 |
| chunk | torch.chunk(tensor, chunks, dim) | 均分张量 | chunks 份数 |
| split | torch.split(tensor, sizes, dim) | 按大小拆分 | sizes 每份大小 |
| requires_grad | requires_grad=True | 开启梯度追踪 | 默认 False |
| backward | .backward() | 计算梯度 | 标量输出直接调用 |
| detach | .detach() | 脱离计算图 | 共享数据,无梯度 |
| grad | .grad | 访问梯度值 | backward 后才有值 |
| zero_grad | .grad.zero_() | 梯度清零 | 避免累加 |
六、下一篇预告
PyTorch 深度学习笔记(六):PyTorch 实战——线性回归预测房价完整案例
将使用 PyTorch 的各个组件,构建一个带噪声数据的线性回归模型,模拟"房屋面积-房价"预测任务。包括数据准备、模型构建、损失函数、优化器、训练循环和可视化全流程。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注!你的支持是我持续创作的动力。
编程学习
技术分享
实战经验