跳到主要内容

张量 (Tensor)

张量(Tensor)是 PyTorch 的核心数据结构,它是多维数组的泛化形式。理解张量及其操作是学习 PyTorch 的第一步,也是深度学习的基础。

什么是张量?

在数学中,张量是一个描述标量、向量、矩阵在各个参考系之间变换的线性代数对象。在 PyTorch 和深度学习中,张量通常指多维数组——一种可以在 GPU 上高效计算、支持自动微分的数据结构。

张量的维度

张量按维度分类:

维度数学名称示例常见用途
0 维标量 (Scalar)tensor(3.14)损失值、偏置
1 维向量 (Vector)tensor([1, 2, 3])特征向量、偏置向量
2 维矩阵 (Matrix)tensor([[1,2],[3,4]])权重矩阵、灰度图像
3 维3D 张量形状 (C, H, W)RGB 图像
4 维4D 张量形状 (N, C, H, W)批量图像
N 维N 维张量...视频、高维数据

张量 vs NumPy 数组

PyTorch 张量与 NumPy 数组非常相似,但有两个关键区别:

  1. GPU 加速:张量可以在 GPU 上运行,利用并行计算能力大幅提升速度
  2. 自动微分:张量支持自动计算梯度(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"PyTorch 张量: {tensor}")

# 修改 NumPy 数组会影响张量
np_array[0] = 999
print(f"修改后张量: {tensor}") # tensor([999, 2, 3, 4])

创建张量

PyTorch 提供了多种创建张量的方法,可以根据不同场景选择。

从 Python 数据创建

最直接的方式是使用 torch.tensor() 从 Python 列表或元组创建:

import torch

# 从列表创建
data = [[1, 2], [3, 4]]
x = torch.tensor(data)
print(x)
# tensor([[1, 2],
# [3, 4]])

# 指定数据类型
x_float = torch.tensor(data, dtype=torch.float32)
print(x_float.dtype) # torch.float32

# 强制指定设备
x_gpu = torch.tensor(data, device='cuda:0')
print(x_gpu.device) # cuda:0

torch.tensor() 会复制数据。如果希望避免复制,可以使用 torch.as_tensor()

import numpy as np

np_array = np.array([1, 2, 3])

# 复制数据
x1 = torch.tensor(np_array)

# 共享内存(修改会影响原数组)
x2 = torch.as_tensor(np_array)
np_array[0] = 999
print(x2) # tensor([999, 2, 3])

使用工厂函数创建

PyTorch 提供了多种"工厂函数"来创建特定形状和内容的张量:

import torch

# 创建未初始化张量(值不确定,仅分配内存)
empty = torch.empty(2, 3)
print(f"未初始化:\n{empty}")

# 创建全零张量
zeros = torch.zeros(2, 3)
print(f"全零:\n{zeros}")

# 创建全一张量
ones = torch.ones(2, 3)
print(f"全一:\n{ones}")

# 创建填充特定值的张量
full = torch.full((2, 3), fill_value=7)
print(f"填充7:\n{full}")

# 创建单位矩阵
eye = torch.eye(3)
print(f"单位矩阵:\n{eye}")

创建随机张量

随机张量在深度学习中非常重要,常用于初始化模型权重:

import torch

# 设置随机种子(确保可重复性)
torch.manual_seed(42)

# [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))
print(f"随机整数:\n{randint}")

# 随机排列
perm = torch.randperm(5)
print(f"随机排列: {perm}") # 如 tensor([2, 4, 0, 1, 3])

创建序列张量

import torch

# 类似 Python range
arange = torch.arange(0, 10, 2) # [start, end), step=2
print(f"arange: {arange}") # tensor([0, 2, 4, 6, 8])

# 等间隔张量
linspace = torch.linspace(0, 1, 5) # [start, end], 5个点
print(f"linspace: {linspace}") # tensor([0.00, 0.25, 0.50, 0.75, 1.00])

# 对数间隔
logspace = torch.logspace(0, 2, 5) # 10^0 到 10^2
print(f"logspace: {logspace}") # tensor([1., 3.16, 10., 31.62, 100.])

