张量
PyTorch 张量完全指南:深度学习的核心数据结构
核心认知:张量(Tensor)是深度学习世界的”通用语言”——无论是图像、文本、语音还是视频,都会被转换为张量进行处理。理解张量的创建、变换和运算,是掌握 PyTorch 的第一步,也是构建神经网络的基础。
张量是 PyTorch 中最基本的数据结构,类似于 NumPy 的 ndarray,但增加了 GPU 加速和自动求导等深度学习特性。本文将系统讲解张量的核心概念、创建方式、维度变换和常用运算。
目录
什么是张量?
从标量到高阶张量
张量是标量、向量、矩阵的泛化,是 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) |
张量的三个核心属性
每个张量都有三个核心属性:
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 |
创建张量
从已有数据创建
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]
创建特殊值的张量
# 全零张量
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)
创建随机张量
# 均匀分布 [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))
创建序列张量
# 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 互转
# 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] ← 被修改了!
张量属性
查看和修改属性
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) # 启用梯度追踪
形状的表示
# 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)
索引与切片
基础索引
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]])
高级索引
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
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
# 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
# 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) |
任意重排顺序 |
张量运算
基本算术运算(逐元素)
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])
矩阵乘法
# 矩阵乘法要求: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)
聚合操作
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)
基本设备操作
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
模型与数据设备对齐
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,或其中一个不存在,则可以广播
- 否则无法广播
# 示例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)
手动广播
# expand:复制数据(不复制内存,高效)
a = torch.rand(3, 1)
expanded = a.expand(3, 4) # 形状 (3, 4)
# repeat:实际复制数据(消耗内存)
repeated = a.repeat(1, 4) # 形状 (3, 4)
实战案例
案例1:图像数据预处理(NHWC ↔ NCHW)
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:批量矩阵计算
# 场景:计算多个向量的点积
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:掩码操作
# 创建序列数据(模拟 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:创建神经网络层
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() |
移动张量 |