欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

Pytorch 学习笔记 (8):正则化(L1、L2、Dropout)和规范化(BN、LN、IN、GN)

最编程 2024-04-24 14:36:28
...

网络异常,图片无法展示
|


前期回顾

Pytorch学习笔记(1):基本概念、安装、张量操作、逻辑回归

Pytorch学习笔记(2):数据读取机制(DataLoader与Dataset)

Pytorch学习笔记(3):图像的预处理(transforms)

Pytorch学习笔记(4):模型创建(Module)、模型容器(Containers)、AlexNet构建

Pytorch学习笔记(5):torch.nn---网络层介绍(卷积层、池化层、线性层、激活函数层)

Pytorch学习笔记(6):模型的权值初始化与损失函数

Pytorch学习笔记(7):优化器、学习率及调整策略、动量


一、正则化之weight_decay(L2正则)

1.1 正则化及相关概念

Regularization,中文翻译过来可以称为正则化,或者是规范化。什么是规则?闭卷考试中不能查书,这就是规则,一个限制。同理,在这里,规则化就是说损失函数加上一些限制,通过这种规则去规范他们再接下来的循环迭代中,不要自我膨胀。

介绍正则化之前,我们先来了解泛化误差:

泛化误差可分解为偏差、方差与噪声泛化性能是由学习算法的能力数据的充分性以及学习任务本身的难度所共同决定的。即泛化误差=偏差+方差+噪声

  • 偏差度量了学习算法的期望预测与真实结果的偏离程度,即刻画了学习算法本身的拟合能力
  • 方差度量了同样大小的训练集的变动所导致的学习性能的变化,即刻画了数据扰动所造成的影响
  • 噪声表达了在当前任务上任何学习算法所能够达到的期望泛化误差的下界,即刻画了学习问题本身的难度

那么偏差、方差与我们的数据集划分到底有什么关系呢?

  • 1、训练集的错误率较小,而验证集/测试集的错误率较大,说明模型存在较大方差,可能出现了过拟合
  • 2、训练集和测试集的错误率都较大,且两者相近,说明模型存在较大偏差,可能出现了欠拟合
  • 3、训练集和测试集的错误率都较小,且两者相近,说明方差和偏差都较小,这个模型效果比较好

所以我们最终总结,方差一般指的是数据模型得出来了,能不能对未知数据的扰动预测准确。而偏差说明在训练集当中就已经误差较大了,基本上在测试集中没有好的效果。

通过分析,我们可以看出,正则化是用来防止模型过拟合而采取的手段。我们对代价函数增加一个限制条件,限制其较高次的参数大小不能过大。


1.2 正则化策略(L1、L2)

(1)L1正则化

L1正则化,又称Lasso Regression,是指权值向量w中各个元素的绝对值之和。比如:向量A=[1,-1,3], 那么A的L1范数为 |1|+|-1|+|3|。

L1正则化可以让一部分特征的系数缩小到0,所以L1适用于特征之间有关联的情况可以产生稀疏权值矩阵(很多权重为0,则一些特征被过滤掉),即产生一个稀疏模型,可以用于特征选择。L1也可以防止过拟合。

Q:L1为什么会产生一个稀疏权值矩阵呢?

L1正则化是权值的 绝对值之和,所以L1是带有绝对值符号的函数,因此是不完全可微的。机器学习的任务就是要通过一些方法(比如梯度下降)求出损失函数的最小值。当我们在原始损失函数后添加L1正则化项时,相当于对损失函数做了一个约束。

公式如下:

网络异常,图片无法展示
|

λ是正则化系数,是一个超参数,调节惩罚的力度,越大则惩罚力度越大。

此时我们的任务变成在约束下求出取最小值的解。

考虑二维的情况,即只有两个权值和 ,此时对于梯度下降法,求解函数的过程可以画出等值线,同时L1正则化的函数可以在二维平面上画出来。如下图:

网络异常,图片无法展示
|

蓝色圆圈线是Loss中前半部分待优化项的等高线,就是说在同一条线上其取值相同,且越靠近中心其值越小。

黄色菱形区域是L1正则项约束条件。

