tricks经验总结

仅为个人学习使用,侵权立删

1 熟悉数据

模型是数据的浓缩版----Andrew NG说过二八定律,即80%的数据+20%的模型=更好的AI

对于新上手的一任务来说,需要熟悉你的数据。拿检测任务来说,可以写个可视化代码查看标注是否合理,查看一下待检测物体的大小分布情况(例如anchor的预设),查看类别分布情况(例如是否有极端的分布)等等。

##2 算法选型

在接到一个新领域的新任务时,需要调研相关领域算法,对该领域的发展有个大概的了解,掌握一些关键算法(比如历年SOTA)的思路。虽然调研需要花费一些时间,但是这样在算法选型上可以少做一些实验,性价比是很高的。站在他们的肩膀上就好了。

不太可取的思路:

  1. **在指标上太钻牛角尖。**有些算法工程师遇到指标在自己数据集效果不太好的情况时,立马换别的算法,或者立马换个backbone,或者立马换个loss去做实验。(需要认真分析为什么效果不好,是自己训练有问题,还是当前数据不太适合该算法,是评测指标不合理,还是评测指标实现有问题。)
  2. **不进行相关调研,直接上SOTA算法。**这样做会有一些不太理想的问题,比如SOTA可能没有针对自己场景的数据做优化,比如当前任务是小目标居多(通过分析数据得到),虽然SOTA的mAP很高,但是small mAP比之前算法还低,那就要慎用 。比如SOTA用的是很重的网络,但是任务是速度快,或者速度与效果兼顾,那要慎用。

##3 基于已有实现来优化算法

对于某个任务在选择好合适的算法以后,如果有相应的效果比较好的开源实现,最好用开源项目进行算法的复现。

这样做的目的:

  1. 更方便深入的理解算法的具体细节,比如可能代码在文章没有提到的某些层上偷摸的加了一个shift操作,比如文章提到的一些trick代码根本没有实现,比如代码用了额外的数据训练但文章没有提到,比如文章描述的数据增强方式与代码的实现不一样等。(这些可能发生在开源复现者没有“一比一”复现论文的情况,也可能发生在论文作者自己没有实现的情况)
  2. 能快速掌握算法的基础性能,比如复现算法大概的运行速度(特别是文章没给出的时候)和达到的效果
  3. 不用自己做一些无用功。要知道重写和调试一份新的模型不仅费时费力,可能还因为文章没有写清楚一些细节,导致你几乎无法复现到相应的结果。

利用开源项目已复现的算法(这里复现不是完全能与代码作者或者文章作者结果一致,可能是数据增强,随机种子导致结果有偏差,但已获取到八九不离十的结果)来改进模型可以有下面几点思路:

  1. 代码是否实现了文章一些涨点的trick,如果没有可以尝试
  2. 文章一般会分析实验结果,后面会有作者自己的一些观点,他们可能会说明为什么有些文章的算法效果较差
  3. 有些文章会写他们将来可能的工作,这也是一个改进思路。
  4. 需要可视化查看实验结果(特别是跑自己的数据集),结果可能与作者在公开数据集展示出的问题不一样,分析效果差的原因

##4 从0复现算法

复现算法是一个比较大的工程,这里的大不是只代码多或者工作量大,而是没有一个基础版,导致引入的不可控因素太多调试困难,比如数据接口是否有问题,模型是否搭建正确,训练方式是否存在问题。在复现算法或者优化算法是比较头疼的是一切训练正常,loss曲线比你想象的还好看,训练了一年后,(just kidding, maybe longer),测试一下发现效果奇差无比,都不好意思说是自己写的代码。一年就过去了。

这里有下面一些建议:

  1. 尽量测试每一个细节,从数据接口,模型,到loss输出,到最终的评测代码。保证每个部分都可控。
  2. 测试数据接口,从单进程,batch为1开始,方便打印数值进行对比。
  3. 不要随意的去随机,尽量保证问题可以复现比如先不要加入随机数据增强,模型的随机种子固定。
  4. 用少量的数据,这样可以快速的做实验,也可以让模型快速过拟合。模型能过拟合可以大概确定模型是可以学到点什么的。
  5. 尽量按照原文来复现,能复现前,先不要过多的添加自己独特的想法。比如训练参数,模型backbone,数据增强方式等等先按照文章来。不清楚的可以尝试email作者或者寻找相关圈子讨论。
  6. 日志打印全,比如解loss为nan的情况,需要知道是forward的导致还是bp导致。

##5 Model训练

这一部分比较重要,会有专门的博客介绍

6 调参

  • 调参:
  • trial-and-error
  • 没有捷径可走。有人思考后再尝试,有人盲目尝试。
  • 快速尝试:调参的关键

7 大方向

【1】刚开始,先上小规模数据,模型往大了放(能用256个filter就别用128个),直接奔着过拟合去(此时都可不用测试集验证集)

  • 验证训练脚本的流程。小数据量,速度快,便于测试。
  • 如果小数据、大网络,还不能过拟合,需要检查输入输出、代码是否错误、模型定义是否恰当、应用场景是否正确理解。比较神经网络没法拟合的问题,这种概率太小了。

