0%

张量

PyTorch 张量完全指南:深度学习的核心数据结构

核心认知:张量(Tensor)是深度学习世界的”通用语言”——无论是图像、文本、语音还是视频,都会被转换为张量进行处理。理解张量的创建、变换和运算,是掌握 PyTorch 的第一步,也是构建神经网络的基础。

张量是 PyTorch 中最基本的数据结构,类似于 NumPy 的 ndarray,但增加了 GPU 加速和自动求导等深度学习特性。本文将系统讲解张量的核心概念、创建方式、维度变换和常用运算。


目录

  1. 什么是张量?
  2. 创建张量
  3. 张量属性
  4. 索引与切片
  5. 维度变换(核心重点)
  6. 张量运算
  7. 设备管理(CPU/GPU)
  8. 广播机制
  9. 实战案例
  10. 总结速查表

什么是张量?

从标量到高阶张量

张量是标量、向量、矩阵的泛化,是 AI 中统一的数据格式。

阶 (Rank) 名称 数学表示 示例 形状 (Shape)
0 标量 (Scalar) 单个数字 5 ()
1 向量 (Vector) 一维数组 [1, 2, 3] (3,)
2 矩阵 (Matrix) 二维表格 [[1, 2], [3, 4]] (2, 2)
3 3阶张量 三维数组 彩色图片 (高, 宽, 通道) (224, 224, 3)
4 4阶张量 四维数组 一批图片 (批次, 高, 宽, 通道) (32, 224, 224, 3)
5 5阶张量 五维数组 视频数据 (批次, 帧, 通道, 高, 宽) (16, 30, 3, 224, 224)

张量的三个核心属性

每个张量都有三个核心属性:

1
2
3
4
5
6
7
import torch

tensor = torch.rand(3, 4)

print(f"形状 (shape): {tensor.shape}") # torch.Size([3, 4])
print(f"数据类型 (dtype): {tensor.dtype}") # torch.float32
print(f"设备 (device): {tensor.device}") # cpu
属性 说明 常见取值
shape 每个维度的大小 (3, 224, 224)
dtype 元素类型 torch.float32, torch.int64, torch.bool
device 存储位置 cpu, cuda:0

创建张量

从已有数据创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import torch
import numpy as np

# 1. 从 Python 列表创建(最常用)
tensor_list = torch.tensor([[1, 2], [3, 4]])
print(tensor_list)
# tensor([[1, 2],
# [3, 4]])

# 2. 从 NumPy 数组创建(共享内存)
np_array = np.array([1, 2, 3])
tensor_numpy = torch.from_numpy(np_array)
print(tensor_numpy) # tensor([1, 2, 3])

# 3. 指定数据类型
tensor_float = torch.tensor([1, 2, 3], dtype=torch.float32)
tensor_int = torch.tensor([1.5, 2.7], dtype=torch.int64) # 小数会被截断为 [1, 2]

创建特殊值的张量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 全零张量
zeros = torch.zeros(2, 3) # shape: (2, 3)
print(zeros)
# tensor([[0., 0., 0.],
# [0., 0., 0.]])

# 全一张量
ones = torch.ones(2, 3) # shape: (2, 3)

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

# 填充指定值
full = torch.full((2, 3), 7) # shape: (2, 3),所有元素为 7

# 空张量(未初始化,值随机,速度快)
empty = torch.empty(2, 3)

创建随机张量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 均匀分布 [0, 1)
rand_uniform = torch.rand(2, 3) # shape: (2, 3)

# 标准正态分布 N(0, 1)
rand_normal = torch.randn(2, 3) # shape: (2, 3)

# 均匀分布 [low, high)
rand_uniform_range = torch.empty(2, 3).uniform_(0, 10)

# 随机整数 [low, high)
rand_int = torch.randint(0, 10, (2, 3)) # shape: (2, 3)

# 正态分布(可指定均值和标准差)
normal = torch.normal(mean=0, std=1, size=(2, 3))

创建序列张量

1
2
3
4
5
6
7
8
9
# arange:类似 Python 的 range
arange_1 = torch.arange(5) # tensor([0, 1, 2, 3, 4])
arange_2 = torch.arange(1, 10, 2) # tensor([1, 3, 5, 7, 9])

