张量 (Tensor)
张量是 PyTorch 的核心数据结构,它是标量、向量、矩阵的多维泛化。本章将详细介绍 PyTorch 张量的概念、创建方式、操作方法和属性。
什么是张量?
在数学中,张量(Tensor)是一个描述标量、向量、矩阵在各个参考系之间变换的线性代数对象。在深度学习中,张量通常指多维数组。
| 维度 | 数学名称 | PyTorch 示例 | 常见用途 |
|---|---|---|---|
| 0 维 | 标量 (Scalar) | tensor(3.14) | 损失值、偏置 |
| 1 维 | 向量 (Vector) | tensor([1, 2, 3]) | 特征向量 |
| 2 维 | 矩阵 (Matrix) | tensor([[1,2],[3,4]]) | 权重矩阵、图像 |
| 3 维 | 3D 张量 | tensor([[[1,2],[3,4]],[[5,6],[7,8]]]) | 时间序列、RGB 图像 |
| N 维 | N 维张量 | ... | 视频、批量数据 |
张量 vs NumPy 数组
PyTorch 张量与 NumPy 数组非常相似,主要区别在于:
- GPU 支持:张量可以在 GPU 上运行,显著加速计算
- 自动微分:张量支持自动计算梯度(通过
requires_grad属性) - 共享内存:张量与 NumPy 可以共享底层内存,无需复制数据
import torch
import numpy as np
# NumPy 数组
np_array = np.array([1, 2, 3, 4])
# 转换为 PyTorch 张量
tensor = torch.from_numpy(np_array)
print(f"NumPy 数组: {np_array}")
print(f"PyTorch 张量: {tensor}")
创建张量
PyTorch 提供了多种创建张量的方法。
从数据创建
import torch
# 从 Python 列表创建
data = [[1, 2], [3, 4]]
tensor = torch.tensor(data)
print(tensor)
# 输出:
# tensor([[1, 2],
# [3, 4]])
# 指定数据类型
tensor_float = torch.tensor(data, dtype=torch.float32)
tensor_int = torch.tensor(data, dtype=torch.int64)
print(tensor_float.dtype) # torch.float32
print(tensor_int.dtype) # torch.int64
使用内置函数创建
import torch
# 创建全 0 张量
zeros = torch.zeros(2, 3) # 2 行 3 列
print(f"全0张量:\n{zeros}")
# 创建全 1 张量
ones = torch.ones(2, 3)
print(f"全1张量:\n{ones}")
# 创建全为某个值的张量
full = torch.full((2, 3), 7) # 填充值 7
print(f"全7张量:\n{full}")
# 创建单位矩阵
eye = torch.eye(3) # 3x3 单位矩阵
print(f"单位矩阵:\n{eye}")
创建随机张量
import torch
# [0, 1) 均匀分布的随机数
rand = torch.rand(2, 3)
print(f"均匀分布:\n{rand}")
# 标准正态分布 N(0, 1)
randn = torch.randn(2, 3)
print(f"正态分布:\n{randn}")
# 随机整数 [low, high)
randint = torch.randint(0, 10, (2, 3)) # 0-9 的随机整数
print(f"随机整数:\n{randint}")
# 随机排列
arrange = torch.arange(10) # [0, 1, 2, ..., 9]
print(f"数列: {arrange}")
# 等差数列
linspace = torch.linspace(0, 10, 5) # 0 到 10 之间等距的 5 个点
print(f"等差数列: {linspace}")
创建与已有张量相似的新张量
import torch
# 创建一个示例张量
x = torch.randn(2, 3)
# 创建与 x 形状相同的全 0 张量
zeros_like = torch.zeros_like(x)
print(f"形状相同的全0张量: {zeros_like.shape}")
# 创建与 x 形状相同的全 1 张量
ones_like = torch.ones_like(x)
# 创建与 x 形状相同的随机张量
rand_like = torch.rand_like(x)
张量属性
每个 PyTorch 张量都有以下重要属性:
import torch
x = torch.randn(3, 4, 5) # 3D 张量
# 形状 (shape)
print(f"形状: {x.shape}") # torch.Size([3, 4, 5])
print(f"形状: {x.size()}") # torch.Size([3, 4, 5])
# 数据类型 (dtype)
print(f"数据类型: {x.dtype}") # torch.float32
# 设备 (device) - CPU 或 GPU
print(f"设备: {x.device}") # cpu 或 cuda:0
# 维度数 (ndim)
print(f"维度数: {x.ndim}") # 3
# 元素总数 (numel)
print(f"元素总数: {x.numel()}") # 3*4*5 = 60
张量索引与切片
PyTorch 张量支持与 NumPy 类似的高级索引功能。
基本索引
import torch
# 创建一个 2x3 的张量
x = torch.tensor([[1, 2, 3],
[4, 5, 6]])
# 访问单个元素
print(x[0, 0]) # tensor(1) - 第一行第一列
print(x[1, 2]) # tensor(6) - 第二行第三列
# 访问整行
print(x[0]) # tensor([1, 2, 3])
# 访问整列
print(x[:, 0]) # tensor([1, 4])
切片操作
import torch
x = torch.arange(12).reshape(3, 4)
print(f"原始张量:\n{x}")
# 切片: [起始:结束:步长]
print(x[0:2]) # 前两行
print(x[:, 1:3]) # 第2-3列
print(x[::2, ::2]) # 步长为2的切片
# 负索引
print(x[-1]) # 最后一行
print(x[:, -1]) # 最后一列
高级索引
import torch
x = torch.arange(12).reshape(3, 4)
# 布尔索引
mask = x > 5
print(f"大于5的元素: {x[mask]}")
# 整数数组索引
indices = torch.tensor([0, 2])
print(f"选择第1和第3行:\n{x[indices]}")
# 行列组合索引
rows = torch.tensor([0, 1, 2])
cols = torch.tensor([0, 2, 3])
print(f"选择特定位置:\n{x[rows, cols]}")
张量操作
形状操作
import torch
x = torch.randn(2, 3)
# 重塑 (reshape)
y = x.reshape(3, 2) # 变成 3 行 2 列
z = x.view(6) # 展平成 1 维
w = x.view(-1, 2) # -1 表示自动推断维度
# 转置 (transpose)
t = x.t() # 2x3 -> 3x2
t2 = x.transpose(0, 1) # 交换第0和第1维
# 扩展维度 (expand)
a = torch.randn(3, 1)
b = a.expand(3, 4) # 复制扩展到 3x4
# 压缩维度 (squeeze) / 添加维度 (unsqueeze)
c = torch.randn(1, 2, 3, 1)
d = c.squeeze() # 移除大小为1的维度 -> 2x3
e = c.unsqueeze(0) # 在第0维添加维度 -> 1x2x3x1
# 拼接 (concatenate)
x1 = torch.randn(2, 3)
x2 = torch.randn(2, 3)
cat = torch.cat([x1, x2], dim=0) # 沿第0维拼接 -> 4x3
cat2 = torch.cat([x1, x2], dim=1) # 沿第1维拼接 -> 2x6
# 分割 (split)
result = torch.split(cat, 2, dim=0) # 沿第0维分成每份2个
# 堆叠 (stack) - 与拼接不同,会增加一个新维度
stack = torch.stack([x1, x2], dim=0) # 2x2x3
数学运算
import torch
a = torch.randn(2, 3)
b = torch.randn(2, 3)
# 逐元素运算
c = a + b # 加法
d = a - b # 减法
e = a * b # 逐元素乘法(Hadamar 积)
f = a / b # 逐元素除法
g = a ** 2 # 逐元素幂运算
# 矩阵运算
matmul = torch.mm(a, b.t()) # 矩阵乘法 (2x3) @ (3x2) -> 2x2
dot = torch.dot(a.flatten(), b.flatten()) # 向量点积
matvec = torch.mv(a, b[:, 0]) # 矩阵向量乘法
# 求和、均值、最大值、最小值
sum_all = a.sum()
mean_all = a.mean()
max_all = a.max()
min_all = a.min()
# 沿维度运算
sum_dim0 = a.sum(dim=0) # 沿第0维求和
mean_dim1 = a.mean(dim=1) # 沿第1维求均值
max_dim1 = a.max(dim=1) # 返回 (values, indices)
# 归一化
norm_l2 = a.norm() # L2 范数
norm_l1 = a.norm(p=1) # L1 范数
# 激活函数(需要 import torch.nn.functional)
import torch.nn.functional as F
relu = F.relu(a)
sigmoid = torch.sigmoid(a)
softmax = F.softmax(a, dim=1)
比较运算
import torch
a = torch.tensor([[1, 2, 3],
[4, 5, 6]])
# 逐元素比较
print(a > 2) # tensor([[False, False, True],
# [True, True, True]])
print(a == 3) # tensor([[False, False, True],
# [False, False, False]])
# 返回最大值/最小值的索引
max_idx = a.argmax() # 返回展平后的索引
max_idx_dim = a.argmax(dim=1) # 沿维度返回索引
# 条件选择
result = torch.where(a > 3, a, torch.zeros_like(a)) # 大于3保留,否则设为0
张量与 GPU
张量可以在 CPU 或 GPU 上运行。
移动张量
import torch
# 检查 CUDA 是否可用
if torch.cuda.is_available():
# 创建 CPU 张量
x_cpu = torch.randn(3, 4)
# 移动到 GPU
x_gpu = x_cpu.cuda()
# 或者
x_gpu = x_cpu.to('cuda')
# 移动回 CPU
x_cpu2 = x_gpu.cpu()
# 直接在 GPU 上创建张量
x_gpu_direct = torch.randn(3, 4, device='cuda')
# 获取当前设备
print(x_gpu.device) # cuda:0
GPU 计算示例
import torch
import time
# 创建测试数据
size = 1000
a = torch.randn(size, size)
b = torch.randn(size, size)
# CPU 计算
start = time.time()
for _ in range(10):
c = torch.mm(a, b)
cpu_time = time.time() - start
# GPU 计算(如果有GPU)
if torch.cuda.is_available():
a_gpu = a.cuda()
b_gpu = b.cuda()
torch.cuda.synchronize() # 等待 GPU 完成
start = time.time()
for _ in range(10):
c_gpu = torch.mm(a_gpu, b_gpu)
torch.cuda.synchronize()
gpu_time = time.time() - start
print(f"CPU 耗时: {cpu_time:.4f}s")
print(f"GPU 耗时: {gpu_time:.4f}s")
张量与 NumPy 互转
PyTorch 张量和 NumPy 数组可以高效互转。
张量转 NumPy
import torch
import numpy as np
# 创建张量
tensor = torch.randn(3, 4)
# 转换为 NumPy 数组(共享内存)
numpy_array = tensor.numpy()
# 注意:修改 NumPy 数组会影响原始张量
numpy_array[0, 0] = 999
print(tensor[0, 0]) # tensor(999.)
# 如果张量在 GPU 上,需要先移到 CPU
if tensor.is_cuda:
tensor = tensor.cpu()
numpy_array = tensor.numpy()
NumPy 转张量
import torch
import numpy as np
# 创建 NumPy 数组
np_array = np.array([[1, 2, 3],
[4, 5, 6]])
# 转换为张量
tensor = torch.from_numpy(np_array)
# 注意:修改张量会影响原始 NumPy 数组
tensor[0, 0] = 999
print(np_array[0, 0]) # 999
# 如果需要复制(不共享内存)
tensor_copy = torch.tensor(np_array)
tensor_copy[0, 0] = 888
print(np_array[0, 0]) # 仍然是 999
张量的内存模型
理解张量的内存模型对于编写高效的 PyTorch 代码非常重要。
内存视图
import torch
x = torch.randn(3, 4)
# 检查是否是连续内存
print(x.is_contiguous()) # True
# transpose 创建的非连续张量
y = x.transpose(0, 1)
print(y.is_contiguous()) # False
# 可以调用 contiguous() 使其连续
y_cont = y.contiguous()
原地操作 vs 非原地操作
import torch
x = torch.randn(3, 4)
# 非原地操作:返回新的张量,原张量不变
y = x + 1
print(id(x) == id(y)) # False
# 原地操作:在原张量上修改
x.add_(1) # 下划线表示原地操作
print(x)
x.sub_(1) # 减法原地操作
x.mul_(2) # 乘法原地操作
x.div_(2) # 除法原地操作
常用技巧
克隆与复制
import torch
x = torch.randn(3, 4)
# 克隆(共享数据,但独立计算图)
x_clone = x.clone()
# 深度复制(完全独立)
x_deepcopy = x.detach().clone()
随机种子
为了结果可复现:
import torch
# 设置随机种子
torch.manual_seed(42)
# 每次运行结果相同
x = torch.randn(2, 3)
print(x)
# CUDA 随机种子
if torch.cuda.is_available():
torch.cuda.manual_seed_all(42)
类型转换
import torch
x = torch.randn(3, 4)
# 转换为不同类型
x_float = x.float() # float32
x_double = x.double() # float64
x_int = x.int() # int32
x_long = x.long() # int64
x_half = x.half() # float16 (GPU)
# 使用 to 方法
x_new = x.to(dtype=torch.float16)
下一步
张量是 PyTorch 的基础数据结构。下一章我们将学习 PyTorch 的自动微分系统(Autograd),这是深度学习训练的核心。