张量基础#

张量(Tensor)是 PyTorch 中最基本的数据结构,类似于 NumPy 的数组,但可以在 GPU 上运行。理解张量是学习 PyTorch 的第一步。

什么是张量?#

张量是一个多维数组:

  • 0维张量:标量(scalar)
  • 1维张量:向量(vector)
  • 2维张量:矩阵(matrix)
  • 3维及以上:高阶张量

创建张量#

从 Python 列表创建#

import torch

# 创建一维张量
x = torch.tensor([1, 2, 3, 4])
print(x)

# 创建二维张量(矩阵)
y = torch.tensor([[1, 2], [3, 4]])
print(y)

# 创建三维张量
z = torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print(z)

使用构造函数创建#

# 创建全零张量
zeros = torch.zeros(3, 4)
print(zeros)

# 创建全一张量
ones = torch.ones(2, 3)
print(ones)

# 创建随机张量
rand = torch.rand(3, 3)  # 均匀分布 [0, 1)
print(rand)

# 创建正态分布随机张量
randn = torch.randn(3, 3)  # 标准正态分布
print(randn)

# 创建指定范围的张量
arange = torch.arange(0, 10, 2)  # [0, 10),步长为 2
print(arange)

# 创建线性空间
linspace = torch.linspace(0, 1, 5)  # [0, 1] 之间 5 个等间距点
print(linspace)

从 NumPy 数组创建#

import numpy as np

# 从 NumPy 数组创建
numpy_array = np.array([1, 2, 3])
tensor_from_numpy = torch.from_numpy(numpy_array)
print(tensor_from_numpy)

# 转换回 NumPy(仅限 CPU 张量)
numpy_from_tensor = tensor_from_numpy.numpy()
print(numpy_from_tensor)

重要提示torch.from_numpy().numpy() 创建的张量和 NumPy 数组共享内存,修改一个会影响另一个:

import numpy as np
import torch

# 共享内存的情况
numpy_array = np.array([[1, 2, 3], [4, 5, 6]])
tensor = torch.from_numpy(numpy_array)

# 修改 NumPy 数组
numpy_array[0, 0] = 100
print("修改后的 NumPy 数组:", numpy_array)
print("PyTorch 张量也会同步变化:", tensor)

# 修改张量
tensor[0, 0] = 200
print("修改后的张量:", tensor)
print("NumPy 数组也会同步变化:", numpy_array)

# 如果需要独立的数据,使用 clone()
tensor_independent = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)
numpy_independent = tensor_independent.clone().numpy()  # 使用 clone 复制数据
tensor_independent[0, 0] = 999
print("修改后的张量:", tensor_independent)
print("NumPy 数组(不会同步变化):", numpy_independent)

张量属性#

x = torch.randn(3, 4)

# 形状
print(f"形状: {x.shape}")
print(f"形状: {x.size()}")

# 维度数
print(f"维度数: {x.dim()}")

# 元素总数
print(f"元素总数: {x.numel()}")

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

# 设备(CPU 或 GPU)
print(f"设备: {x.device}")

# 是否需要梯度
print(f"requires_grad: {x.requires_grad}")

# 是否在 GPU 上
print(f"is_cuda: {x.is_cuda}")

# 是否连续存储
print(f"is_contiguous: {x.is_contiguous()}")

# 转置(仅适用于 2D 张量)
print(f"转置: {x.T}")

# 获取单元素张量的值
single = torch.tensor(42)
print(f"单元素值: {single.item()}")

张量操作#

索引和切片#

x = torch.randn(5, 4)

# 索引
print(x[0])        # 第一行
print(x[0, 1])    # 第一行第二列
print(x[:, 0])     # 第一列
print(x[1:3, :])   # 第2到3行
print(x[1:3, 1:3]) # 第2到3行,第2到3列

形状操作#

x = torch.randn(3, 4)

# 改变形状(不改变数据)
y = x.view(12)      # 展平为一维
z = x.view(2, 6)    # 改变为 2x6
w = x.view(-1, 2)   # -1 表示自动计算

# reshape(推荐使用,更灵活)
y = x.reshape(12)
z = x.reshape(2, 6)

# 展平
flattened = x.flatten()

# 转置
transposed = x.t()  # 只适用于 2D
permuted = x.permute(1, 0)  # 通用方法

# 增加维度
x_3d = x.unsqueeze(0)  # 在第0维增加维度,形状变为 (1, 3, 4)
x_3d = x.unsqueeze(1)  # 在第1维增加维度,形状变为 (3, 1, 4)

# 减少维度(去掉大小为1的维度)
x_2d = x_3d.squeeze(0)  # 去掉第0维
x_2d = x_3d.squeeze()   # 去掉所有大小为1的维度

数学运算#

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

# 加法
print(a + b)
print(torch.add(a, b))

# 减法
print(a - b)

# 乘法(元素级)
print(a * b)

# 除法
print(a / b)

# 矩阵乘法
x = torch.randn(3, 4)
y = torch.randn(4, 5)
z = torch.matmul(x, y)  # 或 x @ y
print(z.shape)  # (3, 5)