基于现有张量创建

*_like 系列函数可以创建与现有张量形状相同的新张量:

import torch

x = torch.tensor([[1, 2, 3], [4, 5, 6]])

# 相同形状的全零张量
zeros_like = torch.zeros_like(x)
print(f"zeros_like:\n{zeros_like}")

# 相同形状的全一张量
ones_like = torch.ones_like(x)
print(f"ones_like:\n{ones_like}")

# 相同形状的随机张量(可覆盖 dtype)
rand_like = torch.rand_like(x, dtype=torch.float32)
print(f"rand_like:\n{rand_like}")

# 相同形状的未初始化张量
empty_like = torch.empty_like(x)

张量属性

每个 PyTorch 张量都有几个重要属性,描述其形状、数据类型和存储设备:

import torch

x = torch.randn(3, 4, 5)

# 形状
print(f"形状: {x.shape}") # torch.Size([3, 4, 5])
print(f"形状: {x.size()}") # torch.Size([3, 4, 5])

# 维度数
print(f"维度数: {x.dim()}") # 3(也可用 x.ndim)

# 元素总数
print(f"元素总数: {x.numel()}") # 3*4*5 = 60

# 数据类型
print(f"数据类型: {x.dtype}") # torch.float32

# 存储设备
print(f"设备: {x.device}") # cpu 或 cuda:0

数据类型

PyTorch 支持多种数据类型,最常用的是 float32int64

类型说明字节数
torch.float32 / torch.float32位浮点数(默认)4
torch.float64 / torch.double64位浮点数8
torch.float16 / torch.half16位浮点数2
torch.bfloat16Brain Float162
torch.int88位有符号整数1
torch.int16 / torch.short16位整数2
torch.int32 / torch.int32位整数4
torch.int64 / torch.long64位整数(默认整数)8
torch.uint88位无符号整数1
torch.bool布尔类型1

类型转换:

import torch

x = torch.tensor([1, 2, 3])
print(x.dtype) # torch.int64

# 方法1:调用类型方法
x_float = x.float() # 或 x.to(torch.float32)
x_long = x_float.long() # 或 x.to(torch.int64)

# 方法2:使用 to 方法
x_half = x.to(torch.float16)

print(x_float.dtype) # torch.float32
print(x_half.dtype) # torch.float16

索引与切片

PyTorch 张量支持与 NumPy 类似的索引和切片操作。

基本索引

import torch

