2. 深度学习 - Pytorch

1)是什么

PyTorch 是由 Facebook(现 Meta)AI 团队开发的开源深度学习框架,于 2016 年首次发布,是目前最主流的深度学习工具之一。它基于 Python 设计,以 动态计算图(Dynamic Computation Graph) 为核心特性,支持张量运算、自动微分和 GPU 加速。

🔹 关键特性:

  • 动态图机制:模型在运行时构建计算图,允许灵活控制流程(如循环、条件判断),非常适合研究和实验。
  • Tensor 张量操作:类似 NumPy 的多维数组,支持 GPU 加速。
  • Autograd 自动求导:通过 torch.autograd 实现梯度自动计算,简化反向传播。
  • 模块化设计:torch.nn 提供神经网络组件(层、损失函数、优化器等)。
  • TorchScript:将动态图转换为静态图,便于部署到生产环境。
  • 分布式训练支持:支持多 GPU、多机训练(如 DistributedDataParallel)。

✅ 总结:PyTorch 是由 Meta 开发的开源深度学习框架,以动态计算图为核心,提供灵活的张量运算、自动微分和 GPU 加速能力。其设计贴近 Python 习惯,强调易用性与研究友好性,是当前学术界和工业界广泛采用的主流工具之一。

2)为什么

选择 PyTorch 的主要原因在于其灵活性、易用性和强大的社区生态。
✅ 优点:

  • Python 风格自然流畅:语法简洁,与 Python 生态无缝集成(如 NumPy、Matplotlib、Pandas)。
  • 调试友好:支持断点调试、打印变量、使用 IDE 调试器(如 VS Code、PyCharm)。
  • 研究友好:动态图机制允许即兴修改模型结构,适合新算法实验。
  • 丰富的库支持:
    • torchvision:图像处理和预训练模型(如 ResNet、EfficientNet)。
    • torchaudio:音频处理。
    • torchtext:NLP 工具。
    • Hugging Face Transformers:集成 Transformer 模型。
  • 活跃社区与文档:官方教程完善,GitHub 社区活跃,大量开源项目(如 Detectron2、Fairseq)。
  • 工业级支持:Meta、Google、Microsoft 等公司广泛采用,支持 ONNX 导出和移动端部署。

❌ 对比 TensorFlow:

特性PyTorchTensorFlow
图机制动态图静态图(TF 2.x 支持 Eager Execution)
开发体验更直观、易调试较复杂(尤其早期版本)
研究领域主导地位逐渐被追赶
生产部署通过 TorchScript / ONNX原生 TFLite / TF Serving

✅ 总结:PyTorch 凭借Python 原生风格、出色的调试体验、动态图灵活性以及丰富的生态库(如 torchvision、Hugging Face),成为研究人员和开发者的首选。虽然 TensorFlow 在早期部署方面占优,但 PyTorch 在开发体验和社区活跃度上已形成显著优势。

3)什么时候用

PyTorch 是以下场景的首选:

✅ 适合使用的情况:

  • 🔬 学术研究与原型开发:需要快速验证新模型结构或算法(如 GAN、Transformer 变体)。
  • 🖼️ 计算机视觉任务:图像分类、目标检测、语义分割(使用 torchvision)。
  • 💬 自然语言处理:BERT、GPT 等模型训练(结合 Hugging Face)。
  • 🤖 强化学习:PyTorch RL 库(如 Stable-Baselines3、Ray RLlib)。
  • 🧠 自定义模型架构:需要动态控制流程(如变长序列、递归神经网络)。
  • 🚀 快速迭代与实验:支持 Jupyter Notebook、Colab 等交互式环境。

📌 典型应用案例:

  • Meta 的 Llama 系列大模型基于 PyTorch 构建。
  • 医疗影像分析、自动驾驶感知系统中广泛应用。

✅ 总结:PyTorch 特别适合科研探索、快速原型开发、自定义模型结构以及多模态任务(如 CV、NLP、强化学习)。在需要频繁修改模型逻辑或使用交互式环境(如 Jupyter、Colab)时,其动态图机制能极大提升开发效率。

4)什么时候不用

尽管 PyTorch 强大,但在某些场景下可能不是最佳选择:

🚫 不适合使用的情况:

  • ⚡ 极致性能要求的生产部署:虽然 TorchScript 和 TorchServe 支持部署,但 TensorFlow Lite 或 ONNX Runtime 在边缘设备上可能更成熟。
  • 🏗️ 已有 TensorFlow 生态系统:如果团队已投入大量资源在 TensorFlow 上(如 TFLite、TensorBoard、TF Serving),迁移成本高。
  • 🔐 严格合规或安全要求的工业系统:部分企业对开源框架有审计限制,而 TensorFlow 有更成熟的工业支持体系。
  • 📦 轻量级嵌入式设备:PyTorch 的运行时较重,不如 TensorFlow Lite 或 TinyML 方案高效。
  • 🔄 静态图优化需求强:如需编译优化、图融合、算子融合等,TensorFlow 的 XLA 编译器更成熟。

