PyTorch进阶技巧 | 自定义损失函数 | 动态调整学习率 | 模型微调 | 半精度训练 | 使用argparse进行调参


PyTorch基础篇:


一、自定义损失函数

  PyTorchtorch.nn模块为我们提供了许多常用的损失函数,比如:MSELoss1L1LossBCELoss… 但是随着深度学习的发展,出现了越来越多的非官方提供的Loss,比如DiceLossHuberLossSobolevLoss… 这些Loss Function专门针对一些非通用的模型,PyTorch不能将他们全部添加到库中去,因此这些损失函数的实现则需要我们通过自定义损失函数来实现。另外,在科学研究中,我们往往会提出全新的损失函数来提升模型的表现,这时我们既无法使用PyTorch自带的损失函数,也没有相关的博客供参考,此时自己实现损失函数就显得更为重要了。

1.以函数方式定义

  事实上,损失函数仅仅是一个函数而已,因此我们可以通过直接以函数定义的方式定义一个自己的函数,如下所示:

def my_loss(output, target):
    loss = torch.mean((output - target)**2)
    return loss

2.以类方式定义

  虽然以函数定义的方式很简单,但是以类方式定义更加常用,在以类方式定义损失函数时,我们如果看每一个损失函数的继承关系我们就可以发现Loss函数部分继承自_loss, 部分继承自_WeightedLoss, 而_WeightedLoss继承自_loss_loss继承自 nn.Module。我们可以将其当作神经网络的一层来对待,同样地,我们的损失函数类就需要继承自nn.Module类。以处理不平衡数据集的Focal Loss为例,

#PyTorch
ALPHA = 0.8
GAMMA = 2

class FocalLoss(nn.Module):
    def __init__(self, weight=None, size_average=True):
        super(FocalLoss, self).__init__()

    def forward(self, inputs, targets, alpha=ALPHA, gamma=GAMMA, smooth=1):
        
        #comment out if your model contains a sigmoid or equivalent activation layer
        inputs = F.sigmoid(inputs)       
        
        #flatten label and prediction tensors
        inputs = inputs.view(-1)
        targets = targets.view(-1)
        
        #first compute binary cross-entropy 
        BCE = F.binary_cross_entropy(inputs, targets, reduction='mean')
        BCE_EXP = torch.exp(-BCE)
        focal_loss = alpha * (1-BCE_EXP)**gamma * BCE
                       
        return focal_loss

其他实现可参考如下网址
Dice Loss、BCE-Dice Loss、Jaccard、Focal Loss、Tversky Loss、Focal Tversky Loss、Lovasz Hinge Loss、Combo Loss、Usage Tips的实现
注:在自定义损失函数时,涉及到数学运算时,我们最好全程使用PyTorch提供的张量计算接口,这样就不需要我们实现自动求导功能并且我们可以直接调用cuda,使用numpy或者scipy的数学运算时,操作会有些麻烦。

二、学习率调整策略

  学习率的选择是深度学习中一个困扰人们许久的问题,学习速率设置过小,会极大降低收敛速度,增加训练时间;学习率太大,可能导致参数在最优解两侧来回振荡。但是当我们选定了一个合适的学习率后,经过许多轮的训练后,可能会出现准确率震荡或loss不再下降等情况,说明当前学习率已不能满足模型调优的需求。此时我们就可以通过一个适当的学习率衰减策略来改善这种现象,提高我们的精度。这种设置方式在PyTorch中被称为scheduler

1.使用官方scheduler

  PyTorch的六种学习率调整策略都继承于class _LRScheduler(object)这个基类,所以我们首先介绍这个基类

class _LRScheduler(object):
	def __init__(self, optimizer, last_epoch=-1):
		...

	def get_lr(self):
		...

	def step(self):
		...

主要属性

  • optimizer:关联的优化器
  • last_epoch:记录epoch数
  • base_lrs:记录初始学习率

主要方法

  • get_lr():虚函数,计算下一个epoch的学习率
  • step():更新下一个epoch的学习率
1.1 StepLR
lr_scheduler.StepLR(optimizer, step_size, gamma=0.1, last_epoch=-1)

功能:等间隔调整学习率
主要参数:

  • step_size:调整间隔数
  • gamma:调整系数
    调整方式:lr = lr * gamma
import torch
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
torch.manual_seed(1)

LR = 0.1
iteration = 10
max_epoch = 200

weights = torch.randn((1), requires_grad=True)
target = torch.zeros((1))

