自定义损失函数
以函数的方式定义
def my_loss(output, target):
loss = torch.mean((output - target)**2)
return loss
以类的方式定义
- Loss函数部分继承自_loss, 部分继承自_WeightedLoss, 而_WeightedLoss继承自_loss, _loss继承自 nn.Module。我们可以将其当作神经网络的一层来对待,同样地,我们的损失函数类就需要继承自nn.Module类
class DiceLoss(nn.Module):
def __init__(self,weight=None,size_average=True):
super(DiceLoss,self).__init__()
def forward(self,inputs,targets,smooth=1):
inputs = F.sigmoid(inputs)
inputs = inputs.view(-1) //代表的是reshape的含义
targets = targets.view(-1)
intersection = (inputs * targets).sum()
dice = (2.*intersection + smooth)/(inputs.sum() + targets.sum() + smooth)
return 1 - dice
# 使用方法
criterion = DiceLoss()
loss = criterion(input,targets)
值得注意的是:在自定义损失函数的时候,要学会使用pytorch自带的张量接口,这样可以省去繁琐的求导步骤;
动态学习调整率
- 学习率:学习率过小,导致收敛速度很慢,需要花费大量的时间去收敛;学习率过大,可能导致结果在最优解两侧不断震荡,甚至无法收敛
- 在选定一个学习率之后 ,可能出现准确率震荡或者loss不再下降的情况,这种时候可以选择使用学习率衰减策略(scheduler)进行改善,来提高我们 的精度
pytorch提供的有如下:
- lr_scheduler.LambdaLR
- lr_scheduler.MultiplicativeLR
- lr_scheduler.StepLR
- lr_scheduler.MultiStepLR
- lr_scheduler.ExponentialLR
- lr_scheduler.CosineAnnealingLR
- lr_scheduler.ReduceLROnPlateau
- lr_scheduler.CyclicLR
- lr_scheduler.OneCycleLR
- lr_scheduler.CosineAnnealingWarmRestarts
# 选择一种优化器
optimizer = torch.optim.Adam(...)
# 选择上面提到的一种或多种动态调整学习率的方法
scheduler1 = torch.optim.lr_scheduler....
scheduler2 = torch.optim.lr_scheduler....
...
schedulern = torch.optim.lr_scheduler....
# 进行训练
for epoch in range(100):
train(...)
validate(...)
optimizer.step()
# 需要在优化器参数更新之后再动态调整学习率
scheduler1.step()
...
schedulern.step()
- 注:我们在使用官方给出的torch.optim.lr_scheduler时,需要将scheduler.step()放在optimizer.step()后面进行使用
自定义scheduler
def adjust_learning_rate(optimizer, epoch):
lr = args.lr * (0.1 ** (epoch // 30)) //学习率每30轮下降为原来的1/10
for param_group in optimizer.param_groups:
param_group['lr'] = lr
定义好了之后,如何使用呢?
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)
模型微调
模型微调的引入以及背景
- 许多开源的参数模型都是基于大,非常大的数据集进行训练的,但是当我们的训练集比较少的时候,用那些开源的模型肯定会导致过拟合
- 一个解决方法是增加我们本身的数据集,采集更多的数据(图片)等
- 另外一种解决方法是应用迁移学习,从开源数据集学到的知识迁移到我们自身的数据集上(比如抽取图像特征这种常规操作)
- 迁移学习的一大应用场景就是模型微调:我们先找到一个同类的别人训练好的模型,把别人现成的训练好了的模型拿过来,换成自己的数据,通过训练调整一下参数
模型微调的步骤
- 在源数据集(如ImageNet数据集)上预训练一个神经网络模型,即源模型。
- 创建一个新的神经网络模型,即目标模型。它复制了源模型上除了输出层外的所有模型设计及其参数。我们假设这些模型参数包含了源数据集上学习到的知识,且这些知识同样适用于目标数据集。我们还假设源模型的输出层跟源数据集的标签紧密相关,因此在目标模型中不予采用。
- 为目标模型添加一个输出⼤小为⽬标数据集类别个数的输出层,并随机初始化该层的模型参数。
- 在目标数据集上训练目标模型。我们将从头训练输出层,而其余层的参数都是基于源模型的参数微调得到的。
1.实例化网络
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()
2.传递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)
当然,我们也可以只训练特定的层
- .requires_grad = True代从头开始训练整个模型;
- .requires_grad = False来冻结一部分层(一部分参数不改变),只训练某些特定层;
冻结层的函数如下:
def set_parameter_requires_grad(model, feature_extracting):
if feature_extracting:
for param in model.parameters():
param.requires_grad = False
然后只改变某些特定层的函数,不改变其它层的参数
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=512, out_features=4, bias=True)
半精度训练
- PyTorch默认的浮点数存储方式用的是torch.float32,但是很多情况下,用16位的也有同样的效果,且可以大大节省空间和时间,使得可以读入更多数据进行训练
如何实现呢?
- 1:
from torch.cuda.amp import autocast
- 2:用autocast修饰forward
@autocast()
def forward(self, x):
...
return x
- 3:在训练过程中,只需在将数据输入模型及其之后的部分放入“with autocast():“即可
for x in train_loader:
x = x.cuda()
with autocast():
output = model(x)
...