⚠️ 注意:PyTorch 近年来也在加强部署能力(如 TorchServe、Torch Mobile、ONNX 导出),差距正在缩小。

✅ 总结:在已有 TensorFlow 技术栈、对边缘设备部署有极致性能要求、或依赖静态图优化(如 XLA 编译)的场景下,PyTorch 可能不是最优解。此外,在某些对框架合规性审查严格的工业系统中,TensorFlow 的成熟支持体系仍具优势——但需注意 PyTorch 的部署能力正在快速追赶。

5)总结

✅ PyTorch 凭借其灵活性、易用性和强大的研究支持,已成为现代深度学习领域的事实标准之一,尤其是在科研和快速开发中占据主导地位。

✅ 它的动态图机制让模型构建更加直观,Python 集成让开发体验极佳,同时拥有活跃的社区和丰富的生态。

✅ 尽管在传统工业部署方面略逊于 TensorFlow,但随着 TorchScript、ONNX 和移动部署工具的发展,其应用场景正不断扩展。

✅ 总体而言,如果你是研究人员、学生或开发者,PyTorch 是学习和实践深度学习的首选框架;若在大型工业系统中已有成熟方案,则需权衡迁移成本。


概念
安装
~1.安装CPU版本
阿里镜像

pip install torch torchvision torchaudio -i https://mirrors.aliyun.com/pypi/simple/

~2.安装GPU版本(非英伟达不可为)

清华镜像

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

2.1 张量的创建

在 PyTorch 中,张量(Tensor) 是所有计算的核心数据结构,类似于 NumPy 数组,但支持 GPU 加速和自动微分。掌握张量的创建与类型操作是使用 PyTorch 的基础。

2.1.1 创建普通张量

import torch

# 从列表创建一维张量
x = torch.tensor([1, 2, 3])
print(x)  # tensor([1, 2, 3])

# 创建二维张量(矩阵)
y = torch.tensor([[1, 2], [3, 4]])
print(y)
# tensor([[1, 2],
#         [3, 4]])

# 指定数据类型
z = torch.tensor([1, 2, 3], dtype=torch.float32)
print(z.dtype)  # torch.float32

📌 注意事项:

  • torch.tensor() 会保留输入数据的类型(如整数转为 int64),若需指定类型,需显式设置 dtype。
  • 若希望强制转换为浮点型,可使用 float() 或 torch.float32。
  • 如果你看到有人写 Tensor,那可能是旧代码、习惯问题,或者对 PyTorch 最佳实践不够熟悉。现代 PyTorch 开发中,小写的 tensor 是标准做法。

✅ 总结:torch.tensor() 是从 Python 列表或嵌套列表创建张量的基础方法,支持显式指定数据类型(如 dtype=torch.float32)。注意默认保留输入类型,且应使用小写 tensor 而非大写 Tensor。

2.1.2 创建线性和随机张量

线性张量

# linspace:在指定范围内均匀采样 n 个点
a = torch.linspace(0, 10, steps=5)
print(a)  # tensor([ 0.,  2.5,  5.,  7.5, 10.])

# arange:类似 range,生成等差序列
b = torch.arange(0, 10, 2)
print(b)  # tensor([0, 2, 4, 6, 8])

随机张量

# 均匀分布:[0, 1) 区间内随机数
c = torch.rand(2, 3)
print(c)

# 正态分布:均值为0,标准差为1
d = torch.randn(2, 3)
print(d)

# 指定范围的均匀分布
e = torch.randint(1, 10, (2, 2))  # 1~9之间的整数
print(e)

✅ 总结:torch.arange() 和 torch.linspace() 用于生成等差或均匀分布序列;torch.rand()、torch.randn()、torch.randint() 分别生成均匀、正态和整数随机张量,广泛用于模型初始化与数据模拟。

2.1.3 创建指定值张量

# 全零张量
zeros = torch.zeros(3, 4)
print(zeros)

# 全一张量
ones = torch.ones(2, 2)
print(ones)

# 单位矩阵(对角阵)
eye = torch.eye(3)
print(eye)
# tensor([[1., 0., 0.],
#         [0., 1., 0.],
#         [0., 0., 1.]])