optimizer = optim.SGD([weights], lr=LR, momentum=0.9)

# 设置学习率下降策略
scheduler_lr = optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.1)  

lr_list, epoch_list = list(), list()
for epoch in range(max_epoch):
    # 获取当前lr,新版本用 get_last_lr()函数,旧版本用get_lr()函数,具体看UserWarning
    lr_list.append(scheduler_lr.get_lr())
    epoch_list.append(epoch)

    for i in range(iteration):

        loss = torch.pow((weights - target), 2)
        loss.backward()

        optimizer.step()
        optimizer.zero_grad()

    scheduler_lr.step()

plt.plot(epoch_list, lr_list, label="Step LR Scheduler")
plt.xlabel("Epoch")
plt.ylabel("Learning rate")
plt.legend()
plt.show()

在这里插入图片描述

1.2 MultiStepLR
lr_scheduler.MultiStepLR(optimizer, milestones, gamma=0.1, last_epoch=-1)

功能:按给定间隔调整学习率
主要参数:

  • milestones:设定调整时刻数
  • gamma:调整系数
    调整方式:lr = lr * gamma
import torch
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
torch.manual_seed(1)

LR = 0.1
iteration = 10
max_epoch = 200

weights = torch.randn((1), requires_grad=True)
target = torch.zeros((1))

optimizer = optim.SGD([weights], lr=LR, momentum=0.9)

# 设定调整时刻数
milestones = [50, 125, 160]
# 设置学习率下降策略
scheduler_lr = optim.lr_scheduler.MultiStepLR(optimizer, milestones=milestones, gamma=0.1)

lr_list, epoch_list = list(), list()
for epoch in range(max_epoch):

    lr_list.append(scheduler_lr.get_lr())
    epoch_list.append(epoch)

    for i in range(iteration):

        loss = torch.pow((weights - target), 2)
        loss.backward()

        optimizer.step()
        optimizer.zero_grad()

    scheduler_lr.step()

plt.plot(epoch_list, lr_list, label="Multi Step LR Scheduler\nmilestones:{}".format(milestones))
plt.xlabel("Epoch")
plt.ylabel("Learning rate")
plt.legend()
plt.show()

在这里插入图片描述

1.3 ExponentialLR
lr_scheduler.ExponentialLR(optimizer, gamma, last_epoch=-1)

功能:按指数衰减调整学习率
主要参数:
gamma:指数的底
调整方式:
l r = l r ∗ g a m m a e p o c h lr = lr * gamma^{epoch} lr=lrgammaepoch

import torch
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
torch.manual_seed(1)

LR = 0.1
iteration = 10
max_epoch = 200

weights = torch.randn((1), requires_grad=True)
target = torch.zeros((1))

optimizer = optim.SGD([weights], lr=LR, momentum=0.9)

gamma = 0.95
scheduler_lr = optim.lr_scheduler.ExponentialLR(optimizer, gamma=gamma)

lr_list, epoch_list = list(), list()
for epoch in range(max_epoch):

    lr_list.append(scheduler_lr.get_lr())
    epoch_list.append(epoch)

    for i in range(iteration):

        loss = torch.pow((weights - target), 2)
        loss.backward()

        optimizer.step()
        optimizer.zero_grad()

    scheduler_lr.step()

plt.plot(epoch_list, lr_list, label="Exponential LR Scheduler\ngamma:{}".format(gamma))
plt.xlabel("Epoch")
plt.ylabel("Learning rate")
plt.legend()
plt.show()

在这里插入图片描述

1.4 CosineAnnealingLR
lr_scheduler.CosineAnnealingLR(optimizer, T_max, eta_min=0, last_epoch=-1)

功能:余弦周期调整学习率
主要参数:

  • T_max:下降周期
  • eta_min:学习率下限

调整方式为:
在这里插入图片描述

import torch
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
torch.manual_seed(1)

LR = 0.1
iteration = 10
max_epoch = 200

weights = torch.randn((1), requires_grad=True)
target = torch.zeros((1))

optimizer = optim.SGD([weights], lr=LR, momentum=0.9)

t_max = 50
scheduler_lr = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=t_max, eta_min=0.)

lr_list, epoch_list = list(), list()
for epoch in range(max_epoch):

    lr_list.append(scheduler_lr.get_lr())
    epoch_list.append(epoch)

    for i in range(iteration):

        loss = torch.pow((weights - target), 2)
        loss.backward()

        optimizer.step()
        optimizer.zero_grad()

    scheduler_lr.step()