# 幂运算
print(a ** 2)

# 开方
print(torch.sqrt(a.float()))

统计操作#

x = torch.randn(3, 4)

# 求和
print(x.sum())
print(x.sum(dim=0))  # 沿第0维求和
print(x.sum(dim=1))  # 沿第1维求和

# 均值
print(x.mean())
print(x.mean(dim=0))

# 最大值和最小值
print(x.max())
print(x.min())
print(x.max(dim=1))  # 返回值和索引
values, indices = x.max(dim=1)  # 分别获取值和索引

# 最大值索引
print(torch.argmax(x, dim=1))  # 返回最大值的索引

# 标准差
print(x.std())

# 累积和
print(x.cumsum(dim=0))

# Softmax(常用于分类)
print(torch.softmax(x, dim=1))  # 沿第1维计算softmax

比较操作#

a = torch.tensor([1, 2, 3, 4])
b = torch.tensor([2, 2, 3, 5])

# 相等
print(a == b)

# 大于
print(a > b)

# 大于等于
print(a >= b)

# 不等于
print(a != b)

# 逻辑与/或
print((a > 2) & (b < 4))
print((a > 2) | (b < 4))

广播(Broadcasting)#

PyTorch 支持广播,允许不同形状的张量进行运算。广播机制可以自动扩展较小张量的维度,使其与较大张量兼容,从而进行元素级运算。

广播的条件#

两个张量可以广播,需要满足以下条件:

  1. 维度对齐:从右到左(从最后一个维度开始)比较两个张量的形状
  2. 维度兼容:对于每个维度,必须满足以下条件之一:
    • 两个维度的大小相等
    • 其中一个维度的大小为 1
    • 其中一个张量在该维度不存在(维度数较少)

如果所有维度都满足上述条件,则两个张量可以广播。

广播的规则#

广播遵循以下步骤:

  1. 维度补齐:如果两个张量的维度数不同,在维度数较少的张量前面补 1,使其维度数相同
  2. 维度扩展:对于每个维度,如果大小不匹配:
    • 如果其中一个维度大小为 1,则将该维度扩展为另一个张量对应维度的大小
    • 如果两个维度都不为 1 且不相等,则无法广播,会抛出错误

广播示例#

# 标量与张量
x = torch.randn(3, 4)
y = x + 1  # 每个元素加1

# 2. 添加偏置项
features = torch.randn(32, 128)  # 批量大小32,特征维度128
bias = torch.randn(128)          # 形状: (128,)
output = features + bias         # bias 被广播为 (32, 128)

# 3. 逐元素缩放
x = torch.randn(3, 4, 5)
scales = torch.randn(4, 1)      # 形状: (4, 1)
scaled = x * scales              # scales 被广播为 (3, 4, 5)

GPU 操作#

将张量移到 GPU#

# 检查 CUDA 是否可用
if torch.cuda.is_available():
    device = torch.device("cuda")
    
    # 创建时指定设备
    x = torch.randn(3, 4, device=device)
    
    # 或使用 .to() 方法
    x = torch.randn(3, 4)
    x = x.to(device)
    
    # 或使用 .cuda()
    x = x.cuda()
    
    # 移回 CPU
    x = x.cpu()

设备管理#

# 设置默认设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 创建张量时指定设备
x = torch.randn(3, 4, device=device)

常用技巧#

1. 类型转换#

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

# 转换为浮点数
x_float = x.float()
x_float = x.type(torch.FloatTensor)

# 转换为整数
x_int = x.int()
x_int = x.type(torch.IntTensor)

# 转换为双精度
x_double = x.double()

2. 内存共享#

x = torch.randn(3, 4)

# view 共享内存
y = x.view(12)
y[0] = 999
print(x[0, 0])  # 也会改变

# clone 创建副本
z = x.clone()
z[0, 0] = 888
print(x[0, 0])  # 不会改变

3. 拼接和分割#

# 拼接
a = torch.randn(2, 3)
b = torch.randn(2, 3)
c = torch.cat([a, b], dim=0)  # 沿 dim=0 拼接,结果 (4, 3)
d = torch.cat([a, b], dim=1)  # 沿 dim=1 拼接,结果 (2, 6)

# 堆叠(增加新维度)
e = torch.stack([a, b], dim=0)  # 结果 (2, 2, 3)

# 分割
x = torch.randn(6, 3)
chunks = torch.chunk(x, 3, dim=0)  # 分成3块
splits = torch.split(x, 2, dim=0)  # 每块2个元素

4. 条件判断和筛选#

x = torch.randn(3, 4)

# 创建布尔掩码
mask = x > 0.5
print("大于0.5的布尔掩码:", mask)

# 使用掩码筛选
filtered = x[x > 0.5]
print("大于0.5的元素:", filtered)

# 条件赋值
x[x > 0.5] = 1.0
print("条件赋值后:", x)

# 逻辑运算
mask1 = x > 0
mask2 = x < 1
combined = mask1 & mask2  # 逻辑与
combined = mask1 | mask2  # 逻辑或