# 填充指定值
filled = torch.full((2, 3), 5.0)
print(filled)
# tensor([[5., 5., 5.],
#         [5., 5., 5.]])

✅ 特殊函数:

  • torch.zeros_like(tensor):创建与给定张量形状相同、值为 0 的新张量。
  • torch.ones_like(tensor):同理,值为 1。
  • torch.empty(size):分配内存但不初始化(速度快,值不确定)。

✅ 总结:zeros()、ones()、eye()、full() 可快速构建全零、全一、单位矩阵或填充常数的张量。配合 *_like() 函数可基于现有张量形状创建新张量,提升代码简洁性与可读性。

2.1.4 总结

子章节核心内容应用场景
2.1.1 普通张量使用 torch.tensor() 从数据构造张量数据加载、手动赋值
2.1.2 线性/随机张量arange, linspace, rand, randn 等模型初始化、数据生成
2.1.3 指定值张量zeros, ones, eye, full初始化权重、构建特殊矩阵

2.2 张量的转换

2.2.1 张量元素类型转换

x = torch.tensor([1, 2, 3], dtype=torch.int64)
print(x.dtype)  # torch.int64

# 转换为 float32
y = x.float()  # 或 x.to(torch.float32)
print(y.dtype)  # torch.float32

# 转换为 int32
z = y.long()  # 或 y.to(torch.int64)
print(z.dtype)  # torch.int64

⚠️ 类型转换不会改变原始张量,而是返回新张量。

✅ 总结:通过 .float()、.long() 或 .to(dtype) 实现张量类型转换,适用于精度控制(如训练用 float32)或兼容性需求。转换操作返回新张量,不修改原张量。

2.2.2 张量与Numpy数组的转换

import numpy as np

# 张量 → NumPy
x = torch.tensor([1, 2, 3])
np_array = x.numpy()  # 必须在 CPU 上
print(type(np_array))  # <class 'numpy.ndarray'>

# NumPy → 张量
arr = np.array([1, 2, 3])
tensor = torch.from_numpy(arr)
print(tensor)  # tensor([1, 2, 3])

✅ 总结:.numpy() 将 CPU 张量转为 NumPy 数组,torch.from_numpy() 反向转换。两者共享内存(修改一方会影响另一方),但仅限 CPU 张量;GPU 张量需先 .cpu() 再转换。

2.2.3 张量与数字的转换

x = torch.tensor(3.14)
value = x.item()  # 返回 float: 3.14

# 仅适用于标量张量(shape=())
y = torch.tensor([1, 2, 3])
# y.item() 会报错!必须是单元素
scalar = y[0].item()  # 正确:获取第一个元素

✅ 总结:.item() 用于从标量张量(单元素、shape=())中提取 Python 数值(如 float 或 int),常用于记录损失、准确率等指标。对多元素张量调用会报错。

2.2.4 总结

子章节核心内容应用场景
2.1.4 类型转换float(), long(), to(dtype)控制精度、兼容性处理
2.1.5 张量与 NumPy 转换tensor.numpy() / torch.from_numpy()科学计算集成、数据预处理
2.1.6 张量与数字转换tensor.item() 提取标量值获取损失、准确率等单值输出

2.3 张量运算

2.3.1 四则运算

import torch

x = torch.tensor([1, 2, 3])
y = torch.tensor([4, 5, 6])

print("加法:", x + y)        # [5, 7, 9]
print("减法:", x - y)        # [-3, -3, -3]
print("乘法:", x * y)        # [4, 10, 18]
print("除法:", x / y)        # [0.25, 0.4, 0.5]

🔹 支持广播机制(broadcasting),例如 x + 2 等价于 x + torch.tensor([2,2,2])

2.3.2 取反

# 取负
import torch
x = torch.tensor([1, -2, 3])
print(-x)  # [-1, 2, -3]
print(x.neg()) # [-1, 2, -3]

2.3.3 幂运算与指数/对数

x = torch.tensor([1, 2, 3])

print("平方:", torch.pow(x, 2))       # [1, 4, 9]
print("立方:", torch.pow(x, 3))       # [1, 8, 27]
print("指数:", torch.exp(x))          # [e^1, e^2, e^3]
print("对数:", torch.log(x))          # [0, log(2), log(3)]

2.3.4 三角函数

支持常见的三角函数:sin, cos, tan, asin, acos, atan

x = torch.tensor([0, torch.pi/4, torch.pi/2])

print("正弦:", torch.sin(x))         # [0, √2/2, 1]
print("余弦:", torch.cos(x))         # [1, √2/2, 0]
print("正切:", torch.tan(x))         # [0, 1, inf]

2.3.5 四舍五入与截断

x = torch.tensor([1.2, -1.8, 2.7, -2.3])