# linspace:等间隔
linspace = torch.linspace(0, 1, 5) # tensor([0.0000, 0.2500, 0.5000, 0.7500, 1.0000])

# logspace:对数等间隔
logspace = torch.logspace(0, 2, 5) # tensor([1., 3.16, 10., 31.6, 100.])

与 NumPy 互转

1
2
3
4
5
6
7
8
9
10
11
12
13
# NumPy → Tensor
np_array = np.array([1, 2, 3])
tensor = torch.from_numpy(np_array) # 共享内存

# Tensor → NumPy
tensor = torch.tensor([1, 2, 3])
np_array = tensor.numpy() # 共享内存

# 警告:共享内存意味着修改一个会影响另一个
tensor = torch.tensor([1, 2, 3])
np_array = tensor.numpy()
tensor[0] = 100
print(np_array) # [100, 2, 3] ← 被修改了!

张量属性

查看和修改属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
tensor = torch.rand(3, 4)

# 查看形状
print(tensor.shape) # torch.Size([3, 4])
print(tensor.size()) # torch.Size([3, 4])
print(tensor.numel()) # 12(元素总数)

# 查看数据类型
print(tensor.dtype) # torch.float32

# 修改数据类型
tensor_int = tensor.to(torch.int64)
tensor_float16 = tensor.half() # 转为 float16
tensor_float32 = tensor.float() # 转为 float32

# 查看设备
print(tensor.device) # cpu

# 查看是否需要梯度(自动求导)
print(tensor.requires_grad) # False
tensor.requires_grad_(True) # 启用梯度追踪

形状的表示

1
2
3
4
5
6
7
8
9
# torch.Size 可以像元组一样使用
shape = torch.rand(2, 3, 4).shape
print(len(shape)) # 3(维度数)
print(shape[0]) # 2
print(shape[1]) # 3
print(shape[2]) # 4

# 获取维度数
dim = tensor.dim() # 等价于 len(tensor.shape)

索引与切片

基础索引

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
tensor = torch.arange(24).reshape(4, 6)
print(tensor)
# tensor([[ 0, 1, 2, 3, 4, 5],
# [ 6, 7, 8, 9, 10, 11],
# [12, 13, 14, 15, 16, 17],
# [18, 19, 20, 21, 22, 23]])

# 获取单个元素
element = tensor[1, 2] # 第2行第3列 → 8

# 获取一行
first_row = tensor[0] # 第一行 → [0, 1, 2, 3, 4, 5]

# 获取一列
first_col = tensor[:, 0] # 第一列 → [0, 6, 12, 18]

# 获取子矩阵
sub = tensor[1:3, 2:4] # 行1~2,列2~3
# tensor([[ 8, 9],
# [14, 15]])

# 步长切片
every_other = tensor[::2, ::2] # 每隔一行/一列取一个
# tensor([[ 0, 2, 4],
# [12, 14, 16]])

高级索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
tensor = torch.arange(12).reshape(3, 4)
# tensor([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11]])

# 整数索引(取特定位置的元素)
indices = tensor[[0, 2], [1, 3]] # 取 (0,1) 和 (2,3) → tensor([1, 11])

# 布尔掩码(取满足条件的元素)
mask = tensor > 5
selected = tensor[mask] # tensor([ 6, 7, 8, 9, 10, 11])

# 使用 where 进行条件替换
result = torch.where(tensor > 5, tensor, torch.zeros_like(tensor))
# tensor([[0, 0, 0, 0],
# [0, 0, 6, 7],
# [8, 9, 10, 11]])

维度变换(核心重点)

reshape 与 view

1
2
3
4
5
6
7
8
9
10
11
12
13
14
tensor = torch.arange(12)  # shape: (12,)

# reshape:改变形状(自动处理内存连续性)
reshaped = tensor.reshape(3, 4) # shape: (3, 4)
reshaped = tensor.reshape(2, 2, 3) # shape: (2, 2, 3)
reshaped = tensor.reshape(-1, 4) # shape: (3, 4)(-1 表示自动计算)

