张量(Tensor)
1. 张量是什么
1.1 本质
张量在数据本身上可以理解为数组:一个可以存在于任意维度(0 到 N 维)的数组。
如果只看"长得像什么",张量和数组很像;但在深度学习框架里,张量通常还带有更强的计算属性:
- 可以在 GPU 等硬件上高效并行计算;
- 可以记录计算图(computational graph) ,并支持自动微分(自动求梯度)。
可以把张量记成一个"超级数组":不仅能装数据,还能高效计算、并自动参与求导。
计算图 可先宏观理解为:把"张量是如何一步步算出来的"记录成一张有向依赖图,后续
backward()可沿这张图自动反向求梯度。
1.2 PyTorch 与 NumPy 的关系
| 库 | 定位 |
|---|---|
NumPy |
通用数值计算(主要在 CPU) |
PyTorch |
面向深度学习的张量框架(CPU/GPU + 自动求导) |
- NumPy 是数学计算器;PyTorch 是带 GPU 和自动求导的数学计算器。
- 张量数据操作概念用
NumPy同样可以练习;进入模型训练后建议统一使用PyTorch,避免类型来回转换。
python
import numpy as np
import torch
A_np = np.array([[1, 2], [3, 4]], dtype=np.float32)
B_t = torch.tensor([[5, 6], [7, 8]], dtype=torch.float32)
# A_np @ B_t # TypeError: 类型不同,不能直接混算
C = torch.tensor(A_np) @ B_t # 先统一类型
D = A_np @ B_t.numpy()
2. 维度与张量阶
2.1 维度(阶 / ndim)
这里说的"维度 "是指张量的阶 (ndim),即轴的数量,不是数学里"向量有几个分量"的那个维度。
| 阶 | 名称 | 形状示例 | 典型场景 |
|---|---|---|---|
| 0 | 标量 | () |
损失值、单个数值 |
| 1 | 向量 | (n,) |
一条样本特征 |
| 2 | 矩阵 | (m, n) |
表格数据(样本×特征)、灰度图 |
| 3 | 三维张量 | (c, h, w) 或 (h, w, c) |
单张 RGB 图 |
| 4 | 四维张量 | (b, c, h, w) 或 (b, h, w, c) |
批次图像 |
补充说明:
- 4 维图像:把多张 3 维图片沿
batch轴堆叠后的结果。 - 表格/结构化数据通常只用 2 维:
[样本数, 特征数]。 - 常见约定:
PyTorch多用[b, c, h, w],TensorFlow多用[b, h, w, c]。b= batch(批次大小)、c= channel(通道数)、h= height、w= width。- 例:32 张 RGB 图片、每张 224×224,在 PyTorch 中常写为
[32, 3, 224, 224]。 - 轴顺序弄错会导致卷积输入维度不匹配或结果错位。
2.2 轴、形状与常用属性
- 轴(axis / dim) :描述数据延伸的方向,编号从
0开始。 - 张量维度(阶 / ndim):张量拥有的轴的总数。
- 长度:某一个具体轴上元素的个数。
- 形状(shape):将所有轴的长度按顺序组合成的元组。
以 shape=(3, 4) 为例:
- 包含 2 个数字 → 2 维张量(有 2 个轴)
- 第 0 个数字是 3 → 第 0 轴(行方向)长度为 3
- 第 1 个数字是 4 → 第 1 轴(列方向)长度为 4
| 类型 | ndim |
shape |
len() 行为 |
|---|---|---|---|
| 标量张量 | 0 |
torch.Size([]) |
报错(无轴) |
| 向量张量 | 1 |
torch.Size([n]) |
返回 n |
| 矩阵张量 | 2 |
torch.Size([m, n]) |
返回 m(第 0 轴长度) |
常用属性与方法:
| 属性/方法 | 含义 |
|---|---|
ndim / dim() / ndimension() |
轴数(阶) |
shape / size() |
每个轴的长度 |
len() |
第 0 轴的长度 |
numel() |
元素总数(number of elements) |
2.3 数学语境 vs 张量语境(易混淆)
python
v = torch.tensor([1, 2, 3])
- 数学上:3 维向量(内部含有 3 个分量)
- 张量上 :1 维张量(只有 1 个轴,
shape=(3,))
1 维张量不区分"行/列" :shape=(3,) 既不是行向量也不是列向量。只有升维为 2 维时,行列方向才明确:
- 行向量:
shape=(1, 3)(1 行 3 列) - 列向量:
shape=(3, 1)(3 行 1 列)
python
import torch
# 标量张量
s = torch.tensor(3)
print(s.ndim, s.shape, s.numel()) # 0, torch.Size([]), 1
# print(len(s)) # TypeError: 0 维张量无轴
# 向量张量
a = torch.arange(12)
print(a.ndim, a.size(), len(a), a.numel()) # 1, [12], 12, 12
# 矩阵张量
x = torch.ones((3, 4))
print(x.ndim, x.shape, len(x), x.numel()) # 2, [3,4], 3, 12
3. 线性代数视角下的基本对象
在 PyTorch 中,标量、向量、矩阵通常都由 Tensor 类型承载:
| 数学对象 | 数学示例 | 张量示例 | 阶 |
|---|---|---|---|
| 标量 | 3.0 |
torch.tensor(3.0) |
0 |
| 向量 | [1, 2, 3] |
torch.tensor([1., 2., 3.]) |
1 |
| 矩阵 | [[1,2],[3,4]] |
torch.tensor([[1.,2.],[3.,4.]]) |
2 |
| 张量 | 更高维数组 | 3 维及以上 | ≥3 |
记法速记:
- 标量 变量:普通小写字母(如
x、y) - 向量 :粗体小写(如 x∈Rn\mathbf{x} \in \mathbb{R}^nx∈Rn)
- 矩阵 :粗体大写(如 A∈Rm×n\mathbf{A} \in \mathbb{R}^{m \times n}A∈Rm×n)
4. 创建张量
4.1 常见创建方式
| 类别 | API | 说明 |
|---|---|---|
| 从数据 | torch.tensor() |
从列表/数组创建 |
| 顺序/区间 | arange(start, end, step) |
范围 [start, end),默认 int64 |
linspace(start, end, steps) |
等间距,范围 [start, end],默认 float32 |
|
| 指定形状 | zeros / ones / eye |
全 0、全 1、单位矩阵 |
| 随机 | randn |
标准正态分布(参数初始化常用) |
rand |
[0, 1) 均匀分布 | |
randint(low, high, size) |
整数均匀,范围 [low, high) |
|
| 特殊 | full(size, value) |
全填充某值 |
empty(size) |
未初始化(值不确定!) | |
| 复制 | clone() |
深拷贝 |
函数名速记:arange = array range · linspace = linear space · randn = random normal · reshape = re-shape
4.2 示例
python
import torch
# 从数据创建
s = torch.tensor(3.0) # 标量
a = torch.tensor([1.0, 2.0, 3.0]) # 向量
m = torch.tensor([[1., 2., 3.], [4., 5., 6.]]) # 矩阵
# 顺序创建
torch.arange(6) # [0,1,2,3,4,5],int64
torch.arange(0, 1, 0.2) # 不含 end=1.0,float32
torch.linspace(0, 1, 5) # 含 end=1.0,共 5 个点
# 指定形状
torch.zeros(3, 3)
torch.ones(3, 3)
torch.eye(3)
# 随机
torch.randn(10) # 标准正态
torch.rand(10) # [0,1) 均匀
torch.randint(10, 20, (10,)) # [10,20) 整数
# 特殊初始化
f = torch.full((2, 3), 3.0)
e = torch.empty((2, 3)) # 值不确定
e.fill_(3.0) # 原地填充
# 复制
c = f.clone()
torch.allclose(f, c) # True
默认设备通常是 CPU(除非显式放到 GPU)。
5. 形状变换
reshape 只改"看待方式",不改元素值与元素总数。
- 可以用
-1自动推断某一维:x.reshape(-1, 4)或x.reshape(3, -1) - 实战心法:改形状前先确认总元素数一致
python
import torch
X = torch.arange(12)
X1 = X.reshape(3, 4) # (3, 4)
X2 = X.reshape(-1, 4) # 自动推断为 (3, 4)
X3 = X.reshape(3, -1) # 自动推断为 (3, 4)
6. 按元素运算与聚合
6.1 按元素运算
同形状张量可直接做按元素运算:
| 运算 | 符号 |
|---|---|
| 加 / 减 / 乘 / 除 | + - * / |
| 求幂 | ** |
| 逻辑比较 | == > 等 |
python
import torch
a = torch.tensor([[1, 2], [3, 4]])
b = torch.tensor([[10, 20], [30, 40]])
print(a + b) # 按元素加法
print(a ** 2) # 按元素幂
print(a == b) # 按元素比较
print(a.sum()) # 聚合为标量 tensor(10)
6.2 张量连结(cat)
torch.cat(concatenate 简写)沿指定轴拼接多个张量:
| 参数 | 效果 | 例:两个 (2, 3) |
|---|---|---|
dim=0 |
按行方向拼接(样本数增加) | → (4, 3) |
dim=1 |
按列方向拼接(特征数增加) | → (2, 6) |
python
c0 = torch.cat([a, b], dim=0) # (4, 2)
c1 = torch.cat([a, b], dim=1) # (2, 4)
7. 广播机制(Broadcasting)
当张量形状不同但"可对齐"时,框架会自动扩展维度为 1 的轴,再做按元素运算。
对齐规则 :从最后一维往前看,每一维要么相等,要么其中一个为 1。
- 1 维向量
(n,)在与矩阵(m, n)运算时,自动视为行向量(1, n)。 - 广播是"虚拟扩展",不一定真实拷贝内存。
经典例子:
a形状(3, 1),b形状(1, 2)→a + b结果(3, 2)
快检口诀:从右往左看,维度要么相等,要么有 1;否则报错。
python
import torch
a = torch.arange(3).reshape(3, 1) # (3, 1)
b = torch.arange(2).reshape(1, 2) # (1, 2)
c = a + b # (3, 2)
# tensor([[0,1],[1,2],[2,3]])
反例(会报错):
python
x = torch.ones((3, 2)) # 3 行 2 列
# y = torch.ones((3,)) # 1 维 3 元素,末维 2 vs 3 不匹配 → RuntimeError
m = torch.ones((2,)) # 可视为 (1, 2),可以与 x 相加
n = x + m
8. 索引、切片与赋值
8.1 规则
- 索引从
0开始,负索引从末尾反向计数(-1表示最后一个)。 - 切片语法:
start:end:stepstart:起始下标(包含)end:结束下标(不包含)step:步长(默认1)
:表示该轴"全选"。
8.2 常用模板
| 写法 | 含义 |
|---|---|
X[r, c] |
取单个元素 |
X[r1:r2, c1:c2] |
取子矩阵 |
X[:, c] |
取某一列 |
X[r, :] 或 X[r] |
取某一行 |
X[::2, :] |
每隔一行取一次 |
赋值规则:左侧切片选中的区域形状,需要与右侧值可对齐(相同形状或可广播)。
python
import torch
X = torch.arange(12).reshape(3, 4)
# [[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11]]
print(X[-1]) # 最后一行
print(X[1:3]) # 第 2~3 行
print(X[:, 1]) # 所有行的第 2 列
print(X[0:3:2, :]) # 第 1 行和第 3 行
X[1, 2] = 9 # 单点赋值
X[0:2, :] = 12 # 区域赋值
9. 维度坍塌:求和与均值
维度坍塌(Dimensionality Collapse):通过聚合运算(求和、求平均等)减少张量维度数量的过程。沿某些轴运算时,这些轴会被"压缩"掉。
| 类型 | 说明 | 示例 |
|---|---|---|
| 完全坍塌 | 所有维度压缩为 0 维标量 | A.sum()、A.mean() |
| 部分坍塌 | 只压缩指定轴(按轴降维) | A.sum(dim=0) |
| 防止坍塌 | 保留被压缩轴(长度为 1) | A.sum(dim=0, keepdim=True) |
python
import torch
# shape: (2, 3)
A = torch.arange(6, dtype=torch.float32).reshape(2, 3)
# 完全坍塌
print(A.sum()) # tensor(15.),标量
print(A.mean()) # tensor(2.5000)
# 部分坍塌
print(A.sum(dim=0), A.sum(dim=0).shape) # [3., 5., 7.] → (3,)
print(A.sum(dim=1), A.sum(dim=1).shape) # [3., 12.] → (2,)
# 防止坍塌
print(A.sum(dim=0, keepdim=True).shape) # (1, 3)
print(A.sum(dim=1, keepdim=True).shape) # (2, 1)
三维示例(shape: [学生, 科目, 考试次数]):
python
scores = torch.tensor([
[[70, 75, 80, 85], [80, 82, 84, 86]], # 学生 0
[[60, 65, 70, 75], [90, 91, 92, 93]], # 学生 1
[[85, 87, 89, 91], [75, 78, 81, 84]] # 学生 2
], dtype=torch.float32)
print(scores.sum(dim=0).shape) # 按学生轴降维 → (2, 4)
print(scores.mean(dim=0).shape) # 同上
sum(dim=...)后维度消失,可能导致后续广播失败------可用keepdim=True保留轴。
10. 内存与类型转换
10.1 原地操作(高频坑点)
| 写法 | 行为 |
|---|---|
Y = Y + X |
通常新分配内存(对象地址变了) |
Y[:] = Y + X 或 Y += X |
原地更新(更省内存,训练中更常见) |
python
import torch
X = torch.ones((2, 2))
Y = torch.ones((2, 2))
before = id(Y)
Y += X
print(id(Y) == before) # True(原地更新)
# Y = Y + X
# print(id(Y) == before) # False(新对象)
10.2 与 NumPy / Python 标量互转
python
import torch
X = torch.tensor([1.0, 2.0, 3.0])
A = X.numpy() # Tensor → NumPy
B = torch.tensor(A) # NumPy → Tensor
s = X[0].item() # 单元素张量 → Python float
注意:PyTorch 与 NumPy 在部分场景下共享底层内存,原地改动可能连带修改另一个对象。
11. 易错点速查
| 易错点 | 正确理解 |
|---|---|
| 混淆"向量长度"与"张量阶数" | [1,2,3] 数学上是 3 维向量,张量上是 1 维张量 |
| 1 维向量当行/列向量 | 需 reshape(1,n) 或 reshape(n,1) 明确方向 |
| 广播末维不匹配 | (3,2) 与 (3,) 不能直接按元素运算 |
sum(dim=...) 后广播失败 |
使用 keepdim=True |
Y = Y + X 频繁新分配 |
训练中优先 Y += X 或 Y[:] = ... |
| NumPy 与 PyTorch 混算 | 先 torch.tensor() 或 .numpy() 统一类型 |
| 图像轴顺序 | PyTorch [b,c,h,w] vs TensorFlow [b,h,w,c] |
12. 小结
张量 = 能装数据、能在 GPU 上算、能自动求导的 N 维数组。
看懂
shape、会用reshape和切片、理解广播与维度坍塌,是后续模型训练的基本功。