跳到主要内容

张量 (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 数组非常相似,主要区别在于:

  1. GPU 支持:张量可以在 GPU 上运行,显著加速计算
  2. 自动微分:张量支持自动计算梯度(通过 requires_grad 属性)
  3. 共享内存:张量与 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),这是深度学习训练的核心。