【2】loss设计要合理

  • 分类问题:softmax,回归:L2 loss。

  • 输出也要做归一化。如果label为10000,输出为0,loss会巨大。

  • 多任务情况时,各个loss限制在同一个量级上。

    (原因如下:

    • 分类问题:softmax,回归:L2 loss。指在神经网络中,不同的任务需要选择合适的损失函数(loss function)来衡量模型的预测误差。分类问题是指将输入数据分到一定的类别中,例如判断一张图片是猫还是狗。回归问题是指预测一个连续的数值,例如预测房价。softmax是一种常用的分类损失函数,它可以将模型的输出转化为每个类别的概率,并最小化正确类别的负对数概率。L2 loss是一种常用的回归损失函数,它可以计算模型输出和真实值之间的平方差,并最小化它们的和。
    • 输出也要做归一化。如果label为10000,输出为0,loss会巨大。这是指在神经网络中,为了避免损失函数的值过大或过小,影响梯度下降的效率和稳定性,通常需要对模型的输出和真实值做归一化处理,使它们在一个合理的范围内,例如[0,1]或[-1,1]。如果不做归一化,那么当真实值和输出值相差很大时,损失函数的值也会很大,导致梯度爆炸或梯度消失的问题。例如,如果使用L2 loss,真实值为10000,输出为0,那么损失函数的值为(10000-0)^2=100000000,这个值对于神经网络来说太大了,会导致梯度更新不稳定或无法收敛。
    • 多任务情况时,各个loss限制在同一个量级上。这是指在神经网络中,如果同时进行多个任务的学习,例如同时进行分类和回归任务,那么需要对不同任务的损失函数进行加权平衡,使它们在同一个量级上,避免某个任务的损失函数占据主导地位,影响其他任务的学习效果。例如,如果使用softmax和L2 loss作为分类和回归任务的损失函数,那么可能需要对L2 loss乘以一个较小的系数,使其和softmax loss在同一个数量级上。)

【3】观察loss胜于观察准确率

  • 优化目标是loss
  • 准确率是突变的,可能原来一直未0,保持上千代迭代,突变为1
  • loss不会突变,可能之前没有下降太多,之后才稳定学习

【4】确认分类网络学习充分

  • 分类 =》类别之间的界限
  • 网络从类别模糊到清晰,可以看softmax输出的概率分布。刚开始,可能预测值都在0.5左右(模糊),学习之后才慢慢移动到0、1的极值

【5】学习速率是否设置合理

  • 太大:loss爆炸或者nan
  • 太小:loss下降太慢
  • 当loss在当前LR下一路下降,但是不再下降了 =》可进一步降低LR

(原因如下:

  • 学习速率是否设置合理。这是指在神经网络中,使用梯度下降法或其他优化算法来更新模型参数时,需要选择一个合适的学习速率(learning rate),也就是每次更新参数的步长。学习速率会影响模型的收敛速度和效果。
  • 太大:loss爆炸或者nan。这是指如果学习速率设置得太大,那么模型参数可能会在最优值附近来回跳动,无法收敛到最小值,甚至可能会越过最小值,导致损失函数(loss function)的值变得非常大或者无穷大(nan)。例如,如果使用L2 loss,真实值为1,输出为0,学习速率为10,那么第一次更新后,输出变为10,损失函数的值变为(1-10)2=81,比原来的(1-0)2=1大了很多。
  • 太小:loss下降太慢。这是指如果学习速率设置得太小,那么模型参数可能会以非常缓慢的速度向最优值靠近,导致损失函数的值下降得很慢,需要更多的迭代次数才能收敛到最小值。例如,如果使用L2 loss,真实值为1,输出为0,学习速率为0.01,那么第一次更新后,输出变为0.01,损失函数的值变为(1-0.01)2=0.9801,比原来的(1-0)2=1只小了一点点。
  • 当loss在当前LR下一路下降,但是不再下降了 =》可进一步降低LR。这是指如果学习速率设置得适中,那么模型参数可能会以较快的速度向最优值靠近,导致损失函数的值持续下降。但是当模型参数接近最优值时,固定的学习速率可能会导致模型参数无法精确地收敛到最小值,而是在最小值附近震荡。这时可以通过进一步降低学习速率来使模型参数更加稳定地收敛到最小值。例如,如果使用L2 loss,真实值为1,输出为0,学习速率为0.5,那么第一次更新后,输出变为0.5,损失函数的值变为(1-0.5)2=0.25;第二次更新后,输出变为0.75,损失函数的值变为(1-0.75)2=0.0625;第三次更新后,输出变为0.875,损失函数的值变为(1-0.875)2=0.015625;第四次更新后,输出变为0.9375,损失函数的值变为(1-0.9375)2=0.00390625…可以看到损失函数的值在不断下降,但是输出始终无法达到1。这时可以将学习速率减小一半(例如变为0.25),继续更新模型参数。)

【6】对比训练集和验证集的loss

  • 可判断是否过拟合
  • 训练是否足够
  • 是否需要early stop

