2 GPU运算 自动微分 数据加载与处理

1 GPU 运算

函数 说明 用例
torch.cuda_is_available() 检验 CUDA 是否可用
torch.cuda.device_count() 检查可用 GPU 数量
torch.device() cpu: 使用 CPU 设备;
cuda: 只有一块 GPU 时启用它;
cuda:0: 使用第一块 GPU; 以此类推
.to(device) 将张量转移到某个设备[1] gpu = torch.device('cuda:0')
a_cpu = torch.tensor([1])
a_gpu = a_cpu.to(gpu)
del 删除指定的张量 del a_gpu
torch.cuda.empty_cache() 清空缓存

2 自动微分

自动微分 (Aotograd) 是 PyTorch 最重要的组件! 它能自动完成反向传播的链式求导.

2.1 使用

考虑下面的计算关系

z=cosx1+y,u=ln(2z+1),L=ux+y.
x = torch.tensor([torch.pi/3, requires_grad=True]) # 启用梯度追踪
y = torch.tensor([1.0]) # 不需要求导, 默认不启用
z = torch.cos(x) / (1 + y)
u = torch.log(2 * z + 1)
L = u * x + y

L.backward() # 反向传播, 计算梯度
print(x.grad)

多次调用 .backward() 会导致梯度累加, 因此每次更新参数需要手动清零梯度.
我们可以调用 optimizer.zero_grad() 来一次性清空, 或者 x.grad.zero_() 来手动清空.

2.2 detach

如果我们想复制一个张量, 并不让它追踪梯度、影响计算图里主张量的梯度计算, 需要用 detach(). 它会共享数据, 但剥离计算图. 通常我们仅仅是为了读取中间结果.

a = torch.tensor([1.0], requires_grad=True)
b = torch.tensor([2.0], requires_grad=True)
c = a * b

d = c.detach() # 创建剥离计算图的d, 共享数据[2.0], 但不参与计算图
e = d * 3 # 无法进行 e.backward()
d[0] = 10.0
print(c) # tensor([10.]), 数据也会同步改变

f = c ** 2
f.backward() # 原始计算图依然完整

2.3 no_grad

with torch.no_grad(): 内, 创建的张量都不会参与梯度计算. 用在验证集/测试集评估模型性能时.

x = torch.tensor([2.0], requires_grad=True)

with torch.no_grad():
    z = x ** 3
    print(z.requires_grad) # False

t = x ** 2
print(t.requires_grad) # True

3 数据加载与处理

3.1 DataSet

PyTorch 硬性规定要实现 __len__ (返回数据集的大小) 和 __getitem__ 索引 (必须返回 样本 标签 格式)

from torch.utils.data import Dataset

class MyDataset(Dataset):
    def __init__(self, data, labels):
        self.data = data
        self.labels = labels
        
    def __len__(self):
        return len(self.data)
    
    def __getitem__(self, idx):
        """根据索引idx返回样本及其标签"""
        sample = self.data[idx]
        label = self.labels[idx]
        return sample, label
        
# 用法: 准备data的tensor和label的tensor
dataset = MyDataset(mydata, mylabel)
print(dataset[2])
# (tensor([...]), tensor(..)) 是一个元组

3.2 DataLoader

相比 dataset, 实现批量读取. 具体来说它可以做三件事

from torch.utils.data import Dataset, DataLoader

class MyDataset(Dataset):
    # 和上面一样
    
data = torch.randn(200, 4)
labels = torch.randint(0, 2, (200,))

 dataset = Dataset(data, labels)
 # 创建dataloader
 dataloader = DataLoader(
     dataset,        #dataset实例
     batch_size=5,   #每批5个样本
     shuffle,        #每个epoch打乱数据
     num_workers=4,  #4个子进程加载数据
     drop_last=True, #丢弃最后不足batch_size的批次
     pin_memory=True,#有GPU时建议True, 加速CPU->GPU传输
 )
 
 # 用法: for循环遍历
 for batch_data, batch_labels in dataloader:
     print(batch_data, batch_data.shape)
     print(batch_labels, batch_labels.shape)

在 Windows 中, 一般会把 DataLoader 相关代码放入 if __name__ == '__main__' 中, 防止无限递归创建子进程.

if __name__ == '__main__':
    dataset = MyDataset(data, labels)
    dataloader = DataLoader(dataset, batch_size=128, shuffle=True, num_workers=4)
    for batch_data, batch_labels in dataloader:
        pass

3.3 划分数据集

random_split(dataset, lengths) 可用于将数据集划分为训练集和测试集.

from torch.utils.data import random_split

train_size = int(0.8 * len(dataset))
val_size = len(dataset) - train_size

train_set, val_set = random_split(dataset, [train_size, val_size])

然后我们为训练集、验证集创建 dataloader 即可 (验证集无需 shuffle).


  1. 为了提高读写效率, 尽量在数据初始化时就放在 GPU 上, 到输出结果、保存模型时才放回 CPU ↩︎