# view:类似 reshape,但要求内存连续
viewed = tensor.view(3, 4) # shape: (3, 4)

# 区别:view 要求内存连续,reshape 更灵活
tensor = torch.rand(3, 4).t() # 转置后内存不连续
# tensor.view(2, 6) # 可能报错
tensor.reshape(2, 6) # 可以工作

squeeze 与 unsqueeze

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# squeeze:移除大小为 1 的维度
tensor = torch.rand(1, 3, 1, 4) # shape: (1, 3, 1, 4)
squeezed = tensor.squeeze() # shape: (3, 4) 移除所有 size=1 的维度
squeezed_dim = tensor.squeeze(0) # shape: (3, 1, 4) 只移除维度0
squeezed_dim = tensor.squeeze(2) # shape: (1, 3, 4) 只移除维度2

# unsqueeze:添加大小为 1 的维度
tensor = torch.rand(3, 4) # shape: (3, 4)
unsqueezed = tensor.unsqueeze(0) # shape: (1, 3, 4) 在第0维前添加
unsqueezed = tensor.unsqueeze(1) # shape: (3, 1, 4) 在第1维前添加
unsqueezed = tensor.unsqueeze(-1) # shape: (3, 4, 1) 在最后添加

# 💡 实用场景:批量处理时添加 batch 维度
single_image = torch.rand(224, 224, 3) # shape: (224, 224, 3)
batch_image = single_image.unsqueeze(0) # shape: (1, 224, 224, 3)

transpose 与 permute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# transpose:交换两个维度
matrix = torch.tensor([[1, 2], [3, 4]]) # shape: (2, 2)
transposed = matrix.t() # 转置,shape: (2, 2)
transposed = torch.transpose(matrix, 0, 1) # 交换维度0和1

# permute:重排多个维度(更灵活)
tensor = torch.rand(2, 3, 4) # shape: (2, 3, 4)

# 将 (batch, height, width) → (batch, width, height)
permuted = tensor.permute(0, 2, 1) # shape: (2, 4, 3)

# 图像格式转换:(batch, height, width, channel) → (batch, channel, height, width)
image = torch.rand(32, 224, 224, 3) # NHWC 格式
image_nchw = image.permute(0, 3, 1, 2) # NCHW 格式,shape: (32, 3, 224, 224)

维度变换速查表

操作 代码 输入形状 输出形状 说明
展平 tensor.flatten() (2, 3, 4) (24,) 一维化
展平(保留batch) tensor.flatten(1) (32, 3, 224, 224) (32, 3×224×224) 保留第一维
reshape tensor.reshape(4, 6) (24,) (4, 6) 任意改变形状
增维 tensor.unsqueeze(0) (3, 4) (1, 3, 4) 在第0维添加
降维 tensor.squeeze() (1, 3, 1, 4) (3, 4) 移除size=1的维度
转置 tensor.t() (3, 4) (4, 3) 2D转置
交换维度 tensor.transpose(0, 1) (2, 3, 4) (3, 2, 4) 交换指定两维
重排 tensor.permute(1, 2, 0) (2, 3, 4) (3, 4, 2) 任意重排顺序

张量运算

基本算术运算(逐元素)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
a = torch.tensor([1, 2, 3])
b = torch.tensor([4, 5, 6])

# 加减乘除
print(a + b) # tensor([5, 7, 9])
print(a - b) # tensor([-3, -3, -3])
print(a * b) # tensor([4, 10, 18]) 逐元素乘法,不是矩阵乘法!
print(a / b) # tensor([0.2500, 0.4000, 0.5000])

# 幂运算
print(a ** 2) # tensor([1, 4, 9])
print(torch.pow(a, 2)) # 同上

# 平方根
print(torch.sqrt(a)) # tensor([1.0000, 1.4142, 1.7321])

# 指数/对数
print(torch.exp(a)) # tensor([2.7183, 7.3891, 20.0855])
print(torch.log(a)) # tensor([0.0000, 0.6931, 1.0986])