print("四舍五入:", torch.round(x))   # [1., -2., 3., -2.]
print("向下取整:", torch.floor(x))   # [1., -2., 2., -3.]
print("向上取整:", torch.ceil(x))    # [2., -1., 3., -2.]
print("截断:", torch.trunc(x))       # [1., -1., 2., -2.]

2.3.6 比较操作

x = torch.tensor([1, 2, 3])
y = torch.tensor([2, 1, 3])

print("x > y:", x > y)      # [False, True, False]
print("x == y:", x == y)    # [False, False, True]
print("x <= y:", x <= y)    # [True, False, True]

2.3.7 聚合操作

x = torch.tensor([[1, 2], [3, 4]])

print("总和:", torch.sum(x))           # 10
print("均值:", torch.mean(x.float()))  # 2.5
print("最大值:", torch.max(x))         # 4
print("最小值:", torch.min(x))         # 1

# 沿行方向(dim=1)
print("每行最大值:", torch.max(x, dim=1).values)  # [2, 4]
print("每列最大值:", torch.max(x, dim=0).values)  # [3, 4]

2.3.8 元素级逻辑操作(需搭配比较使用)

x = torch.tensor([1, 2, 3])
y = torch.tensor([2, 1, 4])

cond1 = x > 1
cond2 = y < 3

print("cond1:", cond1)     # [False, True, True]
print("cond2:", cond2)     # [True, True, False]

print("与操作:", cond1 & cond2)  # [False, True, False]
print("或操作:", cond1 | cond2)  # [True, True, True]
print("非操作:", ~cond1)        # [True, False, False]

2.3.9 张量乘法

2.3.9.1 逐元素乘法

使用 * 操作符或 torch.mul() 实现,要求两个张量形状相同(或可广播)

import torch

# 定义两个相同形状的张量
A = torch.tensor([[1, 2], [3, 4]])
B = torch.tensor([[5, 6], [7, 8]])

# 逐元素相乘
C = A * B
print("逐元素乘法:")
print(C)
# 输出:
# tensor([[ 5, 12],
#         [21, 32]])
2.3.9.2 广播机制下的逐元素乘法
A = torch.tensor([[1, 2], [3, 4]])  # shape: (2, 2)
B = torch.tensor([2, 3])           # shape: (2,)

C = A * B
print("广播后的逐元素乘法:")
print(C)
# 输出:
# tensor([[ 2,  6],
#         [ 6, 12]])

🔍 解释:B 被自动扩展为 [2,3] -> [[2,3], [2,3]],然后与 A 对应位置相乘。

2.3.9.3 矩阵乘法运算

在这里插入图片描述在这里插入图片描述

A = torch.tensor([[1, 2], [3, 4]])  # (2, 2)
B = torch.tensor([[5, 6], [7, 8]])  # (2, 2)
#方法1
C = A @ B
print("矩阵乘法 (@):")
print(C)
# 输出:
# tensor([[19, 22],
#         [43, 50]])
# 方法2:
C = torch.matmul(A, B)
print("矩阵乘法 (matmul):")
print(C)
#方法3:
C = torch.mm(A, B)
print("矩阵乘法 (mm):")
print(C)
操作符号/函数类型示例
逐元素乘法* 或 torch.mul()Element-wiseA * B
矩阵乘法@ 或 torch.matmul()Matrix multiplicationA @ B
二维矩阵乘法torch.mm()仅限 2Dtorch.mm(A, B)

2.1.9 张量索引操作

2.1.9.1 行列索引
import torch

# 创建一个 2x3 的张量
x = torch.tensor([[1, 2, 3],
                  [4, 5, 6]])

print("原张量:")
print(x)

# 获取第0行第1列的元素
print("x[0, 1]:", x[0, 1])  # 输出: 2

# 获取第1行第2列的元素
print("x[1, 2]:", x[1, 2])  # 输出: 6

# 使用负索引获取最后一个元素
print("x[-1, -1]:", x[-1, -1])  # 输出: 6

✅ 小结:

  • 使用 [行索引, 列索引] 形式访问二维张量中的单个元素。
  • 索引从 0 开始,负数表示倒序(如 -1 表示最后一个)。
  • 可用于任意维度张量,但需对应维度顺序。
2.1.9.2 列表索引
x = torch.tensor([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])

# 选择第0行和第2行
rows = [0, 2]
selected_rows = x[rows, :]
print("选择第0行和第2行:")
print(selected_rows)
# 输出:
# tensor([[1, 2, 3],
#         [7, 8, 9]])

