Torch类的基本操作,数据预处理,数据加载

参考书籍

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
    
  • 梯度分离的应用场景

    1. 防止梯度传递到某些部分模型(冻结部分权重)。
    2. 提高性能,避免不必要的梯度计算和显存占用。
    3. 用于保存计算结果以进行后续分析,而无需跟踪梯度。

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 原生数值(如 floatint):

loss = torch.tensor(1.23, requires_grad=True)
print(loss.item())  # 输出: 1.23
  • 应用场景
    • 打印标量损失值或准确率,便于监控训练过程。
    • 提高性能,因为 item() 返回的数值不再占用显存。

3. 梯度分离的应用场景

梯度分离在深度学习模型开发中有广泛的应用,尤其在需要控制梯度传播路径或者冻结部分模型参数时非常重要。


3.1 常见的梯度分离方法
  1. detach() 方法从计算图中分离张量,避免梯度向后传播。分离后的张量不再追踪操作历史。

    x = torch.tensor(2.0, requires_grad=True)
    y = x ** 2
    z = y.detach()  # 分离
    print(z.requires_grad)  # False
    
  2. torch.no_grad() 上下文临时关闭计算图,适用于推理阶段或冻结某部分操作的梯度。

    x = torch.tensor(2.0, requires_grad=True)
    with torch.no_grad():
        y = x ** 2
    print(y.requires_grad)  # False
    
  3. requires_grad=False 设置
    冻结特定参数或张量,避免计算梯度。

    x = torch.tensor(2.0, requires_grad=True)
    x.requires_grad_(False)  # 修改属性
    print(x.requires_grad)  # False
    

3.2 梯度分离的典型应用场景
  1. 冻结预训练模型参数在迁移学习中,我们通常会冻结预训练模型的参数,避免对其进行更新。

    import torch.nn as nn
    from torchvision.models import resnet18
    
    model = resnet18(pretrained=True)
    for param in model.parameters():
        param.requires_grad = False  # 冻结参数
    
  2. 停止梯度传播以稳定训练在计算损失的中间步骤中,某些分支可能不需要回传梯度。

    # 示例:将辅助分支的输出分离
    logits = model(x)
    aux_output = logits.detach()  # 分离辅助输出
    loss = main_loss(logits) + aux_loss(aux_output)
    
  3. 提取特征而不影响原模型当仅提取特征而不需要梯度时,detach() 可以避免显存浪费和计算开销。

    features = feature_extractor(input).detach()
    
  4. GAN 中的对抗训练在生成对抗网络 (GAN) 中,生成器和判别器之间的梯度需要有选择性地传播。例如:

    • 更新生成器时,冻结判别器的梯度。
    • 更新判别器时,冻结生成器的梯度。
    real_loss = criterion(discriminator(real_data), real_labels)
    fake_loss = criterion(discriminator(generator(noise).detach()), fake_labels)
    
  5. 计算验证集指标
    在模型验证或推理阶段,不需要计算梯度以节省计算资源:

    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_()
    

注意事项
  1. 分离后无法更新参数分离后的张量或参数不会参与梯度更新。例如:

    param = torch.nn.Parameter(torch.tensor(1.0))
    param.detach_()  # 直接修改
    
  2. 性能和显存节省
    使用 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每次加载的样本数,默认为 1batch_size=32
shuffle是否在每个 epoch 打乱数据,默认 Falseshuffle=True
num_workers使用多少个子线程加载数据,默认 0num_workers=4
drop_last如果最后一个批次样本数不足 batch_size,是否丢弃,默认 Falsedrop_last=True
pin_memory数据加载到固定内存(提高 GPU 数据传输效率),默认 Falsepin_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 数据加载器的优化建议

  1. **设置合理的 batch_size**根据 GPU 显存和任务需求调整批量大小(通常是 2 的幂次)。
  2. **使用 pin_memory**如果使用 GPU 训练,启用 pin_memory=True 提高内存到 GPU 的数据传输速度。
  3. 充分利用多核 CPU调整 num_workers 以加速数据加载。
  4. 预处理与缓存
    对于静态预处理任务,尽量一次性完成并缓存到磁盘,避免每次运行重复计算。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梓仁沐白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值