PyTorch使用-张量数值计算
文章目录
- 张量数值计算
- 1. 张量基本运算
- 1.1. 基本运算函数—非 in-place 操作
- 1.2. 基本运算函数— in-place 操作
- 1.3. 关键区别
- 1.4. In-place 操作注意事项
- 1.4.1. 梯度跟踪:
- 1.4.2. 设备一致性:
- 1.4.3. 广播限制:
- 1.5. 使用建议
- 2. 阿达玛积
- 2.1. 阿达玛积定义
- 2.2. PyTorch 中的阿达玛积
- 2.2.1. 实现方式
- 2.2.2. 广播机制下的阿达玛积
- 2.3. 应用场景
- 2.3.1. 注意力机制:
- 2.3.2. 激活函数:
- 2.3.3. 图像处理:
- 2.4. 注意事项
- 2.4.1. 形状一致性:
- 2.4.2. 梯度计算:
- 2.4.3数据类型匹配:
- 2.5. 对比其他乘积
- 2.6. 代码示例
- 3. 点积运算
- 3.1. 点积运算规则
- 3.1.1矩阵乘法(点积)要求:
- 3.2. PyTorch 中的点积运算函数
- 3.2.1. @ 运算符
- 3.2.2. torch.mm
- 3.2.3. torch.bmm
- 3.2.4. torch.matmul
- 3.3. 形状匹配规则
- 3.3.1. 基础规则
- 3.3.2. 非法形状示例
- 3.4. 函数对比表
- 3.5. 高阶示例
- 3.5.1. 混合维度乘法
- 3.5.2. 广播机制
- 3.6. 常见错误及解决
- 3.6.1. 维度不匹配
- 3.6.2. 误用 torch.bmm
- 3.7. 总结
- 4.指定运算设备
- 4.1. 检查 GPU 可用性
- 4.2. 将张量移动到 GPU 的方法
- 4.2.1. 使用 .cuda() 方法
- 4.2.2. 直接在 GPU 上创建张量
- 4.2.3. 使用 .to() 方法
- 4.3. 关键操作对比
- 4.4. 注意事项
- 4.4.1. 设备一致性
- 4.4.2. 内存管理
- 4.4.3. 梯度跟踪
- 4.5. 完整代码示例
- 4.6. 最佳实践
张量数值计算
PyTorch 是一个深度学习框架,张量是其核心数据结构,用于表示多维数组。张量支持丰富的数学运算,如加法、乘法、矩阵运算等,且能在 CPU 和 GPU 上高效计算。GPU 加速可以大大提高大规模数据处理和深度学习模型训练的速度。
1. 张量基本运算
PyTorch 中张量基本运算包括 add、sub、mul、div、neg 及其带下划线的 in-place 版本。
操作 | 函数 | In-place 版本 | 用途 |
---|---|---|---|
加法 | add() | add_() | 元素级相加 |
减法 | sub() | sub_() | 元素级相减 |
乘法 | mul() | mul_() | 元素级相乘 |
除法 | div() | div_() | 元素级相除 |
取负 | neg() | neg_() | 元素级取负数 |
1.1. 基本运算函数—非 in-place 操作
这些操作会 返回新的张量,原始张量保持不变
import torch
a = torch.tensor([1, 2, 3])
b = torch.tensor([4, 5, 6])
# 加法
c_add = torch.add(a, b) # 或直接使用 c_add = a + b
# 减法
c_sub = torch.sub(a, b) # 或 c_sub = a - b
# 乘法(元素级)
c_mul = torch.mul(a, b) # 或 c_mul = a * b
# 除法(元素级)
c_div = torch.div(a, b) # 或 c_div = a / b
# 取负数
c_neg = torch.neg(a) # 或 c_neg = -a
1.2. 基本运算函数— in-place 操作
带下划线的函数(如 add_)会 直接修改原始张量,不返回新张量:
a = torch.tensor([1, 2, 3])
b = torch.tensor([4, 5, 6])
a.add_(b) # a 变为 [5, 7, 9]
a.sub_(b) # a 变为 [1, 2, 3](等价于 a = a - b)
a.mul_(2) # a 变为 [2, 4, 6]
a.div_(2) # a 变为 [1, 2, 3]
a.neg_() # a 变为 [-1, -2, -3]
1.3. 关键区别
操作类型 | 示例 | 是否修改原数据 | 返回值 | 适用场景 |
---|---|---|---|---|
非 in-place | a.add(b) | 否 | 新张量 | 需要保留原始数据时 |
in-place | a.add_(b) | 是 | 修改后的原张量 | 节省内存,但可能破坏梯度计算 |
1.4. In-place 操作注意事项
1.4.1. 梯度跟踪:
对 requires_grad=True 的张量执行 in-place 操作会破坏梯度计算,可能导致错误:
x = torch.tensor(2.0, requires_grad=True)
y = x**2
x.add_(1) # 错误!会破坏 x 的梯度跟踪
1.4.2. 设备一致性:
操作张量必须在同一设备上(CPU 或 GPU):
a = a.cuda()
b = b.cpu()
a.add_(b) # 错误!设备不一致
1.4.3. 广播限制:
In-place 操作不支持广播机制中的维度扩展:
a = torch.ones(3, 1)
b = torch.ones(1, 3)
a.add_(b) # 错误!形状不匹配且无法广播
1.5. 使用建议
优先使用非 in-place 操作:避免因修改原数据导致的梯度计算错误。
谨慎使用 in-place 操作:
当明确知道操作不会影响梯度时(例如预处理数据)。
在内存受限时(如大模型训练)节省内存。
避免在自动求导中使用:PyTorch 的自动微分机制(Autograd)依赖张量历史,in-place 操作会破坏历史记录。
2. 阿达玛积
在深度学习和线性代数中,阿达玛积(Hadamard Product)是一种重要的元素级运算,以下是其核心概念及 PyTorch 实现详解:
2.1. 阿达玛积定义
数学表示:若矩阵A 和 B形状相同(均为 m×n),则它们的阿达玛积 A⊙B 定义为:
即对应位置元素相乘,结果仍为 m×n的矩阵。
与矩阵乘法的区别:
阿达玛积: 元素级操作,形状严格相同。
矩阵乘法(点积):线性代数操作,要求 A 的列数 = B 的行数,结果形状为(A的行数,B的列数)。
2.2. PyTorch 中的阿达玛积
2.2.1. 实现方式
在 PyTorch 中,阿达玛积可以通过以下方法实现:
import torch
# 创建相同形状的张量
A = torch.tensor([[1, 2], [3, 4]]) # shape: (2, 2)
B = torch.tensor([[5, 6], [7, 8]]) # shape: (2, 2)
# 方法1:使用 * 运算符
C = A * B # tensor([[5, 12], [21, 32]])
# 方法2:使用 torch.mul()
D = torch.mul(A, B) # 与 C 结果相同
2.2.2. 广播机制下的阿达玛积
当张量形状不同但满足广播规则时,PyTorch 会自动扩展维度后执行元素相乘:
A = torch.tensor([[1, 2], [3, 4]]) # shape: (2, 2)
B = torch.tensor([10, 20]) # shape: (2,)
# 广播后 B 扩展为 [[10, 20], [10, 20]]
C = A * B # tensor([[10, 40], [30, 80]])
2.3. 应用场景
2.3.1. 注意力机制:
在 Transformer 中,阿达玛积用于计算注意力权重与值的加权求和。
attention_weights = torch.softmax(scores, dim=-1) # 形状: (batch, seq_len, seq_len)
weighted_values = attention_weights * values # 形状: (batch, seq_len, d_model)
2.3.2. 激活函数:
如门控线性单元(GLU)中使用阿达玛积控制信息流:
gate = torch.sigmoid(linear(x)) # 形状: (batch, hidden_dim)
output = gate * x # 形状: (batch, hidden_dim)
2.3.3. 图像处理:
对图像掩码(如 ROI 区域)进行元素级过滤:
masked_image = image * mask # mask 为 0-1 的二值张量
2.4. 注意事项
2.4.1. 形状一致性:
非广播情况下,输入张量必须形状一致,否则会报错:
A = torch.rand(2, 3)
B = torch.rand(3, 2)
C = A * B # 报错:形状不匹配
2.4.2. 梯度计算:
若张量需要梯度(requires_grad=True),避免使用 *_() 的 in-place 操作,可能导致梯度中断:
A = torch.tensor([1.0], requires_grad=True)
B = torch.tensor([2.0])
A.mul_(B) # 错误!破坏计算图
2.4.3数据类型匹配:
确保张量数据类型一致(如 float32 与 int64 无法直接相乘):
A = torch.tensor([1, 2], dtype=torch.int32)
B = torch.tensor([3.0, 4.0])
C = A * B # 错误!数据类型不匹配
2.5. 对比其他乘积
操作类型 | 符号 | 输入要求 | 输出形状 | 示例 |
---|---|---|---|---|
阿达玛积 | * | 同形状或可广播 | 与输入同形状 | A * B |
矩阵乘法 | @ | A 列数 = B | 行数 | (A行数, B列数) |
点积(向量) | torch.dot | 一维向量 | 标量 | torch.dot(v1, v2) |
外积 | torch.outer | 一维向量 | (len(v1), len(v2)) | torch.outer(v1, v2) |
2.6. 代码示例
import torch
# 示例1:严格形状匹配
A = torch.tensor([[1, 2], [3, 4]])
B = torch.tensor([[5, 6], [7, 8]])
hadamard = A * B # tensor([[ 5, 12], [21, 32]])
# 示例2:广播机制
C = torch.tensor([[1, 2]]) # shape: (1, 2)
D = torch.tensor([[3], [4]]) # shape: (2, 1)
hadamard_broadcast = C * D # tensor([[3, 6], [4, 8]])
# 示例3:梯度计算
x = torch.tensor([2.0], requires_grad=True)
y = torch.tensor([3.0])
z = x * y # 非 in-place 操作
z.backward()
print(x.grad) # 输出: tensor([3.])
3. 点积运算
点积运算要求第一个矩阵 shape: (n, m),第二个矩阵 shape: (m, p), 两个矩阵点积运算 shape 为: (n, p)。
3.1. 点积运算规则
3.1.1矩阵乘法(点积)要求:
输入形状:
第一个矩阵:(n, m)
第二个矩阵:(m, p)
输出形状:
结果矩阵:(n, p)
核心操作:
3.2. PyTorch 中的点积运算函数
3.2.1. @ 运算符
功能:执行矩阵乘法,等同于 torch.matmul。
灵活性:支持多维张量,自动处理广播。
示例:
A = torch.randn(2, 3) # shape: (2, 3)
B = torch.randn(3, 4) # shape: (3, 4)
C = A @ B # shape: (2, 4)
3.2.2. torch.mm
功能:严格处理 二维矩阵乘法。
限制:输入必须为 2D 张量。
示例:
A = torch.randn(2, 3) # shape: (2, 3)
B = torch.randn(3, 4) # shape: (3, 4)
C = torch.mm(A, B) # shape: (2, 4)
3.2.3. torch.bmm
功能:批量处理 三维张量的矩阵乘法。
输入要求:
输入1:(batch_size, n, m)
输入2:(batch_size, m, p)
输出形状:(batch_size, n, p)
示例:
batch_size = 5
A = torch.randn(batch_size, 2, 3) # shape: (5, 2, 3)
B = torch.randn(batch_size, 3, 4) # shape: (5, 3, 4)
C = torch.bmm(A, B) # shape: (5, 2, 4)
3.2.4. torch.matmul
功能:通用矩阵乘法,支持 多维张量和广播。
输入灵活性:
支持不同维度的张量(如 2D 与 3D)。
最后两维必须满足矩阵乘法规则。
示例:
# 二维输入(等同 torch.mm)
A = torch.randn(2, 3)
B = torch.randn(3, 4)
C = torch.matmul(A, B) # shape: (2, 4)
# 三维输入(等同 torch.bmm)
A = torch.randn(5, 2, 3)
B = torch.randn(5, 3, 4)
C = torch.matmul(A, B) # shape: (5, 2, 4)
# 广播机制(不同维度)
A = torch.randn(2, 5, 3) # shape: (2, 5, 3)
B = torch.randn(3, 4) # shape: (3, 4)
C = torch.matmul(A, B) # shape: (2, 5, 4)(自动广播 B 到 (2, 3, 4))
3.3. 形状匹配规则
3.3.1. 基础规则
最后两维必须满足矩阵乘法:
前一个张量的列数 = 后一个张量的行数。
合法示例:
A: (3, 4), B: (4, 5) → Output: (3, 5) # 标准乘法
A: (5, 3, 4), B: (4, 2) → Output: (5, 3, 2) # 广播 B 到 (5, 4, 2)
3.3.2. 非法形状示例
错误原因:最后两维不匹配。
A = torch.randn(2, 3)
B = torch.randn(4, 5)
C = torch.mm(A, B) # 报错:3 != 4
3.4. 函数对比表
函数 | 输入维度 | 广播支持 | 典型场景 |
---|---|---|---|
@/torch.matmul | 任意维度(需满足最后两维规则) | 是 | 通用多维张量乘法,支持广播 |
torch.mm | 严格 2D | 否 | 简单二维矩阵乘法 |
torch.bmm | 严格 3D | 否 | 批量处理固定批次的矩阵乘法 |
3.5. 高阶示例
3.5.1. 混合维度乘法
# 输入1: 4D 张量 (2, 5, 3, 4)
# 输入2: 3D 张量 (4, 6)
A = torch.randn(2, 5, 3, 4)
B = torch.randn(4, 6)
C = torch.matmul(A, B) # 输出形状: (2, 5, 3, 6)
3.5.2. 广播机制
# 输入1: (3, 1, 4)
# 输入2: (5, 4, 2)
A = torch.randn(3, 1, 4)
B = torch.randn(5, 4, 2)
C = torch.matmul(A, B) # 输出形状: (3, 5, 1, 2)
3.6. 常见错误及解决
3.6.1. 维度不匹配
A = torch.randn(2, 3)
B = torch.randn(4, 5)
try:
C = torch.mm(A, B) # 报错:3 != 4
except RuntimeError as e:
print("错误:", e)
3.6.2. 误用 torch.bmm
A = torch.randn(2, 3, 4) # shape: (2, 3, 4)
B = torch.randn(3, 4, 5) # shape: (3, 4, 5)
try:
C = torch.bmm(A, B) # 报错:批次维度 2 != 3
except RuntimeError as e:
print("错误:", e)
3.7. 总结
优先使用 @ 或 torch.matmul:灵活支持多维张量和广播。
明确场景选择函数:简单 2D 乘法 → torch.mm;固定批次 3D 乘法 → torch.bmm
形状检查:始终确保最后两维满足矩阵乘法规则。
4.指定运算设备
在 PyTorch 中,指定运算设备(CPU 或 GPU)是优化计算效率的关键步骤。以下是 将张量移动至 GPU 的三种方法,以及相关注意事项的详细说明:
4.1. 检查 GPU 可用性
在操作设备前,需确保当前环境支持 GPU:
import torch
# 检查 CUDA 是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"当前设备: {device}")
# 输出示例(若有 GPU):
# 当前设备: cuda:0
4.2. 将张量移动到 GPU 的方法
4.2.1. 使用 .cuda() 方法
功能:将 CPU 张量复制到 GPU(默认选择第一个 GPU)。
示例:
tensor_cpu = torch.tensor([1, 2, 3])
tensor_gpu = tensor_cpu.cuda() # 移动到默认 GPU(索引 0)
tensor_gpu_1 = tensor_cpu.cuda(device=1) # 移动到指定 GPU(索引 1)
注意:若未安装 CUDA 驱动,调用 .cuda() 会报错。
4.2.2. 直接在 GPU 上创建张量
方法:通过 device 参数指定设备。
示例:
# 直接创建在 GPU 上
tensor_gpu = torch.tensor([1, 2, 3], device="cuda")
# 使用工厂函数(如 zeros, randn)指定设备
ones_gpu = torch.ones(3, 3, device="cuda")
4.2.3. 使用 .to() 方法
功能:灵活转移设备,支持同时指定数据类型。
示例:
tensor_cpu = torch.randn(2, 2)
# 移动到 GPU
tensor_gpu = tensor_cpu.to(device="cuda")
# 同时转换数据类型和设备
tensor_gpu_float16 = tensor_cpu.to(device="cuda", dtype=torch.float16)
4.3. 关键操作对比
方法 | 语法 | 优点 缺点 |
---|---|---|
.cuda() | tensor.cuda(device=idx) | 简单直接,适合单 GPU 需手动指定设备索引 |
直接创建 | device=“cuda” | 避免数据复制,效率高 需提前规划设备分配 |
.to() | tensor.to(device) | 支持设备+类型转换,代码通用性强 语法稍复杂 |
4.4. 注意事项
4.4.1. 设备一致性
所有参与运算的张量必须在同一设备上:
a = torch.tensor([1], device="cuda")
b = torch.tensor([2], device="cpu")
c = a + b # 报错:不同设备的张量无法运算
4.4.2. 内存管理
显存释放:GPU 显存不会自动释放,需手动清理或使用上下文管理器:
with torch.cuda.device(0): # 在指定 GPU 上操作
tensor = torch.randn(3, 3, device="cuda")
# 退出上下文后显存可能被释放(依赖垃圾回收)
4.4.3. 梯度跟踪
保持计算图连续性:移动带梯度的张量时,使用 .to() 而非 .cuda() 以保留梯度历史:
x = torch.tensor(2.0, requires_grad=True)
x_gpu = x.to("cuda") # 保留梯度跟踪
y = x_gpu**2
y.backward()
4.5. 完整代码示例
import torch
# 检查 CUDA 可用性
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"当前设备: {device}")
# 方法1:使用 .cuda()
tensor_cpu = torch.ones(2, 2)
tensor_gpu = tensor_cpu.cuda() # 移动到默认 GPU
# 方法2:直接在 GPU 创建
tensor_gpu_direct = torch.randn(3, 3, device="cuda")
# 方法3:使用 .to()
tensor_gpu_to = tensor_cpu.to(device="cuda", dtype=torch.float16)
# 验证设备
print(tensor_gpu.device) # 输出: cuda:0
print(tensor_gpu_direct.device) # 输出: cuda:0
print(tensor_gpu_to.device) # 输出: cuda:0
# 错误示例:设备不一致
try:
a = torch.tensor([1], device="cuda")
b = torch.tensor([2], device="cpu")
c = a + b
except RuntimeError as e:
print("错误信息:", e)
4.6. 最佳实践
统一设备管理:
# 全局定义设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 模型与数据统一迁移
model = MyModel().to(device)
data = data.to(device)
避免频繁设备切换:减少 CPU-GPU 数据传输开销。
多 GPU 训练:使用 torch.nn.DataParallel 或 torch.distributed 进行分布式训练。