x = torch.tensor([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

# 访问单个元素
print(x[0, 0]) # tensor(1) - 第一行第一列
print(x[1, 2]) # tensor(6) - 第二行第三列

# 访问整行
print(x[0]) # tensor([1, 2, 3])
print(x[0, :]) # tensor([1, 2, 3])

# 访问整列
print(x[:, 0]) # tensor([1, 4, 7])
print(x[:, -1]) # tensor([3, 6, 9]) - 最后一列

切片操作

import torch

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

# 切片: [起始:结束:步长]
print(x[0:2]) # 前两行
# tensor([[0, 1, 2, 3],
# [4, 5, 6, 7]])

print(x[:, 1:3]) # 第2-3列
# tensor([[ 1, 2],
# [ 5, 6],
# [ 9, 10]])

print(x[::2, ::2]) # 步长为2
# tensor([[0, 2],
# [8, 10]])

高级索引

import torch

x = torch.arange(12).reshape(3, 4)

# 布尔索引
mask = x > 5
print(mask)
# tensor([[False, False, False, False],
# [False, False, True, True],
# [True, True, True, True]])

print(x[mask]) # tensor([6, 7, 8, 9, 10, 11])

# 条件筛选
print(x[x > 5]) # tensor([6, 7, 8, 9, 10, 11])

# 整数索引
rows = torch.tensor([0, 2])
cols = torch.tensor([1, 3])
print(x[rows, cols]) # tensor([1, 11])

# torch.index_select
indices = torch.tensor([0, 2])
print(torch.index_select(x, dim=0, index=indices))
# tensor([[ 0, 1, 2, 3],
# [ 8, 9, 10, 11]])

修改张量

import torch

x = torch.zeros(2, 3)

# 修改单个元素
x[0, 0] = 1
print(x)

# 修改一行
x[1, :] = torch.tensor([4, 5, 6])
print(x)

# 条件修改
x[x == 0] = -1
print(x)

# scatter_ 方法(常用於 one-hot 编码)
x = torch.zeros(3, 5)
indices = torch.tensor([0, 2, 4])
x.scatter_(1, indices.unsqueeze(1), 1)
print(x)
# tensor([[1., 0., 0., 0., 0.],
# [0., 0., 1., 0., 0.],
# [0., 0., 0., 0., 1.]])

张量运算

PyTorch 提供了丰富的张量运算,涵盖数学运算、线性代数、聚合操作等。

算术运算

import torch

x = torch.tensor([1.0, 2.0, 3.0])
y = torch.tensor([4.0, 5.0, 6.0])

# 四则运算
print(x + y) # tensor([5., 7., 9.])
print(x - y) # tensor([-3., -3., -3.])
print(x * y) # tensor([4., 10., 18.]) - 逐元素乘法
print(x / y) # tensor([0.25, 0.40, 0.50])

# 幂运算
print(x ** 2) # tensor([1., 4., 9.])
print(torch.sqrt(x)) # tensor([1.00, 1.41, 1.73])

# 指数和对数
print(torch.exp(x)) # e^x
print(torch.log(x)) # ln(x)

# 整除和取余
print(torch.div(y, x, rounding_mode='floor')) # 整除
print(torch.remainder(y, x)) # 取余

矩阵运算

import torch

A = torch.tensor([[1, 2], [3, 4]], dtype=torch.float)
B = torch.tensor([[5, 6], [7, 8]], dtype=torch.float)

# 矩阵乘法
print(torch.matmul(A, B)) # 或 A @ B 或 A.mm(B)
# tensor([[19., 22.],
# [43., 50.]])

# 向量点积
v1 = torch.tensor([1., 2., 3.])
v2 = torch.tensor([4., 5., 6.])
print(torch.dot(v1, v2)) # tensor(32.)

# 批量矩阵乘法
A_batch = torch.randn(10, 3, 4)
B_batch = torch.randn(10, 4, 5)
C_batch = torch.bmm(A_batch, B_batch) # (10, 3, 5)

# 转置
print(A.T)
print(A.transpose(0, 1))

# 逆矩阵
print(torch.inverse(A))

# 行列式
print(torch.det(A)) # tensor(-2.0000)

聚合运算

聚合运算将张量缩减为标量或更低维度的张量:

import torch

x = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float)

# 全局聚合
print(f"求和: {torch.sum(x)}") # tensor(21.)
print(f"均值: {torch.mean(x)}") # tensor(3.5000)
print(f"最大值: {torch.max(x)}") # tensor(6.)
print(f"最小值: {torch.min(x)}") # tensor(1.)
print(f"标准差: {torch.std(x)}") # tensor(1.8708)
print(f"方差: {torch.var(x)}") # tensor(3.5000)
print(f"乘积: {torch.prod(x)}") # tensor(720.)
print(f"L2范数: {torch.norm(x)}") # tensor(9.5394)

# 沿维度聚合
print(f"按列求和: {torch.sum(x, dim=0)}") # tensor([5., 7., 9.])
print(f"按行求和: {torch.sum(x, dim=1)}") # tensor([6., 15.])

# 返回值和索引
values, indices = torch.max(x, dim=1)
print(f"最大值: {values}") # tensor([3., 6.])
print(f"索引: {indices}") # tensor([2, 2])

# argmax / argmin
print(torch.argmax(x)) # 全局最大值索引
print(torch.argmax(x, dim=1)) # 每行最大值索引

比较运算

import torch

x = torch.tensor([[1, 2, 3], [4, 5, 6]])

# 逐元素比较(返回布尔张量)
print(x > 3)
# tensor([[False, False, False],
# [ True, True, True]])

print(x == 3)
# tensor([[False, False, True],
# [False, False, False]])

# 相等判断
y = torch.tensor([[1, 2, 3], [4, 5, 6]])
print(torch.equal(x, y)) # True
print(torch.eq(x, y)) # 逐元素比较,返回布尔张量

# 条件选择
result = torch.where(x > 3, x, torch.zeros_like(x))
print(result)
# tensor([[0, 0, 0],
# [4, 5, 6]])

广播机制

当两个张量形状不同时,PyTorch 会自动进行广播(Broadcasting):

import torch

x = torch.tensor([[1, 2, 3], [4, 5, 6]]) # (2, 3)
y = torch.tensor([10, 20, 30]) # (3,)

# 广播后 y 变成 (2, 3)
print(x + y)
# tensor([[11, 22, 33],
# [14, 25, 36]])

广播规则:

  1. 从右向左比较维度
  2. 维度相等,或其中一个为 1,或维度不存在
  3. 缺失的维度视为 1
# 广播示例
a = torch.ones(4, 3, 2)
b = torch.rand(3, 2) # 维度匹配:(3,2) 与 (3,2)
c = torch.rand(3, 1) # 可以广播:最后一个维度为1
d = torch.rand(1, 2) # 可以广播:倒数第二维为1

print((a * b).shape) # torch.Size([4, 3, 2])
print((a * c).shape) # torch.Size([4, 3, 2])
print((a * d).shape) # torch.Size([4, 3, 2])

形状操作

深度学习中经常需要改变张量的形状。

改变形状

import torch

x = torch.arange(12)

# reshape(返回新张量,可能共享内存)
y = x.reshape(3, 4)
print(f"reshape: {y.shape}") # torch.Size([3, 4])

# view(返回视图,共享内存,要求连续)
z = x.view(2, 6)
print(f"view: {z.shape}") # torch.Size([2, 6])

# view 要求张量在内存中连续
x_t = x.reshape(3, 4).t() # 转置后不连续
# z = x_t.view(2, 6) # 报错!
z = x_t.contiguous().view(2, 6) # 正确

# 展平
x = torch.randn(2, 3, 4)
y = x.flatten() # (24,)
y = x.flatten(1) # (2, 12) - 从第1维开始展平

# 自适应展平
y = torch.flatten(x, start_dim=1)

增减维度

import torch

# unsqueeze:增加维度
x = torch.tensor([1, 2, 3]) # (3,)
y = x.unsqueeze(0) # (1, 3)
z = x.unsqueeze(1) # (3, 1)

print(y.shape) # torch.Size([1, 3])
print(z.shape) # torch.Size([3, 1])

# squeeze:移除大小为1的维度
x = torch.randn(1, 3, 1, 4)
y = x.squeeze() # (3, 4) - 移除所有大小为1的维度
z = x.squeeze(0) # (3, 1, 4) - 只移除第0维
w = x.squeeze(2) # (1, 3, 4) - 只移除第2维

# 实际应用:添加 batch 维度
image = torch.randn(3, 224, 224) # 单张图像
batch = image.unsqueeze(0) # (1, 3, 224, 224) - 批量

拼接与分割

import torch

x = torch.tensor([[1, 2], [3, 4]])
y = torch.tensor([[5, 6], [7, 8]])

# cat:拼接(不增加维度)
z = torch.cat([x, y], dim=0) # 按行拼接 -> (4, 2)
print(z)
# tensor([[1, 2],
# [3, 4],
# [5, 6],
# [7, 8]])

z = torch.cat([x, y], dim=1) # 按列拼接 -> (2, 4)
print(z)
# tensor([[1, 2, 5, 6],
# [3, 4, 7, 8]])

# stack:堆叠(增加新维度)
z = torch.stack([x, y], dim=0) # (2, 2, 2)
print(z.shape)

# split:分割
x = torch.arange(10)
a, b, c = torch.split(x, [3, 3, 4]) # 按指定大小分割
print(a, b, c) # tensor([0, 1, 2]) tensor([3, 4, 5]) tensor([6, 7, 8, 9])

# chunk:均匀分割
x = torch.arange(12).reshape(3, 4)
chunks = torch.chunk(x, 2, dim=1) # 分成2份
print(chunks[0].shape, chunks[1].shape)

转置与置换

import torch

x = torch.arange(24).reshape(2, 3, 4)

# 转置(仅适用于 2D)
x_2d = torch.arange(6).reshape(2, 3)
print(x_2d.t())
# tensor([[0, 3],
# [1, 4],
# [2, 5]])

# transpose:交换两个维度
print(x.transpose(0, 1).shape) # torch.Size([3, 2, 4])

# permute:重新排列所有维度
print(x.permute(2, 0, 1).shape) # torch.Size([4, 2, 3])

GPU 加速

GPU 是深度学习的关键硬件。PyTorch 可以轻松将张量移动到 GPU 上计算。

检查 GPU 可用性

import torch

# 检查 CUDA 是否可用
print(f"CUDA 可用: {torch.cuda.is_available()}")

# 获取 GPU 数量
print(f"GPU 数量: {torch.cuda.device_count()}")

# 获取当前 GPU
if torch.cuda.is_available():
print(f"当前 GPU: {torch.cuda.current_device()}")
print(f"GPU 名称: {torch.cuda.get_device_name(0)}")

设备管理

import torch

# 推荐方式:自动选择设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"使用设备: {device}")

# 创建张量时指定设备
x = torch.tensor([1, 2, 3], device=device)
# 或
x = torch.tensor([1, 2, 3]).to(device)

# 移动现有张量
x_cpu = torch.randn(3, 4)
x_gpu = x_cpu.to('cuda')
# 或
x_gpu = x_cpu.cuda()
x_gpu = x_cpu.to('cuda:0') # 指定 GPU 编号

# 移回 CPU
x_cpu2 = x_gpu.cpu()
x_cpu2 = x_gpu.to('cpu')

# 查看张量所在设备
print(x_cpu.device) # cpu
print(x_gpu.device) # cuda:0

GPU 计算示例

import torch
import time

# 创建大矩阵
size = 5000
a_cpu = torch.randn(size, size)
b_cpu = torch.randn(size, size)

# CPU 计算
start = time.time()
for _ in range(10):
c_cpu = torch.mm(a_cpu, b_cpu)
cpu_time = time.time() - start
print(f"CPU 时间: {cpu_time:.3f}s")

# GPU 计算
if torch.cuda.is_available():
a_gpu = a_cpu.cuda()
b_gpu = b_cpu.cuda()

# 预热
_ = torch.mm(a_gpu, b_gpu)
torch.cuda.synchronize()

start = time.time()
for _ in range(10):
c_gpu = torch.mm(a_gpu, b_gpu)
torch.cuda.synchronize() # 等待 GPU 完成
gpu_time = time.time() - start
print(f"GPU 时间: {gpu_time:.3f}s")
print(f"加速比: {cpu_time/gpu_time:.1f}x")
注意

不同设备上的张量不能直接运算:

x = torch.tensor([1, 2])          # CPU
y = torch.tensor([1, 2], device='cuda') # GPU
# z = x + y # 错误!
z = x.cuda() + y # 正确

原地操作

_ 结尾的操作是原地操作(In-place Operation),直接修改张量本身:

import torch

x = torch.tensor([1.0, 2.0, 3.0])

# 非原地操作:返回新张量,原张量不变
y = x.add(1)
print(x) # tensor([1., 2., 3.])

# 原地操作:直接修改 x
x.add_(1)
print(x) # tensor([2., 3., 4.])

# 常见原地操作
x.zero_() # 置零
x.fill_(5) # 填充
x.mul_(2) # 乘法
x.div_(2) # 除法
x.scatter_() # 散射
x.copy_(y) # 复制
注意

原地操作会丢失梯度信息,在自动求导时需谨慎使用。PyTorch 官方不推荐在需要梯度计算的场景下使用原地操作。

克隆与复制

理解张量的复制机制对于避免意外修改至关重要。

赋值 vs 克隆

import torch

# 赋值:只是创建新标签,指向同一对象
a = torch.tensor([1, 2, 3])
b = a
b[0] = 999
print(a) # tensor([999, 2, 3]) - a 也被修改了!

# clone:创建数据副本
a = torch.tensor([1, 2, 3])
b = a.clone()
b[0] = 999
print(a) # tensor([1, 2, 3]) - a 未被修改

clone 与 detach

clone()detach() 的组合使用:

import torch

x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)