(原因如下:

  • 对比训练集和验证集的loss。这是指在神经网络中,为了评估模型的泛化能力,通常需要将数据集分为训练集和验证集,使用训练集来训练模型,使用验证集来评估模型的性能。在训练过程中,可以计算模型在训练集和验证集上的损失函数(loss function)值,并比较它们的大小,以判断模型是否过拟合或欠拟合。
  • 可判断是否过拟合。过拟合是指模型在训练集上表现很好,但是在验证集或测试集上表现很差的现象。这种现象通常是由于模型过于复杂,或者训练数据太少,导致模型过度拟合了训练数据中的噪声或特定特征。如果模型在训练集上的损失函数值很小,但是在验证集上的损失函数值很大,那么就说明模型可能存在过拟合问题。
  • 训练是否足够。如果模型在训练集和验证集上的损失函数值都很小,并且两者之间的差距不大,那么就说明模型可能已经足够训练了。如果模型在训练集上的损失函数值很小,但是在验证集上的损失函数值很大,那么就说明模型可能需要更多的训练数据或者更好的正则化方法来提高泛化能力。
  • 是否需要early stop。Early stop是指在神经网络中,在验证集上计算损失函数值,并记录最小值。如果连续多个epoch(例如3个)都没有出现更小的损失函数值,那么就可以停止训练并使用最小损失函数值对应的模型参数作为最终结果。这种方法可以避免过拟合,并提高模型泛化能力。)

【7】清楚receptive field大小

  • CV中context window很重要
  • 对模型的receptive field要有数

(原因如下:

  • 清楚receptive field大小。这是指在计算机视觉(CV)中,为了理解图像中的物体和场景,需要使用卷积神经网络(CNN)等模型来提取特征。在CNN中,每个卷积层的神经元只响应输入图像中一定区域内的像素,这个区域就称为神经元的感受野(receptive field)。感受野的大小会随着卷积层数的增加而增加。清楚感受野的大小可以帮助我们理解模型如何提取特征,并优化模型的结构和参数。
  • CV中context window很重要。这是指在计算机视觉中,为了识别图像中的物体和场景,需要考虑物体和场景的上下文信息。例如,在识别一张猫的图片时,不仅需要考虑猫本身的特征,还需要考虑猫所处的环境、背景等信息。为了捕捉上下文信息,可以使用滑动窗口(sliding window)或者卷积操作来提取局部特征,并将它们组合起来得到全局特征。
  • 对模型的receptive field要有数。这是指在计算机视觉中,为了优化CNN等模型的结构和参数,需要对模型的感受野大小进行分析和调整。如果感受野太小,那么模型可能无法捕捉到图像中重要的特征;如果感受野太大,那么模型可能会过度拟合训练数据或者无法处理大尺寸图像。因此,需要根据具体任务和数据集来选择合适的感受野大小,并对模型进行调整。)

##8 预处理

  • -mean/std zero center已然足够,PCA、白化都用不上
  • 注意shuffle

(原因如下:

  • 预处理。这是指在神经网络中,为了提高模型的性能和稳定性,需要对输入数据进行预处理。预处理可以包括多种操作,例如归一化、标准化、PCA、白化等。预处理的目的是使输入数据满足模型的假设和要求,例如数据分布均匀、方差相同、无相关性等。
  • -mean/std zero center已然足够,PCA、白化都用不上。这是指在神经网络中,为了对输入数据进行归一化和标准化,通常只需要计算数据集的均值和标准差,并将每个样本减去均值并除以标准差即可。这种方法可以使输入数据分布在[0,1]或[-1,1]之间,并且方差相同。如果使用PCA或白化等方法,可能会增加计算复杂度,并且对模型的性能提升不大。
  • 注意shuffle。这是指在神经网络中,为了避免模型过度拟合训练数据中的顺序或特定特征,通常需要对训练数据进行随机打乱(shuffle)。这样可以使模型更好地学习到数据中的统计规律和特征,并提高模型的泛化能力。例如,在训练一个图像分类模型时,如果按照类别顺序将所有猫的图片放在一起,所有狗的图片放在一起,那么模型可能会学习到一个简单的规则:如果图片中有猫,则输出“猫”的标签;否则输出“狗”的标签。如果随机打乱训练数据,则可以避免这种问题。)

9 模型优化

###9.1 梯度裁剪(Gradient Clipping)

import torch.nn as nn

outputs = model(data)
loss= loss_fn(outputs, target)
optimizer.zero_grad()
loss.backward()
nn.utils.clip_grad_norm_(model.parameters(), max_norm=20, norm_type=2)
optimizer.step()

nn.utils.clip_grad_norm_ 的参数:

  • parameters – 一个基于变量的迭代器,会进行梯度归一化
  • max_norm – 梯度的最大范数
  • norm_type – 规定范数的类型,默认为L2

注意:梯度裁剪在某些任务上会额外消耗大量的计算时间,

(梯度裁剪(Gradient Clipping)是指在神经网络中,为了避免梯度爆炸或梯度消失的问题,对梯度进行截断或缩放的操作。梯度爆炸或梯度消失通常是由于模型过于复杂、学习速率过大或者数据分布不均匀等原因导致的。如果不进行梯度裁剪,那么可能会导致模型无法收敛或者收敛速度非常缓慢。

在上面的代码中,使用了PyTorch中的nn.utils.clip_grad_norm_()函数对模型的梯度进行裁剪。clip_grad_norm_()函数可以将所有参数的梯度按照其范数进行缩放,使其不超过指定的最大范数(max_norm)。如果某个参数的梯度范数超过了最大范数,则将其缩放到最大范数以下。这样可以避免梯度爆炸的问题。

在实际应用中,通常需要根据具体任务和数据集来选择合适的最大范数,并根据模型训练的情况进行调整。如果模型在训练过程中出现了梯度爆炸或梯度消失的问题,那么可以尝试使用梯度裁剪来解决这个问题。但是需要注意,如果使用不当,可能会影响模型的性能和泛化能力。)