带有正则化的loss函数的最优解要在黄色菱形区域和蓝色圆圈线之间折中,也就是说最优解出现在图中优化项等高线与约束条件相交处。从图中可以看出,当待优化项的等高线逐渐向正则项约束区域扩散时,L1正则化的交点大多在坐标轴上,则很多特征维度上其参数w为0,因此会产生稀疏解;而正则化前面的系数,可以控制图形的大小。越小,约束项的图形越大(上图中的黄色方框);越大,约束项的图形就越小,可以小到黑色方框只超出原点范围一点点,这是最优点的值中的可以取到很小的值。

 (2)L2正则化

L2正则化,指权值向量中各个元素的平方和然后再求平方根,对参数进行二次约束,参数w变小,但不为零,不会形成稀疏解 。它会使优化求解稳定快速,使权重平滑。所以L2适用于特征之间没有关联的情况。

网络异常,图片无法展示
|
同样,L2正则化的函数也可以在二维平面上画出来,圆心就是样本值,半径就是误差,而约束条件则就是红色边界。等高线与约束条件相交的地方就是最优解。

网络异常,图片无法展示
|

蓝色圆圈线和上面一样

黄色圆形区域是L2正则项约束条件。

同样,最优解出现在图中优化项等高线与正则化区域相交处。从图中可以看出,当待优化项的等高线逐渐向正则项限制区域扩散时L2正则化的交点大多在非坐标轴上,二维平面下L2正则化的函数图形是个圆,与方形相比,被磨去了棱角。因此与相交时使得或等于零的机率小了许多,这就是为什么L2正则化不具有稀疏性的原因。

总结:

(1)使用L1正则化在取得最优解的时候w1的值为0,相当于去掉了一个特征,而使用L2正则化在取得最优解的时候特征参数都有其值。

(2)L1会趋向于产生少量的特征,而其他的特征都为0,而L2会选择更多的特征,特征值都趋近于0。


1.3 L2正则项——weight_decay

从直观上讲,L2正则化(weight_decay)使得训练的模型在兼顾最小化分类(或其他目标)的Loss的同时,使得权重w尽可能地小,从而将权重约束在一定范围内,减小模型复杂度;同时,如果将w约束在一定范围内,也能够有效防止梯度爆炸。

L2 Regularization = weight decay(权值衰减)

第一个wi+1为未加正则项的权重计算方式

第二个wi+1加入正则项之后的权重计算方式,化简后的公式如下,wi的系数小于1,实现了权重的衰减

网络异常,图片无法展示
|

Pytorch中的 weight decay 是在优化器中实现的,在优化器中加入参数weight_decay即可,参数中的weight_decay等价于正则化系数λ 。

例如下面的两个随机梯度优化器,一个是没有加入正则项,一个加入了正则项,区别仅仅在于是否设置了参数weight_decay的值:

 optim_normal = torch.optim.SGD(net_normal.parameters(), lr=lr_init, momentum=0.9)
 optim_wdecay = torch.optim.SGD(net_weight_decay.parameters(), lr=lr_init, momentum=0.9, weight_decay=1e-2)

我们来看看输出结果:

可以看到,模型迭代1000次后,红线的loss基本为0,而蓝线的loss是0.035左右,虽然红线的loss很低,但是它产生了过拟合现象。

网络异常,图片无法展示
|


二、正则化之Dropout

2.1 Dropout概念

Dropout(随机失活):正常神经网络需要对每一个节点进行学习,而添加了Dropout的神经网络通过删除部分单元(随机),即暂时将其从网络中移除,以及它的所有传入和传出连接。将Dropout应用于神经网络相当于从神经网络中采样了一个“更薄的”网络,即单元个数较少。如下图所示,Dropout是从左图采样了一个更薄的网络,如图右

我们有时候之所以会出现过拟合现象,就是因为我们的网络太复杂了,参数太多了,并且我们后面层的网络也可能太过于依赖前层的某个神经元,加入Dropout之后, 首先网络会变得简单,减少一些参数,并且由于不知道浅层的哪些神经元会失活,导致后面的网络不敢放太多的权重在前层的某个神经元,这样就减轻了一个过渡依赖的现象, 对特征少了依赖, 从而有利于缓解过拟合。

注意事项:

数据尺度变化只在训练的时候开启Dropout,而测试的时候是不用Dropout的,也就是说模型训练的时候会随机失活一部分神经元, 而测试的时候我们用所有的神经元,那么这时候就会出现这个数据尺度的问题,首先介绍一下,drop_prob就是随机失活概率。