# clone():复制数据,保留梯度追踪
y = x.clone()
print(y.requires_grad) # True

# detach():分离梯度追踪,共享数据
z = x.detach()
print(z.requires_grad) # False

# detach().clone():完全不相关的副本
w = x.detach().clone()
# 常用于将张量移出计算图

内存模型

理解张量的内存模型有助于编写高效的代码。

连续性

import torch

x = torch.randn(3, 4)

# 检查是否连续
print(x.is_contiguous()) # True

# transpose 创建非连续张量
y = x.transpose(0, 1)
print(y.is_contiguous()) # False

# 使张量连续
y_cont = y.contiguous()
print(y_cont.is_contiguous()) # True

# 非连续张量无法使用 view
# y.view(12) # 报错
y_cont.view(12) # 正确

存储偏移和步长

import torch

x = torch.arange(12).reshape(3, 4)

# 存储偏移
print(f"存储偏移: {x.storage_offset()}") # 0

# 步长(每个维度移动需要的步数)
print(f"步长: {x.stride()}") # (4, 1)

# 转置后的步长
y = x.transpose(0, 1)
print(f"转置后步长: {y.stride()}") # (1, 4)

与 NumPy 互转

PyTorch 张量与 NumPy 数组可以高效互转,通常共享底层内存。