###9.2 学习率衰减

import torch.optim as optim
from torch.optim import lr_scheduler

# 训练前的初始化
optimizer = optim.Adam(net.parameters(), lr=0.001)
scheduler = lr_scheduler.StepLR(optimizer, 10, 0.1)  # # 每过10个epoch,学习率乘以0.1

# 训练过程中
for n in n_epoch:
    scheduler.step()
    ...

可以随时查看学习率的值: optimizer.param_groups[0][‘lr’]。

还有其他学习率更新的方式:

1、自定义更新公式:

scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda epoch:1/(epoch+1))

2、不依赖epoch更新学习率:

lr_scheduler.ReduceLROnPlateau()提供了基于训练中某些测量值使学习率动态下降的方法,它的参数说明到处都可以查到。 提醒一点就是参数 mode=‘min’ 还是’max’,取决于优化的的损失还是准确率,即使用 scheduler.step(loss)还是scheduler.step(acc)

###9.3 冻结某些层的参数

参考:Pytorch 冻结预训练模型的某一层

在加载预训练模型的时候,我们有时想冻结前面几层,使其参数在训练过程中不发生变化。

我们需要先知道每一层的名字,通过如下代码打印:

net = Network()  # 获取自定义网络结构
for name, value in net.named_parameters():
    print('name: {0},\t grad: {1}'.format(name, value.requires_grad))

假设前几层信息如下:

name: cnn.VGG_16.convolution1_1.weight,	 grad: True
name: cnn.VGG_16.convolution1_1.bias,	 grad: True
name: cnn.VGG_16.convolution1_2.weight,	 grad: True
name: cnn.VGG_16.convolution1_2.bias,	 grad: True
name: cnn.VGG_16.convolution2_1.weight,	 grad: True
name: cnn.VGG_16.convolution2_1.bias,	 grad: True
name: cnn.VGG_16.convolution2_2.weight,	 grad: True
name: cnn.VGG_16.convolution2_2.bias,	 grad: True

后面的True表示该层的参数可训练,然后我们定义一个要冻结的层的列表:

no_grad = [
    'cnn.VGG_16.convolution1_1.weight',
    'cnn.VGG_16.convolution1_1.bias',
    'cnn.VGG_16.convolution1_2.weight',
    'cnn.VGG_16.convolution1_2.bias'
]

冻结方法如下:

net = Net.CTPN()  # 获取网络结构
for name, value in net.named_parameters():
    if name in no_grad:
        value.requires_grad = False
    else:
        value.requires_grad = True

冻结后我们再打印每层的信息:

name: cnn.VGG_16.convolution1_1.weight,	 grad: False
name: cnn.VGG_16.convolution1_1.bias,	 grad: False
name: cnn.VGG_16.convolution1_2.weight,	 grad: False
name: cnn.VGG_16.convolution1_2.bias,	 grad: False
name: cnn.VGG_16.convolution2_1.weight,	 grad: True
name: cnn.VGG_16.convolution2_1.bias,	 grad: True
name: cnn.VGG_16.convolution2_2.weight,	 grad: True
name: cnn.VGG_16.convolution2_2.bias,	 grad: True

可以看到前两层的weight和bias的requires_grad都为False,表示它们不可训练。

最后在定义优化器时,只对requires_grad为True的层的参数进行更新。

optimizer = optim.Adam(filter(lambda p: p.requires_grad, net.parameters()), lr=0.01)

###9.4 对不同层使用不同学习率

我们对模型的不同层使用不同的学习率。

还是使用这个模型作为例子:

net = Network()  # 获取自定义网络结构
for name, value in net.named_parameters():
    print('name: {}'.format(name))

# 输出:
# name: cnn.VGG_16.convolution1_1.weight
# name: cnn.VGG_16.convolution1_1.bias
# name: cnn.VGG_16.convolution1_2.weight
# name: cnn.VGG_16.convolution1_2.bias
# name: cnn.VGG_16.convolution2_1.weight
# name: cnn.VGG_16.convolution2_1.bias
# name: cnn.VGG_16.convolution2_2.weight
# name: cnn.VGG_16.convolution2_2.bias

对 convolution1 和 convolution2 设置不同的学习率,首先将它们分开,即放到不同的列表里:

conv1_params = []
conv2_params = []

for name, parms in net.named_parameters():
    if "convolution1" in name:
        conv1_params += [parms]
    else:
        conv2_params += [parms]

# 然后在优化器中进行如下操作:
optimizer = optim.Adam(
    [
        {"params": conv1_params, 'lr': 0.01},
        {"params": conv2_params, 'lr': 0.001},
    ],
    weight_decay=1e-3,
)