实现细节

  • 训练时,所有权重乘以1/(1-p),即除以1-p
  • 测试时,所有权重乘以1-drop_prob,drop_prob = 0.3,1-drop_prob = 0.7

2.2 nn.Dropout

nn.Dropout

功能:Dropout层

参数:

  • p:被舍弃概率,失活概率

代码及输出结果:

红色曲线出现了过拟合

蓝色曲线没有过拟合

Dropout实现了类似L2的权重衰减的功能


三、归一化之Batch Normalization(BN层)

3.1 Batch Normalization介绍

Batch Normalization,简称BN,是google团队在2015年论文《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》提出的。这是一个神经网络训练的技巧,它不仅可以加快了模型的收敛速度,而且更重要的是在一定程度缓解了深层网络中“梯度弥散(特征分布较散)”的问题,从而使得训练深层网络模型更加容易和稳定。所以目前BN已经成为几乎所有卷积神经网络的标配技巧了。

从字面意思看来Batch Normalization就是对每一批数据进行归一化,确实如此,对于训练中某一个batch的数据{x1,x2,…,xn},注意这个数据是可以输入也可以是网络中间的某一层输出。在BN出现之前,我们的归一化操作一般都在数据输入层,对输入的数据进行求均值以及求方差做归一化,但是BN的出现打破了这一个规定,我们可以在网络中任意一层进行归一化处理,因为我们现在所用的优化方法大多都是min-batch SGD,所以我们的归一化操作就称为Batch Normalization。

 

为什么加了BN之后就不用精心设计权值初始化了呢?

我们来看看代码:

from tools.common_tools import set_seed
 
set_seed(1)  # 设置随机种子
 
 
class MLP(nn.Module):
    def __init__(self, neural_num, layers=100):
        super(MLP, self).__init__()
        self.linears = nn.ModuleList([nn.Linear(neural_num, neural_num, bias=False) for i in range(layers)])
        self.bns = nn.ModuleList([nn.BatchNorm1d(neural_num) for i in range(layers)])
        self.neural_num = neural_num
 
    def forward(self, x):
 
        for (i, linear), bn in zip(enumerate(self.linears), self.bns):
            x = linear(x)
            # x = bn(x)
            x = torch.relu(x)
 
            if torch.isnan(x.std()):
                print("output is nan in {} layers".format(i))
                break
 
            print("layers:{}, std:{}".format(i, x.std().item()))
 
        return x
 
    def initialize(self):
        for m in self.modules():
            if isinstance(m, nn.Linear):
 
                # method 1
                nn.init.normal_(m.weight.data, std=1)    # normal: mean=0, std=1
 
                # method 2 kaiming
                # nn.init.kaiming_normal_(m.weight.data)
 
 
neural_nums = 256
layer_nums = 100
batch_size = 16
 
net = MLP(neural_nums, layer_nums)
# net.initialize()
 
inputs = torch.randn((batch_size, neural_nums))  # normal: mean=0, std=1
 
output = net(inputs)
print(output)

输出结果:

不进行权值初始化,导致梯度消失

接下来,我们进行权值初始化来观察一下梯度的变化情况

代码:

net.initialize()

输出结果:

采用标准正态分布的初始化方法,可以发现发生了梯度爆炸

因为此时用到的激活函数是RELU,所以使用kaiming初始化方法

代码:

# method 2 kaiming
nn.init.kaiming_normal_(m.weight.data)

输出结果:

梯度在0.6左右

以上使我们第六节讲的内容(Pytorch学习笔记(6):模型的权值初始化与损失函数)刚才我们按照权值初始化的方法来防止梯度爆炸,在这中间,考虑到relu,我们就得用Kaiming初始化,考虑到tanh,我们还得用Xavier, 这样就相当麻烦了。

那么我们假设不用权值初始化,而是在网络层的激活函数前加上BN呢?

代码:

x = bn(x)

输出结果:

可以发现,BN依然可以保证数据的尺度,并且好处就是我们不用再考虑用什么样的方式进行权值的初始化。


3.2 Pytorch的Batch Normalization 1d/2d/3d实现

Pytorch中提供了三种BatchNorm方法:

  • nn.BatchNorm1d
  • nn.BatchNorm2d<