# 选择第1列和第3列(注意:只有3列,所以是第1和第2列)
cols = [1, 2]
selected_cols = x[:, cols]
print("选择第1列和第2列:")
print(selected_cols)
# 输出:
# tensor([[2, 3],
#         [5, 6],
#         [8, 9]])

✅ 小结:

  • 使用 Python 列表或整数张量作为索引,一次性选取多个位置的元素。
  • 支持非连续索引,例如 [0, 2] 可跳过中间元素。
  • 常用于批量选择某些行或列。
2.1.9.3 范围索引
x = torch.tensor([[1, 2, 3, 4],
                  [5, 6, 7, 8],
                  [9, 10, 11, 12]])

# 获取前两行所有列
print("前两行:", x[:2, :])
# 输出:
# tensor([[1, 2, 3, 4],
#         [5, 6, 7, 8]])

# 获取所有行的后两列
print("后两列:", x[:, 2:])
# 输出:
# tensor([[ 3,  4],
#         [ 7,  8],
#         [11, 12]])

# 获取第1行第1到第3列(不含第3列)
print("第1行第1~2列:", x[1, 1:3])
# 输出: tensor([6, 7])

# 步长为2的切片
print("每隔一行取一次:", x[::2, :])
# 输出:
# tensor([[1, 2, 3, 4],
#         [9, 10, 11, 12]])

✅ 小结:

  • 使用切片语法 start:end 来选取连续范围内的元素。
  • 支持省略起始/结束,默认为从头到尾。
  • 可配合步长 start : end : step 使用。
  • 在 PyTorch 中,切片返回的是视图(view),不复制数据。
2.1.9.4 布尔索引
import torch

x = torch.tensor([[1, 2, 3],
                  [4, 5, 6]])
# 创建布尔条件:大于3的元素
mask = x > 3
print("布尔掩码:")
print(mask)
# 输出:
# tensor([[False, False, False],
#         [ True,  True,  True]])

# 使用掩码进行索引
filtered = x[mask]
print("大于3的元素:", filtered)
# 输出: tensor([4, 5, 6])

# 直接写法(更简洁)
print("直接条件索引:", x[x > 3])
# 输出: tensor([4, 5, 6])

# 大于2且小于6
condition = (x > 2) & (x < 6)
print("2 < x < 6:", x[condition])  # 输出: tensor([3, 4, 5])

✅ 小结:

  • 使用布尔条件生成掩码(mask),然后用该掩码筛选张量元素。
  • 返回结果是一维张量,顺序按内存布局排列。
  • 常用于过滤满足特定条件的数据(如大于某值、非零等)。
2.1.9.5 多维索引
import torch

#固定随机性
torch.manual_seed(3)
# 创建一个 3D 张量:(batch=2, height=3, width=4)
x = torch.randn(2, 3, 4)

print(x)
print("3D 张量 shape:", x.shape)  # torch.Size([2, 3, 4])

# 获取第一个 batch 的第二行第三列
print("x[0, 1, 2]:", x[0, 1, 2])

# 获取所有 batch 的第一行所有列
print("第一行所有列:", x[:, 0, :])  # shape: (2, 4)

# 使用高级索引选择特定 batch 和 row
batch_indices = torch.tensor([0, 1])  # 选第0和第1个 batch
row_indices = torch.tensor([1, 2])    # 选第1和第2行
result = x[batch_indices, row_indices, :]  # shape: (2, 4)
print("指定 batch 和 row:", result.shape)  # torch.Size([2, 4])

✅ 小结:

  • 对于三维及以上张量,可以通过多个索引组合访问元素。
  • 支持混合使用整数、切片、列表和布尔索引。
  • 高级索引(Advanced Indexing)可用于跨维度选择。
2.1.9.6 总结
索引类型适用场景推荐方式
行列索引精确访问单个元素x[i, j]
列表索引非连续选择多行/列x[[0,2], :]
范围索引连续数据块提取x[1:3, :]
布尔索引条件过滤(如 x > 0)x[x > 0]
多维索引高维张量(如 batch, channel)x[b, h, w] 或高级索引

2.1.10 张量形状操作

2.1.10.1 reshape
import torch

x = torch.tensor([[1, 2], [3, 4]])  # shape: (2, 2)
print("原张量:")
print(x)

# 改为一维向量
y = x.reshape(-1)  # -1 表示自动计算该维度大小
print("reshape(-1):")
print(y)  # tensor([1, 2, 3, 4])

# 改为 (4, 1)
z = x.reshape(4, 1)
print("reshape(4, 1):")
print(z)
# 输出:
# tensor([[1],
#         [2],
#         [3],
#         [4]])