我们将模型划分为两部分,存放到一个列表里,每部分就对应上面的一个字典,在字典里设置不同的学习率。当这两部分有相同的其他参数时,就将该参数放到列表外面作为全局参数,如上面的weight_decay

也可以在列表外设置一个全局学习率,当各部分字典里设置了局部学习率时,就使用该学习率,否则就使用列表外的全局学习率。

10 参数初始化方法

  • 用高斯分布初始化
  • 用xavier
  • word embedding:xavier训练慢结果差,改为uniform,训练速度飙升,结果也飙升。
  • 良好的初始化,可以让参数更接近最优解,这可以大大提高收敛速度,也可以防止落入局部极小。
  • relu激活函数:初始化推荐使用He normal
  • tanh激活函数:推荐使用xavier(Glorot normal)\

(原因如下:

  • 用高斯分布初始化。这是指在神经网络中,为了初始化模型的参数,通常可以使用高斯分布来生成随机数,并将其作为参数的初始值。高斯分布可以使参数的初始值在均值附近分布,并且方差相同。但是需要注意,如果方差过大或过小,可能会导致模型无法收敛或者收敛速度非常缓慢。
  • 用xavier。这是指在神经网络中,为了初始化模型的参数,通常可以使用Xavier方法来生成随机数,并将其作为参数的初始值。Xavier方法可以使参数的初始值在均值附近分布,并且方差与输入和输出神经元数量的平方根成反比。这样可以避免参数过大或过小的问题,并提高模型的性能和泛化能力。
  • word embedding:xavier训练慢结果差,改为uniform,训练速度飙升,结果也飙升。这是指在自然语言处理(NLP)中,为了将单词转换为向量表示,并输入到神经网络中进行处理,通常需要使用word embedding技术。在初始化word embedding时,通常可以使用Xavier方法或者均匀分布来生成随机数,并将其作为参数的初始值。但是需要注意,在某些情况下,Xavier方法可能会导致训练速度较慢或者结果较差。例如,在某些NLP任务中,如果使用Xavier方法来初始化word embedding,则可能会导致模型无法捕捉到单词之间的语义关系或者上下文信息。此时可以尝试使用均匀分布来初始化word embedding,并根据具体任务和数据集来选择合适的初始值。
  • 良好的初始化,可以让参数更接近最优解,这可以大大提高收敛速度,也可以防止落入局部极小。这是指在神经网络中,良好的参数初始化可以使模型更容易收敛到最优解,并且避免落入局部极小点。如果参数初始化不当,则可能会导致模型无法收敛或者收敛速度非常缓慢。
  • relu激活函数:初始化推荐使用He normal tanh激活函数:推荐使用xavier(Glorot normal)。这是指在神经网络中,在使用ReLU或tanh等激活函数时,需要选择合适的参数初始化方法。对于ReLU激活函数,通常建议使用He normal方法来初始化参数;对于tanh激活函数,则建议使用Xavier(Glorot normal)方法来初始化参数。这样可以避免梯度消失或爆炸等问题,并提高模型性能和泛化能力。

)

网络参数初始化

神经网络的初始化是训练流程的重要基础环节,会对模型的性能、收敛性、收敛速度等产生重要的影响。

以下介绍两种常用的初始化操作。

(1) 使用pytorch内置的torch.nn.init方法。

常用的初始化操作,例如正态分布、均匀分布、xavier初始化、kaiming初始化等都已经实现,可以直接使用。具体详见PyTorch 中 torch.nn.init 中文文档

init.xavier_uniform(net1[0].weight)

(2) 对于一些更加灵活的初始化方法,可以借助numpy。

对于自定义的初始化方法,有时tensor的功能不如numpy强大灵活,故可以借助numpy实现初始化方法,再转换到tensor上使用。

for layer in net1.modules():
    if isinstance(layer, nn.Linear): # 判断是否是线性层
        param_shape = layer.weight.shape
        layer.weight.data = torch.from_numpy(np.random.normal(0, 0.5, size=param_shape)) 
        # 定义为均值为 0,方差为 0.5 的正态分布

##11 激活函数的选取

  • 给神经网络加入一些非线性因素,使得网络可以解决较为复杂的问题

  • 输出层:

  • 多分类任务:softmax输出

  • 二分类任务:sigmoid输出

  • 回归任务:线性输出

  • 中间层:优先选择relu,有效的解决sigmoid和tanh出现的梯度弥散问题

  • CNN:先用ReLU

  • RNN:优先选用tanh激活函数

