PyTorch基础小结

2022-10-05 14:09:14

        前面几篇整理了主要记录了PyTorch学习过程,详细目录如下:

        本节主要介绍初始化、训练与评估以及随机种子的使用,当然也会随着后面的学习,不断完善PyTorch的使用技巧。

1 权值初始化

1.1 初始化方法

  • 全零初始化:网络中不同的神经元有相同的输出,进行同样的参数更新;因此,这些神经元学到的参数都一样,等价于一个神经元。
  • 随机权值初始化,常采用权值采样自N(0,0.01) 的高斯分布
  • Xavier 初始化适合双曲正切激活函数,不适合ReLU函数
  • HE初始化(MSRA)

权值初始化小结

  • 好的初始化方法可以防止前向传播过程中的信息消失,也可以解决反向传递过程中的梯度消失。
  • 激活函数选择双曲正切或者Sigmoid时,建议使用Xaizer 初始化方法;
  • 激活函数选择ReLULeakly ReLU时,推荐使用He初始化方法。

1.2 torch.nn.init使用

        PyTorch也在torch.nn.init中为我们提供了常用的初始化方法。通过访问torch.nn.init的官方文档链接 ,我们发现torch.nn.init提供了以下初始化方法:

  1. torch.nn.init.uniform_(tensor, a=0.0, b=1.0)
  2. torch.nn.init.normal_(tensor, mean=0.0, std=1.0)
  3. torch.nn.init.constant_(tensor, val)
  4. torch.nn.init.ones_(tensor)
  5. torch.nn.init.zeros_(tensor)
  6. torch.nn.init.eye_(tensor)
  7. torch.nn.init.dirac_(tensor, groups=1)
  8. torch.nn.init.xavier_uniform_(tensor, gain=1.0)
  9. torch.nn.init.xavier_normal_(tensor, gain=1.0)
  10. torch.nn.init.kaiming_uniform_(tensor, a=0, mode=‘fan__in’, nonlinearity=‘leaky_relu’)
  11. torch.nn.init.kaiming_normal_(tensor, a=0, mode=‘fan_in’, nonlinearity=‘leaky_relu’)
  12. torch.nn.init.orthogonal_(tensor, gain=1)
  13. torch.nn.init.sparse_(tensor, sparsity, std=0.01)
  14. torch.nn.init.calculate_gain(nonlinearity, param=None)

        我们可以发现这些函数除了calculate_gain,所有函数的后缀都带有下划线,意味着这些函数将会直接原地更改输入张量的值。

1.3 初始化函数的封装

        定义为一个initialize_weights()的函数并在模型初始后进行使用。

definitialize_weights(self):for min self.modules():ifisinstance(m, nn.Conv2d):
            torch.nn.init.xavier_normal_(m.weight.data)if m.biasisnotNone:
                torch.nn.init.constant_(m.bias.data,0.3)elifisinstance(m, nn.Linear):
            torch.nn.init.normal_(m.weight.data,0.1)if m.biasisnotNone:
                torch.nn.init.zeros_(m.bias.data)elifisinstance(m, nn.BatchNorm2d):
            m.weight.data.fill_(1)
            m.bias.data.zeros_()

        这段代码流程是遍历当前模型的每一层,然后判断各层属于什么类型,然后根据不同类型层,设定不同的权值初始化方法。我们可以通过下面的例程进行一个简短的演示:

# 模型的定义classMLP(nn.Module):# 声明带有模型参数的层,这里声明了两个全连接层def__init__(self,**kwargs):# 调用MLP父类Block的构造函数来进行必要的初始化。这样在构造实例时还可以指定其他函数super(MLP, self).__init__(**kwargs)
    self.hidden= nn.Conv2d(1,1,3)
    self.act= nn.ReLU()
    self.output= nn.Linear(10,1)# 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出defforward(self, x):
    out= self.act(self.hidden(x))return self.output(out)

mlp= MLP()print(list(mlp.parameters()))print("-------初始化-------")

initialize_weights(mlp)print(list(mlp.parameters()))

2 训练与评估

        如果是训练状态,那么模型的参数应该支持反向传播的修改;如果是验证/测试状态,则不应该修改模型参数。在PyTorch中,模型的状态设置非常简便,如下的两个操作二选一即可:

model.train()# 训练状态
model.eval()# 验证、测试状态

        在DataLoader构建完成后介绍了如何从中读取数据,在训练过程中使用类似的操作即可,区别在于此时要用for循环读取DataLoader中的全部数据。

for data, labelin train_loader:

        之后将数据放到GPU上用于后续计算,此处以.cuda()为例

data, label= data.to(device), label.to(device)# 如果有GPU,可以使用data.cuda(),label.cuda()

        开始用当前批次数据做训练时,应当先将优化器的梯度置零:

optimizer.zero_grad()

        之后将data送入模型中训练:

output= model(data)

        根据预先定义的criterion计算损失函数:

loss= criterion(output, label)

        将loss反向传播回网络:

loss.backward()

        使用优化器更新模型参数:

optimizer.step()

        这样一个训练过程就完成了,后续还可以计算模型准确率等指标,这部分会在下一节的图像分类实战中加以介绍。

        验证/测试的流程基本与训练过程一致,不同点在于:

  • 需要预先设置torch.no_grad,以及将model调至eval模式
  • 不需要将优化器的梯度置零
  • 不需要将loss反向回传到网络
  • 不需要更新optimizer

        一个完整的图像分类的训练过程如下所示:

deftrain(epoch):
    model.train()
    train_loss=0for data, labelin train_loader:
        data, label= data.to(device), label.to(device)
        optimizer.zero_grad()
        output= model(data)
        loss= criterion(output, label)
        loss.backward()
        optimizer.step()
        train_loss+= loss.item()print('Epoch: {} \tTraining Loss: {:.4f}'.format(epoch, train_loss/len(train_loader)))

        对应的,一个完整图像分类的验证过程如下所示:

defval(epoch):
    model.eval()
    val_loss=0
    eval_acc=0for data, labelin test_loader:
        data, label= data.to(device), label.to(device)
        output= model(data)
        loss= criterion(output, label)
        _, pred= torch.max(output, dim=1)
        eval_loss+= loss.item()
        num_correct=(pred==label).sum().item()
        acc= num_correct/data.shape[0]
        eval_acc+= accprint('Epoch: {} \t Testing Loss: {:.4f} \t Testing Acc: {:.4f}'.format(epoch, eval_loss/len(test_loader), eval_acc/len(test_loader)))

3 使用技巧

3.1 随机种子

        在神经网络中,参数默认是进行随机初始化的。如果不设置的话每次训练时的初始化都是随机的,导致结果不确定。如果设置初始化,则每次初始化都是固定的。

import torchimport numpyas np

np.random.seed(1)
torch.manual_seed(1)# 为CPU设置种子用于生成随机数,以使得结果是确定的
torch.cuda.manual_seed(1)# 为当前GPU设置随机种子#如果使用多个GPU,应该使用torch.cuda.manual_seed_all()为所有的GPU设置种子。
torch.cuda.manual_seed_all(1)

        为什么使用相同的网络结构,跑出来的效果完全不同,用的学习率,迭代次数,batch size 都是一样?固定随机数种子是非常重要的。但是如果你使用的是PyTorch等框架,还要看一下框架的种子是否固定了。还有,如果你用了cuda,别忘了cuda的随机数种子。这里还需要用到torch.backends.cudnn.deterministic.

torch.backends.cudnn.deterministic是啥?顾名思义,将这个 flag 置为True的话,每次返回的卷积算法将是确定的,即默认算法。如果配合上设置 Torch 的随机种子为固定值的话,应该可以保证每次运行网络的时候相同输入的输出是固定的,代码大致这样

definit_seeds(seed=0):
    torch.cuda.manual_seed_all(seed)if seed==0:
        torch.backends.cudnn.deterministic=True
        torch.backends.cudnn.benchmark=False

3.2 显存设置

        Pytorch 训练时有时候会因为加载的东西过多而爆显存,有些时候这种情况还可以使用cuda的清理技术进行修整。

torch.cuda.empty_cache()

        PyTorch上限制GPU显存的函数

# 限制0号设备的显存的使用量为0.5,就是半张卡那么多,比如12G卡,设置0.5就是6G。
torch.cuda.set_per_process_memory_fracton(0.5,0)
torch.cuda.empty_cache()# 计算一下总内存有多少
total_memory= torch.cuda.get_device_paoperties(0).total_memory# 使用0.499的显存:
tmp_tensor= torch.empty(int(total_memory*0.499), dtype=torch.int8, device='cuda')# 清空该显存del tmp_tensor
torch.cuda.empty_cache()# 下面这句话会触发显存OOM错误,因为刚好触碰到了上限:
torch.empty(total_empty//2, dtype=torch.int8, device='cuda')"""
It raises an error as follows: 
RuntimeError: CUDA out of memory. Tried to allocate 5.59 GiB (GPU 0; 11.17 GiB total capacity; 0 bytes already allocated; 10.91 GiB free; 5.59 GiB allowed; 0 bytes reserved in total by PyTorch)
显存超标后,比不设置限制的错误信息多了一个提示,“5.59 GiB allowed;”
"""

        详细内容可以参考:PyTorch上进行GPU显存限制/切分的函数

补充
torch.cuda主要函数:

  • 查看是否有可用GPU、可用GPU数量:torch.cuda.is_available(),torch.cuda.device_count()
  • 查看当前使用的GPU序号:torch.cuda.current_device()
  • 查看指定GPU的容量、名称:torch.cuda.get_device_capability(device),torch.cuda.get_device_name(device)
  • 清空程序占用的GPU资源:torch.cuda.empty_cache()
  • 为GPU设置随机种子:torch.cuda.manual_seed(seed),torch.cuda.manual_seed_all(seed)
  • 作者:长路漫漫2021
  • 原文链接:https://blog.csdn.net/xq151750111/article/details/125286529
    更新时间:2022-10-05 14:09:14