矩阵乘法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 矩阵乘法要求:A的列数 = B的行数
# 公式:(m, n) × (n, p) → (m, p)

A = torch.rand(2, 3) # shape: (2, 3)
B = torch.rand(3, 4) # shape: (3, 4)

# 方式1:@ 运算符(推荐)
C = A @ B # shape: (2, 4)

# 方式2:torch.matmul
C = torch.matmul(A, B) # shape: (2, 4)

# 方式3:torch.mm(仅适用于2D)
C = torch.mm(A, B) # shape: (2, 4)

# 批量矩阵乘法(3D+)
batch_A = torch.rand(16, 2, 3) # (batch, m, n)
batch_B = torch.rand(16, 3, 4) # (batch, n, p)
batch_C = torch.bmm(batch_A, batch_B) # (16, 2, 4)

聚合操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
tensor = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float32)

# 求和
print(tensor.sum()) # 21.0(所有元素)
print(tensor.sum(dim=0)) # tensor([5., 7., 9.])(按列)
print(tensor.sum(dim=1)) # tensor([6., 15.])(按行)

# 均值
print(tensor.mean()) # 3.5
print(tensor.mean(dim=0)) # tensor([2.5, 3.5, 4.5])

# 最大值/最小值
print(tensor.max()) # 6.0
print(tensor.max(dim=1)) # 返回值 (values, indices)

# 其他聚合
print(tensor.prod()) # 720(乘积)
print(tensor.std()) # 1.7078(标准差)
print(tensor.var()) # 2.9167(方差)

# 保持维度的聚合(keepdim=True)
sum_keepdim = tensor.sum(dim=1, keepdim=True) # shape: (2, 1)

设备管理(CPU/GPU)

基本设备操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import torch

# 检查 CUDA 是否可用
print(torch.cuda.is_available()) # True/False
print(torch.cuda.device_count()) # GPU 数量
print(torch.cuda.get_device_name(0)) # GPU 名称

# 创建设备对象
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 在指定设备上创建张量
tensor_on_cpu = torch.rand(3, 4) # 默认 CPU
tensor_on_gpu = torch.rand(3, 4, device="cuda") # 直接在 GPU 创建
tensor_on_device = torch.rand(3, 4, device=device) # 使用设备对象

# 移动张量到设备
tensor = torch.rand(3, 4)
tensor = tensor.to(device) # 移动到目标设备
tensor = tensor.cuda() # 移动到 GPU
tensor = tensor.cpu() # 移动到 CPU

# 检查张量所在设备
print(tensor.device) # cpu 或 cuda:0

模型与数据设备对齐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import torch.nn as nn

# 创建模型和数据
model = nn.Linear(10, 2)
data = torch.rand(32, 10)

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 将模型和数据移到同一设备
model = model.to(device)
data = data.to(device)

# 推理
output = model(data) # 输出也在 GPU 上

广播机制

当两个张量形状不同时,PyTorch 会自动尝试广播(Broadcasting),使它们形状兼容。

广播规则

  1. 从尾部维度开始对齐
  2. 如果两个维度大小相等,或其中一个为 1,或其中一个不存在,则可以广播
  3. 否则无法广播
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 示例1:标量广播
a = torch.rand(3, 4)
b = 2
c = a * b # 2 被广播为 (3, 4) 形状

# 示例2:向量广播到矩阵
a = torch.rand(3, 4)
b = torch.rand(4)
c = a + b # b 被广播为 (3, 4)

# 示例3:列向量广播
a = torch.rand(3, 4)
b = torch.rand(3, 1)
c = a + b # b 被广播为 (3, 4)

# 示例4:无法广播的情况
a = torch.rand(3, 4)
b = torch.rand(2, 4)
# c = a + b # 形状不兼容!(3,4) vs (2,4)

# 两列都不相同,但是都存在1
a = torch.rand(3, 1)
b = torch.rand(1, 3)
c = a + b # b 被广播为 (3, 3)

手动广播

1
2
3
4
5
6
# expand:复制数据(不复制内存,高效)
a = torch.rand(3, 1)
expanded = a.expand(3, 4) # 形状 (3, 4)