(原因如下:

  • 激活函数的选取。这是指在神经网络中,为了给模型加入一些非线性因素,使得网络可以解决较为复杂的问题,需要在每个神经元的输出后面加上一个激活函数(activation function)。激活函数可以对输入进行一定的变换,例如将其映射到一个固定的范围内,或者增加其非线性程度。
  • 输出层。这是指在神经网络中,最后一层神经元的输出,也就是模型的预测结果。根据不同的任务类型,需要选择合适的激活函数来对输出层进行处理。
    • 多分类任务:softmax输出。这是指如果模型需要将输入数据分到多个类别中,例如识别图片中的动物种类,那么可以使用softmax激活函数来对输出层进行处理。softmax激活函数可以将输出层的每个值转化为一个概率值,并且保证所有概率值的和为1。这样可以表示模型对每个类别的置信度,并且方便计算损失函数和评估指标。
    • 二分类任务:sigmoid输出。这是指如果模型需要将输入数据分到两个类别中,例如判断邮件是否为垃圾邮件,那么可以使用sigmoid激活函数来对输出层进行处理。sigmoid激活函数可以将输出层的值转化为一个概率值,并且保证该概率值在[0,1]之间。这样可以表示模型对某个类别的置信度,并且方便计算损失函数和评估指标。
    • 回归任务:线性输出。这是指如果模型需要预测一个连续的数值,例如预测房价或股票价格,那么可以使用线性激活函数来对输出层进行处理。线性激活函数可以保持输出层的值不变,并且不限制其范围。这样可以表示模型对目标数值的预测,并且方便计算损失函数和评估指标。
  • 中间层:优先选择relu,有效地解决sigmoid和tanh出现的梯度弥散问题。这是指在神经网络中,除了输出层之外,其他层的神经元的输出称为中间层。在选择中间层的激活函数时,通常优先选择ReLU(Rectified Linear Unit)激活函数。ReLU激活函数可以将输入小于0的值变为0,保持输入大于0的值不变。这样可以有效地解决sigmoid和tanh等激活函数出现的梯度弥散(gradient vanishing)问题。梯度弥散是指在反向传播过程中,由于sigmoid和tanh等激活函数的导数在输入绝对值较大时接近于0,导致梯度乘积越来越小,使得网络无法有效地更新参数。
  • CNN:先用ReLU。这是指在卷积神经网络(CNN)中,在每个卷积层或者池化层之后,通常使用ReLU激活函数来增加网络的非线性程度,并提高网络的性能和泛化能力。
  • RNN:优先选用tanh激活函数。这是指在循环神经网络(RNN)中,在每个循环单元或者门控单元之后,通常使用tanh激活函数来增加网络的非线性程度,并提高网络的性能和泛化能力。tanh激活函数可以将输入映射到[-1,1]之间,并且保持输入为0时输出为0的性质。这样可以避免梯度爆炸(gradient exploding)的问题,并且有利于网络的记忆和信息传递。

)

12 模型调试

###12.1 查看模型每层输出详情

Keras有一个简洁的API来查看模型的每一层输出尺寸,这在调试网络时非常有用。现在在PyTorch中也可以实现这个功能。

使用很简单,如下用法:

from torchsummary import summary
summary(your_model, input_size=(channels, H, W))

input_size 是根据你自己的网络模型的输入尺寸进行设置。

参考:https://github.com/sksq96/pytorch-summary

###12.2 防止验证模型时爆显存

验证模型时不需要求导,即不需要梯度计算,关闭autograd,可以提高速度,节约内存。如果不关闭可能会爆显存。

with torch.no_grad():
    # 使用model进行预测的代码
    pass

这是原回答:

Pytorch 训练时无用的临时变量可能会越来越多,导致 out of memory ,可以使用下面语句来清理这些不需要的变量。

官网 上的解释为:

Releases all unoccupied cached memory currently held by the caching allocator so that those can be used in other GPU application and visible innvidia-smi. torch.cuda.empty_cache()

意思就是PyTorch的缓存分配器会事先分配一些固定的显存,即使实际上tensors并没有使用完这些显存,这些显存也不能被其他应用使用。这个分配过程由第一次CUDA内存访问触发的。

而 torch.cuda.empty_cache() 的作用就是释放缓存分配器当前持有的且未占用的缓存显存,以便这些显存可以被其他GPU应用程序中使用,并且通过 nvidia-smi命令可见。注意使用此命令不会释放tensors占用的显存。

对于不用的数据变量,Pytorch 可以自动进行回收从而释放相应的显存。

更详细的优化可以查看 优化显存使用显存利用问题

###12.3 加载内置预训练模型

torchvision.models模块的子模块中包含以下模型:

  • AlexNet
  • VGG
  • ResNet
  • SqueezeNet
  • DenseNet

导入这些模型的方法为:

import torchvision.models as models
resnet18 = models.resnet18()
alexnet = models.alexnet()
vgg16 = models.vgg16()

有一个很重要的参数为pretrained,默认为False,表示只导入模型的结构,其中的权重是随机初始化的。

如果pretrained 为 True,表示导入的是在ImageNet数据集上预训练的模型。

import torchvision.models as models
resnet18 = models.resnet18(pretrained=True)
alexnet = models.alexnet(pretrained=True)
vgg16 = models.vgg16(pretrained=True)

更多的模型可以查看 torchvision.models

##13 dropout

  • 可防止过拟合,人力成本最低的Ensemble
  • 加dropout,加BN,加Data argumentation
  • dropout可以随机的失活一些神经元,从而不参与训练
  • 例子【Dropout 缓解过拟合

