02.Tensor 张量
# 01.Tensor(张量)
# 0)概述
# 1)Tensor是什么
Tensor 本质上就是多维数组,它在不同场景下的语义取决于上下文和解释方式
- 1D Tensor(一维):类似于一个向量或列表
- 2D Tensor(二维):类似于一个表格或矩阵
- 3D Tensor(三维):类似于多个表格堆叠在一起
你可以把 Tensor 理解为数据的载体,至于这些数据代表什么,完全取决于你的具体应用场景
同一个 Tensor 可能表示不同的数据
- 例如,一个形状为
(3, 3)
的 Tensor - 在图像处理中,它可能表示 灰度图像的像素值(3x3 的图像)
- 在物理计算中,它可能是 某个网格的温度分布
- 在机器学习模型中,它可能是 一个小批量的数据样本
- 例如,一个形状为
# 2)Tensor作用
1D Tensor(一维张量)
- 适用于一维数据,如时间序列、文本序列、特征向量等
- 自然语言处理(NLP):表示一个单词的嵌入向量
- 时间序列分析:表示一段时间内的数据点
2D Tensor(二维张量)
- 图像处理:表示灰度图像(高度 × 宽度)
- 特征矩阵:表示批量样本的特征数据(样本数 × 特征数)
3D Tensor(三维张量)
- 图像处理:表示彩色图像(通道数 × 高度 × 宽度)
- 自然语言处理:表示批量文本序列(批量大小 × 序列长度 × 词向量维度)
4D Tensor(四维张量)
- 计算机视觉:表示一批图像(批量大小 × 通道数 × 高度 × 宽度)
- 视频处理:表示单帧视频数据(批量大小 × 通道数 × 高度 × 宽度)
4D Tensor 通常用于表示图像数据(
批量大小 × 通道数 × 高度 × 宽度
)例如,在计算机视觉任务中,一个 4D Tensor 可以表示一批图像
- 这个 4D Tensor 可以理解为 2 张 3 通道的 4x4 图
批量大小(2)
:表示有 2 张图像通道数(3)
:每张图像有 3 个通道(例如 RGB)高度和宽度(4, 4)
:每张图像的分辨率是 4x4 像素
import torch
x = torch.randn(2, 3, 4, 4)
1
2
2
# 1、张量操作
# 1)张量创建
import torch
import numpy as np
# 从数据创建
a = torch.tensor([1, 2, 3]) # 从列表创建
b = torch.tensor(np.array([4, 5])) # 从 NumPy 数组创建
# 特殊初始化
c = torch.rand(3, 3) # 均匀分布随机数 [0,1)
d = torch.randn(3, 3) # 标准正态分布随机数
e = torch.eye(3) # 3x3 单位矩阵
f = torch.zeros_like(e) # 创建与 e 形状相同的全零张量
# 序列生成
g = torch.arange(0, 10, 2) # [0, 2, 4, 6, 8]
h = torch.linspace(0, 1, 5) # [0.0, 0.25, 0.5, 0.75, 1.0]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 2)张量形状
import torch
x = torch.rand(2, 3, 4)
# 形状变换
y = x.view(2, 12) # 改变形状(总元素数需一致)
z = x.reshape(6, 4) # 自动推断维度
w = x.permute(2, 0, 1) # 维度重排(原维度顺序为0,1,2 → 新顺序为2,0,1)
# 压缩与扩展维度
squeezed = x.squeeze(1) # 移除大小为1的维度
expanded = x.unsqueeze(0) # 添加新维度
# 拼接与分割
cat = torch.cat([x, x], dim=0) # 沿维度0拼接
stack = torch.stack([x, x], dim=0) # 创建新维度拼接
chunks = torch.chunk(x, 2, dim=1) # 沿维度1均等切分
split = torch.split(x, 1, dim=2) # 按指定大小切分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 3)索引与切片
x = torch.rand(5, 3, 3)
# 基础索引
a = x[0, :, 1:3] # 第一维取0,第二维全取,第三维取1到2
# 高级索引
mask = x > 0.5 # 布尔掩码
y = x[mask] # 筛选出大于0.5的元素
indices = torch.tensor([0, 2])
z = x[indices, :, :] # 使用张量索引第一维
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 4)数学运算
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = torch.tensor([4.0, 5.0, 6.0])
# 逐元素运算
add = x + y # 或 torch.add(x, y)
mul = x * y # 或 torch.mul(x, y)
pow = x ** 2 # 平方
sqrt = torch.sqrt(x) # 平方根
# 矩阵运算
mat_a = torch.rand(2, 3)
mat_b = torch.rand(3, 2)
matmul = torch.mm(mat_a, mat_b) # 矩阵乘法(2x2)
bmm = torch.bmm(mat_a.unsqueeze(0), mat_b.unsqueeze(0)) # 批量矩阵乘法
# 约简操作
sum_all = x.sum() # 所有元素求和
sum_dim = x.sum(dim=0) # 沿维度0求和
mean = x.mean() # 均值
max_val, max_idx = x.max(dim=0) # 最大值及其索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 5)广播机制
- 广播机制是一种在不同形状的张量之间进行操作的规则
- 它允许 PyTorch 和 NumPy 在执行逐元素操作时,自动扩展较小的张量以匹配较大张量的形状
a = torch.rand(3, 1) # 形状 (3, 1)
b = torch.rand(1, 4) # 形状 (1, 4)
c = a + b # 广播后形状 (3, 4)
1
2
3
2
3
# 2、自动微分
# 1)计算梯度
- 对 $w$ 求导
w.grad = Loss = (y−5)² = (wx+b−5)² = 2(y−5) * x
y = y=3×2+1=7
w.grad = 2(7−5)⋅2 = 8
import torch
# 变量
x = torch.tensor(2.0, requires_grad=True)
w = torch.tensor(3.0, requires_grad=True)
b = torch.tensor(1.0, requires_grad=True)
# 计算
y = w * x + b # 线性变换(计算模型预测值)
# 5:真实值 y:模型预测值
loss = (y - 5) ** 2 # 假设损失是均方误差 (y=3×2+1=7 )
print(loss) # Loss = (7−5)**2 = 4
loss.backward() # 计算梯度
# 输出梯度
print(w.grad) # 计算 d(loss)/d(w) = 8
print(b.grad) # 计算 d(loss)/d(b) = 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 最简单例子
x = torch.tensor(2.0, requires_grad=True)
y = x ** 2 + 3 * x
y.backward() # 计算梯度
print(x.grad) # dy/dx = 2x + 3 → 7.0
1
2
3
4
2
3
4
# 2)梯度控制
# 禁用梯度跟踪
with torch.no_grad():
y = x * 2 # 此操作不会记录梯度
# 手动梯度清零
optimizer.zero_grad() # 训练循环中常用
1
2
3
4
5
6
2
3
4
5
6
# 3)自定义梯度函数
torch.autograd.Function
- 每一个原始的自动求导运算实际上是两个在Tensor上运行的函数
forward(前向传播)
- 计算从输入Tensors获得的输出Tensors
backward(反向传播)
- 接收输出Tensors对于某个标量值的梯度
- 并且
计算输入Tensors相对于该相同标量值的梯度
import torch
class line(torch.autograd.Function):
@staticmethod
def forward(ctx, w, x, b):
# 保存输入张量以供 backward 使用,避免不必要的计算和内存占用
ctx.save_for_backward(w, x, b)
return w * x + b # y = w*x +b
@staticmethod
def backward(ctx, grad_out):
w, x, b = ctx.saved_tensors # 取出 forward 过程中保存的 w, x, b
grad_w = grad_out * x # 对 w 求导:dy/dw = x
grad_x = grad_out * w # 对 x 求导:dy/dx = w
grad_b = grad_out # 对 b 求导:dy/db = 1
return grad_w, grad_x, grad_b
# 随机生成的张量,requires_grad=True 说明这些变量会计算梯度
w = torch.rand(2, 2, requires_grad=True)
x = torch.rand(2, 2, requires_grad=True)
b = torch.rand(2, 2, requires_grad=True)
# 这里 line.apply(w, x, b) 调用了 line 类的 forward() 方法
# 计算 out = w * x + b,并存储 w, x, b 以备反向传播使用
# 并没有打印出来 out[i][j]=w[i][j] × x[i][j] + b[i][j]
out = line.apply(w, x, b)
# 计算 out 对 w, x, b 的梯度,并存储到 w.grad, x.grad, b.grad 中
out.backward(torch.ones(2, 2))
print(w, x, b) # 打印 w, x, b 的值
print(w.grad, x.grad, b.grad) # 打印 w, x, b 的梯度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 3、torch.nn神经网络
# 1)常用层类型
import torch.nn as nn
# 全连接层
fc = nn.Linear(in_features=10, out_features=5)
# 卷积层
conv2d = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1)
# 循环神经网络层
lstm = nn.LSTM(input_size=10, hidden_size=20, num_layers=2)
# 归一化层
bn = nn.BatchNorm2d(num_features=16)
dropout = nn.Dropout(p=0.5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2)损失函数
mse_loss = nn.MSELoss() # 回归任务
ce_loss = nn.CrossEntropyLoss() # 分类任务
bce_loss = nn.BCEWithLogitsLoss() # 二分类(带 Sigmoid)
kl_div = nn.KLDivLoss() # KL 散度
1
2
3
4
2
3
4
# 3)优化器
from torch.optim import SGD, Adam, RMSprop
optimizer = SGD(model.parameters(), lr=0.01, momentum=0.9)
optimizer = Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999))
optimizer = RMSprop(model.parameters(), lr=0.01, alpha=0.99)
1
2
3
4
5
2
3
4
5
# 4)权重初始化
def init_weights(m):
if isinstance(m, nn.Linear):
nn.init.xavier_normal_(m.weight)
nn.init.zeros_(m.bias)
model.apply(init_weights)
1
2
3
4
5
6
2
3
4
5
6
# 4、DataLoader
数据管道
from torch.utils.data import Dataset, DataLoader, random_split
# 自定义数据集
class MyDataset(Dataset):
def __init__(self, data, labels):
self.data = data
self.labels = labels
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
return self.data[idx], self.labels[idx]
# 数据集划分
dataset = MyDataset(torch.rand(100, 3), torch.randint(0, 2, (100,)))
train_set, val_set = random_split(dataset, [80, 20])
# 数据加载器
train_loader = DataLoader(train_set, batch_size=16, shuffle=True, num_workers=4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 5、模型保存与部署
- 保存与加载
# 保存完整模型
torch.save(model, "model.pth")
# 保存状态字典(推荐)
torch.save(model.state_dict(), "model_weights.pth")
# 加载模型
model.load_state_dict(torch.load("model_weights.pth"))
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- ONNX 导出
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(model, dummy_input, "model.onnx", opset_version=11)
1
2
2
上次更新: 2025/3/13 17:01:28