PyTorch 深度学习笔记(五)张量拼接与自动微分——构建神经网络基础

系列导读:本系列共 6 篇,从 PyTorch 框架入门到实战案例,带你系统掌握深度学习开发。上一篇:张量运算与变形——数值计算与索引进阶

一、张量拼接操作

1.1 cat / concat:沿现有维度拼接

torch.cat 沿现有维度连接一系列张量,不改变维度数

import torch

# 初始化数据
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 = []
for i in range(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))  # 64
print(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)

  1. 跟踪哪些数据通过哪些操作组合产生输出
  2. 通过反向传播算法,根据损失函数的梯度调整参数(模型权重)
  3. torch.autograd 引擎支持任意计算图的自动梯度计算

2.2 计算图可视化

在这里插入图片描述

import torch
from torchviz import make_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)  # True

3.3 标量张量求导

# 定义变量(开启梯度追踪)
x = torch.tensor(10, requires_grad=True, dtype=torch.float32)
print("x -->", x)

# 定义曲线:y = x²
y = x ** 2
print("y -->", y)

# 查看梯度函数类型
print(y.grad_fn)  # <PowBackward0 object>

# 计算梯度(y 是标量,直接 backward)
y.backward()

# x.grad 就是导数值
print("x 的梯度值是:", x.grad)  # 2*10 = 20.0

backward() 执行过程

  1. 从输出 y 出发,初始化上游梯度 dy/dy = 1
  2. 进入 PowBackward0 算子,套用导数公式 dy/dx = 2x
  3. 梯度流入 AccumulateGrad 节点,把算出的 2x 存入 x.grad
  4. 执行完毕后,x.grad 才有具体数值

3.4 向量张量求导

# 定义向量张量
x = torch.tensor([10, 20], requires_grad=True, dtype=torch.float32)
print("x -->", x)

# 定义曲线
y = x ** 2
print("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 ** 2
y.backward()
print(x.grad)  # 4.0

# 再次 backward 会累加!
y = x ** 2
y.backward()
print(x.grad)  # 8.0(累加了)

# 解决方法:每次 backward 前清零
x.grad.zero_()

五、拼接与微分总结

操作方法说明关键参数
cattorch.cat(tensors, dim)沿现有维度拼接dim 指定拼接轴
stacktorch.stack(tensors, dim)在新维度拼接增加一个新维度
chunktorch.chunk(tensor, chunks, dim)均分张量chunks 份数
splittorch.split(tensor, sizes, dim)按大小拆分sizes 每份大小
requires_gradrequires_grad=True开启梯度追踪默认 False
backward.backward()计算梯度标量输出直接调用
detach.detach()脱离计算图共享数据,无梯度
grad.grad访问梯度值backward 后才有值
zero_grad.grad.zero_()梯度清零避免累加

六、下一篇预告

PyTorch 深度学习笔记(六):PyTorch 实战——线性回归预测房价完整案例

将使用 PyTorch 的各个组件,构建一个带噪声数据的线性回归模型,模拟"房屋面积-房价"预测任务。包括数据准备、模型构建、损失函数、优化器、训练循环和可视化全流程。


如果这篇文章对你有帮助,欢迎点赞、收藏、关注!你的支持是我持续创作的动力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值