ch07-Pytorch的训练技巧

news2025/1/23 12:07:48

ch07-Pytorch的训练技巧

    • 0.引言
    • 1.模型保存与加载
      • 1.1.序列化与反序列化
      • 1.2.PyTorch 中的模型保存与加载
      • 1.3.模型的断点续训练
    • 2.模型 Finetune
      • 2.1.Transfer Learning & Model Finetune
      • 2.2.PyTorch中的Finetune
    • 3.使用 GPU 训练模型
      • 3.1.CPU与GPU
      • 3.2.数据迁移至GPU
      • 3.3. 多 GPU 的分发并行
    • 4.pytorch常见报错

0.引言

1.模型保存与加载

本节主要介绍序列化与反序列化,以及 PyTorch 中的模型保存于加载的两种方式,模型的断点续训练。

1.1.序列化与反序列化

模型在内存中是以对象的逻辑结构保存的,但是在硬盘中是以二进制流的方式保存的。

  • 序列化是指将内存中的数据以二进制序列的方式保存到硬盘中。PyTorch 的模型保存就是序列化。
  • 反序列化是指将硬盘中的二进制序列加载到内存中,得到模型的对象。PyTorch 的模型加载就是反序列化。

在这里插入图片描述

1.2.PyTorch 中的模型保存与加载

  • 参考

在这里插入图片描述

(1) 模型保存torch.save

  • torch.save(obj, f, pickle_module, pickle_protocol=2, _use_new_zipfile_serialization=False)
  • 主要参数:
    • obj:保存的对象,可以是模型。也可以是 dict。因为一般在保存模型时,不仅要保存模型,还需要保存优化器、此时对应的 epoch 等参数。这时就可以用 dict 包装起来。
    • f:输出路径

其中模型保存还有两种方式:

  • 保存整个 Module:这种方法比较耗时,保存的文件大:torch.savev(net, path)
  • 只保存模型的参数:推荐这种方法,运行比较快,保存的文件比较小
    state_sict = net.state_dict()
    torch.savev(state_sict, path)
    

下面是保存 LeNet 的例子。在网络初始化中,把权值都设置为 2020,然后保存模型。

import torch
import numpy as np
import torch.nn as nn
from common_tools import set_seed

class LeNet2(nn.Module):
    def __init__(self, classes):
        super(LeNet2, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 6, 5),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(6, 16, 5),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )
        self.classifier = nn.Sequential(
            nn.Linear(16*5*5, 120),
            nn.ReLU(),
            nn.Linear(120, 84),
            nn.ReLU(),
            nn.Linear(84, classes)
        )

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size()[0], -1)
        x = self.classifier(x)
        return x

    def initialize(self):
        for p in self.parameters():
            p.data.fill_(2020)

net = LeNet2(classes=2019)
# "训练"
print("训练前: ", net.features[0].weight[0, ...])
net.initialize()
print("训练后: ", net.features[0].weight[0, ...])

path_model = "./model.pkl"
path_state_dict = "./model_state_dict.pkl"
# 保存整个模型
torch.save(net, path_model)
# 保存模型参数
net_state_dict = net.state_dict()
torch.save(net_state_dict, path_state_dict)

运行完之后,文件夹中生成了model.pkl和model_state_dict.pkl,分别保存了整个网络和网络的参数

(2) 模型加载torch.load

  • torch.load(f, map_location=None, pickle_module, **pickle_load_args)
  • 主要参数:
    • f:文件路径
    • map_location:指定存在 CPU 或者 GPU。

加载模型也有两种方式:

  • 加载整个 Module

如果保存的时候,保存的是整个模型,那么加载时就加载整个模型。这种方法不需要事先创建一个模型对象,也不用知道模型的结构,代码如下:

path_model = "./model.pkl"
net_load = torch.load(path_model)

print(net_load)

输出如下:

LeNet2(
  (features): Sequential(
    (0): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
    (1): ReLU()
    (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (3): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
    (4): ReLU()
    (5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (classifier): Sequential(
    (0): Linear(in_features=400, out_features=120, bias=True)
    (1): ReLU()
    (2): Linear(in_features=120, out_features=84, bias=True)
    (3): ReLU()
    (4): Linear(in_features=84, out_features=2019, bias=True)
  )
)
  • 只加载模型的参数

如果保存的时候,保存的是模型的参数,那么加载时就参数。这种方法需要事先创建一个模型对象,再使用模型的load_state_dict()方法把参数加载到模型中,代码如下:

path_state_dict = "./model_state_dict.pkl"
state_dict_load = torch.load(path_state_dict)
net_new = LeNet2(classes=2019)

print("加载前: ", net_new.features[0].weight[0, ...])
net_new.load_state_dict(state_dict_load)
print("加载后: ", net_new.features[0].weight[0, ...])

1.3.模型的断点续训练

在训练过程中,可能由于某种意外原因如断点等导致训练终止,这时需要重新开始训练。断点续练是在训练过程中每隔一定次数的 epoch 就保存模型的参数和优化器的参数,这样如果意外终止训练了,下次就可以重新加载最新的模型参数和优化器的参数,在这个基础上继续训练。

下面的代码中,每隔 5 个 epoch 就保存一次,保存的是一个 dict,包括模型参数、优化器的参数、epoch。然后在 epoch 大于 5 时,就break模拟训练意外终止。关键代码如下:

    if (epoch+1) % checkpoint_interval == 0:

        checkpoint = {"model_state_dict": net.state_dict(),
                      "optimizer_state_dict": optimizer.state_dict(),
                      "epoch": epoch}
        path_checkpoint = "./checkpoint_{}_epoch.pkl".format(epoch)
        torch.save(checkpoint, path_checkpoint)

在 epoch 大于 5 时,就break模拟训练意外终止

    if epoch > 5:
        print("训练意外中断...")
        break

断点续训练的恢复代码如下:

path_checkpoint = "./checkpoint_4_epoch.pkl"
checkpoint = torch.load(path_checkpoint)

net.load_state_dict(checkpoint['model_state_dict'])

optimizer.load_state_dict(checkpoint['optimizer_state_dict'])

start_epoch = checkpoint['epoch']

scheduler.last_epoch = start_epoch

需要注意的是,还要设置scheduler.last_epoch参数为保存的 epoch。模型训练的起始 epoch 也要修改为保存的 epoch。

2.模型 Finetune

  • m本节主要任务:了解transfer learning 与 model finetune

  • 详细介绍:学习模型微调(Finetune)的方法,以及认识Transfer Learning(迁移学习)与Model Finetune之间的关系。

2.1.Transfer Learning & Model Finetune

迁移学习:机器学习分支,研究源域(source domain)的知识如何应用到目标域(target domain)。

在这里插入图片描述

所谓的模型微调,其实就是模型的迁移学习,在深度学习中,通过不断的迭代,更新卷基层中的权值,这里的权值可以称之为 knowledge , 然后我们可以将这些 knowledge 进行迁移,主要目的是将这些 knowledge 运用到新的模型中,这样既可以减小由于数据量不足导致的过拟合现象,同时又能加快模型的训练速度。

在这里插入图片描述

比如做人脸识别,可以把 ImageNet 看作 source domain,人脸数据集看作 target domain。通常来说 source domain 要比 target domain 大得多。可以利用 ImageNet 训练好的网络应用到人脸识别中。

具体说来,对于卷积神经网络,我们可以把前面的卷基层,池化层看作是 feature extactor(特征提取) ,是一个非常有共性的部分。得到一系列的feature map。
而后面的全连接层,可以称之为 classifier (分类器), 与具体的任务有关。并且改变最后一个全连接层的输出来适应目标任务,训练后面 classifier 的权值,这就是 Finetune。通常 target domain 的数据比较小,不足以训练全部参数,容易导致过拟合,因此不改变 feature extractor 的权值。

在这里插入图片描述

Finetune 步骤如下:

  • 1.获取预训练模型的参数
  • 2.使用load_state_dict()把参数加载到模型中
  • 3.修改输出层
  • 4.固定 feature extractor 的参数。这部分通常有 2 种做法:
    • 1.固定卷积层的预训练参数。可以设置requires_grad=False或者lr=0
    • 2.可以通过params_group给 feature extractor 设置一个较小的学习率

下面微调 ResNet-18,用于蜜蜂和蚂蚁图片的二分类。训练集每类数据各 120 张,验证集每类数据各 70 张图片。

在这里插入图片描述

  • 数据下载地址

  • 预训练好的模型参数下载地址

Resnet-18模型结构如下图所示:

前面四层是特征提取,接下来四层(layer1~layer4)是残差网络,然后接avgpool池化层,最后接FC分类(原模型是1000分类,ImageNet上训练的)。

在这里插入图片描述

(1) 不使用 Finetune

第一次我们首先不使用 Finetune,而是从零开始训练模型,这时只需要修改全连接层即可:

# 首先拿到 fc 层的输入个数
num_ftrs = resnet18_ft.fc.in_features
# 然后构造新的 fc 层替换原来的 fc 层
resnet18_ft.fc = nn.Linear(num_ftrs, classes)

输出如下:

use device :cpu
Training:Epoch[000/025] Iteration[010/016] Loss: 0.7192 Acc:47.50%
Valid:  Epoch[000/025] Iteration[010/010] Loss: 0.6885 Acc:51.63%
...
Valid:  Epoch[024/025] Iteration[010/010] Loss: 0.5923 Acc:70.59%

训练了 25 个 epoch 后的准确率为:70.59%。

训练的 loss 曲线如下:

在这里插入图片描述

损失值一直在0.6附近,并且得到的Accuracy只有70%

(2) 使用 Finetune

然后我们把下载的模型参数加载到模型中:

path_pretrained_model = enviroments.resnet18_path
state_dict_load = torch.load(path_pretrained_model)
resnet18_ft.load_state_dict(state_dict_load)

不冻结卷积层

这时我们不冻结卷积层,所有层都是用相同的学习率,输出如下:

use device :cpu
Training:Epoch[000/025] Iteration[010/016] Loss: 0.6299 Acc:65.62%
...
Valid:  Epoch[024/025] Iteration[010/010] Loss: 0.1808 Acc:96.08%

训练了 25 个 epoch 后的准确率为:96.08%。

训练的 loss 曲线如下:

可以看出,损失值最后收敛到在0.2附近,并且在第二个Epoch的Accuracy就达到了90%。

在这里插入图片描述

在这里插入图片描述

2.2.PyTorch中的Finetune


  • 冻结卷积层

  • 设置requires_grad=False

这里先冻结所有参数,然后再替换全连接层,相当于冻结了卷积层的参数:

for param in resnet18_ft.parameters():
 param.requires_grad = False
 # 首先拿到 fc 层的输入个数
num_ftrs = resnet18_ft.fc.in_features
# 然后构造新的 fc 层替换原来的 fc 层
resnet18_ft.fc = nn.Linear(num_ftrs, classes)

这里不提供实验结果。


  • 设置学习率为 0

这里把卷积层的学习率设置为 0,需要在优化器里设置不同的学习率。首先获取全连接层参数的地址,然后使用 filter 过滤不属于全连接层的参数,也就是保留卷积层的参数;接着设置优化器的分组学习率,传入一个 list,包含 2 个元素,每个元素是字典,对应 2 个参数组。其中卷积层的学习率设置为 全连接层的 0.1 倍。

# 首先获取全连接层参数的地址
fc_params_id = list(map(id, resnet18_ft.fc.parameters()))     # 返回的是parameters的 内存地址
# 然后使用 filter 过滤不属于全连接层的参数,也就是保留卷积层的参数
base_params = filter(lambda p: id(p) not in fc_params_id, resnet18_ft.parameters())
# 设置优化器的分组学习率,传入一个 list,包含 2 个元素,每个元素是字典,对应 2 个参数组
optimizer = optim.SGD([{'params': base_params, 'lr': 0}, {'params': resnet18_ft.fc.parameters(), 'lr': LR}], momentum=0.9)

这里不提供实验结果。


  • 使用分组学习率

这里不冻结卷积层,而是对卷积层使用较小的学习率,对全连接层使用较大的学习率,需要在优化器里设置不同的学习率。首先获取全连接层参数的地址,然后使用 filter 过滤不属于全连接层的参数,也就是保留卷积层的参数;接着设置优化器的分组学习率,传入一个 list,包含 2 个元素,每个元素是字典,对应 2 个参数组。其中卷积层的学习率设置为 全连接层的 0.1 倍。

# 首先获取全连接层参数的地址
fc_params_id = list(map(id, resnet18_ft.fc.parameters()))     # 返回的是parameters的 内存地址
# 然后使用 filter 过滤不属于全连接层的参数,也就是保留卷积层的参数
base_params = filter(lambda p: id(p) not in fc_params_id, resnet18_ft.parameters())
# 设置优化器的分组学习率,传入一个 list,包含 2 个元素,每个元素是字典,对应 2 个参数组
optimizer = optim.SGD([{'params': base_params, 'lr': LR*0}, {'params': resnet18_ft.fc.parameters(), 'lr': LR}], momentum=0.9)

这里不提供实验结果。


  • 使用 GPU 的 tips

PyTorch 模型使用 GPU,可以分为 3 步:

  • 首先获取 device:device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  • 把模型加载到 device:model.to(device)
  • 在 data_loader 取数据的循环中,把每个 mini-batch 的数据和 label 加载到 device:inputs, labels = inputs.to(device), labels.to(device)

3.使用 GPU 训练模型

本节主要介绍 GPU 的使用。

3.1.CPU与GPU

  • CPU(Central Processing Unit,中央处理器):主要包括控制器和运算器
  • GPU(Graphics Processing Unit,图形处理器):处理统一的,无依赖的大规模数据运算

二者的结构图如下,可以看到绿色部分(计算单元)GPU明显要比CPU要多。

在这里插入图片描述

GPU的ALU(算术运算单元)比CPU多,而CPU中的缓存区多,用于加速程序的运行,两者适用于不同的任务,计算密集型的程序和易于并行的程序通常在GPU上完成。

3.2.数据迁移至GPU

在数据运算时,两个数据进行运算,那么它们必须同时存放在同一个设备,要么同时是 CPU,要么同时是 GPU。而且数据和模型都要在同一个设备上。数据和模型可以使用to()方法从一个设备转移到另一个设备。而数据的to()方法还可以转换数据类型。

在这里插入图片描述

  • 从 CPU 到 GPU
    device = torch.device("cuda")
    tensor = tensor.to(device)
    module.to(device)
    
  • 从 GPU 到 CPU
    device = torch.device(cpu)
    tensor = tensor.to("cpu")
    module.to("cpu")
    

  • .to()函数:转换数据类型设备
    在这里插入图片描述
x=torch.ones((3,3))#定义一个张量
x=x.to(torch.float64)#把默认的float32转换为float64
x=torch.ones(3,3)#定义一个张量
x=x.to("cuda")#迁移到GPU

linear=nn.Linear(2,2)#定义一个module
linear.to(torch.double)#把module中所有的参数从默认的float32转换为float64(double就是float64)

gpu1=torch.device("cuda")#定义设备
linear.to(gpu1)#迁移到gpu
  • 可以看到上面两个例子中,tensor是需要用等号进行赋值的,而module是直接执行to函数即可。

  • tensor和module的 to()方法的区别是:tensor.to()执行的不是 inplace 操作,因此需要赋值;module.to()执行的是 inplace 操作。

  • tensor.to() 和 module.to()

首先导入库,获取 GPU 的 device

import torch
import torch.nn as nn
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
  • 下面的代码是执行Tensor的to()方法
x_cpu = torch.ones((3, 3))
print("x_cpu:\ndevice: {} is_cuda: {} id: {}".format(x_cpu.device, x_cpu.is_cuda, id(x_cpu)))

x_gpu = x_cpu.to(device)
print("x_gpu:\ndevice: {} is_cuda: {} id: {}".format(x_gpu.device, x_gpu.is_cuda, id(x_gpu)))

输出如下:

x_cpu:
device: cpu is_cuda: False id: 1415020820304
x_gpu:
device: cpu is_cuda: True id: 2700061800153

可以看到Tensor的to()方法不是 inplace 操作,x_cpu和x_gpu的内存地址不一样。

  • 下面代码执行的是Module的to()方法
net = nn.Sequential(nn.Linear(3, 3))

print("\nid:{} is_cuda: {}".format(id(net), next(net.parameters()).is_cuda))

net.to(device)
print("\nid:{} is_cuda: {}".format(id(net), next(net.parameters()).is_cuda))

输出如下:

id:2325748158192 is_cuda: False
id:2325748158192 is_cuda: True

可以看到Module的to()方法是 inplace 操作,内存地址一样。


torch.cuda常用方法

  • torch.cuda.device_count():返回当前可见可用的 GPU 数量

  • torch.cuda.get_device_name():获取 GPU 名称

  • torch.cuda.manual_seed():为当前 GPU 设置随机种子

  • torch.cuda.manual_seed_all():为所有可见 GPU 设置随机种子

  • torch.cuda.set_device():设置主 GPU 为哪一个物理 GPU,此方法不推荐使用

  • os.environ.setdefault(“CUDA_VISIBLE_DEVICES”, “2”, “3”):设置可见 GPU

在 PyTorch 中,有物理 GPU 可以逻辑 GPU 之分,可以设置它们之间的对应关系。

在这里插入图片描述

在上图中,如果执行了os.environ.setdefault("CUDA_VISIBLE_DEVICES", "2", "3"),那么可见 GPU 数量只有 2 个。对应关系如下:

在这里插入图片描述

如果执行了os.environ.setdefault("CUDA_VISIBLE_DEVICES", "0", "3", "2"),那么可见 GPU 数量只有 3 个。对应关系如下:

在这里插入图片描述

设置的原因是可能系统中有很多用户和任务在使用 GPU,设置 GPU 编号,可以合理分配 GPU。通常默认gpu0为主 GPU。主 GPU 的概念与多 GPU 的分发并行机制有关。

3.3. 多 GPU 的分发并行

通常而言,多GPU的并行运算有三个步骤:分发 → 并行运算 →结果回收

  • 分发:由主GPU分发数据到各GPU
  • 并行运算:各GPU分别进行运算
  • 结果回收:各GPU将运算得到的结果发回主GPU

PyTorch实现:

  • torch.nn.DataParallel(module, device_ids=None, output_device=None, dim=0)

  • 功能:包装模型,实现分发并行机制。可以把数据平均分发到各个 GPU 上,每个 GPU 实际的数据量为 b a t c h _ s i z e G P U 数量 \frac {batch\_size}{GPU数量} GPU数量batch_size ,实现并行计算。

  • 主要参数:

    • module:需要包装分发的模型
    • device_ids:可分发的 GPU,默认分发到所有可见可用的 GPU
    • output_device:结果输出设备

需要注意的是:使用 DataParallel 时,device 要指定某个 GPU 为 主 GPU,否则会报错:

RuntimeError: module must have its parameters and buffers on device cuda:1 (device_ids[0]) but found one of them on device: cuda:2

这是因为,使用多 GPU 需要有一个主 GPU,来把每个 batch 的数据分发到每个 GPU,并从每个 GPU 收集计算好的结果。如果不指定主 GPU,那么数据就直接分发到每个 GPU,会造成有些数据在某个 GPU,而另一部分数据在其他 GPU,计算出错。

  • 详情请参考 RuntimeError: module must have its parameters and buffers on device cuda:1 (device_ids0) but found one of them on device: cuda:2

下面的代码设置两个可见 GPU,batch_size 为 2,那么每个 GPU 每个 batch 拿到的数据数量为 8,在模型的前向传播中打印数据的数量。

# 设置 2 个可见 GPU
    gpu_list = [0,1]
    gpu_list_str = ','.join(map(str, gpu_list))
    os.environ.setdefault("CUDA_VISIBLE_DEVICES", gpu_list_str)
    # 这里注意,需要指定一个 GPU 作为主 GPU。
    # 否则会报错:module must have its parameters and buffers on device cuda:1 (device_ids[0]) but found one of them on device: cuda:2
    # 参考:https://stackoverflow.com/questions/59249563/runtimeerror-module-must-have-its-parameters-and-buffers-on-device-cuda1-devi
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

    batch_size = 16

    # data
    inputs = torch.randn(batch_size, 3)
    labels = torch.randn(batch_size, 3)

    inputs, labels = inputs.to(device), labels.to(device)

    # model
    net = FooNet(neural_num=3, layers=3)
    net = nn.DataParallel(net)
    net.to(device)

    # training
    for epoch in range(1):

        outputs = net(inputs)

        print("model outputs.size: {}".format(outputs.size()))

    print("CUDA_VISIBLE_DEVICES :{}".format(os.environ["CUDA_VISIBLE_DEVICES"]))
    print("device_count :{}".format(torch.cuda.device_count()))

输出如下:

batch size in forward: 8
model outputs.size: torch.Size([16, 3])
CUDA_VISIBLE_DEVICES :0,1
device_count :2

下面的代码是根据 GPU 剩余内存来排序。

 def get_gpu_memory():
        import platform
        if 'Windows' != platform.system():
            import os
            os.system('nvidia-smi -q -d Memory | grep -A4 GPU | grep Free > tmp.txt')
            memory_gpu = [int(x.split()[2]) for x in open('tmp.txt', 'r').readlines()]
            os.system('rm tmp.txt')
        else:
            memory_gpu = False
            print("显存计算功能暂不支持windows操作系统")
        return memory_gpu


    gpu_memory = get_gpu_memory()
    if not gpu_memory:
        print("\ngpu free memory: {}".format(gpu_memory))
        gpu_list = np.argsort(gpu_memory)[::-1]

        gpu_list_str = ','.join(map(str, gpu_list))
        os.environ.setdefault("CUDA_VISIBLE_DEVICES", gpu_list_str)
        device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

其中nvidia-smi -q -d Memory是查询所有 GPU 的内存信息,-q表示查询,-d是指定查询的内容。

nvidia-smi -q -d Memory | grep -A4 GPU是截取 GPU 开始的 4 行,如下:

Attached GPUs                       : 2
GPU 00000000:1A:00.0
    FB Memory Usage
        Total                       : 24220 MiB
        Used                        : 845 MiB
        Free                        : 23375 MiB
--
GPU 00000000:68:00.0
    FB Memory Usage
        Total                       : 24217 MiB
        Used                        : 50 MiB
        Free                        : 24167 MiB

nvidia-smi -q -d Memory | grep -A4 GPU | grep Free是提取Free所在的行,也就是提取剩余内存的信息,如下:

        Free                        : 23375 MiB
        Free                        : 24167 MiB

nvidia-smi -q -d Memory | grep -A4 GPU | grep Free > tmp.txt是把剩余内存的信息保存到tmp.txt中。

[int(x.split()[2]) for x in open('tmp.txt', 'r').readlines()]是用列表表达式对每行进行处理。

假设x=" Free : 23375 MiB",那么x.split()默认以空格分割,结果是:

['Free', ':', '23375', 'MiB']

x.split()[2]的结果是23375。

假设gpu_memory=['5','9','3']np.argsort(gpu_memory)的结果是array([2, 0, 1], dtype=int64),是从小到大取排好序后的索引。np.argsort(gpu_memory)[::-1]的结果是array([1, 0, 2], dtype=int64),也就是把元素的顺序反过来。

在 Python 中,list[<start>:<stop>:<step>]表示从start到stop取出元素,间隔为step,step=-1表示从stop到start取出元素。start默认为第一个元素的位置,stop默认为最后一个元素的位置。

‘,’.join(map(str, gpu_list))的结果是’1,0,2’。

最后os.environ.setdefault(“CUDA_VISIBLE_DEVICES”, gpu_list_str)就是根据 GPU 剩余内存从大到小设置对应关系,这样默认最大剩余内存的 GPU 为主 GPU。

在这里插入图片描述

提高 GPU 的利用率
nvidia-smi命令查看可以 GPU 的利用率,如下图所示。

在这里插入图片描述

上面的截图中,有两张显卡(GPU),其中上半部分显示的是显卡的信息下半部分显示的是每张显卡运行的进程。可以看到编号为 0 的 GPU 运行的是 PID 为 14383 进程。Memory Usage表示显存的使用率,编号为 0 的 GPU 使用了 16555 MB 显存,显存的利用率大概是70% 左右。Volatile GPU-Util表示计算 GPU 实际运算能力的利用率,编号为 0 的 GPU 只有 27% 的使用率。

虽然使用 GPU 可以加速训练模型,但是如果 GPU 的 Memory Usage 和 Volatile GPU-Util 太低,表示并没有充分利用 GPU。

因此,使用 GPU 训练模型,需要尽量提高 GPU 的 Memory Usage 和 Volatile GPU-Util 这两个指标,可以更进一步加速你的训练过程。

下面谈谈如何提高这两个指标。

Memory Usage
这个指标是由数据量主要是由模型大小,以及数据量的大小决定的。

模型大小是由网络的参数和网络结构决定的,模型越大,训练反而越慢。

我们主要调整的是每个 batch 训练的数据量的大小,也就是 batch_size。

在模型结构固定的情况下,尽量将batch size设置得比较大,充分利用 GPU 的内存。

Volatile GPU-Util
上面设置比较大的 batch size可以提高 GPU 的内存使用率,却不一定能提高 GPU 运算单元的使用率。

从前面可以看到,我们的数据首先读取到 CPU 中的,并在循环训练的时候,通过tensor.to()方法从 CPU 加载到 CPU 中,如下代码所示。

# 遍历 train_loader 取数据
for i, data in enumerate(train_loader):
    inputs, labels = data
    inputs = inputs.to(device) # 把数据从 CPU 加载到 GPU
    labels = labels.to(device) # 把数据从 CPU 加载到 GPU
    .
    .
    .

如果batch size得比较大,那么在 Dataset和 DataLoader ,CPU 处理一个 batch 的数据就会很慢,这时你会发现Volatile GPU-Util的值会在 0%,20%,70%,95%,0% 之间不断变化。

nvidia-smi命令查看可以 GPU 的利用率,但不能动态刷新显示。如果你想每隔一秒刷新显示 GPU 信息,可以使用watch -n 1 nvidia-smi 。

其实这是因为 GPU 处理数据非常快,而 CPU 处理数据较慢。GPU 每接收到一个 batch 的数据,使用率就跳到逐渐升高,处理完这个 batch 的数据后,使用率又逐渐降低,等到 CPU 把下一个 batch 的数据传过来。

解决方法是:设置 Dataloader的两个参数:

  • num_workers:默认只使用一个 CPU 读取和处理数据。可以设置为 4、8、16 等参数。但线程数并不是越大越好。因为,多核处理需要把数据分发到每个 CPU,处理完成后需要从多个 CPU 收集数据,这个过程也是需要时间的。如果设置num_workers过大,分发和收集数据等操作占用了太多时间,反而会降低效率。
  • pin_memory:如果内存较大,建议设置为 True。
    • 设置为 True,表示把数据直接映射到 GPU 的相关内存块上,省掉了一点数据传输时间。
    • 设置为 False,表示从 CPU 传入到缓存 RAM 里面,再给传输到 GPU 上。

GPU模型加载出现的报错与解决

  • 报错1:

如果模型是在 GPU 上保存的,在无 GPU 设备上加载模型时torch.load(path_state_dict),会出现下面的报错:

RuntimeError: Attempting to deserialize object on a CUDA device but torch.cuda.is_available() is False. If you are running on a CPU-only machine, please use torch.load with map_location=torch.device('cpu') to map your storages to the CPU.

可能的原因:gpu 训练的模型保存后,在无 gpu 设备上无法直接加载。解决方法是设置map_location="cpu":torch.load(path_state_dict, map_location="cpu")

  • 报错2:

如果模型经过net = nn.DataParallel(net)包装后,那么所有网络层的名称前面都会加上mmodule.。保存模型后再次加载时没有使用nn.DataParallel()包装,就会加载失败,因为state_dict中参数的名称对应不上。

Missing key(s) in state_dict: xxxxxxxxxx

Unexpected key(s) in state_dict:xxxxxxxxxx

解决方法是加载参数后,遍历 state_dict 的参数,如果名字是以module.开头,则去掉module.。代码如下:


from collections import OrderedDict
new_state_dict = OrderedDict()
for k, v in state_dict.items():
    namekey = k[7:] if k.startswith('module.') else k
    new_state_dict[namekey] = v

然后再把参数加载到模型中。

4.pytorch常见报错

  • 参考1
  • 参考2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/519480.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mac下安装cnpm淘宝镜像

在mac安装cnpm时&#xff0c;输入npm install -g cnpm -registryhttps://registry.npm.taobao.org 报错&#xff1a; npm ERR! code EACCES npm ERR! syscall mkdir npm ERR! path /usr/local/lib/node_modules/cnpm npm ERR! errno -13 npm ERR! Error: EACCES: permission de…

单细胞 | label transfer with Seurat4(未知细胞映射到注释好的细胞图谱)

场景&#xff1a;把新的细胞比对到已经注释过的细胞集合上&#xff0c;获取映射后的细胞标签&#xff0c;UMP坐标。 准备&#xff1a; 一个分析好的单细胞图谱数据集&#xff0c;作为reference数据集。一个新的单细胞counts矩阵&#xff0c;记为 query数据集。 主要分为两个步…

在浏览器从输入URL到页面加载完成都经历了什么/一个完整的URL解析过程详细介绍

一、简述在浏览器从输入URL到页面加载完成都经历了什么 浏览器地址栏输入url地址&#xff0c;首先要在客户端上进行url解析 浏览器会首先查看自身的缓存&#xff0c;如果浏览器缓存中有对应的解析记录&#xff0c;直接返回结果 如果浏览器没有缓存&#xff0c;电脑会查看本地操…

Selenium+Unittest自动化测试框架实战(框架源码都给你)

目录 前言 项目框架 首先管理时间 !/usr/bin/env python3 -- coding:utf-8 -- 配置文件 conf.py config.ini# 读取配置文件 记录操作日志 简单理解POM模型 管理页面元素 封装Selenium基类 创建页面对象 熟悉unittest测试框架 编写测试用例 执行用例 生成测试报…

qemu-ARM篇——ARM 栈帧(一)

ARM 栈帧 本系列均已 corter-A7(armv7-a) 为例 在 ARM 中&#xff0c;通常为满减栈&#xff08;Full Descending FD&#xff09;, 也就是说&#xff0c;堆栈指针指向堆栈内存中最后一个填充的位置&#xff0c;并且随着每个新数据项被压入堆栈而递减。 栈的本质 要理解栈的本…

前端CSS学习(三)

1、盒子模型 盒子的概念1、页面中的每一个标签&#xff0c;都可看做是一 个“盒子” &#xff0c;通过盒子的视角更方便的进行布局2、浏览器在渲染 (显示)网页时&#xff0c;会将网页中的元素看做是一个个的矩形区域&#xff0c;我们也形象的称之为盒子CSS中规定每个盒子分别由…

BESV博世蔚发布2023全新款折叠e-bike —— F3,在中国自行车展会上大放异彩

BESV博世蔚身为跨界智慧出行的专家&#xff0c;今年在国内最大规模的中国国际自行车展上发布了其最新的e-bike折叠车款---VOTANI F3。拥有纯正荷兰血统的VOTANI系列车款&#xff0c;在设计外观上沿袭了欧风的极简主义和时尚设计&#xff0c;并搭配上折叠系统更易于携带和收纳。…

AnyStock JS Crack,AnyStock JS功能

AnyStock JS Crack,AnyStock JS功能 添加了新的技术指标-除了已经支持的几十个指标外&#xff0c;股票图表现在还提供了三个新的开箱即用技术指标&#xff1a; Aroon振荡器-通过从Aroon Up中减去Aroon Down&#xff0c;可以很容易地测量趋势的强度。 加权移动平均线(WMA)-通过更…

D-遗迹探险

牛客小白月赛 72 D 题目链接 链接&#xff1a;https://ac.nowcoder.com/acm/contest/56825/D 来源&#xff1a;牛客网 示例1 输入 3 3 1 2 3 4 5 6 7 8 9 2 2 1 1 3 3 3 1 1 1 3 3 1输出 58 41题解&#xff1a; 如果先不考虑传送门&#xff0c;这题就是一道简单dp 设状态 …

【后端随笔】mysql操作语句记录

sql语句不区分大小写 show bases&#xff1b; --号注释 /**/多行注释 DDL定义 DML删改 DQL查询 DCL权限控制语言 1、DDL操作数据库 &#xff08;1&#xff09;查询 SHOW DATABASES; (2)创建 CREATE DATABASES;//创建数据库 CREATE DATABASES IF NOT EXISTS 数据库名称&#xff…

stata学习笔记①stata基础介绍

文章目录 一、为什么要学stata二、软件基本解释1.软件界面2.导入示例数据3.认识几个重要的功能符号 三、数据的基本观测四、统计性描述1.codebook 数据字典使用2.summarize 五、图像初步探索1.histogram 直方图2.graph box /hbox 箱线图3.vioplot小提琴图 一、为什么要学stata …

JavaWeb:过滤器 Filter、监听器 Listener

文章目录 JavaWeb - 04一、Filter1. 概述2. 实现步骤3. 运行结果 二、Filter 应用&#xff1a;实现权限拦截1. 登录步骤2. 添加的过滤器部分3. 运行结果4. 总结 三、监听器注意&#xff1a; JavaWeb - 04 一、Filter 1. 概述 Filter&#xff1a;过滤器&#xff0c;可以用来过…

有用的知识又增加了:为何无法编译某些  WWDC 官方视频中的代码?

概览 作为 Apple 开发者而言&#xff0c;每期 WWDC 官方视频无疑是我们日常必看的内容。 不过&#xff0c;小伙伴们是否发现视频中有些示例代码在我们自己测试时却无法编译这一尴尬的情况呢&#xff1f; 在本篇博文中&#xff0c;我们将通过一则非常简单的示例来向大家展示为…

【Matlab】基于遗传算法的列车发车时刻(发车间隔)优化

【Matlab】基于遗传算法的列车发车时刻&#xff08;发车间隔&#xff09;优化 一、模型介绍&#xff08;一&#xff09;引言&#xff08;二&#xff09;符号定义&#xff08;三&#xff09;目标函数(四&#xff09;约束条件4.1到达乘客数量4.2乘客进站限制4.3乘客总数量&#x…

Kyligence Zen产品体验-从人找数据到数据找人

目录 前言&#xff1a; 一、什么是Kyligence Zen&#xff1f; 1、个人总结 2、官方简介 二、1分钟打开新世界大门 个人总结&#xff1a; 1、注册 2、验证登录 三、上手初体验 1、快速上手&#xff08;入门&#xff09; 2、定制化应用 四、实战体验 综述&#xff1a; 1、卡…

java transient关键字 JSON序列化问题

今天做项目的时候&#xff0c;遇到了一个奇怪的事情看图&#xff1a; 在这个JSONObject中是有这个object对象的&#xff0c;但是我输出的的却没有这个对象&#xff0c;这是怎么回事&#xff1f; 这样不明显我换一个方式去输出 我在查看了代码之后发现了我的ResponseStatus这…

数据结构-查找-树形结构(二叉排序树、二叉平衡树、红黑树、B树、B+树)查找

目录 一、二叉排序树(BST) 查询 插入 构造二叉排序树 *删除 *查找效率分析 二、二叉平衡树 *插入数据保持平衡 LL ​编辑 RR LR RL 结 *查找效率分析 删除 三、红黑树 *插入 *删除 四、B树 *插入 *删除 五、B树 一、二叉排序树(BST) 定义&#xff1a;二叉排序…

python的opencv操作记录(13)-增强之直方图均衡化

文章目录 直方图增强基本逻辑-均衡化calcHist && equalizeHistcalcHistequalizeHist 自适应直方图均衡化 前段时间忙活深度网络和android的东西去了&#xff0c;好久没讲讲传统图像处理了&#xff0c;这一篇继续来说说opencv中的传统图像处理部分——图像增强之直方图增…

【谷粒商城之订单服务-RabbitMQ延时队列】

本笔记内容为尚硅谷谷粒商城订单服务锁库存事务最终一致性部分 目录 一、RabbitMQ延时队列 二、具体实现 1、 创建上述队列和路由组件 2、解锁库存 解锁库存的两种情况 1、当订单业务提交后回滚 2、订单取消解锁库存 三、关闭订单 四、消息丢失、挤压、重复等解决方案…