✅ 小结:

  • torch.reshape() 或 .reshape():改变张量的形状,但不改变数据顺序。
  • 要求总元素数量不变(即 new_shape.prod() == original_shape.prod())。
  • 支持 -1 自动推断某一维度大小。
  • 不保证返回的是连续内存布局(非 contiguous),后续某些操作可能报错。
2.1.10.2 squeeze
x = torch.randn(1, 3, 1, 4)  # shape: (1, 3, 1, 4)

print("原 shape:", x.shape)  # torch.Size([1, 3, 1, 4])

# 移除所有大小为1的维度
y = x.squeeze()
print("squeeze():", y.shape)  # torch.Size([3, 4])

# 只移除第0维
z = x.squeeze(0)
print("squeeze(0):", z.shape)  # torch.Size([3, 1, 4])

✅ 小结:

  • torch.squeeze() 或 .squeeze():移除大小为 1 的维度。
  • 可指定特定维度进行压缩,否则移除所有大小为 1 的维度。
  • 常用于去除 batch size 为 1 的维度,简化模型输入输出。
2.1.10.3 unsqueeze
x = torch.tensor([1, 2, 3])  # shape: (3,)
print("原 shape:", x.shape)  # torch.Size([3])

# 在第0维添加一个维度 → (1, 3)
y = x.unsqueeze(0)
print("unsqueeze(0):", y.shape)  # torch.Size([1, 3])

# 在第1维添加一个维度 → (3, 1)
z = x.unsqueeze(1)
print("unsqueeze(1):", z.shape)  # torch.Size([3, 1])

✅ 小结:

  • torch.unsqueeze() 或 .unsqueeze():在指定维度插入一个大小为 1 的新维度。
  • 常用于将标量或低维张量扩展为高维张量,以便与其它张量进行广播运算。
  • 例如:将 (3,) 变为 (1, 3) 以匹配 (batch, 3) 的输入。
2.1.10.4 transpose
import torch

x = torch.tensor([[1, 2], [3, 4]])  # shape: (2, 2)
print("原张量:")
print(x)

# 交换第0维和第1维(即转置)
y = x.transpose(0, 1)
print("transpose(0, 1):")
print(y)
# 输出:
# tensor([[1, 3],
#         [2, 4]])

# 对三维张量操作
z = torch.randn(2, 3, 4)
w = z.transpose(1, 2)  # 交换第1维和第2维
print("w shape:", w.shape)  # torch.Size([2, 4, 3])

✅ 小结:

  • torch.transpose() 或 .transpose():交换两个指定维度的位置。
  • 仅适用于二维张量或指定两个维度的高维张量。
  • 常用于转置矩阵(如权重矩阵)或调整 channel 与 height/width 的顺序。
2.1.10.5 permute
import torch

x = torch.randn(2, 3, 4)  # shape: (batch, channel, height)
print("原 shape:", x.shape)  # torch.Size([2, 3, 4])

# 将维度顺序改为 (channel, batch, height)
y = x.permute(1, 0, 2)
print("permute(1, 0, 2):", y.shape)  # torch.Size([3, 2, 4])

# 也可以写成:
z = x.transpose(0, 1)  # 等价于 permute(1, 0, 2) 当只换两个维度
print("transpose(0,1):", z.shape)  # 同样是 (3, 2, 4)

✅ 小结:

  • torch.permute() 或 .permute():按指定顺序重新排列维度。
  • 接受一个维度索引列表,表示新的维度顺序。
  • 比 transpose 更灵活,支持任意维度置换。
2.1.10.6 view
import torch

x = torch.tensor([[1, 2], [3, 4]])  # shape: (2, 2)
print("原 shape:", x.shape)  # torch.Size([2, 2])

# 成功:直接 reshape
y = x.view(4, 1)
print("view(4, 1):", y.shape)  # torch.Size([4, 1])

# ❌ 错误示例:先 transpose 再 view
z = x.transpose(0, 1)  # shape: (2, 2),但非 contiguous
try:
    w = z.view(4)  # 报错!
except RuntimeError as e:
    print("错误信息:", e)

✅ 小结:

  • torch.view() 或 .view():将张量视为新的形状,要求数据是连续的(contiguous)。
  • 如果张量不是连续的(如经过 transpose 后),调用 view 会报错。
  • 返回的是原张量的视图(view),不复制数据。
2.1.10.7 contiguous
x = torch.randn(2, 3, 4)
print("原是否连续:", x.is_contiguous())  # True

# 经过 transpose 后变为非连续
y = x.transpose(0, 1)
print("transpose 后是否连续:", y.is_contiguous())  # False

# 使用 contiguous 恢复连续性
z = y.contiguous()
print("contiguous 后是否连续:", z.is_contiguous())  # True

