文章目录
1. Torch类的基本操作
1.1 初始化
- PyTorch 中的张量(
torch.Tensor
)是构建深度学习模型的基础数据结构,它可以在 CPU 或 GPU 上运行,并支持自动梯度计算。
-
手动创建张量
a = torch.tensor([1, 2, 3], dtype=torch.float32) # 创建 1D 张量 b = torch.tensor([[1, 2], [3, 4]], dtype=torch.int64) # 创建 2D 张量 print(a) print(b)
-
随机生成张量
c = torch.rand((2, 3)) # 在 [0, 1) 均匀分布中生成随机数 d = torch.randn((2, 3)) # 生成标准正态分布随机数 print(c) print(d)
-
全零/全一张量
e = torch.zeros((3, 3)) # 创建全零张量 f = torch.ones((2, 2)) # 创建全一张量 print(e) print(f)
-
等差数列
g = torch.arange(0, 10, 2) # 生成从 0 到 10 步长为 2 的张量 print(g)
-
等比数列
h = torch.linspace(0, 1, steps=5) # 在 [0, 1] 区间等分生成 5 个数 print(h)
1.2 Shape 操作
PyTorch 张量支持灵活的形状操作,主要涉及获取张量形状、调整形状、扩展维度等操作。
-
获取形状
tensor = torch.randn((3, 4, 5)) print(tensor.shape) # 输出: torch.Size([3, 4, 5])
-
调整形状
reshaped = tensor.view(12, 5) # 调整为 (12, 5) reshaped_2 = tensor.reshape(-1, 20) # 自动推断第一个维度的大小 print(reshaped.shape) print(reshaped_2.shape)
-
扩展维度
a = torch.tensor([1, 2, 3]) expanded = a.unsqueeze(0) # 在第 0 维扩展 (1, 3) print(expanded.shape)
-
压缩维度
b = torch.tensor([[[1, 2, 3]]]) # (1, 1, 3) compressed = b.squeeze() # 压缩所有维度为 1 的轴 print(compressed.shape)
1.3 常用计算
PyTorch 支持张量的基本数学运算和高级线性代数操作。
-
元素级运算
a = torch.tensor([1, 2, 3], dtype=torch.float32) b = torch.tensor([4, 5, 6], dtype=torch.float32) print(a + b) # 加法 print(a * b) # 乘法 print(a / b) # 除法
-
矩阵运算
mat1 = torch.tensor([[1, 2], [3, 4]]) mat2 = torch.tensor([[5, 6], [7, 8]]) print(torch.matmul(mat1, mat2)) # 矩阵乘法 print(mat1 @ mat2) # 矩阵乘法(简写)
-
广播机制
张量的运算会自动应用广播机制,使形状不同的张量可以进行计算。a = torch.tensor([1, 2, 3]) # 形状: (3,) b = torch.tensor([[4], [5], [6]]) # 形状: (3, 1) result = a + b # 自动广播为 (3, 3) print(result)
-
统计操作
tensor = torch.tensor([[1, 2, 3], [4, 5, 6]]) print(torch.sum(tensor)) # 求和 print(torch.mean(tensor.float())) # 求平均值 print(torch.max(tensor)) # 求最大值 print(torch.argmax(tensor, dim=1)) # 求每行的最大值索引
-
索引与切片
tensor = torch.tensor([[1, 2, 3], [4, 5, 6]]) print(tensor[0, 1]) # 访问第 0 行第 1 列 print(tensor[:, 1]) # 取所有行的第 1 列
2. 梯度
梯度是 PyTorch 实现自动微分的核心功能之一,适用于 torch.Tensor
对象,它在深度学习中被广泛用于反向传播的计算。
2.1 梯度的基本概念
PyTorch 的张量可以通过设置 requires_grad=True
来跟踪其操作。每次对这些张量执行操作,PyTorch 会在后台构建计算图以记录操作历史,并自动计算梯度。
-
创建支持梯度的张量
x = torch.tensor([2.0, 3.0], requires_grad=True) y = x ** 2 + 3 * x print(y)
-
计算梯度
y.sum().backward() # 反向传播,自动计算梯度 print(x.grad) # 输出梯度值 # x.grad = dy/dx = [2*x_1 + 3, 2*x_2 + 3]
2.2 梯度绑定和分离
-
绑定梯度(默认行为)
当一个张量的requires_grad=True
,任何对它的操作都会记录在计算图中,计算图会追踪操作历史以便后续调用backward()
进行反向传播。a = torch.tensor(2.0, requires_grad=True) b = a ** 2 + 3 * a b.backward() print(a.grad) # 输出: 7.0
-
分离梯度(detach 方法)
使用detach()
方法可以从当前计算图中分离一个张量。分离后的张量不会记录其历史操作,因此也不会产生梯度。a = torch.tensor(2.0, requires_grad=True) b = a ** 2 + 3 * a c = b.detach() # 分离梯度 print(c) # 输出: 10.0 print(c.requires_grad) # False
-
梯度分离的应用场景
- 防止梯度传递到某些部分模型(冻结部分权重)。
- 提高性能,避免不必要的梯度计算和显存占用。
- 用于保存计算结果以进行后续分析,而无需跟踪梯度。
2.3 自动梯度机制的更多操作
-
清除梯度
在每次反向传播后,必须清除之前计算的梯度,以免梯度累加:x = torch.tensor(1.0, requires_grad=True) for i in range(3): y = x ** 2 y.backward() print(x.grad) # 梯度累加 x.grad.zero_() # 清除梯度
-
禁用梯度计算
在某些场景下(如推理阶段),我们不需要计算梯度,torch.no_grad()
可以显著提升性能并节省内存:x = torch.tensor(2.0, requires_grad=True) with torch.no_grad(): y = x ** 2 + 3 * x print(y.requires_grad) # False
-
梯度裁剪
在训练神经网络时,梯度可能会因为过大而导致数值不稳定。梯度裁剪可以限制梯度值的范围:torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=2.0)
2.4 torch.item() 和梯度的结合
在反向传播过程中,通常会用 item()
将张量的标量值转为 Python 原生数值(如 float
或 int
):
loss = torch.tensor(1.23, requires_grad=True)
print(loss.item()) # 输出: 1.23
- 应用场景
- 打印标量损失值或准确率,便于监控训练过程。
- 提高性能,因为
item()
返回的数值不再占用显存。
3. 梯度分离的应用场景
梯度分离在深度学习模型开发中有广泛的应用,尤其在需要控制梯度传播路径或者冻结部分模型参数时非常重要。
3.1 常见的梯度分离方法
-
detach()
方法从计算图中分离张量,避免梯度向后传播。分离后的张量不再追踪操作历史。x = torch.tensor(2.0, requires_grad=True) y = x ** 2 z = y.detach() # 分离 print(z.requires_grad) # False
-
torch.no_grad()
上下文临时关闭计算图,适用于推理阶段或冻结某部分操作的梯度。x = torch.tensor(2.0, requires_grad=True) with torch.no_grad(): y = x ** 2 print(y.requires_grad) # False
-
requires_grad=False
设置
冻结特定参数或张量,避免计算梯度。x = torch.tensor(2.0, requires_grad=True) x.requires_grad_(False) # 修改属性 print(x.requires_grad) # False
3.2 梯度分离的典型应用场景
-
冻结预训练模型参数在迁移学习中,我们通常会冻结预训练模型的参数,避免对其进行更新。
import torch.nn as nn from torchvision.models import resnet18 model = resnet18(pretrained=True) for param in model.parameters(): param.requires_grad = False # 冻结参数
-
停止梯度传播以稳定训练在计算损失的中间步骤中,某些分支可能不需要回传梯度。
# 示例:将辅助分支的输出分离 logits = model(x) aux_output = logits.detach() # 分离辅助输出 loss = main_loss(logits) + aux_loss(aux_output)
-
提取特征而不影响原模型当仅提取特征而不需要梯度时,
detach()
可以避免显存浪费和计算开销。features = feature_extractor(input).detach()
-
GAN 中的对抗训练在生成对抗网络 (GAN) 中,生成器和判别器之间的梯度需要有选择性地传播。例如:
- 更新生成器时,冻结判别器的梯度。
- 更新判别器时,冻结生成器的梯度。
real_loss = criterion(discriminator(real_data), real_labels) fake_loss = criterion(discriminator(generator(noise).detach()), fake_labels)
-
计算验证集指标
在模型验证或推理阶段,不需要计算梯度以节省计算资源:with torch.no_grad(): y_pred = model(x)
3.3 使用 detach() 提升效率的场景
在多任务学习或复杂的损失函数中,detach()
可显著减少不必要的梯度计算。
-
案例:多任务学习
task1_loss = loss_function1(task1_model(x)) task2_output = task2_model(x).detach() # 避免传播到 task2 task2_loss = loss_function2(task2_output) total_loss = task1_loss + task2_loss
-
案例:循环更新
x = torch.tensor([1.0], requires_grad=True) for i in range(3): y = x ** 2 z = y.detach() # 当前循环计算结束后,分离历史梯度 z.backward() print(x.grad) x.grad.zero_()
注意事项
-
分离后无法更新参数分离后的张量或参数不会参与梯度更新。例如:
param = torch.nn.Parameter(torch.tensor(1.0)) param.detach_() # 直接修改
-
性能和显存节省
使用detach()
或no_grad()
可以显著减少显存使用和加速推理。
4. 数据加载器的使用(详细讲解与代码示例)
PyTorch 的 DataLoader
是用来高效加载和迭代数据集的工具,特别是在深度学习中,常用于分批次读取数据、打乱数据等操作。
4.1 DataLoader 基本概念
DataLoader
是 PyTorch 中专门设计用于数据加载的类,它与 Dataset
紧密配合。Dataset 是数据的封装类,定义数据的获取逻辑;DataLoader 则在此基础上提供:
- 批量化数据(
batch_size
) - 打乱顺序(
shuffle
) - 多线程加载数据(
num_workers
) - 数据增强或转换(
collate_fn
)
4.2 基本用法
自定义 Dataset 与 DataLoader
from torch.utils.data import DataLoader, Dataset
import torch
# 1. 定义自定义数据集
class MyDataset(Dataset):
def __init__(self):
self.data = torch.arange(100) # 100 个样本
self.labels = torch.arange(100) % 2 # 标签为 0 或 1
def __len__(self): # 必须实现:返回数据集大小
return len(self.data)
def __getitem__(self, idx): # 必须实现:根据索引返回数据和标签
return self.data[idx], self.labels[idx]
# 2. 初始化数据集和数据加载器
dataset = MyDataset()
dataloader = DataLoader(dataset, batch_size=10, shuffle=True, num_workers=0)
# 3. 使用 DataLoader 遍历数据
for batch_idx, (data, labels) in enumerate(dataloader):
print(f"Batch {batch_idx}: Data={data}, Labels={labels}")
输出示例:
Batch 0: Data=tensor([78, 50, 62, 4, 79, 65, 42, 88, 98, 8]), Labels=tensor([0, 0, 0, 0, 1, 1, 0, 0, 0, 0])
Batch 1: Data=tensor([45, 92, 85, 56, 11, 27, 30, 63, 53, 18]), Labels=tensor([1, 0, 1, 0, 1, 1, 0, 1, 1, 0])
4.3 常见参数详解
参数名 | 功能 | 示例 |
---|---|---|
dataset | 数据集实例,需实现 __len__ 和 __getitem__ 方法 | dataset = MyDataset() |
batch_size | 每次加载的样本数,默认为 1 | batch_size=32 |
shuffle | 是否在每个 epoch 打乱数据,默认 False | shuffle=True |
num_workers | 使用多少个子线程加载数据,默认 0 | num_workers=4 |
drop_last | 如果最后一个批次样本数不足 batch_size ,是否丢弃,默认 False | drop_last=True |
pin_memory | 数据加载到固定内存(提高 GPU 数据传输效率),默认 False | pin_memory=True |
collate_fn | 自定义批处理逻辑,默认对 list[tuple] 按列聚合成 tuple[tensor] | 自定义的批处理逻辑(详见下文) |
4.4 数据加载器中的细节功能
1. 数据打乱(shuffle
)
# 启用 shuffle,在每次 epoch 重新打乱数据
dataloader = DataLoader(dataset, batch_size=10, shuffle=True)
# 禁用 shuffle,数据按顺序加载
dataloader = DataLoader(dataset, batch_size=10, shuffle=False)
2. 多线程加载数据(num_workers
)
- 默认情况下,数据加载在主线程进行(
num_workers=0
)。 - 设置
num_workers
后,数据加载会并行处理(适用于多核 CPU)。
dataloader = DataLoader(dataset, batch_size=10, num_workers=4)
注意:
- Windows 系统:建议
num_workers=0
(避免进程启动问题)。 - Linux 系统:推荐值是 CPU 核心数的一半左右。
3. 丢弃最后不完整批次(drop_last
)
# 启用 drop_last
dataloader = DataLoader(dataset, batch_size=12, drop_last=True)
4.5 数据集预处理与转换
PyTorch 支持在加载数据时对数据进行预处理或转换。
预处理(Preprocessing)
预处理通常在 Dataset
定义时完成,例如数据归一化、图像缩放等。
from torchvision import transforms
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,)) # 归一化
])
4.6 加载不同类型数据
1. 加载图像数据
使用 torchvision
中的内置数据集。
from torchvision import datasets, transforms
# 定义图像预处理
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
# 加载 MNIST 数据集
dataset = datasets.MNIST(root='data', train=True, transform=transform, download=True)
dataloader = DataLoader(dataset, batch_size=64, shuffle=True)
# 遍历数据
for images, labels in dataloader:
print(images.shape, labels.shape)
2. 加载 CSV 文件
import pandas as pd
class CSVDataset(Dataset):
def __init__(self, csv_file):
self.data = pd.read_csv(csv_file)
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
row = self.data.iloc[idx]
inputs = torch.tensor(row[:-1].values, dtype=torch.float32) # 特征
label = torch.tensor(row[-1], dtype=torch.long) # 标签
return inputs, label
dataset = CSVDataset('data.csv')
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
for inputs, labels in dataloader:
print(inputs, labels)
3. 加载大型数据集(按需加载)
对于大型数据集,可通过索引按需加载数据,避免内存不足。
class LargeDataset(Dataset):
def __init__(self, file_list):
self.file_list = file_list
def __len__(self):
return len(self.file_list)
def __getitem__(self, idx):
file_path = self.file_list[idx]
data = torch.load(file_path) # 按需加载数据
return data
file_list = ['file1.pt', 'file2.pt', 'file3.pt']
dataset = LargeDataset(file_list)
dataloader = DataLoader(dataset, batch_size=1)
4. 自定义批处理逻辑(collate_fn
)
默认情况下,DataLoader
会自动将 list[tuple]
转为 tuple[tensor]
,但有些场景需要自定义批处理逻辑(例如可变长度数据)。
def my_collate_fn(batch):
data, labels = zip(*batch)
return torch.stack(data), torch.tensor(labels)
dataloader = DataLoader(dataset, batch_size=32, collate_fn=my_collate_fn)
4.7 数据加载器的优化建议
- **设置合理的
batch_size
**根据 GPU 显存和任务需求调整批量大小(通常是 2 的幂次)。 - **使用
pin_memory
**如果使用 GPU 训练,启用pin_memory=True
提高内存到 GPU 的数据传输速度。 - 充分利用多核 CPU调整
num_workers
以加速数据加载。 - 预处理与缓存
对于静态预处理任务,尽量一次性完成并缓存到磁盘,避免每次运行重复计算。