PyTorch张量(Tensor)是深度学习中最基础的数据结构,简单理解就是多维数组。与NumPy数组类似,但PyTorch张量最大的优势是能够利用GPU加速计算,这使得它成为深度学习模型训练的首选数据结构。
创建张量有多种方法,下面介绍最常用的几种:
python复制import torch
# 方法1:从Python列表创建
data = [[1, 2], [3, 4]]
tensor = torch.tensor(data)
print(tensor)
# 方法2:创建指定形状的未初始化张量
empty_tensor = torch.Tensor(2, 3) # 2行3列
print(empty_tensor)
# 方法3:创建特定类型的张量
int_tensor = torch.IntTensor([1, 2, 3]) # 32位整数
float_tensor = torch.FloatTensor([1.0, 2.0, 3.0]) # 32位浮点数
在实际项目中,我经常使用torch.tensor()方法,因为它最直观且能明确控制数据类型。需要注意的是,torch.Tensor()(大写的T)是PyTorch的类构造函数,而torch.tensor()(小写的t)是工厂函数,两者在使用上有细微差别。
PyTorch提供了一些便捷函数来创建特殊张量:
python复制# 创建全零张量
zeros = torch.zeros(2, 3) # 2行3列的全零矩阵
print(zeros)
# 创建全一张量
ones = torch.ones(2, 3) # 2行3列的全一矩阵
print(ones)
# 创建单位矩阵
eye = torch.eye(3) # 3x3单位矩阵
print(eye)
# 创建随机张量
rand_tensor = torch.rand(2, 3) # 2行3列的均匀分布随机数[0,1)
print(rand_tensor)
# 创建正态分布随机张量
randn_tensor = torch.randn(2, 3) # 均值为0,方差为1的正态分布
print(randn_tensor)
在模型初始化时,我通常会使用torch.randn()来初始化权重,因为正态分布随机数在深度学习中有很好的理论支持。记得在需要可重复实验时设置随机种子:
python复制torch.manual_seed(42) # 设置随机种子
张量支持所有基本的算术运算,包括加减乘除:
python复制a = torch.tensor([1, 2, 3])
b = torch.tensor([4, 5, 6])
# 加法
print(a + b) # 等价于 torch.add(a, b)
print(a.add(b)) # 方法形式
# 减法
print(a - b) # 等价于 torch.sub(a, b)
# 乘法(逐元素相乘)
print(a * b) # 等价于 torch.mul(a, b)
# 除法
print(a / b) # 等价于 torch.div(a, b)
需要注意的是,这些运算都是逐元素(element-wise)进行的。在深度学习中,我们经常需要这种逐元素运算来调整张量值。
矩阵运算是深度学习中的核心操作:
python复制# 矩阵乘法(点积)
mat1 = torch.randn(2, 3)
mat2 = torch.randn(3, 4)
result = torch.mm(mat1, mat2) # 2x4矩阵
print(result)
# 批量矩阵乘法
batch1 = torch.randn(10, 3, 4)
batch2 = torch.randn(10, 4, 5)
result = torch.bmm(batch1, batch2) # 10x3x5
print(result.shape)
# 更通用的矩阵乘法
result = torch.matmul(mat1, mat2) # 与mm相同
print(result)
在实际项目中,我更喜欢使用torch.matmul(),因为它更通用,可以处理不同维度的张量。当处理批量数据时,torch.bmm()非常有用。
python复制tensor = torch.randn(4, 5)
# reshape/view:改变形状但不改变数据
reshaped = tensor.reshape(5, 4) # 变为5行4列
print(reshaped.shape)
# transpose:转置操作
transposed = tensor.t() # 对于2D张量,等价于transpose(0,1)
print(transposed.shape)
# permute:更灵活的多维转置
tensor3d = torch.randn(2, 3, 4)
permuted = tensor3d.permute(2, 0, 1) # 维度顺序变为原来的2,0,1
print(permuted.shape)
在使用view()时要注意,它要求张量在内存中是连续的(contiguous)。如果遇到错误,可以先调用contiguous()方法:
python复制non_contiguous = tensor.t() # 转置后通常不连续
contiguous = non_contiguous.contiguous() # 使其连续
viewed = contiguous.view(-1) # 现在可以安全使用view
python复制tensor = torch.randn(3, 4)
# 增加维度
unsqueezed = tensor.unsqueeze(0) # 在第0维增加1维,变为1x3x4
print(unsqueezed.shape)
# 减少维度(只能减少长度为1的维度)
squeezed = unsqueezed.squeeze(0) # 变回3x4
print(squeezed.shape)
在数据处理中,我经常使用unsqueeze()来添加batch维度(当batch_size=1时),或者调整维度顺序以适应不同层的输入要求。
PyTorch张量的索引方式与NumPy非常相似:
python复制tensor = torch.randn(3, 4)
# 基本索引
print(tensor[0]) # 第一行
print(tensor[:, 0]) # 第一列
# 切片
print(tensor[1:3, 0:2]) # 第1-2行,第0-1列
# 高级索引
rows = torch.tensor([0, 2])
cols = torch.tensor([1, 3])
print(tensor[rows, cols]) # (0,1)和(2,3)位置的元素
# 布尔索引
mask = tensor > 0
print(mask) # 布尔张量
print(tensor[mask]) # 只选择大于0的元素
在处理图像数据时,我经常使用切片操作来裁剪或选择特定区域。布尔索引在数据清洗和筛选时特别有用。
PyTorch的自动微分功能是其核心特性之一:
python复制# 创建一个需要计算梯度的张量
x = torch.tensor(2.0, requires_grad=True)
# 定义一个计算图
y = x ** 2 + 3 * x + 1
# 计算梯度
y.backward()
# 查看x的梯度
print(x.grad) # dy/dx = 2x + 3 = 7
在实际训练模型时,我们通常会在每个batch后清零梯度:
python复制optimizer.zero_grad() # 清零梯度
loss.backward() # 计算梯度
optimizer.step() # 更新参数
我曾经遇到过因为忘记清零梯度而导致训练不稳定的问题,所以现在总是特别注意这一点。
PyTorch张量和NumPy数组可以方便地相互转换:
python复制import numpy as np
# 张量转NumPy
tensor = torch.randn(2, 3)
numpy_array = tensor.numpy()
print(type(numpy_array))
# NumPy转张量
new_tensor = torch.from_numpy(numpy_array)
print(type(new_tensor))
需要注意的是,当张量在GPU上时,需要先将其移动到CPU:
python复制gpu_tensor = tensor.cuda()
cpu_tensor = gpu_tensor.cpu()
numpy_array = cpu_tensor.numpy()
PyTorch允许张量在CPU和GPU之间移动:
python复制# 检查是否有可用的GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 创建时指定设备
tensor = torch.randn(2, 3, device=device)
# 转移设备
cpu_tensor = tensor.to("cpu")
gpu_tensor = cpu_tensor.to("cuda")
在实际项目中,我通常会定义一个设备变量,然后统一使用.to(device)来管理张量的位置,这样代码在不同环境下都能运行。
保存和加载张量非常简单:
python复制# 保存张量
torch.save(tensor, "tensor.pt")
# 加载张量
loaded_tensor = torch.load("tensor.pt")
对于模型参数,我更喜欢使用state_dict()来保存:
python复制# 保存模型参数
torch.save(model.state_dict(), "model_weights.pth")
# 加载模型参数
model.load_state_dict(torch.load("model_weights.pth"))
这样保存的只是参数而不是整个模型,更加灵活,也便于在不同架构间共享参数。
python复制a = torch.tensor([1, 2, 3])
b = a.numpy() # a和b共享内存
a[0] = 100
print(b[0]) # 也会变成100
为了避免意外修改,可以使用clone()创建副本:
python复制a = torch.tensor([1, 2, 3])
b = a.clone().numpy() # 不共享内存
使用原地操作可以节省内存:
python复制tensor = torch.randn(2, 3)
# 非原地操作(创建新张量)
new_tensor = tensor + 1
# 原地操作(修改原张量)
tensor.add_(1) # 注意下划线后缀
在处理大型张量时,原地操作可以显著减少内存使用,但要注意它会改变原始数据。
有时我们需要临时禁用梯度计算:
python复制with torch.no_grad():
# 这里的操作不会跟踪梯度
y = x * 2
这在模型评估或推理时特别有用,可以减少内存消耗并提高速度。
float16代替float32可以节省内存在项目中,我发现合理设置torch.backends.cudnn.benchmark = True可以加速卷积运算,但会增加一些初始开销。对于固定大小的输入,这是个不错的选择。