深度学习张量知识总结
·
深度学习中的张量(Tensor)知识总结
1. 什么是张量
张量(Tensor)是深度学习中最基本的数据结构,可以理解为多维数组(multidimensional array)。它是标量、向量、矩阵向更高维度的推广。
| 阶(Rank/Dim) | 数学名称 | 示例 | 形状(Shape) |
|---|---|---|---|
| 0 | 标量(Scalar) | 3.14 |
() |
| 1 | 向量(Vector) | [1, 2, 3] |
(3,) |
| 2 | 矩阵(Matrix) | [[1,2],[3,4]] |
(2, 2) |
| 3 | 3阶张量 | 彩色图像 (C, H, W) | (3, 224, 224) |
| 4 | 4阶张量 | 批量图像 (N, C, H, W) | (32, 3, 224, 224) |
| 5+ | 高阶张量 | 视频数据 (N, T, C, H, W) | (8, 16, 3, 224, 224) |
2. 张量的核心属性
2.1 形状(Shape)
描述张量在每个维度上的大小,是一个整数元组。
import torch
x = torch.randn(2, 3, 4)
print(x.shape) # torch.Size([2, 3, 4])
print(x.ndim) # 3(维度数/阶数)
print(x.size()) # torch.Size([2, 3, 4])
2.2 数据类型(dtype)
常见浮点类型:
| dtype | 精度 | 用途 |
|---|---|---|
float32 / fp32 |
32位 | 默认训练精度 |
float16 / fp16 |
16位 | 混合精度训练 |
bfloat16 / bf16 |
16位 | 与fp32相同指数范围,训练稳定 |
float64 / fp64 |
64位 | 科学计算 |
int8 / int4 |
8位/4位 | 量化推理(QLoRA等) |
2.3 设备(Device)
张量所在的硬件设备:
- CPU:主机内存
- CUDA:NVIDIA GPU(
cuda:0,cuda:1, …) - MPS:Apple Silicon GPU(macOS)
- NPU/TPU:华为昇腾、Google TPU 等
x = torch.tensor([1.0, 2.0])
x_cuda = x.to('cuda') # 移至GPU
x_cpu = x_cuda.cpu() # 移回CPU
x_fp16 = x.half() # 转为fp16
2.4 内存连续性(Contiguity)
连续存储的张量在内存中按行优先(row-major / C-order)排列,访问效率更高。
x = torch.randn(3, 4)
print(x.is_contiguous()) # True
x_t = x.t() # 转置后可能不连续
print(x_t.is_contiguous()) # False
x_t = x_t.contiguous() # 强制连续化
3. 张量的基本操作
3.1 创建张量
import torch
import numpy as np
# 从数据创建
torch.tensor([1, 2, 3]) # 从列表
torch.tensor(np.array([1, 2, 3])) # 从NumPy
torch.from_numpy(np.array([1, 2, 3])) # 共享内存(更快)
# 特殊张量
torch.zeros(3, 4) # 全零
torch.ones(3, 4) # 全一
torch.eye(3) # 单位矩阵
torch.randn(3, 4) # 标准正态分布
torch.rand(3, 4) # [0, 1) 均匀分布
torch.arange(0, 10, 2) # 等差数列 [0, 2, 4, 6, 8]
torch.linspace(0, 1, 5) # 等间距 [0.0, 0.25, 0.5, 0.75, 1.0]
torch.empty(3, 4) # 未初始化(注意随机值!)
# 类张量创建
torch.zeros_like(x) # 形状与x相同的全零张量
torch.ones_like(x)
torch.randn_like(x)
3.2 索引与切片
x = torch.randn(4, 3, 2)
# 基本索引
x[0] # 第0个"样本",shape=(3,2)
x[0, :, :] # 等价写法
x[:, 0] # 所有样本的第0行
x[..., -1] # 最后一个维度取最后一列(省略号展开)
# 高级索引
x[[0, 2]] # 取索引0和2的样本
x[x > 0] # 布尔索引,返回所有正数(展平为1D)
x[torch.tensor([0, 1, 0], dtype=torch.bool)] # bool张量索引
3.3 形状变换
x = torch.randn(2, 3, 4) # shape=(2, 3, 4)
# view vs reshape
x.view(2, 12) # 仅连续张量可用,共享内存(高效)
x.reshape(2, 12) # 自动处理非连续情况(返回拷贝或视图)
x.view(-1, 4) # -1 自动推导 → (6, 4)
# 维度操作
x.unsqueeze(0) # 在位置0增加维度 → (1, 2, 3, 4)
x.unsqueeze(-1) # 在最末增加维度 → (2, 3, 4, 1)
x.squeeze() # 删除所有大小为1的维度
x.permute(2, 0, 1) # 重排维度 → (4, 2, 3)
x.transpose(0, 1) # 交换两维度 → (3, 2, 4)
x.flatten() # 展平为1D
x.flatten(start_dim=1) # 从第1维开始展平 → (2, 12)
# 拼接与堆叠
torch.cat([a, b], dim=0) # 沿dim拼接(其他维度必须相同)
torch.stack([a, b], dim=0) # 新增维度堆叠(所有维度必须相同)
torch.chunk(x, 2, dim=0) # 沿dim均匀切分为2块
torch.split(x, [1, 1], dim=0) # 按指定大小切分
3.4 广播(Broadcasting)
广播使得形状不同的张量可以自动对齐进行运算,无需显式复制数据。
规则(从右向左对齐):
- 如果维度数不同,在较小形状的左侧补1
- 如果某维度大小为1,则沿该维度复制扩展
- 如果某维度大小不同且都不为1,则报错
# 示例:形状(3, 1) + 形状(1, 4) → 形状(3, 4)
a = torch.randn(3, 1) # (3, 1)
b = torch.randn(1, 4) # (1, 4)
c = a + b # (3, 4),自动广播
# 典型应用场景
# 1. 批量归一化:x (N,C,H,W) + mean (C,) → mean自动扩展为(1,C,1,1)
# 2. 注意力掩码:Q@K^T (..., L, L) + mask (L, L) → broadcast
# 3. 偏置项:Wx + b: (out_features,) → (batch, out_features)
4. 张量运算
4.1 逐元素运算
a + b, a - b, a * b, a / b # 逐元素加减乘除
a ** 2 # 逐元素幂
torch.exp(a) # 指数
torch.log(a) # 对数
torch.relu(a) # ReLU激活
torch.sigmoid(a) # Sigmoid
torch.clamp(a, min=-1, max=1) # 截断
4.2 归约运算(Reduction)
x.sum() # 总和
x.sum(dim=0) # 沿第0维求和(消去第0维)
x.sum(dim=0, keepdim=True) # sum后保持维度
x.mean(), x.std(), x.var() # 均值、标准差、方差
x.max(), x.min() # 最大/最小值
x.argmax(dim=-1) # 沿dim的最大值索引
x.topk(3, dim=-1) # 最大的k个值及其索引
4.3 矩阵运算
# 矩阵乘法
a @ b # 等价于 torch.matmul(a, b)
torch.matmul(a, b) # 支持broadcast的矩阵乘
torch.bmm(a, b) # 批量矩阵乘 (b×n×m @ b×m×p → b×n×p)
# 其他线性代数运算
torch.linalg.norm(x) # 范数
torch.linalg.inv(A) # 矩阵求逆
torch.linalg.det(A) # 行列式
torch.linalg.svd(A) # 奇异值分解
torch.linalg.eigh(A) # 对称矩阵特征值分解
torch.linalg.qr(A) # QR分解
4.4 注意力机制相关
# Scaled Dot-Product Attention
Q = torch.randn(B, H, L, D) # (batch, heads, seq_len, head_dim)
K = torch.randn(B, H, L, D)
V = torch.randn(B, H, L, D)
scale = D ** -0.5
attn_scores = Q @ K.transpose(-2, -1) * scale # (B, H, L, L)
attn_weights = torch.softmax(attn_scores, dim=-1)
output = attn_weights @ V # (B, H, L, D)
# 使用内置函数(PyTorch 2.0+)
output = torch.nn.functional.scaled_dot_product_attention(Q, K, V)
5. 自动微分(Autograd)
张量的 requires_grad 属性是PyTorch自动求导的核心:
x = torch.randn(3, requires_grad=True)
y = x ** 2 + 3 * x + 1
loss = y.sum()
loss.backward() # 反向传播
print(x.grad) # dy/dx = 2x + 3
# 梯度清零(训练循环中必须!)
optimizer.zero_grad() # 或 x.grad.zero_()
# 禁用梯度计算(推理/评估时)
with torch.no_grad():
y = model(x)
计算图机制:
- 前向传播时动态构建计算图(Define-by-Run)
backward()沿计算图反向传播梯度- 非叶节点的
.grad默认不保留,中间梯度用完即释放 - 使用
.retain_grad()或torch.set_grad_enabled(True/False)控制
6. 内存管理
6.1 视图(View)与拷贝(Copy)
x = torch.randn(3, 4)
# 视图(共享底层存储,修改视图会影响原张量)
v = x.view(12) # reshape/view(当连续时)
v = x[:, :2] # 切片
v = x.transpose(0, 1) # 转置(非连续视图)
v = x.unsqueeze(0) # 增加维度
# 拷贝(独立存储,互不影响)
c = x.clone() # 深拷贝
c = x.detach() # 断开梯度图,但共享存储
c = x.detach().clone() # 真正独立 + 无梯度
c = x.reshape(12) # 非连续时会返回拷贝
6.2 就地操作(In-place)
以 _ 结尾的方法会直接修改张量,可节省内存:
x.add_(1) # 等价于 x = x + 1
x.zero_() # 清零
x.mul_(2) # 乘2
x.relu_() # 就地ReLU
# ⚠️ 注意:就地操作在autograd中可能引发错误
# 带有requires_grad的叶子节点不能执行就地操作!
6.3 GPU显存管理
# 显存监控
torch.cuda.memory_allocated() # 已分配显存
torch.cuda.memory_reserved() # 已缓存显存
torch.cuda.empty_cache() # 清空PyTorch缓存(不释放还在用的)
# 避免显存泄漏
del x # 删除引用
torch.cuda.empty_cache() # 释放缓存
# 使用torch.cuda.amp混合精度节省显存
with torch.cuda.amp.autocast():
output = model(input) # 自动转为fp16计算
7. 常见深度学习中的张量布局
7.1 图像数据
| 框架 | 布局格式 | 说明 |
|---|---|---|
| PyTorch | (N, C, H, W) |
Channels-First |
| TensorFlow | (N, H, W, C) |
Channels-Last |
| ONNX | (N, C, H, W) |
同PyTorch |
# PyTorch 图像张量
img = torch.randn(32, 3, 224, 224) # batch=32, RGB, 224×224
# 通道转换:Channels-First ↔ Channels-Last
img_nhwc = img.permute(0, 2, 3, 1).contiguous() # (N, H, W, C)
img_nchw = img_nhwc.permute(0, 3, 1, 2).contiguous() # (N, C, H, W)
# 使用 memory_format 优化
img = img.to(memory_format=torch.channels_last) # NHWC内存布局,可能加速
7.2 序列/文本数据
# 序列数据: (N, L, D) — batch, seq_len, hidden_dim
# 或: (L, N, D) — 当 batch_first=False (LSTM/GRU默认)
seq = torch.randn(32, 128, 768) # batch_first=True (Transformer常用)
# 注意力掩码
causal_mask = torch.triu(torch.ones(L, L), diagonal=1).bool() # 上三角
padding_mask = (input_ids != pad_token_id) # padding掩码
7.3 词嵌入(Embedding)
embedding = nn.Embedding(vocab_size=50000, embedding_dim=768)
input_ids = torch.randint(0, 50000, (32, 128)) # (B, L)
embeddings = embedding(input_ids) # (B, L, 768)
7.4 多头注意力中的维度变换
B, L, D = 32, 128, 768 # batch, seq_len, model_dim
num_heads = 12
head_dim = D // num_heads # 64
x = torch.randn(B, L, D)
# 投影并拆分为多头
qkv = x @ W_qkv # (B, L, 3*D)
qkv = qkv.reshape(B, L, 3, num_heads, head_dim) # 拆分
qkv = qkv.permute(2, 0, 3, 1, 4) # (3, B, num_heads, L, head_dim)
Q, K, V = qkv[0], qkv[1], qkv[2] # 各 (B, num_heads, L, head_dim)
# 注意力计算
attn = Q @ K.transpose(-2, -1) / math.sqrt(head_dim) # (B, H, L, L)
attn = attn.softmax(dim=-1)
out = attn @ V # (B, H, L, head_dim)
# 合并多头
out = out.transpose(1, 2).reshape(B, L, D) # (B, L, D)
8. 张量操作实用技巧
8.1 Einstein求和约定(einsum)
# 矩阵乘法
torch.einsum('ij,jk->ik', A, B) # A @ B
# 批量矩阵乘
torch.einsum('bij,bjk->bik', A, B) # torch.bmm
# 外积
torch.einsum('i,j->ij', u, v) # u ⊗ v
# 迹(对角线求和)
torch.einsum('ii->', A) # trace(A)
# 批量点积注意力的得分计算
torch.einsum('bhid,bhjd->bhij', Q, K) # Q @ K^T 的注意力得分
# 通过交换字母即交换维度,极其灵活
torch.einsum('nchw->nhwc', x) # 等价于 x.permute(0, 2, 3, 1)
8.2 高级索引:gather 与 scatter
# gather: 按索引取值
t = torch.tensor([[10, 20], [30, 40]])
idx = torch.tensor([[0, 0], [1, 0]])
torch.gather(t, dim=1, index=idx) # [[10,10],[40,30]]
# scatter: 按索引写入
src = torch.arange(1, 11).reshape(2,5)
idx = torch.tensor([[0,1,2,0,0],[2,0,0,1,2]])
out = torch.zeros(3, 5).scatter(dim=0, index=idx, src=src)
# 常用于分类交叉熵中构造one-hot
# scatter_add_: 按索引累加
out = torch.zeros(3, 5).scatter_add_(dim=0, index=idx, src=src)
8.3 掩码操作
# 布尔掩码
x[x < 0] = 0 # 将负值置零
# masked_fill
mask = torch.tensor([[True, False], [False, True]])
x.masked_fill(mask, float('-inf')) # 对应位置填充为-inf
# masked_select
positive = torch.masked_select(x, x > 0) # 选所有正值(展平返回)
# where(三元运算符)
result = torch.where(x > 0, x, torch.zeros_like(x)) # ReLU的另一种写法
8.4 类型转换与精度控制
# 类型转换
x.float() # → float32
x.half() # → float16
x.bfloat16() # → bfloat16
x.double() # → float64
x.long() # → int64
x.int() # → int32
x.bool() # → bool
# 混合精度训练核心流程
scaler = torch.cuda.amp.GradScaler()
with torch.cuda.amp.autocast():
output = model(inputs) # fp16 前向
loss = criterion(output, target)
scaler.scale(loss).backward() # 放大loss防止梯度下溢
scaler.step(optimizer) # 优化器步进
scaler.update() # 更新scaler因子
9. 张量与NumPy互操作
# NumPy → Tensor
t = torch.from_numpy(np_array) # 共享内存,修改t会影响np_array
t = torch.tensor(np_array) # 独立拷贝
# Tensor → NumPy
np_array = t.numpy() # 仅CPU张量可用,共享内存
np_array = t.cpu().detach().numpy() # GPU张量需先移至CPU
np_array = t.cpu().clone().numpy() # 独立拷贝
# ⚠️ 注意:共享内存时,in-place修改会相互影响
10. 常见陷阱与最佳实践
10.1 常见错误
| 陷阱 | 说明 | 解决 |
|---|---|---|
| 非连续张量报错 | view() 要求张量连续 |
先调用 .contiguous() 或直接用 .reshape() |
| 梯度未清零 | 多次 backward() 会累加梯度 |
每个训练步调用 optimizer.zero_grad() |
| in-place修改叶节点 | x += 1 对 requires_grad=True 的叶节点报错 |
使用 x = x + 1 替代 |
| GPU→CPU类型不匹配 | CUDA张量不能直接转NumPy | 先 .cpu() 再 .numpy() |
| view返回拷贝还是视图? | 取决于连续性,难以预测 | 数据操作用 clone(),形状操作用 reshape() |
| 广播shape不匹配 | 隐式广播失败导致报错 | 用 unsqueeze/expand 显式对齐维度 |
| 内存泄漏 | 中间计算图张量持有梯度 | 推理时使用 torch.no_grad() |
10.2 最佳实践
# ✅ 推荐
x = x.reshape(B, -1, D) # 用 reshape 而非 view
y = torch.where(cond, a, b) # 用 where 而非 if-else
x = x.clone() # 做拷贝时显式调用 clone
image = image.to(memory_format=torch.channels_last) # 性能优化
# ✅ 训练循环范式
for batch in dataloader:
optimizer.zero_grad(set_to_none=True) # True更省显存
with torch.cuda.amp.autocast(): # 混合精度
output = model(batch)
loss = criterion(output, target)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
# ❌ 避免
x.view(-1) # 非连续时崩溃
y = x.numpy() # GPU张量直接转
a += b # 叶节点就地操作
del huge_tensor # 期望但不一定立即释放显存
11. 主流框架中的张量对比
| 特性 | PyTorch | TensorFlow | JAX | NumPy |
|---|---|---|---|---|
| 类名 | torch.Tensor |
tf.Tensor |
jax.numpy.ndarray |
numpy.ndarray |
| 动态/静态图 | 动态(Define-by-Run) | 静态(Graph mode)/ 动态(Eager) | 静态(函数式) | 即时执行 |
| 自动微分 | autograd |
GradientTape |
grad()/vjp() |
无 |
| GPU支持 | .to('cuda') |
自动设备放置 | 显式设备放置 | 无(CuPy可替代) |
| 不可变性 | 可变 | 不可变 | 不可变 | 可变 |
| 编译 | torch.compile |
@tf.function |
jit() (默认理念) |
无 |
12. 参考资源
- PyTorch官方文档 — Tensor
- 张量广播语义 — NumPy Broadcasting
- PyTorch自动微分 — Autograd Mechanics
- 混合精度训练 — NVIDIA Apex / torch.cuda.amp
- JAX文档 — JAX Sharp Bits
总结:张量是深度学习的"通用语言"。理解张量的形状变换、广播机制、内存模型和自动微分,是高效编写深度学习代码的基础。重点掌握:reshape/permute 的维度思维、广播的本质是隐式expand、view与clone在内存上的区别、以及autograd计算图的生命周期。
更多推荐




所有评论(0)