深度学习中的张量(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
  2. 如果某维度大小为1,则沿该维度复制扩展
  3. 如果某维度大小不同且都不为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 += 1requires_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. 参考资源


总结:张量是深度学习的"通用语言"。理解张量的形状变换、广播机制、内存模型和自动微分,是高效编写深度学习代码的基础。重点掌握:reshape/permute 的维度思维广播的本质是隐式expandview与clone在内存上的区别、以及autograd计算图的生命周期

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