# 现在可以安全使用 view
w = z.view(3, 8)
print("view 成功:", w.shape)  # torch.Size([3, 8])

✅ 小结:

  • torch.contiguous() 或 .contiguous():返回一个与原张量相同内容但内存连续的新张量。
  • 当张量经过 transpose、permute 等操作后,内部存储可能不再连续。
  • 通常在使用 view 之前调用,避免运行时错误。
2.1.10.8 总结
操作功能描述是否改变数据是否连续典型用途
reshape改变形状,保持数据顺序不保证展平、重组
squeeze移除大小为1的维度去除冗余 batch 维度
unsqueeze添加大小为1的维度扩展单样本为 batch
transpose交换两个维度不保证矩阵转置、通道重排
permute任意维度重排不保证NCHW ↔ NHWC 格式转换
view创建新视图(需连续)需要数据重组(如展平)
contiguous确保内存连续,生成副本是(复制)修复非连续张量,准备 view

2.1.11张量拼接操作

2.1.11.1 cat
import torch

# 定义两个形状相同的张量
x = torch.tensor([[1, 2], [3, 4]])  # shape: (2, 2)
y = torch.tensor([[5, 6], [7, 8]])  # shape: (2, 2)

# 沿第0维(行)拼接 → 变为 (4, 2)
z1 = torch.cat([x, y], dim=0)
print("cat(dim=0):")
print(z1)
# 输出:
# tensor([[1, 2],
#         [3, 4],
#         [5, 6],
#         [7, 8]])

# 沿第1维(列)拼接 → 变为 (2, 4)
z2 = torch.cat([x, y], dim=1)
print("cat(dim=1):")
print(z2)
# 输出:
# tensor([[1, 2, 5, 6],
#         [3, 4, 7, 8]])

✅ 小结:

  • torch.cat() 或 .cat():沿指定维度将多个张量连接起来。
  • 要求所有张量在除拼接维度外的其他维度大小必须一致。
  • 不会增加新维度,只是“拉长”原有维度。
  • 常用于批量合并样本或特征通道。
2.1.11.2 stack
x = torch.tensor([1, 2])  # shape: (2,)
y = torch.tensor([3, 4])  # shape: (2,)

# 使用 stack 沿新维度堆叠 → shape: (2, 2)
z = torch.stack([x, y])
print("stack():")
print(z)
# 输出:
# tensor([[1, 2],
#         [3, 4]])

# 可以指定堆叠维度(默认是0)
z2 = torch.stack([x, y], dim=1)  # shape: (2, 2)
print("stack(dim=1):")
print(z2)
# 输出:
# tensor([[1, 3],
#         [2, 4]])

✅ 小结:

  • torch.stack() 或 .stack():将多个张量沿新维度堆叠。
  • 所有输入张量必须具有完全相同的形状。
  • 会在输出张量中增加一个新维度,其大小等于输入张量的数量。
  • 常用于将单个样本扩展为 batch,或将多个结果封装为一个序列。
2.1.11.3 总结
操作是否增加维度输入要求典型用途
cat除拼接维度外,其余维度大小一致批量合并、特征融合
stack所有张量形状完全相同构建 batch、时间序列

2.1.12 自动微分模块

训练神经网络时,框架会根据设计好的模型构建一个计算图,来跟踪计算是哪些数据通过哪些操作组合起来产生的输出,并且通过反向传播机制来根据给定参数的损失函数的梯度来调整参数

2.1.12.1 计算图

在这里插入图片描述
计算图描述的是从左到右的前向传播(从左到右)过程。在前向传播的终点,我们得到了损失值(loss)。随后,自动微分模块会基于该计算图执行反向传播(从右到左),利用链式法则自动计算每个参数(如 w 和 b)对损失的梯度。这些梯度将被用于后续的优化步骤,比如更新参数以减小损失。
x:特征
w:权重
b:偏置
z:预测值
y:真实值
MSE:损失函数
loss:损失值
梯度下降法:w新 = w旧 - 学习率 × 梯度

2.1.12.2 案例1
import torch

x = torch.tensor(10.0)
y = torch.tensor(3.0)
# y = torch.tensor([[3.0]])

print(x)
print(y)

w = torch.rand(1,1,requires_grad=True)
b = torch.rand(1,1,requires_grad=True)

# 前向传播得到输出值
z = w * x + b
print(z)

# 叶子节点查看API
print(x.is_leaf) # T
print(y.is_leaf) # T
print(w.is_leaf) # T
print(b.is_leaf) # T
print(z.is_leaf) # F