plt.plot(epoch_list, lr_list, label="CosineAnnealingLR Scheduler\nT_max:{}".format(t_max))
plt.xlabel("Epoch")
plt.ylabel("Learning rate")
plt.legend()
plt.show()

在这里插入图片描述

1.5 ReduceLRonPlateau
lr_scheduler.ReduceLROnPlateau(
	optimizer, mode='min', factor=0.1,
	patience=10, verbose=False, threshold=0.0001, 
	threshold_mode='rel', cooldown=0, min_lr=0, 
	eps=1e-08)

功能:监控指标,当监控指标不再变化则调整,监控指标一般为loss
主要参数:

  • mode:min/max 两种模式
  • factor:调整系数
  • patience:“耐心 ”,接受几次不变化
  • cooldown:“冷却时间”,停止监控一段时间
  • verbose:是否打印日志
  • min_lr:学习率下限
  • eps:学习率衰减最小值
import torch
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
torch.manual_seed(1)

LR = 0.1
iteration = 10
max_epoch = 200

weights = torch.randn((1), requires_grad=True)
target = torch.zeros((1))

optimizer = optim.SGD([weights], lr=LR, momentum=0.9)

loss_value = 0.5
accuray = 0.9

factor = 0.1
mode = "min"
patience = 10
cooldown = 10
min_lr = 1e-4
verbose = True

scheduler_lr = optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=factor, mode=mode, patience=patience,
                                                    cooldown=cooldown, min_lr=min_lr, verbose=verbose)

for epoch in range(max_epoch):
    for i in range(iteration):

        # train(...)

        optimizer.step()
        optimizer.zero_grad()

    scheduler_lr.step(loss_value)
Epoch    12: reducing learning rate of group 0 to 1.0000e-02.
Epoch    33: reducing learning rate of group 0 to 1.0000e-03.
Epoch    54: reducing learning rate of group 0 to 1.0000e-04.
1.6 LambdaLR
lr_scheduler.LambdaLR(optimizer, lr_lambda, last_epoch=-1)

功能:自定义调整策略,可以对不同的参数组设置不同的学习率调整方法
主要参数:

  • lr_lambda:function or list
import torch
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
torch.manual_seed(1)

LR = 0.1
iteration = 10
max_epoch = 200

lr_init = 0.1

weights_1 = torch.randn((6, 3, 5, 5))
weights_2 = torch.ones((5, 5))

optimizer = optim.SGD([
    {'params': [weights_1]},
    {'params': [weights_2]}], lr=lr_init)

lambda1 = lambda epoch: 0.1 ** (epoch // 20)
lambda2 = lambda epoch: 0.95 ** epoch

scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda = [lambda1, lambda2])

lr_list, epoch_list = list(), list()
for epoch in range(max_epoch):
    for i in range(iteration):

        # train(...)

        optimizer.step()
        optimizer.zero_grad()

    scheduler.step()

    lr_list.append(scheduler.get_lr())
    epoch_list.append(epoch)
    if epoch % 20==0 :
        print('epoch:{:5d}, lr:{}'.format(epoch, scheduler.get_lr()))

plt.plot(epoch_list, [i[0] for i in lr_list], label="lambda 1")
plt.plot(epoch_list, [i[1] for i in lr_list], label="lambda 2")
plt.xlabel("Epoch")
plt.ylabel("Learning Rate")
plt.title("LambdaLR")
plt.legend()
plt.show()
epoch:    0, lr:[0.1, 0.095]
epoch:   20, lr:[0.010000000000000002, 0.03405616262881148]
epoch:   40, lr:[0.0010000000000000002, 0.012208654873684797]
epoch:   60, lr:[0.00010000000000000003, 0.004376630903760435]
epoch:   80, lr:[1.0000000000000003e-05, 0.00156896056657629]
epoch:  100, lr:[1.0000000000000004e-06, 0.0005624502759317298]
epoch:  120, lr:[1.0000000000000005e-07, 0.00020163050597632508]
epoch:  140, lr:[1.0000000000000004e-08, 7.228169792062392e-05]
epoch:  160, lr:[1.0000000000000005e-09, 2.5911971152330445e-05]
epoch:  180, lr:[1.0000000000000006e-10, 9.28907687996671e-06]

在这里插入图片描述
注:我们在使用官方给出的torch.optim.lr_scheduler时,需要将scheduler.step()放在optimizer.step()后面进行使用。