(这些术语的意义如下:

  • 加dropout。这是指在神经网络中,为了避免过拟合的问题,通常需要对模型进行正则化。其中一种正则化方法是dropout。dropout可以随机地将一些神经元的输出设置为0,从而使得模型在训练过程中不依赖于某些特定的神经元,从而提高模型的泛化能力。在实际应用中,通常需要根据具体任务和数据集来选择合适的dropout比例,并根据模型训练的情况进行调整。
  • 加BN。这是指在神经网络中,为了加速模型的训练和提高模型的性能,通常需要对输入数据进行标准化。其中一种标准化方法是批量归一化(Batch Normalization,BN)。BN可以将每个批次的输入数据标准化为均值为0、方差为1的分布,并且保持输入数据的分布不变。这样可以使得模型更容易收敛,并且提高模型的泛化能力。在实际应用中,通常需要根据具体任务和数据集来选择合适的BN位置,并根据模型训练的情况进行调整。
  • 加Data argumentation。这是指在神经网络中,为了增加训练数据量和提高模型的泛化能力,通常需要对原始数据进行扩充。其中一种扩充方法是数据增强(Data Augmentation)。数据增强可以通过对原始数据进行旋转、翻转、缩放、裁剪等操作来生成新的训练样本,并且保持样本标签不变。这样可以使得模型更好地学习到数据中的统计规律和特征,并提高模型的泛化能力。在实际应用中,通常需要根据具体任务和数据集来选择合适的数据增强方法,并根据模型训练的情况进行调整。)

学习速率

  • 在优化算法中更新网络权重的幅度大小
  • 可以是恒定的、逐渐下降的、基于动量的或者是自适应的
  • 优先调这个LR:会很大程度上影响模型的表现
  • 如果太大,会很震荡,类似于二次抛物线寻找最小值
  • 一般学习率从0.1或0.01开始尝试
  • 通常取值[0.01, 0.001, 0.0001]
  • 学习率一般要随着训练进行衰减。衰减系数设0.1,0.3,0.5均可,衰减时机,可以是验证集准确率不再上升时,或固定训练多少个周期以后自动进行衰减
  • 有人设计学习率的原则是监测一个比例:每次更新梯度的norm除以当前weight的norm,如果这个值在10e-3附近,且小于这个值,学习会很慢,如果大于这个值,那么学习很不稳定。
  • 红线:初始学习率太大,导致振荡,应减小学习率,并从头开始训练
  • 紫线:后期学习率过大导致无法拟合,应减小学习率,并重新训练后几轮
  • 黄线:初始学习率过小,导致收敛慢,应增大学习率,并从头开始训练

batch size大小

  • 每一次训练神经网络送入模型的样本数
  • 可直接设置为16或者64。通常取值为:[16, 32, 64, 128]
  • CPU讨厌16,32,64,这样的2的指数倍(为什么?)。GPU不会,GPU推荐取32的倍数。

momentum大小

出现锯齿状的原因是:

1、每一轮迭代使用的训练数据一般是小批量的,没有使用全部的训练数据,因此更新方向会发生锯齿状甚至随机震荡状;

2、某些梯度分量的值比另外一些分量的值要大的多,导致个别分量主导了梯度的更新方向,而期望的梯度更新方向却行进的非常缓慢,这正是Momentum算法要解决的问题。

该算法将一段时间内的梯度向量进行了加权平均,分别计算得到梯度更新过程中 w� 和 b� 的大致走向,一定程度上消除了更新过程中的不确定性因素(如摆动现象),使得梯度更新朝着一个越来越明确的方向前进。

1、为 0 时,退化为未优化前的梯度更新;

2、为 1 时, 表示完全没有摩擦,如前所述,这样会存在大的问题;

3、取 0.9 是一个较好的选择。可能是 0.960 次方约等于 0.001,相当仅考虑最近的60轮迭代所产生的的梯度,这个数值看起来相对适中合理。

使用默认的0.9

迭代次数

  • 整个训练集输入到神经网络进行训练的次数
  • 当训练集错误率和测试错误率想相差较小时:迭代次数合适
  • 当测试错误率先变小后变大:迭代次数过大,需要减小,否则容易过拟合

优化器

这里会专门讲一下

  • 自适应:Adagrad, Adadelta, RMSprop, Adam
  • 整体来讲,Adam是最好的选择
  • SGD:虽然能达到极大值,运行时间长,可能被困在鞍点
  • Adam: 学习速率3e-4。能快速收敛。

一言以蔽之,优化器就是在深度学习反向传播过程中,指引损失函数(目标函数)的各个参数往正确的方向更新合适的大小,使得更新后的各个参数让损失函数(目标函数)值不断逼近全局最小。

残差块和BN

  • 残差块:可以让你的网络训练的更深
  • BN:加速训练速度,有效防止梯度消失与梯度爆炸,具有防止过拟合的效果
  • 构建网络时最好加上这两个组件