# repeat:实际复制数据(消耗内存)
repeated = a.repeat(1, 4) # 形状 (3, 4)

实战案例

案例1:图像数据预处理(NHWC ↔ NCHW)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import torch

# 模拟一批图像:batch_size=32, height=224, width=224, channels=3
images_nhwc = torch.rand(32, 224, 224, 3)

print(f"原始形状: {images_nhwc.shape}") # (32, 224, 224, 3)

# PyTorch 模型通常需要 NCHW 格式
images_nchw = images_nhwc.permute(0, 3, 1, 2)

print(f"转换后形状: {images_nchw.shape}") # (32, 3, 224, 224)

# 归一化(假设像素值范围 0-255)
images_normalized = images_nchw / 255.0

# 标准化(mean=0.5, std=0.5)
mean = torch.tensor([0.5, 0.5, 0.5]).view(1, 3, 1, 1)
std = torch.tensor([0.5, 0.5, 0.5]).view(1, 3, 1, 1)
images_standardized = (images_normalized - mean) / std

案例2:批量矩阵计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 场景:计算多个向量的点积
batch_size = 100
vector_dim = 256

# 生成两批向量
A = torch.rand(batch_size, vector_dim)
B = torch.rand(batch_size, vector_dim)

# 方式1:逐元素相乘后求和
dot_products = (A * B).sum(dim=1) # shape: (100,)

# 方式2:使用 einsum(更清晰)
dot_products = torch.einsum('ij,ij->i', A, B) # shape: (100,)

# 场景:注意力分数计算(Q · K^T)
Q = torch.rand(32, 8, 64) # (batch, heads, dim)
K = torch.rand(32, 8, 64) # (batch, heads, dim)
scores = torch.einsum('bhd,bhd->bh', Q, K) # (batch, heads)

案例3:掩码操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 创建序列数据(模拟 padding)
seq_lengths = [3, 5, 2, 4]
max_len = max(seq_lengths)
batch_size = len(seq_lengths)

# 创建 attention mask
mask = torch.zeros(batch_size, max_len, dtype=torch.bool)
for i, length in enumerate(seq_lengths):
mask[i, :length] = 1

print(mask)
# tensor([[ True, True, True, False, False],
# [ True, True, True, True, True],
# [ True, True, False, False, False],
# [ True, True, True, True, False]])

# 使用 mask 过滤
data = torch.rand(batch_size, max_len, 10)
masked_data = data[mask] # 只保留有效位置的数据

案例4:创建神经网络层

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
import torch.nn as nn

class SimpleCNN(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
self.fc = nn.Linear(32 * 56 * 56, 10)

def forward(self, x):
# x shape: (batch, 3, 224, 224)
x = self.conv1(x) # (batch, 16, 224, 224)
x = nn.ReLU()(x)
x = nn.MaxPool2d(2)(x) # (batch, 16, 112, 112)

x = self.conv2(x) # (batch, 32, 112, 112)
x = nn.ReLU()(x)
x = nn.MaxPool2d(2)(x) # (batch, 32, 56, 56)

x = x.view(x.size(0), -1) # (batch, 32*56*56)
x = self.fc(x) # (batch, 10)
return x

# 创建模型
model = SimpleCNN()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)

# 前向传播
x = torch.rand(16, 3, 224, 224).to(device)
output = model(x)
print(output.shape) # (16, 10)

总结:张量操作速查表

操作类型 常用函数 说明
创建 torch.tensor(), torch.zeros(), torch.ones(), torch.rand() 创建张量
转换 .numpy(), torch.from_numpy() 与 NumPy 互转
形状 .shape, .size(), .numel() 查看形状和大小
重塑 .reshape(), .view(), .flatten() 改变形状
增/删维度 .unsqueeze(), .squeeze() 操作 size=1 的维度
重排 .permute(), .transpose() 交换/重排维度
索引 tensor[1, 2], tensor[mask] 获取子集
运算 +, -, *, /, @ 基本运算和矩阵乘法
聚合 .sum(), .mean(), .max() 统计聚合
设备 .to(device), .cuda(), .cpu() 移动张量

欢迎关注我的其它发布渠道