# 设置损失函数
# nn是Neural Network的缩写
loss_fn = torch.nn.MSELoss()
loss_val = loss_fn(z,y)
print(loss_val)
print(loss_val.is_leaf)

# 反向传播
# 找到最右边的根节点loss,然后调用反向传播API,后面就会自动计算叶子节点了
# 不是谁都可以调,必须是一个标量/张量才可以,矩阵、向量这些就不行,损失值的特点就是一个张量类型的标量
loss_val.backward()

# 查看梯度
print(w.grad)
print(b.grad)
"""
中途会弹出UserWarning
    这里的解释是y是一个张量,而z是一个1*1矩阵,它们两进行计算的时候会维度不匹配
    可以在定义y的时候y = torch.tensor([[3.0]])
    也将他变为1*1的矩阵
"""
2.1.12.3 案例小结

1)loss_val.backward()是反向传播的API


2)梯度下降法求最优解requires_grad=True表示计算梯度,一定要开启,否则会将梯度认为是一个常数,不予更新。而这个值如果不填写,它默认就是False。


3)当你在进行计算时,每个结果都会记住它是怎么来的——这是通过一个叫grad_fn的东西实现的。grad_fn就像是一个记忆器,记录了当前操作,图由多个grad_fn链接而成。当你需要计算梯度进行反向传播时,PyTorch会查看这些“记忆器”,顺着它们记录的信息一步一步往回走,以此来计算每个参数对最终损失值的影响(也就是梯度)。这就像是沿着你留下的脚印往回走,找到起点在哪里一样。这样,PyTorch就能知道每个参数应该如何调整才能让损失变得更小。


4)计算图中很容易看得出x、w、b、y这些是叶子结点,叶子节点的数据并非由计算生成,因此它是整个计算图的基石,叶子节点不可以进行in-place操作(就地操作),之前典型的就地操作比如 add_(), sub_(), mul_(), div_(), neg_() 这些API就属于就地操作,它表示的是直接对打点调用的目标进行值覆盖.我们通过is_leaf这个API来查看当前节点是否是叶子节点.对于 requires_grad=True 的叶子节点,PyTorch 会在反向传播后自动将其梯度保存在 .grad 属性中。而中间节点z则需要调整一个参数retain_grad=True来告诉框架你需要记录它

⚠️ 为什么就地操作在自动微分中要小心?
因为 就地操作会覆盖原始数据,而 PyTorch 的 autograd 在反向传播时可能需要这些原始值来计算梯度!


2.1.13 pytorch构建微分模型(实操)

在这里插入图片描述

2.1.13.1 案例2
import torch

"""
演示自动微分模块循环实现计算梯度,来更新模型的参数w
求 y = x**2 + 20 的极小值点 并打印y是最小值时 w的值(梯度)
    # 1 定义点 x=10 requires_grad=True  dtype=torch.float32
    # 2 定义函数 y = w**2 + 20
    # 3 利用梯度下降法 循环迭代100 求最优解
        # 3-1 正向计算(前向传播)
        # 3-2 梯度清零 w.grad.zero_()
        # 3-3 反向传播
        # 3-4 梯度更新参数 w.data = w.data - 0.01 * w.grad
"""

# 1 定义点 x=10 requires_grad=True  dtype=torch.float32
w = torch.tensor([10],requires_grad=True,dtype=torch.float32)
print(f"w:{w}")
# 2 定义函数 y = w**2 + 20
loss = w**2 + 20

# 3 利用梯度下降法 循环迭代100 求最优解
print(f"初始权重w:{w}")
print(f"(- 0.01 * w.grad):{w.grad}")
print(f"loss初始值:{loss}")
for i in range(100):
    # 3-1 正向计算(前向传播)
    loss = w**2 + 20  # 对w求偏导
    # 3-2 梯度清零 w.grad.zero_()
    if w.grad is not None:
        w.grad.zero_()
    # 3-3 反向传播
    loss.mean().backward()# mean的作用是确保张量转换为标量,实现梯度的归一化,使得训练更加稳定。其实根本原因是.backward()只能对标量进行使用
    # 3-4 梯度更新参数 w.data = w.data - 0.01 * w.grad
    w.data = w.data - 0.01 * w.grad
    print(f"w.grad:{w.grad}")
    print(f"第{i+1}次迭代, 权重w: {w}, (0.01 * w.grad): {(0.01 * w.grad).data}, loss.mean(): {loss.mean().data}")

    # 打印最终的结果,权重w,损失值loss 20,梯度 w.grad 0
    print(f"最终结果, w: {w.data}, 梯度 w.grad: {w.grad}, loss.mean(): {loss.mean().data}")

补充点:
梯度归零
detach()

更多推荐