tips:

  1. 保证数据是可靠的:数据集是训练一个好的网络的前提条件,如果数据集的质量得不到保证的话,很难得到一个很好的网络参数,所以需要重新在数据集上进行下功夫。
  2. 预训练模型最好用上
  3. 通常**学习率参数小于1e-5基本没啥用了,**比如cosine或者step操作,最后的学习率到1e-5就好了。当然特殊任务不一样
  4. bn在训练时记得打开更新(特别是tf的小伙伴,容易漏),不然可能出现的问题是训练时loss下降很快,测试感觉模型就没收敛
  5. sgd是很棒的,但是实验用adam或许收敛速度更好
  6. 如果想要很好的压榨出一个算法的性能,请先保证当前模型能到达相应的性能再去压榨。而不是盲目的换模块,疯狂调参,那样可能只是浪费时间。
  7. 不要太相信自己的调参技术,在没有一个较好的baseline情况下,调参不会有质的飞跃(除非是之前参数造成了某种bug)
  8. 数据小时,使用了预训练模型记得固定前几层的模型参数,还可以用小点的学习率
  9. loss balance有时候很有用
  10. 重复训练可能可以提升点数,将一个模型训练好后,用训练好的模型做预训练模型载入,**继续用同一套参数训练。**有点像CyclicLR。
  11. DL没有像机器学习有那么多公式支撑,很多都是make sense就做个实验来验证,所以尽量**多阅读论文,看看别人的实验,**这样就可以减少不必要的实验
  12. **深度学习中网络训练时loss不降的可能原因和解决方法?:**其实这个问题和第一个问题是一样的,详细解释请看第一个问题答案。下面给出一些训练时常出现的问题。 (1)train loss 不断下降,test loss不断下降,说明网络仍在学习; (2)train loss 不断下降,test loss趋于不变,说明网络过拟合; (3)train loss 趋于不变,test loss不断下降,说明数据集100%有问题; (4)train loss 趋于不变,test loss趋于不变,说明学习遇到瓶颈,需要减小学习率或批量数目; (5)train loss 不断上升,test loss不断上升,说明网络结构设计不当,训练超参数设置不当,数据集经过清洗等问题。
  13. Batch size对模型训练有什么影响?其大小是怎么选取的?:一般来说,在合理的范围之内,越大的 batch size 使下降方向越准确,震荡越小;batch size 如果过大,则可能会出现局部最优的情况(这也是其中的一个缺点吧)。小的 bath size 引入的随机性更大,难以达到收敛,极少数情况下可能会效果变好。``会影响训练的稳定性,Batch size过小会使Loss曲线振荡的比较大,大小一般按照2的次幂规律选择,这是为了硬件计算效率考虑的。
  14. **在深度学习中,通常会finetuning已有的成熟模型,再基于新数据,修改最后几层神经网络权值,为什么?:**迁移学习任务就是从相似性出发,将旧领域学习过的模型应用在新领域上。为什么需要迁移学习: 1) 大数据与少标注的矛盾:虽然有大量的数据,但往往都是没有标注的,无法训练机器学习模型。人工进行数据标定太耗时。 2) 大数据与弱计算的矛盾:普通人无法拥有庞大的数据量与计算资源。因此需要借助于模型的迁移。 3) 普适化模型与个性化需求的矛盾:即使是在同一个任务上,一个模型也往往难以满足每个人的个性化需求,比如特定的隐私设置。这就需要在不同人之间做模型的适配。 4) 特定应用(如冷启动)的需求:迁移学习可以初步初始化网络,因为对一些比较类似的任务,其实模型参数的值基本上相同,而且这些参数经过大量的训练,已经有很好的特征提取能力,将backbone参数使用这类模型进行权重的初始化,后面做training的时候,模型收敛速度会更快。 **负迁移问题:**负迁移(Negative Transfer)指的是,在源域上学习到的知识,对于目标域上的学习产生负面作用。产生负迁移的原因主要有: 1)数据问题:源域和目标域压根不相似,谈何迁移? 2)方法问题:源域和目标域是相似的,但是,迁移学习方法不够好,没找到可迁移的成分。 负迁移给迁移学习的研究和应用带来了负面影响。在实际应用中,找到合理的相似性,并且选择或开发合理的迁移学习方法,能够避免负迁移现象。
  15. 样本不均衡你会怎么处理(重采样/Focal loss):样本不均衡的方法无非有两种,一种是增强训练数据,另外一种是采用focal loss
  16. **为什么深层神经网络难以训练?:**在深度神经⽹络中的梯度是不稳定的,在前⾯的层中或会消失,或会激增。这种不稳定性才是深度神经⽹络中基于梯度学习的根本问题。 1)梯度消失:梯度消失是指通过隐藏层从后向前看,梯度会变的越来越小,说明前面层的学习会显著慢于后面层的学习,所以学习会卡住,除非梯度变大。梯度消失的原因受到多种因素影响,例如学习率的大小,网络参数的初始化,激活函数的边缘效应等。在深层神经网络中,每一个神经元计算得到的梯度都会传递给前一层,较浅层的神经元接收到的梯度受到之前所有层梯度的影响。如果计算得到的梯度值非常小,随着层数增多,求出的梯度更新信息将会以指数形式衰减,就会发生梯度消失。 2)**梯度爆炸:**在深度网络或循环神经网络(Recurrent Neural Network, RNN)等网络结构中,梯度可在网络更新的过程中不断累积,变成非常大的梯度,导致网络权重值的大幅更新,使得网络不稳定;在极端情况下,权重值甚至会溢出,变为NaN值,再也无法更新。 3)**权重矩阵的退化导致模型的有效自由度减少:**参数空间中学习的退化速度减慢,导致减少了模型的有效维数,网络的可用自由度对学习中梯度范数的贡献不均衡,随着相乘矩阵的数量(即网络深度)的增加,矩阵的乘积变得越来越退化。在有硬饱和边界的非线性网络中(例如 ReLU 网络),随着深度增加,退化过程会变得越来越快。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值