张量转 NumPy

import torch
import numpy as np

tensor = torch.randn(3, 4)

# 转换为 NumPy 数组(共享内存)
np_array = tensor.numpy()

# 修改会影响原张量
np_array[0, 0] = 999
print(tensor[0, 0]) # tensor(999.)

# GPU 张量需要先移到 CPU
if tensor.is_cuda:
np_array = tensor.cpu().numpy()

NumPy 转张量

import torch
import numpy as np

np_array = np.array([[1, 2, 3], [4, 5, 6]])

# 转换为张量(共享内存)
tensor = torch.from_numpy(np_array)

# 修改会影响原数组
tensor[0, 0] = 999
print(np_array[0, 0]) # 999

# 使用 torch.tensor() 会复制数据
tensor_copy = torch.tensor(np_array)
tensor_copy[0, 0] = 888
print(np_array[0, 0]) # 仍然是 999

常用技巧

随机种子

为了结果可复现:

import torch
import numpy as np
import random

def set_seed(seed=42):
"""设置所有随机种子"""
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
np.random.seed(seed)
random.seed(seed)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False

set_seed(42)
x = torch.randn(2, 3)

梯度控制

import torch

x = torch.randn(3, 4, requires_grad=True)

# 禁用梯度追踪
with torch.no_grad():
y = x * 2
print(y.requires_grad) # False

# 分离梯度
z = x.detach()
print(z.requires_grad) # False

类型转换快捷方式

import torch

x = torch.randn(3, 4)

# 快捷类型转换
x_float = x.float()
x_double = x.double()
x_half = x.half()
x_int = x.int()
x_long = x.long()

小结

本章我们系统学习了 PyTorch 张量的核心知识:

  1. 张量概念:多维数组的泛化,支持 GPU 加速和自动微分
  2. 创建张量:从数据、工厂函数、随机数、NumPy 等多种方式
  3. 张量属性:形状、数据类型、设备、维度数等
  4. 索引切片:基本索引、高级索引、布尔索引
  5. 张量运算:算术、矩阵、聚合、比较运算,以及广播机制
  6. 形状操作:reshape、view、squeeze、unsqueeze、拼接分割
  7. GPU 加速:设备管理、数据移动、性能对比
  8. 内存模型:连续性、原地操作、克隆与复制
  9. NumPy 互转:共享内存的高效转换

张量是 PyTorch 的基础数据结构。下一章我们将学习 PyTorch 的自动微分系统(Autograd),这是深度学习训练的核心机制。

参考资源