1.7 学习率调整小结
  1. 有序调整:StepMultiStepExponentialCosineAnnealing
  2. 自适应调整:ReduceLROnPleateau
  3. 自定义调整:Lambda

2.自定义scheduler

  虽然PyTorch官方给我们提供了许多的API,但是在实验中也有可能碰到需要我们自己定义学习率调整策略的情况,而我们的方法是自定义函数adjust_learning_rate来改变param_grouplr的值,在下面的叙述中会给出一个简单的实现。
  假设我们现在正在做实验,需要学习率每30轮下降为原来的1/10,假设已有的官方API中没有符合我们需求的,那就需要自定义函数来实现学习率的改变。

def adjust_learning_rate(optimizer, epoch):
    lr = args.lr * (0.1 ** (epoch // 30))
    for param_group in optimizer.param_groups:
        param_group['lr'] = lr

有了adjust_learning_rate函数的定义,在训练的过程就可以调用我们的函数来实现学习率的动态变化。

def adjust_learning_rate(optimizer,...):
    ...
optimizer = torch.optim.SGD(model.parameters(),lr = args.lr,momentum = 0.9)
for epoch in range(10):
    train(...)
    validate(...)
    adjust_learning_rate(optimizer,epoch)

三、模型微调(Finetune)

1.模型微调流程

  1. 在源数据集(如ImageNet数据集)上预训练一个神经网络模型,即源模型。
  2. 创建一个新的神经网络模型,即目标模型。它复制了源模型上除了输出层外的所有模型设计及其参数。我们假设这些模型参数包含了源数据集上学习到的知识,且这些知识同样适用于目标数据集。我们还假设源模型的输出层跟源数据集的标签紧密相关,因此在目标模型中不予采用。
  3. 为目标模型添加一个输出⼤小为⽬标数据集类别个数的输出层,并随机初始化该层的模型参数。
  4. 在目标数据集上训练目标模型。我们将从头训练输出层,而其余层的参数都是基于源模型的参数微调得到的。

在这里插入图片描述

2.使用已有模型结构

  这里我们以torchvision中的常见模型为例,列出了如何在图像分类任务中使用PyTorch提供的常见模型结构和参数。对于其他任务和网络结构,使用方式是类似的:

  • 实例化网络

    import torchvision.models as models
    resnet18 = models.resnet18()
    # resnet18 = models.resnet18(pretrained=False)  等价于与上面的表达式
    alexnet = models.alexnet()
    vgg16 = models.vgg16()
    squeezenet = models.squeezenet1_0()
    densenet = models.densenet161()
    inception = models.inception_v3()
    googlenet = models.googlenet()
    shufflenet = models.shufflenet_v2_x1_0()
    mobilenet_v2 = models.mobilenet_v2()
    mobilenet_v3_large = models.mobilenet_v3_large()
    mobilenet_v3_small = models.mobilenet_v3_small()
    resnext50_32x4d = models.resnext50_32x4d()
    wide_resnet50_2 = models.wide_resnet50_2()
    mnasnet = models.mnasnet1_0()
    
  • 传递pretrained参数
    通过True或者False来决定是否使用预训练好的权重,在默认状态下pretrained = False,意味着我们不使用预训练得到的权重,当pretrained = True,意味着我们将使用在一些数据集上预训练得到的权重。

    import torchvision.models as models
    resnet18 = models.resnet18(pretrained=True)
    alexnet = models.alexnet(pretrained=True)
    squeezenet = models.squeezenet1_0(pretrained=True)
    vgg16 = models.vgg16(pretrained=True)
    densenet = models.densenet161(pretrained=True)
    inception = models.inception_v3(pretrained=True)
    googlenet = models.googlenet(pretrained=True)
    shufflenet = models.shufflenet_v2_x1_0(pretrained=True)
    mobilenet_v2 = models.mobilenet_v2(pretrained=True)
    mobilenet_v3_large = models.mobilenet_v3_large(pretrained=True)
    mobilenet_v3_small = models.mobilenet_v3_small(pretrained=True)
    resnext50_32x4d = models.resnext50_32x4d(pretrained=True)
    wide_resnet50_2 = models.wide_resnet50_2(pretrained=True)
    mnasnet = models.mnasnet1_0(pretrained=True)
    

注意事项:

  1. 通常PyTorch模型的扩展为.pt.pth,程序运行时会首先检查默认路径中是否有已经下载的模型权重,一旦权重被下载,下次加载就不需要下载了。

  2. 一般情况下预训练模型的下载会比较慢,我们可以直接通过迅雷或者其他方式去 这里 查看自己的模型里面model_urls,然后手动下载,预训练模型的权重在LinuxMac的默认下载路径是用户根目录下的.cache文件夹。在Windows下就是C:\Users\<username>\.cache\torch\hub\checkpoint。我们可以通过使用 torch.utils.model_zoo.load_url()设置权重的下载地址。

  3. 如果觉得麻烦,还可以将自己的权重下载下来放到同文件夹下,然后再将参数加载网络。

    self.model = models.resnet50(pretrained=False)
    self.model.load_state_dict(torch.load('./model/resnet50-19c8e357.pth'))
    
  4. 如果中途强行停止下载的话,一定要去对应路径下将权重文件删除干净,要不然可能会报错。

3.训练特定层

  在默认情况下,参数的属性.requires_grad = True,如果我们从头开始训练或微调不需要注意这里。但如果我们正在提取特征并且只想为新初始化的层计算梯度,其他参数不进行改变。那我们就需要通过设置requires_grad = False来冻结部分层。在PyTorch官方中提供了这样一个例程。

def set_parameter_requires_grad(model, feature_extracting):
    if feature_extracting:
        for param in model.parameters():
            param.requires_grad = False

在下面我们仍旧使用resnet18为例的将1000类改为4类,但是仅改变最后一层的模型参数,不改变特征提取的模型参数;注意我们先冻结模型参数的梯度,再对模型输出部分的全连接层进行修改,这样修改后的全连接层的参数就是可计算梯度的。

import torchvision.models as models
# 冻结参数的梯度
feature_extract = True
model = models.resnet18(pretrained=True)
set_parameter_requires_grad(model, feature_extract)
# 修改模型
num_ftrs = model.fc.in_features
model.fc = nn.Linear(in_features=num_ftrs, out_features=4, bias=True)

之后在训练过程中,model仍会进行梯度回传,但是参数更新则只会发生在fc层。通过设定参数的requires_grad属性,我们完成了指定训练模型的特定层的目标,这对实现模型微调非常重要。

模型微调训练trick

  • 固定预训练的参数(requires_grad =False or lr=0)

    # 冻结卷积层
    flag_m1 = 0
    # flag_m1 = 1
    if flag_m1:
        for param in resnet18_ft.parameters():
            param.requires_grad = False
    

    在非常小的数据量上,我们认为卷积核参数不能在更新了,因为数据量过小,如果继续更新,容易导致过拟合。

  • Features Extractor设置较小学习率,在分类器中的学习率比较大(params_group),优化器可以对不同的参数组设置不同的超参数,这里,我们就可以在不同部分设置不同的学习率

    # conv 小学习率
    flag = 0
    # flag = 1
    if flag:
        fc_params_id = list(map(id, resnet18_ft.fc.parameters()))     # 返回的是parameters的 内存地址
        base_params = filter(lambda p: id(p) not in fc_params_id, resnet18_ft.parameters())
        # 优化器设置不同的参数组,优化器中的元素是一个list,list中的每一个元素是字典
        optimizer = optim.SGD([
            {'params': base_params, 'lr': LR*0.1},   # 0
            {'params': resnet18_ft.fc.parameters(), 'lr': LR}], momentum=0.9)
    

四、半精度训练

  GPU的性能主要分为两部分:算力和显存,前者决定了显卡计算的速度,后者则决定了显卡可以同时放入多少数据用于计算。在可以使用的显存数量一定的情况下,每次训练能够加载的数据更多(也就是batch size更大),则也可以提高训练效率。另外,有时候数据本身也比较大(比如3D图像、视频等),显存较小的情况下可能甚至batch size为1的情况都无法实现。因此,合理使用显存也就显得十分重要。
  我们观察PyTorch默认的浮点数存储方式用的是torch.float32,小数点后位数更多固然能保证数据的精确性,但绝大多数场景其实并不需要这么精确,只保留一半的信息也不会影响结果,也就是使用torch.float16格式。由于数位减了一半,因此被称为“半精度”,具体如下图:
在这里插入图片描述
显然半精度能够减少显存占用,使得显卡可以同时加载更多数据进行计算。

1.半精度训练的设置

  在PyTorch中使用autocast配置半精度训练,同时需要在下面三处加以设置:

  • import autocast

    from torch.cuda.amp import autocast
    
  • 模型设置
    在模型定义中,使用python的装饰器方法,用autocast装饰模型中的forward函数。

    @autocast()   
    def forward(self, x):
        ...
        return x
    
  • 训练过程
    在训练过程中,只需在将数据输入模型及其之后的部分放入“with autocast():“即可:

    for x in train_loader:
    	x = x.cuda()
    	with autocast():
            output = model(x)
            ...
    

注意:半精度训练主要适用于数据本身的size比较大(比如说3D图像、视频等)。当数据本身的size并不大时(比如手写数字MNIST数据集的图片尺寸只有28*28),使用半精度训练则可能不会带来显著的提升。

五、使用argparse进行调参

  在深度学习中时,超参数的修改和保存是非常重要的一步,尤其是当我们在服务器上跑我们的模型时,如何更方便的修改超参数是我们需要考虑的一个问题。这时候,要是有一个库或者函数可以解析我们输入的命令行参数再传入模型的超参数中该多好。到底有没有这样的一种方法呢?答案是肯定的,这个就是 Python 标准库的一部分:Argparse

1.argparse简介

  argsparsepython的命令行解析的标准模块,内置于python,不需要安装。这个库可以让我们直接在命令行中就可以向程序中传入参数。我们可以使用python file.py来运行python文件。而argparse的作用就是将命令行传入的其他参数进行解析、保存和使用。在使用argparse后,我们在命令行输入的参数就可以以这种形式python file.py --lr 1e-4 --batch_size 32来完成对常见超参数的设置。

2.argparse的使用

  我们可以将argparse的使用归纳为以下三个步骤。

  • 创建ArgumentParser()对象
  • 调用add_argument()方法添加参数
  • 使用parse_args()解析参数
import argparse

# 创建ArgumentParser()对象
parser = argparse.ArgumentParser()

# 添加参数
parser.add_argument('-o', '--output', action='store_true', 
    help="shows output")
# action = `store_true` 会将output参数记录为True
# type 规定了参数的格式
# default 规定了默认值
parser.add_argument('--lr', type=float, default=3e-5, help='select the learning rate, default=1e-3') 

parser.add_argument('--batch_size', type=int, required=True, help='input batch size')  
# 使用parse_args()解析函数
args = parser.parse_args()

if args.output:
    print("This is some output")
    print(f"learning rate:{args.lr} ")

我们在命令行使用python demo.py --lr 3e-4 --batch_size 32,就可以看到以下的输出:

This is some output
learning rate: 3e-4

argparse的参数主要可以分为可选参数和必选参数。可选参数就跟我们的lr参数相类似,未输入的情况下会设置为默认值。必选参数就跟我们的batch_size参数相类似,当我们给参数设置required =True后,我们就必须传入该参数,否则就会报错。看到我们的输入格式后,我们可能会有这样一个疑问,我输入参数的时候不使用–可以吗?答案是肯定的,不过我们需要在设置上做出一些改变。

import argparse

# 位置参数
parser = argparse.ArgumentParser()

parser.add_argument('name')
parser.add_argument('age')

args = parser.parse_args()

print(f'{args.name} is {args.age} years old')

当我们不使用--后,将会严格按照参数位置进行解析。

$ positional_arg.py Peter 23
Peter is 23 years old

3.更加高效使用argparse修改超参数

  每个人都有着不同的超参数管理方式,在这里我将分享我使用argparse管理超参数的方式,希望可以对大家有一些借鉴意义。通常情况下,为了使代码更加简洁和模块化,我一般会将有关超参数的操作写在config.py,然后在train.py或者其他文件导入就可以。具体的config.py可以参考如下内容。

import argparse  
  
def get_options(parser=argparse.ArgumentParser()):  
  
    parser.add_argument('--workers', type=int, default=0,  
                        help='number of data loading workers, you had better put it '  
                              '4 times of your gpu')  
  
    parser.add_argument('--batch_size', type=int, default=4, help='input batch size, default=64')  
  
    parser.add_argument('--niter', type=int, default=10, help='number of epochs to train for, default=10')  
  
    parser.add_argument('--lr', type=float, default=3e-5, help='select the learning rate, default=1e-3')  
  
    parser.add_argument('--seed', type=int, default=118, help="random seed")  
  
    parser.add_argument('--cuda', action='store_true', default=True, help='enables cuda')  
    parser.add_argument('--checkpoint_path',type=str,default='',  
                        help='Path to load a previous trained model if not empty (default empty)')  
    parser.add_argument('--output',action='store_true',default=True,help="shows output")  
  
    opt = parser.parse_args()  
  
    if opt.output:  
        print(f'num_workers: {opt.workers}')  
        print(f'batch_size: {opt.batch_size}')  
        print(f'epochs (niters) : {opt.niter}')  
        print(f'learning rate : {opt.lr}')  
        print(f'manual_seed: {opt.seed}')  
        print(f'cuda enable: {opt.cuda}')  
        print(f'checkpoint_path: {opt.checkpoint_path}')  
  
    return opt  
  
if __name__ == '__main__':  
    opt = get_options()

随后在train.py等其他文件,我们就可以使用下面的这样的结构来调用参数。

# 导入必要库
...
import config

opt = config.get_options()

manual_seed = opt.seed
num_workers = opt.workers
batch_size = opt.batch_size
lr = opt.lr
niters = opt.niters
checkpoint_path = opt.checkpoint_path

# 随机数的设置,保证复现结果
def set_seed(seed):
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    random.seed(seed)
    np.random.seed(seed)
    torch.backends.cudnn.benchmark = False
    torch.backends.cudnn.deterministic = True

...


if __name__ == '__main__':
	set_seed(manual_seed)
	for epoch in range(niters):
		train(model,lr,batch_size,num_workers,checkpoint_path)
		val(model,lr,batch_size,num_workers,checkpoint_path)
  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: PyTorch 是一个基于 Python 的科学计算库,它有着一些非常方便的特性,使得它成为了深度学习领域的开发者们的首选。而 pkuseg 是一个由北大自然语言处理实验室开发的中文分词工具,是目前效果最好的中文分词工具之一。在使用 PyTorch 微调 pkuseg 模型时,我们需要先了解一些基本的原理。 pkuseg 模型是基于 BERT 进行 fine-tune 的,因此我们需要先加载预训练好的 BERT 模型。然后,我们需要将 pkuseg 的数据转换成 BERT 的输入格式,即 tokenization 和 padding。接着,我们可以将这些数据输入到已经加载好的 BERT 模型中,并微调一些特定的层,使其适应我们的任务。最后,我们可以使用训练好的模型进行分词。 具体步骤如下: 1. 加载预训练的 BERT 模型 ```python from transformers import BertModel, BertTokenizer bert_model = BertModel.from_pretrained('bert-base-chinese') tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') ``` 2. 加载 pkuseg 数据集并转换为 BERT 的输入格式 ```python from pkuseg import pkuseg seg = pkuseg() text = '我爱自然语言处理' tokens = tokenizer.tokenize(text) # 将 pkuseg 分词后的结果转换为 BERT 的输入格式 input_ids = tokenizer.convert_tokens_to_ids(tokens) input_ids = tokenizer.build_inputs_with_special_tokens(input_ids) segment_ids = [0] * len(input_ids) input_mask = [1] * len(input_ids) # padding max_length = 128 padding_length = max_length - len(input_ids) if padding_length > 0: input_ids += [0] * padding_length segment_ids += [0] * padding_length input_mask += [0] * padding_length else: input_ids = input_ids[:max_length] segment_ids = segment_ids[:max_length] input_mask = input_mask[:max_length] # 转换为 PyTorch Tensor input_ids = torch.tensor([input_ids]) segment_ids = torch.tensor([segment_ids]) input_mask = torch.tensor([input_mask]) ``` 3. 微调 pkuseg 模型 ```python import torch.nn as nn import torch.optim as optim class PkusegModel(nn.Module): def __init__(self, bert_model): super().__init__() self.bert = bert_model self.fc = nn.Linear(768, 4) # 分类数为4 def forward(self, input_ids, segment_ids, input_mask): _, pooled_output = self.bert(input_ids, token_type_ids=segment_ids, attention_mask=input_mask) output = self.fc(pooled_output) return output model = PkusegModel(bert_model) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.0001) # 训练模型 for epoch in range(10): running_loss = 0.0 for inputs, labels in dataloader: optimizer.zero_grad() outputs = model(*inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() print('Epoch: %d, Loss: %.4f' % (epoch+1, running_loss/len(dataloader))) ``` 4. 使用训练好的模型进行分词 ```python def pkuseg_tokenize(text): tokens = tokenizer.tokenize(text) input_ids = tokenizer.convert_tokens_to_ids(tokens) input_ids = tokenizer.build_inputs_with_special_tokens(input_ids) segment_ids = [0] * len(input_ids) input_mask = [1] * len(input_ids) input_ids = torch.tensor([input_ids]) segment_ids = torch.tensor([segment_ids]) input_mask = torch.tensor([input_mask]) with torch.no_grad(): outputs = model(input_ids, segment_ids, input_mask) _, predicted = torch.max(outputs.data, 1) predicted = predicted.cpu().numpy().tolist() labels = [tokenizer.convert_ids_to_tokens([i])[0] for i in predicted] words = [] for i in range(len(tokens)): if labels[i].startswith('B'): words.append(tokens[i]) elif labels[i].startswith('I'): words[-1] += tokens[i][2:] else: words.append(tokens[i]) return words text = '我爱自然语言处理' words = pkuseg_tokenize(text) print(words) ``` 以上就是使用 PyTorch 微调 pkuseg 模型的基本原理和步骤。 ### 回答2: PyTorch是一个开源的机器学习框架,可以用于搭建、训练优深度学习模型。而pkuseg是一个基于深度学习的中文分词工具,它能够将一段中文文本进行分词处理。使用PyTorch微调pkuseg模型的原理如下: 1. 准备数据集:为了微调pkuseg模型,首先需要准备一个包含大量中文文本的数据集。这个数据集应该包含已经正确切分好的分词结果。 2. 加载模型使用PyTorch加载pkuseg的预训练模型。这个预训练模型是在大规模的中文语料库上进行训练得到的,可以实现良好的中文分词效果。 3. 冻结数:为了避免已经训练好的权重被破坏,我们需要冻结模型中的一些数,例如卷积层的权重。冻结这些数后,我们只对一部分需要微调的层进行训练。 4. 定义微调层:在pkuseg模型中,我们可以选择微调一些层,例如最后几个全连接层。这些层的数可以通过训练进行优,以适应特定的分词任务。 5. 更新梯度:使用已准备好的数据集,通过反向传播算法更新微调层的权重。根据模型的输出和标签数据之间的差距,调整权重来最小化损失函数。 6. 评估性能:在微调过程中,使用一部分数据作为验证集,用于评估模型的性能。可以使用一些指标,如Precision、Recall和F1-score来衡量模型的分词效果。 7. 迭代微调:如果模型的性能不够理想,可以多次迭代进行微调使用不同的数组合和数据子集。通过反复迭代的方式,逐渐提高模型在特定分词任务上的性能。 通过以上步骤,我们可以使用PyTorch对pkuseg模型进行微调,使其适应特定的中文分词任务,提高分词的准确性和性能。 ### 回答3: 使用PyTorch微调pkuseg模型的原理如下: 首先,pkuseg是一个基于深度学习的中文分词工具,采用了LSTM-CRF模型微调是指在已经训练好的模型基础上,通过修改部分数或者加入新的数据集来进行训练,以提高模型性能。 在进行微调pkuseg模型时,首先需要加载预训练模型数。这可以通过使用PyTorch提供的模型加载函数进行实现。加载模型数后,可以固定部分数,如LSTM层的数,以防止它们在微调过程中被修改。 接下来,我们可以选择一些新的数据集来进行微调。这些数据集通常是与原始数据集相似或相关的,例如来自相同领域或主题的数据。通过将新数据集与原始数据集进行合并,可以扩大训练数据规模,有助于提高模型的泛化能力。 在微调过程中,可以使用PyTorch提供的优化器,如随机梯度下降(SGD),来更新模型数。可以通过设定不同的学习、权重衰减等来调整优化器的数,以达到更好的微调效果。 微调过程中,需要选择合适的损失函数来度量模型训练误差,通常选择交叉熵损失函数。在每个训练迭代中,通过计算损失函数的梯度,更新模型中可训练数的数值,以降低损失函数的值。 微调过程需要进行多个训练迭代,直到达到预定的停止条件为止。在每个迭代中,可以通过计算模型在验证集上的性能指标,如准确、召回等,来评估模型的表现,并根据评估结果进行调整。 最后,可以保存微调后的模型数,以备后续使用。这些模型数可以用于分词任务,通过对输入文本进行切分,得到分词结果。 综上所述,使用PyTorch微调pkuseg模型的原理就是加载预训练模型数,固定部分数,选择合适的损失函数和优化器,通过迭代更新模型数,评估模型性能,并保存微调后的模型数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值