05. PyTorch Tensor Fundamentals | PyTorch 张量基础操作
难度: Easy | 环境: CPU-first | 标签: PyTorch, Tensor, dtype, device | 目标人群: 从 NumPy 进入 PyTorch 的学习者
🚀 云端运行环境
本章节的实战代码可以点击以下链接在免费 GPU 算力平台上直接运行:
本节导读
张量在数组之上增加了数据类型、设备、梯度和存储语义。形状相同的张量,仍可能因为数据类型、设备或共享关系不同而产生不同结果。
完成本节后,你应能读取张量的基本契约,区分复制与共享存储,并判断数值张量和索引张量是否满足输入要求。
关键词: tensor, dtype, device, storage sharing
前置阅读
导语: 先掌握 NumPy 的 shape 和广播,再观察 Tensor 新增的 dtype、device 与存储语义。
Q1:如何读取 Tensor 的 shape、dtype 与 device?
shape 描述结构,dtype 描述数值解释和单元素大小,device 描述数据所在计算位置。检查输入时应同时记录这三项,而不是只看打印出的数值。
import numpy as np
import torch
def describe_tensor(tensor):
"""返回 Tensor 的结构、数值类型、设备和单元素字节数。"""
if not isinstance(tensor, torch.Tensor):
raise TypeError('expected a torch.Tensor')
return {
'shape': tuple(tensor.shape),
'dtype': str(tensor.dtype).replace('torch.', ''),
'device': str(tensor.device),
'element_size_bytes': tensor.element_size(),
'requires_grad': tensor.requires_grad,
}
tensor = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)
print(describe_tensor(tensor))Q1 验证:结构与数值属性是否完整?
tensor = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)
description = describe_tensor(tensor)
assert description == {
'shape': (2, 2),
'dtype': 'float32',
'device': 'cpu',
'element_size_bytes': 4,
'requires_grad': False,
}
try:
describe_tensor(np.zeros(2))
except TypeError as error:
assert 'Tensor' in str(error)
else:
raise AssertionError('非 Tensor 输入应触发 TypeError')
print('✅ Tensor 属性通过')Q2:哪些构造方式会共享底层数据?
torch.tensor(array) 会复制输入;torch.from_numpy(array) 与 CPU 上兼容 dtype 的 torch.as_tensor(array) 通常共享 NumPy 内存;clone() 创建独立 Tensor。是否共享决定修改一侧时另一侧会不会变化,因此转换后不能只检查数值相等。
array = np.array([1.0, 2.0, 3.0], dtype=np.float32)
copied = torch.tensor(array)
from_numpy = torch.from_numpy(array)
as_tensor = torch.as_tensor(array)
cloned = from_numpy.clone()
array[0] = 99.0
print({
'numpy': array.tolist(),
'torch.tensor': copied.tolist(),
'from_numpy': from_numpy.tolist(),
'as_tensor': as_tensor.tolist(),
'clone': cloned.tolist(),
})Q2 验证:共享与复制关系是否符合预期?
array = np.array([1.0, 2.0, 3.0], dtype=np.float32)
copied = torch.tensor(array)
shared = torch.from_numpy(array)
wrapped = torch.as_tensor(array)
cloned = shared.clone()
array[1] = 77.0
assert copied[1].item() == 2.0
assert shared[1].item() == 77.0
assert wrapped[1].item() == 77.0
assert cloned[1].item() == 2.0
print('✅ Tensor 复制与共享语义通过')Q3:dtype 或 device 转换什么时候会复用原 Tensor?
.to() 在 dtype 和 device 都不变时通常返回原 Tensor;发生类型或设备变化时会创建新存储,copy=True 则可以显式要求复制。转换必须保持 shape 和数值语义,真实设备迁移还需要目标运行环境可用。
def convert_tensor(tensor, *, dtype=None, device=None, copy=False):
"""转换 Tensor 的 dtype/device;未变化时允许复用,copy=True 强制复制。"""
if not isinstance(tensor, torch.Tensor):
raise TypeError('expected a torch.Tensor')
target_dtype = tensor.dtype if dtype is None else dtype
target_device = tensor.device if device is None else device
return tensor.to(dtype=target_dtype, device=target_device, copy=copy)
source = torch.tensor([1.0, 2.0], dtype=torch.float32)
reused = convert_tensor(source)
converted = convert_tensor(source, dtype=torch.float64)
forced_copy = convert_tensor(source, copy=True)
print({
'reused_object': reused is source,
'converted_dtype': str(converted.dtype),
'forced_copy_storage': forced_copy.data_ptr() != source.data_ptr(),
})Q3 验证:复用、类型转换和强制复制是否保持各自语义?
source = torch.tensor([[1.0, 2.0]], dtype=torch.float32)
reused = convert_tensor(source)
converted = convert_tensor(source, dtype=torch.float64)
forced_copy = convert_tensor(source, copy=True)
assert reused is source
assert converted.shape == source.shape and converted.dtype == torch.float64
assert converted.data_ptr() != source.data_ptr()
assert forced_copy.shape == source.shape and forced_copy.data_ptr() != source.data_ptr()
assert torch.equal(forced_copy, source)
try:
convert_tensor([1.0, 2.0])
except TypeError as error:
assert 'Tensor' in str(error)
else:
raise AssertionError('非 Tensor 输入应触发 TypeError')
print('✅ dtype/device 转换语义通过')Q4:数值 Tensor 与索引 Tensor 的 dtype/device 契约有什么不同?
浮点 Tensor 参与模型计算;Embedding、查表和位置选择通常要求 torch.long 索引。参与同一算子的 Tensor 还要位于兼容 device。入口函数可以显式转换索引 dtype 和 device,但不应静默接受浮点小数索引。
def prepare_indices(indices, *, device):
"""将整数索引放到目标 device,并统一为 torch.long。"""
indices = torch.as_tensor(indices)
if indices.dtype.is_floating_point or indices.dtype == torch.bool:
raise TypeError('indices must contain integers')
return indices.to(device=device, dtype=torch.long)
embedding_table = torch.arange(5 * 4, dtype=torch.float32).reshape(5, 4)
indices = prepare_indices([3, 1, 4], device=embedding_table.device)
selected = embedding_table.index_select(0, indices)
print('indices:', indices.dtype, indices.device)
print('selected shape:', tuple(selected.shape))Q4 验证:索引 dtype、device 与输出 shape 是否正确?
table = torch.arange(20, dtype=torch.float32).reshape(5, 4)
indices = prepare_indices(np.array([3, 1], dtype=np.int32), device=table.device)
selected = table.index_select(0, indices)
assert indices.dtype == torch.long
assert indices.device == table.device
assert selected.shape == (2, 4)
assert torch.equal(selected[0], table[3])
try:
prepare_indices([1.5, 2.0], device=table.device)
except TypeError as error:
assert 'integers' in str(error)
else:
raise AssertionError('浮点索引应触发 TypeError')
print('✅ dtype、device 与索引契约通过')本节小结
- shape、dtype、device 和单元素字节数共同描述 Tensor 的基本契约。
torch.tensor、from_numpy、as_tensor与clone的复制/共享语义不同。.to()在条件不变时可以复用原 Tensor,类型或设备变化会形成新表示。- 索引 Tensor 应使用整数 dtype,并与被索引对象位于兼容设备。
相关阅读
掌握张量入口后,继续学习布局、索引和反向传播。
